From 68ba050030ecbb2e834c3df1fd36b64c944e2f23 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Fri, 22 Mar 2019 20:45:34 +0100 Subject: [PATCH 001/884] Use the extend class macro more. --- vm/src/obj/objfloat.rs | 52 ++++++++++--------- vm/src/obj/objint.rs | 93 ++++++++++++++++----------------- vm/src/obj/objmap.rs | 8 +-- vm/src/obj/objrange.rs | 46 ++++++----------- vm/src/obj/objslice.rs | 12 +++-- vm/src/obj/objstr.rs | 113 +++++++++++++++++++++-------------------- vm/src/vm.rs | 4 +- 7 files changed, 162 insertions(+), 166 deletions(-) diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 849b31d408..af2071184f 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -356,29 +356,31 @@ pub fn init(context: &PyContext) { let float_doc = "Convert a string or number to a floating point number, if possible."; - context.set_attr(&float_type, "__eq__", context.new_rustfunc(PyFloatRef::eq)); - context.set_attr(&float_type, "__lt__", context.new_rustfunc(PyFloatRef::lt)); - context.set_attr(&float_type, "__le__", context.new_rustfunc(PyFloatRef::le)); - context.set_attr(&float_type, "__gt__", context.new_rustfunc(PyFloatRef::gt)); - context.set_attr(&float_type, "__ge__", context.new_rustfunc(PyFloatRef::ge)); - context.set_attr(&float_type, "__abs__", context.new_rustfunc(PyFloatRef::abs)); - context.set_attr(&float_type, "__add__", context.new_rustfunc(PyFloatRef::add)); - context.set_attr(&float_type, "__radd__", context.new_rustfunc(PyFloatRef::add)); - context.set_attr(&float_type, "__divmod__", context.new_rustfunc(PyFloatRef::divmod)); - context.set_attr(&float_type, "__floordiv__", context.new_rustfunc(PyFloatRef::floordiv)); - context.set_attr(&float_type, "__new__", context.new_rustfunc(PyFloatRef::new_float)); - context.set_attr(&float_type, "__mod__", context.new_rustfunc(PyFloatRef::mod_)); - context.set_attr(&float_type, "__neg__", context.new_rustfunc(PyFloatRef::neg)); - context.set_attr(&float_type, "__pow__", context.new_rustfunc(PyFloatRef::pow)); - context.set_attr(&float_type, "__sub__", context.new_rustfunc(PyFloatRef::sub)); - context.set_attr(&float_type, "__rsub__", context.new_rustfunc(PyFloatRef::rsub)); - context.set_attr(&float_type, "__repr__", context.new_rustfunc(PyFloatRef::repr)); - context.set_attr(&float_type, "__doc__", context.new_str(float_doc.to_string())); - context.set_attr(&float_type, "__truediv__", context.new_rustfunc(PyFloatRef::truediv)); - context.set_attr(&float_type, "__rtruediv__", context.new_rustfunc(PyFloatRef::rtruediv)); - context.set_attr(&float_type, "__mul__", context.new_rustfunc(PyFloatRef::mul)); - context.set_attr(&float_type, "__rmul__", context.new_rustfunc(PyFloatRef::mul)); - context.set_attr(&float_type, "real", context.new_property(PyFloatRef::real)); - context.set_attr(&float_type, "is_integer", context.new_rustfunc(PyFloatRef::is_integer)); - context.set_attr(&float_type, "as_integer_ratio", context.new_rustfunc(PyFloatRef::as_integer_ratio)); + extend_class!(context, float_type, { + "__eq__" => context.new_rustfunc(PyFloatRef::eq), + "__lt__" => context.new_rustfunc(PyFloatRef::lt), + "__le__" => context.new_rustfunc(PyFloatRef::le), + "__gt__" => context.new_rustfunc(PyFloatRef::gt), + "__ge__" => context.new_rustfunc(PyFloatRef::ge), + "__abs__" => context.new_rustfunc(PyFloatRef::abs), + "__add__" => context.new_rustfunc(PyFloatRef::add), + "__radd__" => context.new_rustfunc(PyFloatRef::add), + "__divmod__" => context.new_rustfunc(PyFloatRef::divmod), + "__floordiv__" => context.new_rustfunc(PyFloatRef::floordiv), + "__new__" => context.new_rustfunc(PyFloatRef::new_float), + "__mod__" => context.new_rustfunc(PyFloatRef::mod_), + "__neg__" => context.new_rustfunc(PyFloatRef::neg), + "__pow__" => context.new_rustfunc(PyFloatRef::pow), + "__sub__" => context.new_rustfunc(PyFloatRef::sub), + "__rsub__" => context.new_rustfunc(PyFloatRef::rsub), + "__repr__" => context.new_rustfunc(PyFloatRef::repr), + "__doc__" => context.new_str(float_doc.to_string()), + "__truediv__" => context.new_rustfunc(PyFloatRef::truediv), + "__rtruediv__" => context.new_rustfunc(PyFloatRef::rtruediv), + "__mul__" => context.new_rustfunc(PyFloatRef::mul), + "__rmul__" => context.new_rustfunc(PyFloatRef::mul), + "real" => context.new_property(PyFloatRef::real), + "is_integer" => context.new_rustfunc(PyFloatRef::is_integer), + "as_integer_ratio" => context.new_rustfunc(PyFloatRef::as_integer_ratio) + }); } diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 553042ed5f..1a8b565579 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -474,50 +474,51 @@ Base 0 means to interpret the base from the string as an integer literal. >>> int('0b100', base=0) 4"; let int_type = &context.int_type; - - context.set_attr(&int_type, "__doc__", context.new_str(int_doc.to_string())); - context.set_attr(&int_type, "__eq__", context.new_rustfunc(PyIntRef::eq)); - context.set_attr(&int_type, "__ne__", context.new_rustfunc(PyIntRef::ne)); - context.set_attr(&int_type, "__lt__", context.new_rustfunc(PyIntRef::lt)); - context.set_attr(&int_type, "__le__", context.new_rustfunc(PyIntRef::le)); - context.set_attr(&int_type, "__gt__", context.new_rustfunc(PyIntRef::gt)); - context.set_attr(&int_type, "__ge__", context.new_rustfunc(PyIntRef::ge)); - context.set_attr(&int_type, "__abs__", context.new_rustfunc(PyIntRef::abs)); - context.set_attr(&int_type, "__add__", context.new_rustfunc(PyIntRef::add)); - context.set_attr(&int_type, "__radd__", context.new_rustfunc(PyIntRef::add)); - context.set_attr(&int_type, "__and__", context.new_rustfunc(PyIntRef::and)); - context.set_attr(&int_type, "__divmod__", context.new_rustfunc(PyIntRef::divmod)); - context.set_attr(&int_type, "__float__", context.new_rustfunc(PyIntRef::float)); - context.set_attr(&int_type, "__round__", context.new_rustfunc(PyIntRef::round)); - context.set_attr(&int_type, "__ceil__", context.new_rustfunc(PyIntRef::pass_value)); - context.set_attr(&int_type, "__floor__", context.new_rustfunc(PyIntRef::pass_value)); - context.set_attr(&int_type, "__index__", context.new_rustfunc(PyIntRef::pass_value)); - context.set_attr(&int_type, "__trunc__", context.new_rustfunc(PyIntRef::pass_value)); - context.set_attr(&int_type, "__int__", context.new_rustfunc(PyIntRef::pass_value)); - context.set_attr(&int_type, "__floordiv__", context.new_rustfunc(PyIntRef::floordiv)); - context.set_attr(&int_type, "__hash__", context.new_rustfunc(PyIntRef::hash)); - context.set_attr(&int_type, "__lshift__", context.new_rustfunc(PyIntRef::lshift)); - context.set_attr(&int_type, "__rshift__", context.new_rustfunc(PyIntRef::rshift)); - context.set_attr(&int_type, "__new__", context.new_rustfunc(int_new)); - context.set_attr(&int_type, "__mod__", context.new_rustfunc(PyIntRef::mod_)); - context.set_attr(&int_type, "__mul__", context.new_rustfunc(PyIntRef::mul)); - context.set_attr(&int_type, "__rmul__", context.new_rustfunc(PyIntRef::mul)); - context.set_attr(&int_type, "__or__", context.new_rustfunc(PyIntRef::or)); - context.set_attr(&int_type, "__neg__", context.new_rustfunc(PyIntRef::neg)); - context.set_attr(&int_type, "__pos__", context.new_rustfunc(PyIntRef::pass_value)); - context.set_attr(&int_type, "__pow__", context.new_rustfunc(PyIntRef::pow)); - context.set_attr(&int_type, "__repr__", context.new_rustfunc(PyIntRef::repr)); - context.set_attr(&int_type, "__sub__", context.new_rustfunc(PyIntRef::sub)); - context.set_attr(&int_type, "__rsub__", context.new_rustfunc(PyIntRef::rsub)); - context.set_attr(&int_type, "__format__", context.new_rustfunc(PyIntRef::format)); - context.set_attr(&int_type, "__truediv__", context.new_rustfunc(PyIntRef::truediv)); - context.set_attr(&int_type, "__rtruediv__", context.new_rustfunc(PyIntRef::rtruediv)); - context.set_attr(&int_type, "__xor__", context.new_rustfunc(PyIntRef::xor)); - context.set_attr(&int_type, "__rxor__", context.new_rustfunc(PyIntRef::rxor)); - context.set_attr(&int_type, "__bool__", context.new_rustfunc(PyIntRef::bool)); - context.set_attr(&int_type, "__invert__", context.new_rustfunc(PyIntRef::invert)); - context.set_attr(&int_type, "bit_length", context.new_rustfunc(PyIntRef::bit_length)); - context.set_attr(&int_type, "conjugate", context.new_rustfunc(PyIntRef::pass_value)); - context.set_attr(&int_type, "real", context.new_property(PyIntRef::pass_value)); - context.set_attr(&int_type, "imag", context.new_property(PyIntRef::imag)); + extend_class!(context, int_type, { + "__doc__" => context.new_str(int_doc.to_string()), + "__eq__" => context.new_rustfunc(PyIntRef::eq), + "__ne__" => context.new_rustfunc(PyIntRef::ne), + "__lt__" => context.new_rustfunc(PyIntRef::lt), + "__le__" => context.new_rustfunc(PyIntRef::le), + "__gt__" => context.new_rustfunc(PyIntRef::gt), + "__ge__" => context.new_rustfunc(PyIntRef::ge), + "__abs__" => context.new_rustfunc(PyIntRef::abs), + "__add__" => context.new_rustfunc(PyIntRef::add), + "__radd__" => context.new_rustfunc(PyIntRef::add), + "__and__" => context.new_rustfunc(PyIntRef::and), + "__divmod__" => context.new_rustfunc(PyIntRef::divmod), + "__float__" => context.new_rustfunc(PyIntRef::float), + "__round__" => context.new_rustfunc(PyIntRef::round), + "__ceil__" => context.new_rustfunc(PyIntRef::pass_value), + "__floor__" => context.new_rustfunc(PyIntRef::pass_value), + "__index__" => context.new_rustfunc(PyIntRef::pass_value), + "__trunc__" => context.new_rustfunc(PyIntRef::pass_value), + "__int__" => context.new_rustfunc(PyIntRef::pass_value), + "__floordiv__" => context.new_rustfunc(PyIntRef::floordiv), + "__hash__" => context.new_rustfunc(PyIntRef::hash), + "__lshift__" => context.new_rustfunc(PyIntRef::lshift), + "__rshift__" => context.new_rustfunc(PyIntRef::rshift), + "__new__" => context.new_rustfunc(int_new), + "__mod__" => context.new_rustfunc(PyIntRef::mod_), + "__mul__" => context.new_rustfunc(PyIntRef::mul), + "__rmul__" => context.new_rustfunc(PyIntRef::mul), + "__or__" => context.new_rustfunc(PyIntRef::or), + "__neg__" => context.new_rustfunc(PyIntRef::neg), + "__pos__" => context.new_rustfunc(PyIntRef::pass_value), + "__pow__" => context.new_rustfunc(PyIntRef::pow), + "__repr__" => context.new_rustfunc(PyIntRef::repr), + "__sub__" => context.new_rustfunc(PyIntRef::sub), + "__rsub__" => context.new_rustfunc(PyIntRef::rsub), + "__format__" => context.new_rustfunc(PyIntRef::format), + "__truediv__" => context.new_rustfunc(PyIntRef::truediv), + "__rtruediv__" => context.new_rustfunc(PyIntRef::rtruediv), + "__xor__" => context.new_rustfunc(PyIntRef::xor), + "__rxor__" => context.new_rustfunc(PyIntRef::rxor), + "__bool__" => context.new_rustfunc(PyIntRef::bool), + "__invert__" => context.new_rustfunc(PyIntRef::invert), + "bit_length" => context.new_rustfunc(PyIntRef::bit_length), + "conjugate" => context.new_rustfunc(PyIntRef::pass_value), + "real" => context.new_property(PyIntRef::pass_value), + "imag" => context.new_property(PyIntRef::imag) + }); } diff --git a/vm/src/obj/objmap.rs b/vm/src/obj/objmap.rs index 74aabe1072..089c4f94b0 100644 --- a/vm/src/obj/objmap.rs +++ b/vm/src/obj/objmap.rs @@ -63,7 +63,9 @@ pub fn init(context: &PyContext) { each of the iterables. Stops when the shortest iterable is exhausted."; objiter::iter_type_init(context, map_type); - context.set_attr(&map_type, "__new__", context.new_rustfunc(map_new)); - context.set_attr(&map_type, "__next__", context.new_rustfunc(map_next)); - context.set_attr(&map_type, "__doc__", context.new_str(map_doc.to_string())); + extend_class!(context, map_type, { + "__new__" => context.new_rustfunc(map_new), + "__next__" => context.new_rustfunc(map_next), + "__doc__" => context.new_str(map_doc.to_string()) + }); } diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 0730762a84..8f037bbe19 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -164,36 +164,22 @@ pub fn init(context: &PyContext) { These are exactly the valid indices for a list of 4 elements.\n\ When step is given, it specifies the increment (or decrement)."; - context.set_attr(&range_type, "__new__", context.new_rustfunc(range_new)); - context.set_attr(&range_type, "__iter__", context.new_rustfunc(range_iter)); - context.set_attr( - &range_type, - "__reversed__", - context.new_rustfunc(range_reversed), - ); - context.set_attr( - &range_type, - "__doc__", - context.new_str(range_doc.to_string()), - ); - context.set_attr(&range_type, "__len__", context.new_rustfunc(range_len)); - context.set_attr( - &range_type, - "__getitem__", - context.new_rustfunc(range_getitem), - ); - context.set_attr(&range_type, "__repr__", context.new_rustfunc(range_repr)); - context.set_attr(&range_type, "__bool__", context.new_rustfunc(range_bool)); - context.set_attr( - &range_type, - "__contains__", - context.new_rustfunc(range_contains), - ); - context.set_attr(&range_type, "index", context.new_rustfunc(range_index)); - context.set_attr(&range_type, "count", context.new_rustfunc(range_count)); - context.set_attr(&range_type, "start", context.new_property(range_start)); - context.set_attr(&range_type, "stop", context.new_property(range_stop)); - context.set_attr(&range_type, "step", context.new_property(range_step)); + extend_class!(context, range_type, { + "__bool__" => context.new_rustfunc(range_bool), + "__contains__" => context.new_rustfunc(range_contains), + "__doc__" => context.new_str(range_doc.to_string()), + "__getitem__" => context.new_rustfunc(range_getitem), + "__iter__" => context.new_rustfunc(range_iter), + "__len__" => context.new_rustfunc(range_len), + "__new__" => context.new_rustfunc(range_new), + "__repr__" => context.new_rustfunc(range_repr), + "__reversed__" => context.new_rustfunc(range_reversed), + "count" => context.new_rustfunc(range_count), + "index" => context.new_rustfunc(range_index), + "start" => context.new_property(range_start), + "step" => context.new_property(range_step), + "stop" => context.new_property(range_stop) + }); } fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index 263490565d..751f6fd7f1 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -100,10 +100,12 @@ fn slice_step(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn init(context: &PyContext) { - let zip_type = &context.slice_type; + let slice_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)); + extend_class!(context, slice_type, { + "__new__" => context.new_rustfunc(slice_new), + "start" => context.new_property(slice_start), + "stop" => context.new_property(slice_stop), + "step" => context.new_property(slice_step) + }); } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 826396925c..d608aad6b5 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -629,61 +629,64 @@ pub fn init(context: &PyContext) { or repr(object).\n\ encoding defaults to sys.getdefaultencoding().\n\ errors defaults to 'strict'."; - context.set_attr(&str_type, "__add__", context.new_rustfunc(PyStringRef::add)); - context.set_attr(&str_type, "__eq__", context.new_rustfunc(PyStringRef::eq)); - context.set_attr(&str_type, "__contains__", context.new_rustfunc(PyStringRef::contains)); - context.set_attr(&str_type, "__getitem__", context.new_rustfunc(PyStringRef::getitem)); - context.set_attr(&str_type, "__gt__", context.new_rustfunc(PyStringRef::gt)); - context.set_attr(&str_type, "__ge__", context.new_rustfunc(PyStringRef::ge)); - context.set_attr(&str_type, "__lt__", context.new_rustfunc(PyStringRef::lt)); - context.set_attr(&str_type, "__le__", context.new_rustfunc(PyStringRef::le)); - context.set_attr(&str_type, "__hash__", context.new_rustfunc(PyStringRef::hash)); - context.set_attr(&str_type, "__len__", context.new_rustfunc(PyStringRef::len)); - context.set_attr(&str_type, "__mul__", context.new_rustfunc(PyStringRef::mul)); - context.set_attr(&str_type, "__new__", context.new_rustfunc(str_new)); - context.set_attr(&str_type, "__str__", context.new_rustfunc(PyStringRef::str)); - context.set_attr(&str_type, "__repr__", context.new_rustfunc(PyStringRef::repr)); - context.set_attr(&str_type, "format", context.new_rustfunc(str_format)); - context.set_attr(&str_type, "lower", context.new_rustfunc(PyStringRef::lower)); - context.set_attr(&str_type, "casefold", context.new_rustfunc(PyStringRef::casefold)); - context.set_attr(&str_type, "upper", context.new_rustfunc(PyStringRef::upper)); - context.set_attr(&str_type, "capitalize", context.new_rustfunc(PyStringRef::capitalize)); - context.set_attr(&str_type, "split", context.new_rustfunc(PyStringRef::split)); - context.set_attr(&str_type, "rsplit", context.new_rustfunc(PyStringRef::rsplit)); - context.set_attr(&str_type, "strip", context.new_rustfunc(PyStringRef::strip)); - context.set_attr(&str_type, "lstrip", context.new_rustfunc(PyStringRef::lstrip)); - context.set_attr(&str_type, "rstrip", context.new_rustfunc(PyStringRef::rstrip)); - context.set_attr(&str_type, "endswith", context.new_rustfunc(PyStringRef::endswith)); - context.set_attr(&str_type, "startswith", context.new_rustfunc(PyStringRef::startswith)); - context.set_attr(&str_type, "isalnum", context.new_rustfunc(PyStringRef::isalnum)); - context.set_attr(&str_type, "isnumeric", context.new_rustfunc(PyStringRef::isnumeric)); - context.set_attr(&str_type, "isdigit", context.new_rustfunc(PyStringRef::isdigit)); - context.set_attr(&str_type, "isdecimal", context.new_rustfunc(PyStringRef::isdecimal)); - context.set_attr(&str_type, "title", context.new_rustfunc(PyStringRef::title)); - context.set_attr(&str_type, "swapcase", context.new_rustfunc(PyStringRef::swapcase)); - context.set_attr(&str_type, "isalpha", context.new_rustfunc(PyStringRef::isalpha)); - context.set_attr(&str_type, "replace", context.new_rustfunc(PyStringRef::replace)); - context.set_attr(&str_type, "isspace", context.new_rustfunc(PyStringRef::isspace)); - context.set_attr(&str_type, "isupper", context.new_rustfunc(PyStringRef::isupper)); - context.set_attr(&str_type, "islower", context.new_rustfunc(PyStringRef::islower)); - context.set_attr(&str_type, "isascii", context.new_rustfunc(PyStringRef::isascii)); - context.set_attr(&str_type, "splitlines", context.new_rustfunc(PyStringRef::splitlines)); - context.set_attr(&str_type, "join", context.new_rustfunc(PyStringRef::join)); - context.set_attr(&str_type, "find", context.new_rustfunc(PyStringRef::find)); - context.set_attr(&str_type, "rfind", context.new_rustfunc(PyStringRef::rfind)); - context.set_attr(&str_type, "index", context.new_rustfunc(PyStringRef::index)); - context.set_attr(&str_type, "rindex", context.new_rustfunc(PyStringRef::rindex)); - context.set_attr(&str_type, "partition", context.new_rustfunc(PyStringRef::partition)); - context.set_attr(&str_type, "rpartition", context.new_rustfunc(PyStringRef::rpartition)); - context.set_attr(&str_type, "istitle", context.new_rustfunc(PyStringRef::istitle)); - context.set_attr(&str_type, "count", context.new_rustfunc(PyStringRef::count)); - context.set_attr(&str_type, "zfill", context.new_rustfunc(PyStringRef::zfill)); - context.set_attr(&str_type, "ljust", context.new_rustfunc(PyStringRef::ljust)); - context.set_attr(&str_type, "rjust", context.new_rustfunc(PyStringRef::rjust)); - context.set_attr(&str_type, "center", context.new_rustfunc(PyStringRef::center)); - context.set_attr(&str_type, "expandtabs", context.new_rustfunc(PyStringRef::expandtabs)); - context.set_attr(&str_type, "isidentifier", context.new_rustfunc(PyStringRef::isidentifier)); - context.set_attr(&str_type, "__doc__", context.new_str(str_doc.to_string())); + + extend_class!(context, str_type, { + "__add__" => context.new_rustfunc(PyStringRef::add), + "__contains__" => context.new_rustfunc(PyStringRef::contains), + "__doc__" => context.new_str(str_doc.to_string()), + "__eq__" => context.new_rustfunc(PyStringRef::eq), + "__ge__" => context.new_rustfunc(PyStringRef::ge), + "__getitem__" => context.new_rustfunc(PyStringRef::getitem), + "__gt__" => context.new_rustfunc(PyStringRef::gt), + "__hash__" => context.new_rustfunc(PyStringRef::hash), + "__lt__" => context.new_rustfunc(PyStringRef::lt), + "__le__" => context.new_rustfunc(PyStringRef::le), + "__len__" => context.new_rustfunc(PyStringRef::len), + "__mul__" => context.new_rustfunc(PyStringRef::mul), + "__new__" => context.new_rustfunc(str_new), + "__repr__" => context.new_rustfunc(PyStringRef::repr), + "__str__" => context.new_rustfunc(PyStringRef::str), + "capitalize" => context.new_rustfunc(PyStringRef::capitalize), + "casefold" => context.new_rustfunc(PyStringRef::casefold), + "center" => context.new_rustfunc(PyStringRef::center), + "count" => context.new_rustfunc(PyStringRef::count), + "endswith" => context.new_rustfunc(PyStringRef::endswith), + "expandtabs" => context.new_rustfunc(PyStringRef::expandtabs), + "find" => context.new_rustfunc(PyStringRef::find), + "format" => context.new_rustfunc(str_format), + "index" => context.new_rustfunc(PyStringRef::index), + "isalnum" => context.new_rustfunc(PyStringRef::isalnum), + "isalpha" => context.new_rustfunc(PyStringRef::isalpha), + "isascii" => context.new_rustfunc(PyStringRef::isascii), + "isdecimal" => context.new_rustfunc(PyStringRef::isdecimal), + "isdigit" => context.new_rustfunc(PyStringRef::isdigit), + "isidentifier" => context.new_rustfunc(PyStringRef::isidentifier), + "islower" => context.new_rustfunc(PyStringRef::islower), + "isnumeric" => context.new_rustfunc(PyStringRef::isnumeric), + "isspace" => context.new_rustfunc(PyStringRef::isspace), + "isupper" => context.new_rustfunc(PyStringRef::isupper), + "istitle" => context.new_rustfunc(PyStringRef::istitle), + "join" => context.new_rustfunc(PyStringRef::join), + "lower" => context.new_rustfunc(PyStringRef::lower), + "ljust" => context.new_rustfunc(PyStringRef::ljust), + "lstrip" => context.new_rustfunc(PyStringRef::lstrip), + "partition" => context.new_rustfunc(PyStringRef::partition), + "replace" => context.new_rustfunc(PyStringRef::replace), + "rfind" => context.new_rustfunc(PyStringRef::rfind), + "rindex" => context.new_rustfunc(PyStringRef::rindex), + "rjust" => context.new_rustfunc(PyStringRef::rjust), + "rpartition" => context.new_rustfunc(PyStringRef::rpartition), + "rsplit" => context.new_rustfunc(PyStringRef::rsplit), + "rstrip" => context.new_rustfunc(PyStringRef::rstrip), + "split" => context.new_rustfunc(PyStringRef::split), + "splitlines" => context.new_rustfunc(PyStringRef::splitlines), + "startswith" => context.new_rustfunc(PyStringRef::startswith), + "strip" => context.new_rustfunc(PyStringRef::strip), + "swapcase" => context.new_rustfunc(PyStringRef::swapcase), + "title" => context.new_rustfunc(PyStringRef::title), + "upper" => context.new_rustfunc(PyStringRef::upper), + "zfill" => context.new_rustfunc(PyStringRef::zfill), + }); } pub fn get_value(obj: &PyObjectRef) -> String { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index d8bdda0203..f3391a7e9a 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -909,7 +909,7 @@ mod tests { #[test] fn test_add_py_integers() { - let mut vm = VirtualMachine::new(); + let vm = VirtualMachine::new(); let a = vm.ctx.new_int(33_i32); let b = vm.ctx.new_int(12_i32); let res = vm._add(a, b).unwrap(); @@ -919,7 +919,7 @@ mod tests { #[test] fn test_multiply_str() { - let mut vm = VirtualMachine::new(); + let vm = VirtualMachine::new(); let a = vm.ctx.new_str(String::from("Hello ")); let b = vm.ctx.new_int(4_i32); let res = vm._mul(a, b).unwrap(); From 66adc25201bbd08659684c7abaac75d7f628c568 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 21 Mar 2019 13:19:29 +0000 Subject: [PATCH 002/884] objtype - replace AttributeProtocol access with class_get_attr. --- vm/src/function.rs | 11 ++++ vm/src/obj/objtype.rs | 119 ++++++++++++++++++++++++++---------------- 2 files changed, 85 insertions(+), 45 deletions(-) diff --git a/vm/src/function.rs b/vm/src/function.rs index c5ec06cb1c..7159427e56 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -35,6 +35,17 @@ impl From for PyFuncArgs { } } +impl From<(&Args, &KwArgs)> for PyFuncArgs { + fn from(arg: (&Args, &KwArgs)) -> Self { + let Args(args) = arg.0; + let KwArgs(kwargs) = arg.1; + PyFuncArgs { + args: args.clone(), + kwargs: kwargs.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), + } + } +} + impl PyFuncArgs { pub fn new(mut args: Vec, kwarg_names: Vec) -> PyFuncArgs { let mut kwargs = vec![]; diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index d5648f07be..c54bce2b2d 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -2,10 +2,10 @@ use std::cell::RefCell; use std::collections::HashMap; use std::fmt; -use crate::function::PyFuncArgs; +use crate::function::{Args, KwArgs, PyFuncArgs}; use crate::pyobject::{ - AttributeProtocol, FromPyObjectRef, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, - PyRef, PyResult, PyValue, TypeProtocol, + FromPyObjectRef, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyRef, PyResult, + PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -115,6 +115,42 @@ impl PyClassRef { fn prepare(_name: PyStringRef, _bases: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { vm.new_dict() } + + fn getattribute(self, name_ref: PyStringRef, vm: &VirtualMachine) -> PyResult { + let name = &name_ref.value; + trace!("type.__getattribute__({:?}, {:?})", self, name); + let mcl = self.type_pyref(); + + if let Some(attr) = class_get_attr(&mcl, &name) { + let attr_class = attr.type_pyref(); + if class_has_attr(&attr_class, "__set__") { + if let Some(descriptor) = class_get_attr(&attr_class, "__get__") { + return vm.invoke( + descriptor, + vec![attr, self.into_object(), mcl.into_object()], + ); + } + } + } + + if let Some(attr) = class_get_attr(&self, &name) { + let attr_class = attr.type_pyref(); + if let Some(descriptor) = class_get_attr(&attr_class, "__get__") { + let none = vm.get_none(); + return vm.invoke(descriptor, vec![attr, none, self.into_object()]); + } + } + + if let Some(cls_attr) = class_get_attr(&self, &name) { + Ok(cls_attr) + } else if let Some(attr) = class_get_attr(&mcl, &name) { + vm.call_get_descriptor(attr, self.into_object()) + } else if let Some(getter) = class_get_attr(&self, "__getattr__") { + vm.invoke(getter, vec![mcl.into_object(), name_ref.into_object()]) + } else { + Err(vm.new_attribute_error(format!("{} has no attribute '{}'", self, name))) + } + } } /* @@ -136,7 +172,7 @@ pub fn init(ctx: &PyContext) { .create(), "__repr__" => ctx.new_rustfunc(PyClassRef::repr), "__prepare__" => ctx.new_rustfunc(PyClassRef::prepare), - "__getattribute__" => ctx.new_rustfunc(type_getattribute), + "__getattribute__" => ctx.new_rustfunc(PyClassRef::getattribute), "__instancecheck__" => ctx.new_rustfunc(PyClassRef::instance_check), "__subclasscheck__" => ctx.new_rustfunc(PyClassRef::subclass_check), "__doc__" => ctx.new_str(type_doc.to_string()), @@ -218,15 +254,14 @@ pub fn type_new_class( ) } -pub fn type_call(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { - debug!("type_call: {:?}", args); - let cls = args.shift(); - let new = cls.get_attr("__new__").unwrap(); - let new_wrapped = vm.call_get_descriptor(new, cls)?; - let obj = vm.invoke(new_wrapped, args.clone())?; +pub fn type_call(class: PyClassRef, args: Args, kwargs: KwArgs, vm: &VirtualMachine) -> PyResult { + debug!("type_call: {:?}", class); + let new = class_get_attr(&class, "__new__").expect("All types should have a __new__."); + let new_wrapped = vm.call_get_descriptor(new, class.into_object())?; + let obj = vm.invoke(new_wrapped, (&args, &kwargs))?; if let Ok(init) = vm.get_method(obj.clone(), "__init__") { - let res = vm.invoke(init, args)?; + let res = vm.invoke(init, (&args, &kwargs))?; if !res.is(&vm.get_none()) { return Err(vm.new_type_error("__init__ must return None".to_string())); } @@ -234,45 +269,39 @@ pub fn type_call(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { Ok(obj) } -pub fn type_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (cls, Some(vm.ctx.object())), - (name_str, Some(vm.ctx.str_type())) - ] - ); - let name = objstr::get_value(&name_str); - trace!("type.__getattribute__({:?}, {:?})", cls, name); - let mcl = cls.typ(); - - if let Some(attr) = mcl.get_attr(&name) { - let attr_class = attr.typ(); - if attr_class.has_attr("__set__") { - if let Some(descriptor) = attr_class.get_attr("__get__") { - return vm.invoke(descriptor, vec![attr, cls.clone(), mcl]); - } - } +fn class_get_item(class: &PyClassRef, attr_name: &str) -> Option { + if let Some(ref dict) = class.as_object().dict { + dict.borrow().get(attr_name).cloned() + } else { + panic!("Only classes should be in MRO!"); } +} - if let Some(attr) = cls.get_attr(&name) { - let attr_class = attr.typ(); - if let Some(descriptor) = attr_class.get_attr("__get__") { - let none = vm.get_none(); - return vm.invoke(descriptor, vec![attr, none, cls.clone()]); - } +fn class_has_item(class: &PyClassRef, attr_name: &str) -> bool { + if let Some(ref dict) = class.as_object().dict { + dict.borrow().contains_key(attr_name) + } else { + panic!("All classes are expected to have dicts!"); } +} - if let Some(cls_attr) = cls.get_attr(&name) { - Ok(cls_attr) - } else if let Some(attr) = mcl.get_attr(&name) { - vm.call_get_descriptor(attr, cls.clone()) - } else if let Some(getter) = cls.get_attr("__getattr__") { - vm.invoke(getter, vec![mcl, name_str.clone()]) - } else { - Err(vm.new_attribute_error(format!("{} has no attribute '{}'", cls, name))) +// This is the internal get_attr implementation for fast lookup on a class. +pub fn class_get_attr(zelf: &PyClassRef, attr_name: &str) -> Option { + let mro = &zelf.mro; + if let Some(item) = class_get_item(zelf, attr_name) { + return Some(item); } + for class in mro { + if let Some(item) = class_get_item(class, attr_name) { + return Some(item); + } + } + None +} + +// This is the internal has_attr implementation for fast lookup on a class. +pub fn class_has_attr(zelf: &PyClassRef, attr_name: &str) -> bool { + class_has_item(zelf, attr_name) || zelf.mro.iter().any(|d| class_has_item(d, attr_name)) } pub fn get_attributes(cls: PyClassRef) -> PyAttributes { From 909b38e774b880c3bad23c7dfef6a02029b9cd60 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 21 Mar 2019 13:20:16 +0000 Subject: [PATCH 003/884] Adapt objobject to avoid AttributeProtocol. --- vm/src/obj/objobject.rs | 72 +++++++++++++++++++++++------------------ 1 file changed, 41 insertions(+), 31 deletions(-) diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 86c5469022..98700aec3a 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -1,11 +1,12 @@ use super::objlist::PyList; +use super::objmodule::PyModule; use super::objstr::{self, PyStringRef}; use super::objtype; use crate::function::PyFuncArgs; use crate::obj::objproperty::PropertyBuilder; use crate::pyobject::{ - AttributeProtocol, DictProtocol, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, - PyRef, PyResult, PyValue, TypeProtocol, + DictProtocol, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyResult, PyValue, + TypeProtocol, }; use crate::vm::VirtualMachine; @@ -18,8 +19,6 @@ impl PyValue for PyInstance { } } -pub type PyInstanceRef = PyRef; - pub fn new_instance(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { // more or less __new__ operator let cls = args.shift(); @@ -97,28 +96,27 @@ fn object_hash(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn object_setattr( - obj: PyInstanceRef, + obj: PyObjectRef, attr_name: PyStringRef, value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { trace!("object.__setattr__({:?}, {}, {:?})", obj, attr_name, value); - let cls = obj.as_object().typ(); + let cls = obj.type_pyref(); - if let Some(attr) = cls.get_attr(&attr_name.value) { - let attr_class = attr.typ(); - if let Some(descriptor) = attr_class.get_attr("__set__") { + if let Some(attr) = objtype::class_get_attr(&cls, &attr_name.value) { + if let Some(descriptor) = objtype::class_get_attr(&attr.type_pyref(), "__set__") { return vm - .invoke(descriptor, vec![attr, obj.into_object(), value]) + .invoke(descriptor, vec![attr, obj.clone(), value]) .map(|_| ()); } } - if let Some(ref dict) = obj.as_object().dict { + if let Some(ref dict) = obj.clone().dict { dict.borrow_mut().insert(attr_name.value.clone(), value); Ok(()) } else { - let type_name = objtype::get_type_name(&obj.as_object().typ()); + let type_name = objtype::get_type_name(obj.type_ref()); Err(vm.new_attribute_error(format!( "'{}' object has no attribute '{}'", type_name, &attr_name.value @@ -126,23 +124,20 @@ fn object_setattr( } } -fn object_delattr(obj: PyInstanceRef, attr_name: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { - let cls = obj.as_object().typ(); +fn object_delattr(obj: PyObjectRef, attr_name: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + let cls = obj.type_pyref(); - if let Some(attr) = cls.get_attr(&attr_name.value) { - let attr_class = attr.typ(); - if let Some(descriptor) = attr_class.get_attr("__delete__") { - return vm - .invoke(descriptor, vec![attr, obj.into_object()]) - .map(|_| ()); + if let Some(attr) = objtype::class_get_attr(&cls, &attr_name.value) { + if let Some(descriptor) = objtype::class_get_attr(&attr.type_pyref(), "__delete__") { + return vm.invoke(descriptor, vec![attr, obj.clone()]).map(|_| ()); } } - if let Some(ref dict) = obj.as_object().dict { + if let Some(ref dict) = obj.dict { dict.borrow_mut().remove(&attr_name.value); Ok(()) } else { - let type_name = objtype::get_type_name(&obj.as_object().typ()); + let type_name = objtype::get_type_name(obj.type_ref()); Err(vm.new_attribute_error(format!( "'{}' object has no attribute '{}'", type_name, &attr_name.value @@ -259,28 +254,43 @@ fn object_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { ); let name = objstr::get_value(&name_str); trace!("object.__getattribute__({:?}, {:?})", obj, name); - let cls = obj.typ(); + let cls = obj.type_pyref(); - if let Some(attr) = cls.get_attr(&name) { - let attr_class = attr.typ(); - if attr_class.has_attr("__set__") { - if let Some(descriptor) = attr_class.get_attr("__get__") { - return vm.invoke(descriptor, vec![attr, obj.clone(), cls]); + if let Some(attr) = objtype::class_get_attr(&cls, &name) { + let attr_class = attr.type_pyref(); + if objtype::class_has_attr(&attr_class, "__set__") { + if let Some(descriptor) = objtype::class_get_attr(&attr_class, "__get__") { + return vm.invoke(descriptor, vec![attr, obj.clone(), cls.into_object()]); } } } - if let Some(obj_attr) = obj.get_attr(&name) { + if let Some(obj_attr) = object_getattr(&obj, &name) { Ok(obj_attr) - } else if let Some(attr) = cls.get_attr(&name) { + } else if let Some(attr) = objtype::class_get_attr(&cls, &name) { vm.call_get_descriptor(attr, obj.clone()) - } else if let Some(getter) = cls.get_attr("__getattr__") { + } else if let Some(getter) = objtype::class_get_attr(&cls, "__getattr__") { vm.invoke(getter, vec![obj.clone(), name_str.clone()]) } else { Err(vm.new_attribute_error(format!("{} has no attribute '{}'", obj, name))) } } +fn object_getattr(obj: &PyObjectRef, attr_name: &str) -> Option { + // TODO: + // This is an all kinds of wrong work-around for the temporary difference in + // shape between modules and object. It will disappear once that is fixed. + if let Some(PyModule { ref dict, .. }) = obj.payload::() { + return dict.get_item(attr_name); + } + + if let Some(ref dict) = obj.dict { + dict.borrow().get(attr_name).cloned() + } else { + None + } +} + pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes { // Get class attributes: let mut attributes = objtype::get_attributes(obj.type_pyref()); From 50437f8a75feef867c76a7bf4a36d24cb5b6013b Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 21 Mar 2019 13:21:10 +0000 Subject: [PATCH 004/884] Use class_get_attr in various places where we need the internal interface --- vm/src/builtins.rs | 6 ++---- vm/src/obj/objnone.rs | 38 +++++++++++++++++++++++--------------- vm/src/vm.rs | 16 ++++++++-------- 3 files changed, 33 insertions(+), 27 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 68e933db3e..6d3d512a79 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -19,9 +19,7 @@ use crate::obj::objtype; use crate::frame::Scope; use crate::function::{Args, OptionalArg, PyFuncArgs}; -use crate::pyobject::{ - AttributeProtocol, DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol, -}; +use crate::pyobject::{DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; #[cfg(not(target_arch = "wasm32"))] @@ -95,7 +93,7 @@ fn builtin_bin(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_callable(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - let is_callable = obj.typ().has_attr("__call__"); + let is_callable = objtype::class_has_attr(&obj.type_pyref(), "__call__"); Ok(vm.new_bool(is_callable)) } diff --git a/vm/src/obj/objnone.rs b/vm/src/obj/objnone.rs index 6bee900312..b0a790cc16 100644 --- a/vm/src/obj/objnone.rs +++ b/vm/src/obj/objnone.rs @@ -1,9 +1,9 @@ use crate::function::PyFuncArgs; use crate::obj::objproperty::PyPropertyRef; use crate::obj::objstr::PyStringRef; +use crate::obj::objtype::{class_get_attr, class_has_attr}; use crate::pyobject::{ - AttributeProtocol, IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, - TryFromObject, TypeProtocol, + IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -45,7 +45,7 @@ impl PyNoneRef { fn get_attribute(self, name: PyStringRef, vm: &VirtualMachine) -> PyResult { trace!("None.__getattribute__({:?}, {:?})", self, name); - let cls = self.typ().into_object(); + let cls = self.typ(); // Properties use a comparision with None to determine if they are either invoked by am // instance binding or a class binding. But if the object itself is None then this detection @@ -69,25 +69,33 @@ impl PyNoneRef { } } - if let Some(attr) = cls.get_attr(&name.value) { - let attr_class = attr.typ(); - if attr_class.has_attr("__set__") { - if let Some(get_func) = attr_class.get_attr("__get__") { - return call_descriptor(attr, get_func, self.into_object(), cls.clone(), vm); + if let Some(attr) = class_get_attr(&cls, &name.value) { + let attr_class = attr.type_pyref(); + if class_has_attr(&attr_class, "__set__") { + if let Some(get_func) = class_get_attr(&attr_class, "__get__") { + return call_descriptor( + attr, + get_func, + self.into_object(), + cls.into_object(), + vm, + ); } } } - if let Some(obj_attr) = self.as_object().get_attr(&name.value) { - Ok(obj_attr) - } else if let Some(attr) = cls.get_attr(&name.value) { - let attr_class = attr.typ(); - if let Some(get_func) = attr_class.get_attr("__get__") { - call_descriptor(attr, get_func, self.into_object(), cls.clone(), vm) + // None has no attributes and cannot have attributes set on it. + // if let Some(obj_attr) = self.as_object().get_attr(&name.value) { + // Ok(obj_attr) + // } else + if let Some(attr) = class_get_attr(&cls, &name.value) { + let attr_class = attr.type_pyref(); + if let Some(get_func) = class_get_attr(&attr_class, "__get__") { + call_descriptor(attr, get_func, self.into_object(), cls.into_object(), vm) } else { Ok(attr) } - } else if let Some(getter) = cls.get_attr("__getattr__") { + } else if let Some(getter) = class_get_attr(&cls, "__getattr__") { vm.invoke(getter, vec![self.into_object(), name.into_object()]) } else { Err(vm.new_attribute_error(format!("{} has no attribute '{}'", self.as_object(), name))) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index d8bdda0203..4fc0f8433e 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -29,8 +29,8 @@ use crate::obj::objstr::{PyString, PyStringRef}; use crate::obj::objtuple::PyTuple; use crate::obj::objtype; use crate::pyobject::{ - AttributeProtocol, DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, - TryIntoRef, TypeProtocol, + DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, TryIntoRef, + TypeProtocol, }; use crate::stdlib; use crate::sysmodule; @@ -277,8 +277,8 @@ impl VirtualMachine { } pub fn call_get_descriptor(&self, attr: PyObjectRef, obj: PyObjectRef) -> PyResult { - let attr_class = attr.typ(); - if let Some(descriptor) = attr_class.get_attr("__get__") { + let attr_class = attr.type_pyref(); + if let Some(descriptor) = objtype::class_get_attr(&attr_class, "__get__") { let cls = obj.typ(); self.invoke(descriptor, vec![attr, obj.clone(), cls]) } else { @@ -291,8 +291,8 @@ impl VirtualMachine { T: Into, { // This is only used in the vm for magic methods, which use a greatly simplified attribute lookup. - let cls = obj.typ(); - match cls.get_attr(method_name) { + let cls = obj.type_pyref(); + match objtype::class_get_attr(&cls, method_name) { Some(func) => { trace!( "vm.call_method {:?} {:?} {:?} -> {:?}", @@ -575,8 +575,8 @@ impl VirtualMachine { // get_method should be used for internal access to magic methods (by-passing // the full getattribute look-up. pub fn get_method(&self, obj: PyObjectRef, method_name: &str) -> PyResult { - let cls = obj.typ(); - match cls.get_attr(method_name) { + let cls = obj.type_pyref(); + match objtype::class_get_attr(&cls, method_name) { Some(method) => self.call_get_descriptor(method, obj.clone()), None => Err(self.new_type_error(format!("{} has no method {:?}", obj, method_name))), } From c67cb07d9d8430d6b22e294670bb01058c90a42e Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Fri, 22 Mar 2019 20:54:14 +0000 Subject: [PATCH 005/884] Rename class_has_item and class_has_attr. --- vm/src/obj/objtype.rs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index c54bce2b2d..27b137fb21 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -269,7 +269,8 @@ pub fn type_call(class: PyClassRef, args: Args, kwargs: KwArgs, vm: &VirtualMach Ok(obj) } -fn class_get_item(class: &PyClassRef, attr_name: &str) -> Option { +// Very private helper function for class_get_attr +fn class_get_attr_in_dict(class: &PyClassRef, attr_name: &str) -> Option { if let Some(ref dict) = class.as_object().dict { dict.borrow().get(attr_name).cloned() } else { @@ -277,7 +278,8 @@ fn class_get_item(class: &PyClassRef, attr_name: &str) -> Option { } } -fn class_has_item(class: &PyClassRef, attr_name: &str) -> bool { +// Very private helper function for class_has_attr +fn class_has_attr_in_dict(class: &PyClassRef, attr_name: &str) -> bool { if let Some(ref dict) = class.as_object().dict { dict.borrow().contains_key(attr_name) } else { @@ -288,11 +290,11 @@ fn class_has_item(class: &PyClassRef, attr_name: &str) -> bool { // This is the internal get_attr implementation for fast lookup on a class. pub fn class_get_attr(zelf: &PyClassRef, attr_name: &str) -> Option { let mro = &zelf.mro; - if let Some(item) = class_get_item(zelf, attr_name) { + if let Some(item) = class_get_attr_in_dict(zelf, attr_name) { return Some(item); } for class in mro { - if let Some(item) = class_get_item(class, attr_name) { + if let Some(item) = class_get_attr_in_dict(class, attr_name) { return Some(item); } } @@ -301,7 +303,11 @@ pub fn class_get_attr(zelf: &PyClassRef, attr_name: &str) -> Option // This is the internal has_attr implementation for fast lookup on a class. pub fn class_has_attr(zelf: &PyClassRef, attr_name: &str) -> bool { - class_has_item(zelf, attr_name) || zelf.mro.iter().any(|d| class_has_item(d, attr_name)) + class_has_attr_in_dict(zelf, attr_name) + || zelf + .mro + .iter() + .any(|d| class_has_attr_in_dict(d, attr_name)) } pub fn get_attributes(cls: PyClassRef) -> PyAttributes { From cf78d6b3eb383cb998d19c1081b5fb3b89b17261 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 22 Mar 2019 18:09:05 -0500 Subject: [PATCH 006/884] Add logo to README --- README.md | 2 ++ logo.png | Bin 0 -> 18783 bytes 2 files changed, 2 insertions(+) create mode 100644 logo.png diff --git a/README.md b/README.md index 41bde43e07..73f581539a 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ A Python-3 (CPython >= 3.5.0) Interpreter written in Rust :snake: :scream: :meta [![Contributors](https://img.shields.io/github/contributors/RustPython/RustPython.svg)](https://github.com/RustPython/RustPython/graphs/contributors) [![Gitter](https://badges.gitter.im/RustPython/Lobby.svg)](https://gitter.im/rustpython/Lobby) + + # Usage ### Check out our [online demo](https://rustpython.github.io/demo/) running on WebAssembly. diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7d85f5d81a2e9d8d4b50f34ed064b1f10fe5551d GIT binary patch literal 18783 zcma&Oc|6oz^f>;W8OFXu_Ps16TlOuKCu=DCmL=J@ku}@Ql(bqRB#Is)Qj~4VGSdi^ zkPul1ktK}CI_CYop3mp=$M3)2>s2qh?|aWZ=iGD8J!`kmTbpvQ3bR5G#9?msk1Yft z!LLY&84Z4H#0~6%ALvV#rvE@HP$2Yhs%BmijIdzLTp}Sz>FB{s~H=M;b-Nyz-4 z)An&AzsIj8jypVt*M_V{>Kd&ASrxy&VJv*}fO+uq5#Q2ly3bFaKg{>ffY42QF}}PD zriTQz+|S<^vJ()*I5V6*y=oJ4+-#Vk#pp+>qJpAcluf}hovtmn5IR!lveJFAwffgg zgoNT#t4F;pZ;Q5cCL?8Kb~$85`FHTo6GAisxN zHK+=iWyurQl%ZCJW*Nha@q#j? z9Zc2@`L_{YopClpoQVV62V*(0>xOLFLJ!@GZSqko2>xekv@fh8khJ;i;I=1|yfZ%nl9bGc{Z3_;C z+sB>qnJMBINu0d5gfUsmo|#l`5UoKH7zy&}GzoMD>nlGca4I1OxNu~g=naHg! z7tT1wkI=8C;a*76e5CHjs%XJ}0nQlL;!`W(>AhO=m}EzqB@JpO0i*-hOZFNOrpQ{n zx%~urfmX3^i5F=`6$4fu7EZoX^D#_fu zWfncF?GWxyevmBl8Sw%?iucs{mWMFP(p0C>f_Eg4oN^(ZFJ4l^6`Ax|X$*w14rQ>b z2z{=4*5=i>t=85GI2h+xPWOc}AJlbBGK;c`S`KFuQJ?hIOW6o0?LkRN6s|NVbUT}c zY2^!Jx?IR$!5!1b#@c?I(@A&P*G~VB9PzlC=JMD)vJLk+s>3kPIWKS1D>KH`3cTM5#GZ zsq0B+9p%EA6e`9Z!A*EImW0g{5t&&>6$oxD`qqS(E00N&Gslu0bd7%jc5ae=+-^YdgmUe4WocalwliEi6Mt1btWWnVQH z&Eg&GsS~=iB&G0fPtRL}rZL;f(^`_xm|}?x7i}+JV0gmVn%zTH^L%`80FM3d#I&EE zqw9>PIBa5(N-_##30EK7{cCZd--l)CSd6I2^UsH3j=G}@R~ig~?C{lIIK}eYm5e>( z*`L5p5GL?%2kW-|k*`=qlE=9+q{om~E!Y`$R(Fz?gx-*Dr0*VJ^?PpPh)N5C=Q}26 z3+KA61d8%(l%gllG?m{wJZ-dp`h62j{$X7#_7NhMsO(W?z&;$WcY39(N{gI-D(4(M z7i`E$yI*r_^?gwONFES=`HG!Y8*M@VGxtq*V|jY2iHNwW!+U=Y7T%aM)Y63C9Z9;K zLy{qJ;bY1-!Eosx!JSE$(z(gg!ws9uQK@k4NEX7#_NqFZPvSkCJYs079S$#%IMTQO zn;c00o07f3+Cr(mj#=5iy1=gN78n@SEK*#0CZUzN8@@ns_ArJjLum@MwT!u33le=Ma)&#Pr^3|jL z`2Xr(_RZ(N(M5tI+5mx-{+JM0KxMxGi4r`^zJW-)6qdV0cb1r z!nsMP(84>6+Rvf>Osz_3fEd0)KFdKOb=s4s{z=Mkr_EGI(OR|WdI{63PN);irecO% z`bM%_+tmZ3G>tS$bb7#_L*Ut+LbqNjxC!RGpzl3v13GTaz*h8*QGaFhxER)F7 z%5wPY%-=@hp~y?JHD`u~F1%3(f#b4HEaWSO!tjvcbi(WJ*y%qTvj z_bEQU&umk1wLZ)kZ}4Q<9ukI^@ENF1q*~Rj&t{sbP%P8)0B~utzHe-yCs>>1(l_N? z`Eq6~`LvJspcNWKub_>DUHZc%73TC154p~D2JgvdJ?Y{6{xa?m?zWx(dAB_Ag>qNU zLaxH)cnF|ArLo^JOwB-T%7rYf+Z*t8KN2XK`|+EjDtRLYT7;*({CZAZ`=k~lv``+> z`4^JsQARu$7J=Q#pAy5FJS+)Q>E2FfC3dQlt_EV)nVk3Mm#fBo!)2`>8+^;hnUFOH z6g*e~=SMEh81lz4P4PEPU`&0=62@J1orv!~b?P}0Ew|X9t#)&ZLeN z*p4MD#)?pdi58`0G5SBRM8(AP?R-`J)D$p01DwdC~fkM!O4|7;~jH-9# zWJmQIJGVT)Kld))U8@k`faIl}OkUUu!#DA;w8BbuR)7xGhx`^&LO)Q0Dt9}G&^AJ) zH|W?@7DlL8{2@y8AMM5&VM+QRl}U1pst*nH7v{aIZ=e!h&E*3?_1N_0oQLQakvku9nu!x&R0XAA&0G7 z@G*)ADY)uZoePK2%-D^)!j)cPBqT+Y`~c47->^={euvI`z54gNY)AZtVeHhuZ%N_! zyP@=NnP-fQZ(zA5k#q)ij@MkLe)q4i<0^v(3=34oVFNFb6Gvo@Kx5pd8^&U|iJNkg z!jQd28kV2xQOr2pdB^N58M;7XO2z$P}V6H zac0vue>egAcnChUB7AAApPOXUN70nM|H9W#GCyb5XEyU0+>S*VpbJ0FIqg{pUwhIM zeX6=)vhh#8NY6zPLv?(OYqIDRU8#PprRBlu>RUl{E;0kTj<0arS+_y?L&Vn$dZ^;X z09rKZDJc)HrL&l4GVT@}*>b9X`Z=+XDK8s;YR1R%J8utdyE1JODqaNd8R-lqo1~P! zE5lKP-ad?JeWn6Ek2l#*;NjnS{0~9=xH&3KE`%jf55s?5S@HqGi0>PXi!u3(^VI5d zPx<1B6{UFHA&#x;*+1g#nZi@O@nR}>F~XF-l%wfbPIo_U0PW2qm@_4KAD#va{Gj<^ z+w4`Ts*kIX2Xf_hTK(|QkO<4(-j=jGEU|Lr14~K)BnBmB*DzA~DKW+4{u5B~pHTW9 z^a~jdq-(7-D5%I{aKP_JoQT)toxN7s$woPBRb!u()E8RI{hB}6ShD7ai_A^DssExM znmg-lL{O|Lc_c=IJ;r237i8L-wQ#n?Ynb&X3y%NDzgx$v0kxT--)pJfUs!*iBQ#@L zZHNe?XQ6Lmvs|cuB|d`Cs5+SM4gk|&9^aTnQGtAP|nVCtPNR*UR2i1 zb>x2K1-+@~;}#8fFc;w-?SI3i3%PesqCHPg*(bO^Ld98{@KsVQ&X{>GRwgY83aBQp zE{KpaSlF0|&~MA(Bg5v}7TbAo``|3I)ZyKSJ%*OfgC_#n;@^GmuT56em|1-OI8!!9 z{$qsT36{~F%KAgEkgw7+OemD?h(-jEeDWMzl(MY7vYglO@66yYm%Tr>->VbzfquF_ z5hI&jzIvvcaKYx0BI8O0WeIM3%vZA@^53d>iaq({TP2zvVWkYJ01N@Os>W)`t>X$jV1<`*YOn=axJza2bDXdpkTn?v0!J}};E}wGN z(t^-nIeJA40wiD3g0~O?hd7s?*B`O{bc&ItBh?S9LeEpWVFj=7vAh!;mKT#y*=4K$ zbpI6Emnaz6j+Z_6_;h8Az02-taz5SmJppaBbKvs@r`R=0bJ#6!vz!}E!perIC6%1T zMQ4|%Z|=f>NvbClP`EpTG$Kbxutw>9OPf*ryWP|?VW>oLg5u~%R_!~m>DO*pntWPR z6I)@VhNz!mYlzv@B=Z}6f{L$c!WrDwlt)1trEj8meG)7^g&_OcG(*N_ly?7|a?VQ_ zMQ5*SI4rLoi;!;IAqr><397`lJM*5L2#bKKp})Wv%mbbMpY7 zWQQQ>@*N^0VPH=A2TjHw%Ic%p0WH8tS^;uysf>g}*YngM`OE8uS(sCO0KxKl(hZW1 zCN@n*0O8oEg)o#J8#cAwnP+@MYEK!YXZX6gy;Hc$4#|I*hcT2rl82+QyvXB}k|ZMA z7;_Z(8n1BI;=Kkm_=7fiZul4MTI5sl^Bq19*MwWgE!DnM6kk38Xr#_zxv52ffj^%x ztsuf6eiYZpwl8%4WC6^DGY+BK_BAsMAfr(e#!bvx%U4AS{DhG*;EZ}6r4c#VQ(M-C9(5-*r;ia>p8i0n7zh|dI|a?y)YfNITMTYQg>JjL?Z0c= zj+^WZGIg%@CDpDC(I$Tt@D=Q-lDn_I!waR9)XDwYf9pU(X(kCl>fgKJQBqTqHE;jV zA>I}4MpkEWxCs@flN@4|7}b%QGikJkJQEZwflV0td}@La_G3+B!}{a>BYf~j=48+H zFND;B9`jyT{Rh>0*D`EwbrbxI%$$pyO3J6J)$q&sPTc0Uzb=#h?#74RXp~O)=aQgl z^@v$57)?uioGspQ&D{VSG6TlG! zcPvVL>pLe#RVLX8?x^pPxG~1MZEi`AfY)In4jEWFuNk%n**9yDT8vUL+UNWm>U~{w zXl=TWez0uw)_5Wem)l4M_+Q_I3LSxaC2X<|3u0uwPiD#6aK`ZwaxlGHO`dJNxEu|#z|{}le8rHM^s~V;)Mt-ge**3 zoF-hcB3NkDIFU?LmpXJIyW_F+3g(_5<1J7M`gA)7ZJRQIDzFL;xpq;>+%_=Z7(rpV%nYn#8 z@Rv3(#?(}8az3D}FYn)<>_+wJK*zo$i6lPx+@V3jo`1%+$Ew1M5yN~CZ1He9q|{W0 z1P-RcmQW@;H29U~&)cvkHWg{lFV0}i_^RZyz$Tw01F)$-4tFcX99qc-Sl^6F^)+D2 zLKNE;0EH?-DvxOLtY{G}x*M5QS5pm1QEF3sD&_7?<#iugxf6%-mNVgx>o|KIEC7^t zT2kjK(q5vH^Fu>-v@B0fb7Lf_30{sYt%;M04`5CjCqX89{%|NKiL}8_3*aD+cnXDTs+fM zp$D<+Sj8#L8Y{;V=_hDwl+x#h25nn~kP=VY<)jlN~k(ymC8)_Uiq8KTnY$e&V~<1XC8 zoN)qEZ%CYc8#b#551GPK=j+7KV%;=oJMlg-PXKqF9oXY(N$W+%giV_6i<5rgV~vn! zPea34n9k=T3eqFF9Hvm zLFjRLV|QP;Jl!u$rYhdk%vnz5*%{(9VRG2bQ!_LFXo{Yz6Aw66f?W#n#P>kY2_>)z z#Wcn3lk48}RZ(qVG+cT5?D@6}-h>iIDZ-mH-?;jE>_JZrNmcr;;W?inng~uATCLd; zu+s8&YDX4e2fwh^m8&I+JT%odq}Zgi71%q+x*&m7^2?0EKxXyin{Xu`d1C{f_$Ljb z4gw6h^})bL!+B`nY4cr1)!wPcvpwAtvx10%rT|uW{!R-;{cLB+x*oa*qn> zJ_?i+mE8y2mLFY|M-2L&k~8y=~Xz_QIi9SsS#?74C*+^_hMc4o~>jChTj_t|7y z<%BurQJw^x68ShzxkEQ}l^qAR38zOg-~F~(Bh0v^@9n!?6ha@dkbI76IQqNMP}*QK zElv62)79%qjXUEb!nbl#T;2#w&m>bE!`T#2MiB*AF{&EnsACDm5i06#XebOnu1-Gx zHTH*>1bO9}ANl2-&b%GNeFw^)sML%-h0oCW)^4H@;nruv?y9k7WFipoEBI&U5vbF~ z>?l)IxLlRoL;H@^`&%dpOMgPYzYQAqhZR!82jhlZ)2_LmCFJ+oDBiduh%SR_aXTqT zZ*xhFy{>u1KB?A=w6^hgURIO)pX)sJd{TKvhwa9OJSHA!Z@JtvK7!~!v4WzkG+ytB zsRpCad>T*&28$s;jR3D8I_1=-yT_^{TThG&Q(pY|o?EMUnh&-tib(qRYf}s6Fg~)zE+OM+W2qR z_olN$K&%>>TA361SKl}vTf+6@C4SHx@OVmhN;JbB)Xy9XS%uCeX*s2AO*i3e;$6TF zuUVi@-h<_v>c zNYR?u+c#1wb}#K6(&qJozM7fBdWcoILguo*tK8eU-Ja$GraxEFVRgdbweVlBKI*3ugX8_!Q7k8QG=zkDG%iWMwS>bqf ze?fLkaV^nOisJ+`O?W$ctP_~h;$Cv(Ge6fNjD{Lzk7Gk^m8Z&FiHxn@GVOjI(j)QE zHaFGSD|Jxtz6ZsY!V83LHrH7AF-YolAU=uUTu7T&;e_(-C0k14!o9on%aCB;dbqUc zg#-dvB8p+6_JSo$3OA*5a)Y7NmC%#E!Iy!d=)0Z=tQVOyx<9HDHR_yp$c2R_3Aony zEc0OeDifyISMa_y#Sk~?%xbO**?*hIa#B4cLp4$rncNu)i$E~cM@PCMk-&lJ>Lzl= zodA)fdmoGN%A>Sr^42?+b6OBe!j6gdnve8{W?EU%{TRs8n+CC|RKs-o%~cl4Hn0ZS za?e!0OPv&yXlnP=5&r*k4Mk7&1Z`0_8kf@hN>PysiVYg@H=Q5(9 ziqUD$i?`tr#f5;!ZYADJXltNZ+`W&NRtC@_S++JmXbw%IC4H9jDSqBO^&M;NDKPr35N*jCZAs z?(G`OJ8V9O=K+v?@nHc5Vf^?N%Q?@55F>9LvY6zP-7TpTClyYS@{PXRn(>X&G7#1% z16w~-amRMJE{C=(-EEfL=t3gr!!V)LSzSt=^}Z$F51NS2y5aF2=s`*A4;;Gqy(RL- z)HtLizORLheN8$oA(7zMJ6x7pn0MWt zZ+0@Cg1zWwG;v^No;HC&bnCUSaf~rge~P)M-|Xn|QrL}T^!T0VT%C7GPKwv%L-U|K#TfEI>KPVK_b`CyIIQjzTlu#D4E>IaPjfyP;<8V(=iib3+yM{utssq+lH_3IggB4L^(hC5W*KTYC1ozWGlrXTIM&ipc|j7@EW z&QptS-MGuiIM$)5-i2QUR)F}M!?x{dbS_IXpF%d#IRzZT>zFlqF-a)IfeWL7xehRV zb6lxdx17%OnV1d2+t<_SBKX){9>J_m=25!-lIN9;Rj{gfx`plGoTb-`?H{u z2p?3X_&?c}bBKZhHumxmUTyPm8tGiouHqtNGPsdDC4iRiVWbrnIz2W+BNGAPj;G8V zu`~GVws9Eq)HhS!u6c8u`J5F-s0^9*3Z(*VGcs}v92?;^3I03*UTVCzEqWR^AM8ZaK3z@`R}_ArZv3M8!q zeYr?D#`xv^`qRd_LzKiWNr$u0aF1%uV+2VEU=s{yCw^|!-X#u)Vr?BBTD%c}T$xZt z#AUqJUQ~GBsdW0Y%9AXD{z8JkNXqXKlGgAl^Y7XImi9jQ?ZG!zNFx_7j52!T50ycy z;1u66{dFQ4V@nrVq553BEBJykn%p!D^$?(5G=*&)KDFPBuHn4R2_>LvlPZ~_^GxX$ ztwcA}T|0;Azi=bv(!=*r$jMrE+d=amwB3Y4f1LlXB- zf3$>HIG{9=fkAgcJ(@sLH3MFeu-MsQ#$u~|M#8wNnS|#51TL-P%6FUakVRqVQ<*8E?7L$9b>F{+If{l z5|5z#HE1cQXD5sSpxuGt!S}yFlnt1FAJ}-(6|Vk}aKzr^ToqH#Yk+s;$f&EdF7%ty z-*j6~bl>teMF~f`2(hq32^2bvkH^6c>{+Fi?9kdS9Ma4;XJiAxy{cvh(>PA`0HPit z0-&A zM|d%LxfP8hw~!-aRz?zTjMq(<1<q4zL`9o2Q2vreWmn|tj2>SSxpc!79j}n?2+LXu%C<+6vB0HJ&=*SNn}#E{=jSn zIsA17;FSrfOqAD6%6q*9P(B?%AwV_PrHUu-gam`+TGoSGpfo6Pq4o=p+3nf7t<*kZ zy_p#_{9On~7uaV7Wbd9LRfK~8pgc4{A?F$Nx*|co=Xz!q57f-^6&UC`3 zz2t*TsF-GX>5pj@I8$}{rZ3pR`TCy0o*y*EC@Hgc6Z?sV_PUlcUxaWD6v{dTMr)-0S_7du zq5*CTf>{pn>J9rRW(cq+GkzQ_(Spv^u^Txv%(EMvL6YpVl|Kj**o;&m>G3Q2Op+%N zUK+0uP%OgCoC^x@TjyVqkto)w= z+bheLS$0vR^;{_(1dwt30j!Y;9%#yTzV?5|ME-~PWhOwpiC1$DM4l!JC>mcuf{iu5 z033`go|ssEt-g!lDdyn;Ea5&FDr3wo_B=3CARxQ{yP0ZBAJ0a@Hy-H|V3j0B0QcbB zp7XWj4CiZ?84rkFL1tzJ=E}^+R?C<<0!m1!LjUn*_o=5UR@?*Gy@mV#eE2loOY0a> zJpJYX6(Fx>pCapI<{Onj@&ht+{y$b+-K=HUe*)YhCl&v9_#=7g{v>n1Sx76W~ps*5dJ0JUijFIWQV*hO}lWH4xoLy;pSD-4w`X1l3^ zeA518(Q4X$kqnqW-+mUC)PaapxNaXEgXV1e(UW+Q^ep$i5RT8_CHJIg8F)t*o5hE_ zRYQ27@2;r~OgQj>Dz$PqOPyzi!tEZV3vl-uvlj}2#0*~U3*&`yPge?uckCsy4{&JjYC#)vrv;E`Ugm@wG7bb_ zq^6NB#I@Dpb@4K}?;*%r>Q*qM8CSjpl~!U@CU#>ZpCH2O*wGNK6T@ND+7j_T)1G63 z@mxK7L7YJk@*`x#^a}N{QIDz#4)YZNw?5PR`Yw!qfF@`S4m5nR6@(-U8NaUBjfU$} z`N5nzYvJ3h*eS&eBFi9 zJseIuyL*~>0XkmqQ~w6o716@ zx9slWb5Kk@fUN<$kn&h+qlDd=F!kW_vbVpCJO6?7E8(3UqV&zW2if~#*xu+FYvdCs ztl{vDlS&BXhfH{_UHh$qe}-Ak4;-&cpL-8`>ds_K&Zf~P846hkT+$R55hMDvP9dDs zCy>9A66K#dL$X2{^$EkdkFfM2q0dD*JQNY_zlMi{@rK~+Nf;X6DqCjtD`$jPY4;d+ zF+&EOcr$iUw6YxEq-Z>K&yuu7kZ%o`nC|CD*>7j$g4_b8|B_ls1|-cCy|{6Nz9w}w zA<&2=m@a`fxSzQ-)whUz6#sjEec>8ypE&K$5j_C;P$Q597f~r$7#x% zp^WfAOk)p~sUgbPXIA$`sJdQ~^(=b(t-Lsh; zBY{y7uYId8yLjdYdYe(D8Y)_{ir}YQ0+o)IEN6YSzY1!?I;4EUY`C^A-mnb#riwQZ zUh28roA<5Fw0w<5Od5}*@I3ksK zz9;HSz)k(<3DSwQu{$+HvL2?9c*xMwEJ_5>=1fBH(&yMttE?dFb@> zz;Dd-uw=96Y2MOS+OU@?`RPCG%q&Yj7sVyVOyW;cRlK)8)=c33pzz+3Yu#8*jM7>C z@*HKcu&`bahmy}|ix{`nqT{qM*=iuUaQL%v1ixejTf_*9 z-Q-!2;9@0!EMf4>tvxo9;i2zd?9X23=@yn<3ix4Qz=<%hBq+Vr{l3=F>{auiS&h9x zQ_qXr?4Nb4A*K)ndo;ancTYm+=h533GRkPrIr+UH!wN(CQb5`L8lHFg7%Sp8zhc`p z|1v3qO*sX}-?wTRoOl~f8hmtAs*TGpUhHOUB~$%d561-Wb4~pzxbs}rdoo;aY6bU# zciUZ=>5Y#VgigV4g4iq+F6n;fYU*dALnMc{Kbf9VJ1)@ag0ID0XATy!bz?(K9C}@8 z0;i>%#WuO79eT@APUnDbx1wEdv@fcWEB-YbmP>+=L3tlQ~2IP z)|xan?}DF8!=KYU*^v%?G!f>!rblaYK1W~kY`2uaukU{tJ>S3^c6M)sA$f9n_D{w_ zK-rTTO|M__$t6`s$M_-)$x+nnDSD6gl?((PUuVQO*OY#~B8}mhn2s%7VR#k25e^;m zJNgC5X4dn)Fv|x1rLnI=N?`Fv(@>vuc#|#_GRBEbDQs&}oKoz1azg_hNs{MJmcaYW zYOWJeA(tHydEioszdr19^uC=vusvLj2v!3yCn;yhi@y>y1sIyu&x0g4&_&Oi8Qi_rCHAz6e@4SK z+$&|5wAxi!^|bgR*E~V;>WD&26YI7RRgwJmR{~GNURvW$_~2nLDGUcS$!pUw=+&P< zkX>AT!*dj*TzDppLFllyBTmM*PURKz$jp{=U2%Wkmu?c=(btRVS(=yk>4}^yv}YhzobSeF20>slPIE$&ku-sp9#g zKW&o}NI7c>3b~ADcbkuc!XK8DwrKi4gviORlpJ!f=DM>bK?dWWXt$7-;eLbZRw%w2 z)yT7wPRN!j{+Wi|1Lle+Q)_^R$#MjFL?8#3;9y+RESXN2bNsPo|J$qfVNJomU&E~Y z33_z;X-c7Y&$$8ahD)JbzfTjMv{OQgXvd!o1LavOB%zd-RrQX(_TLePHX8Ng-p4JrIH-n(frR}v$Yhl(;psUac%7*38!s4yRH z-WlTUd9{6n8b7JWHXTE`UQyN%W9bbYi)IeECq*e`^LyPLTX6lu`Jz@1H@_fCQ29%{yGoh|xo3 zW|I~VZ6^ybg6HY1n?*eQ4K41s)8xZ*y_CG4P9w<%bX!>eB}32A!lN}`xK%3W3~z!` zKLq(?k9=V{E(2Iouj;|udxZnh`GNcV3L9#VpLX%W=Q%@326oG5TF>KHeiq@jR6e*t z0=2OSF5U)ik5Fd83+phqcfzC+NI-o@_#-DH4s^jU?yoA63=c-{zwcsFhu`RQc^#7~ z78bE>wy!}^IVo)*>ymA;5!t}+{Liq47qg)2jNz@C7qiS+k=d26V-iI7m0tr)ESM3I z_!1w%0EKLx-fxOs>_Db1IAKA3j*w${lj1D>zFy==(0ljYJP zxaVaQdYa`Hq$FRhU?rx;GWH|hVHL)>XqJ@~%Lmf*JXcahEB&|dU2b$PZ`tms&wAsU z)6hMVJB61NVHD49Od7=pXY(U3^@mH3HBB3*bRBuMA|dhuk3}h&Yt+AN6h-5LSddBG zSQvQ&kGF!Kw89N*PNCex0^3~V`L2 z%zIZP61I5wo&D^rLUp>lAu|g}!5FA!O3r}coUFTRkd$bt{TCrTC%NwT+d*#Rv>BFl zO7{HCL*Jdhl+^jFN{_!tIa_^T%f&0)L>PVhe%%N)^^HXA>dbj=eLqd+ZRWiVi-y>j&`LOFM)n<`dwD(1`0gQ`?q z#gplaJbm+ua_;YRdza_{ts5MO=m-Xy7oebx!po!IP&7_$s>2-29a1BKoVf2vZ%aP;$4_|! zmIC}@DThpA!$|*7J_yRP#+Nq`ZiPFF{vh7PT*Pv=8&b`DO4zo`x+g*7*Ry<%VJ-rj zpy3^x^+eGeLxV>`xMqjFw3N{Jb$rq#W-4ue@f(|hg+_hSH(>io=O{5GvTc7pmi#J# zWt2ntjvD*Xz};13jFfwCk@>^#TS;Z{`-+1Dn!T#_vY;_%aE6HIW!@?gye&*AA?2j# z5kf1bdXJ2mYz2@tXxER?gc#++PvCpq(Eb2+rvj;**|a8o!#{yz>Kx7uA^SuJdxhp+ zEfw-9frDyNd_0|6?(+AaMWg|!hu(Yp41)$OC)<1&-Kl%Drv>NPqG!yk9&78=_dn=O z!~RmrwvE=%Lsc`yNQ$;Jp{5FxoEGKWlrfB${Qp+&e1S@Fj+CF>bdrr_nF;Wcf*w6Oz+ebhec5yRNU7L1c~Qk9~NFtG}ZVM-tse$f3hXH zP49Qfi>v)2Wv^#8blF~B$&v}#atk@0)vW;`$6po1=pqOEb1CV=Rd$2M2OrA6A8qEeId~I=(L)YZ1BlNu*jr z|L;$+z_H_@ISxVNDS>eBC@rT~1G0#R0u(;T2^%W4AD3JH+;_8=vR1A)?31US@H@+o zrfoCoX#SGv6|x6h*zrp`4|-$eWI$X4W~cr*^99;i_`Ue-#fi@Cwd0E&dhg4!Tv6au z#|P@cKaW^YW)WV5KDshtMl|E|SDZ8F&%Vzgzof`38)udwZy|?4VNAgL&v^i(8iw2L)0|F+I~WMv=oOnlbwZ(R5CC zvxn)Eh4!KgHphp;FSRd*6`4C+;{+88d*C92@&nfHtLHg}97ukoBu+w zC<&x>!%mkFl#{KX!WCC|=+y^_8=~HP8}VKjY0=JD?3H&hi8eN`#2@~)t5B`IS5pq3 zq0HLb6%4(&44rpzfoN~cx(%5&csR8e7#z9z1(cY?QBQ7#XDzS!v~8xAg?bqJ{vlu) zIsa7H=R2b=jA_VaL_PG7gyrKfm4xEHzdU6Au`{8EIw%gp8yN&|FGBT3T;HN*>?CwCZMb0xX8 zX!8sj_hdr!gi~~F%td9p@_Qx8wlAw6UUst_R!U&RB!$pkgdD82ZP7vFd@ijwAECS6 z9+?r@K$=_?D)vcI-ZE&u!zAI^_9tijw`TAWz@q$?QEAVUpVp5|*$!*04VePzn7C@; zd)$}hf&E*V^$aQZ?}BRzMmFK;<+(3sg8dCYw>#Y!mH6_bB^P^j^_&5{_3>jCGshWN z45Nb~#x8>Ji0w`ez)`Bv!U+-t%`wQl%E|5&@Qf5u0vlQ-N1(Q ztkkP_zw)2E$HM3Jl@(WfS(-%V(l7HDH^{c9tI1Lqcw=W@ zu4{QP%>RY&F-3af*8Hm4@C7dhf|#oh{$GGf5e65e41GNL6OA#Y9mi&} zt22gnUW;dm%>~^E*)`#?Zb54yBC`_9Pq8RIX^{F7Y*1G*ldm~=$jdzbppo!S0f+WI zOpw>N>qbfj?|j-kMNBti5nH^DOVv>BkAnte`_R)~N+Myh_K&?KnM&u=(ppzvXVq}5 zUeHt+PEY~%L5I_=gQibzhTTUn{i?TDZ1I~Fu7{}j$gc%QhqAA4e~jMDavMJzl-T`O zL(fWAOOxiSG?0DJRmy?dQA4WX#BiZL#{`)Mp!(O#-#?hrdoPF6>itN0LceEDU9a0% zX(sk(Fa3UO60UUCU-@m1hx4Ry*hQP|`jVStkHW6Jc<0p~f91sYw$IT&J(lP9+{;1p zz_Oa6gF!f2nosq@&Hni}YkdODnp+PaY<&!$f7^^Pc$85s=bo`DJ;oBQ`1N$U!$soN z>BX@}uXNuf6u%I77ymIjrfoOOd9t=Xyssh&bQcHS!WW06KVJ^;6EQJZ&pR42m2J-$ zq=0{LZ10}xHZgz&Fsk%(|2nHwXSgPH9O#nv5_xTAz-2o&n^flvOpOPt3bn z{jqnw?}LNhZT}5Vro{U-%a~Gnn9P3=u0WU%$LPt*ASdyEGq`A4C2sq$Jf?Q=zoAY6 z{JzPK&MOQ5fW`;ljBS1I(YcVV!@#ahD}$Muy#y>sYtsHvJIz82rL4twbOETaRrcl=O>%A~xHh)FAd{r?x~1Qz@L zSE@!SOP~@ouGzo>;LX4rfY-43^(fuI|HNkY7-1_~Vne_d8velMe+l8#ztOrMI3%56 zrLS7a03a1&(%M*_DdDFh6$>DK}cgzbJ`D$tki0*(T%O=rmY%9W~* zmjE=@77U~L_l4B$;_usHQ@XmNneJ)|0Yhx`ed>UnfxUpY18<7IpIE?u(nY|{Hh;+g zAQj49qx}1S1a1L7+|kSvl@Q-a0!GoqhpQgg33w~;Uc$`Dq$2;^t^`g>XSl*wt7HI>3f(|2 z0p2&n(Q6N3f#Bv|t2yAKzy(!OnpuS15C;PX5{~cPK92s!;%BuB_)I$Eseh6IKq_7c zI34(W9OZ6JFN2?2xe|CS@Kgo;%uNAy0QLap#zucY3;^l)XU6(xvm5wOI>V*DIwb>u zRO}GY2>cj$C!<@Ba!UL><<#I`lh@oE2~+vg2&?rc0TUQ}W@D^%0SBiu{LNRTWB`!H zG6;MDI2G7tgrDMU;Il>iC)VeU*!KYT26l-bn;QS!lL_k@#>X&IQz1WNy}-Wd3=jEA zl#<0=04fK+0ImeS2^<|q$HOs;utjj^IQkwxpz-GaJ{SKxm-pW_13uo@yldmfY6pCt zY6{Nh0Z;o9l$-&O#(MN$7e}Gp@k0v;x1QZ=7_ZNqujLVW6_}UK@Pwt1lE>QttJJ#k znm@o|!V35w0jmwNMe{tcr_En707#{>xs&Er5q1c?7Pt&pZB zs2zAe;T`}F4H6bKbgO}P11EUIuOtJ2D#2pHWr6bumkc!YhW{*ZJMbOCu7xYSrLrV& z7l10qD&S1QfjRqGjxFj0ZYJE&=SJWe!ikF5{3RIxR1zJ{916@QOz%H9gugNk*qyMw zZwBE&9c?)%eGB+${5>{)Nt@&9X^my`{{`WC{`T=h>j_8s4rT)3aGi04z4*0w*=n#6 z|8u~#!1sVhZ2FQ604k@E|6+=B``RR02RuMHKu z!fuGGfh&OabcW@YMv?(Qm+Ui?g8!xmZmeTu|$##0IC#!1Kw&G z`sL!I8(0ghAY8k@8@LI07@HG61NO{gtrjZ$g!SpSiqId~f`oR}&UHE+_2E ze*(A{_&e}eIr+ z{sLiZ<9)zmguVB#I9)HPT)7KCW2U2-lYk#kxb*;?z&XGo!YzSk5Y7#l6oWut^RCTn z;JNtUy?OsVF8+5-UXx!AyckF9&jT+z9WTiMAdNf(%mJ>84SO`!0N+k$__kG*Bm;md zBLvJQ98)`wksa*w0`UHHhQ0FE63x9U;de<5($ST;58 zUzs000000000000000000000000000000000000DtfW%|JL0$awbh00000 LNkvXXu0mjf)kf6t literal 0 HcmV?d00001 From c430b5a5970135106245bfe5ef36f789c4b572f3 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 22 Mar 2019 18:10:46 -0500 Subject: [PATCH 007/884] Fix logo in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 73f581539a..88c4d21ce8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ + + # RustPython A Python-3 (CPython >= 3.5.0) Interpreter written in Rust :snake: :scream: :metal:. @@ -9,8 +11,6 @@ A Python-3 (CPython >= 3.5.0) Interpreter written in Rust :snake: :scream: :meta [![Contributors](https://img.shields.io/github/contributors/RustPython/RustPython.svg)](https://github.com/RustPython/RustPython/graphs/contributors) [![Gitter](https://badges.gitter.im/RustPython/Lobby.svg)](https://gitter.im/rustpython/Lobby) - - # Usage ### Check out our [online demo](https://rustpython.github.io/demo/) running on WebAssembly. From 79a7aed46dea0730c0e72b9f4719a3ca383439a2 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 22 Mar 2019 18:12:08 -0500 Subject: [PATCH 008/884] Make logo larger --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 88c4d21ce8..3febdc13cd 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - + # RustPython From 5c7812734de4e43069c5b81db653fbfc48919e11 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 23 Mar 2019 08:40:38 +1300 Subject: [PATCH 009/884] Use more specific ref type than PyObjectRef in PyContext --- vm/src/builtins.rs | 42 +++--- vm/src/exceptions.rs | 54 ++++---- vm/src/frame.rs | 12 +- vm/src/macros.rs | 2 +- vm/src/obj/objbool.rs | 6 +- vm/src/obj/objbytearray.rs | 38 +++--- vm/src/obj/objbytes.rs | 2 +- vm/src/obj/objcode.rs | 2 +- vm/src/obj/objcomplex.rs | 30 ++--- vm/src/obj/objellipsis.rs | 2 +- vm/src/obj/objfloat.rs | 20 +-- vm/src/obj/objframe.rs | 8 +- vm/src/obj/objgenerator.rs | 10 +- vm/src/obj/objiter.rs | 19 +-- vm/src/obj/objlist.rs | 54 ++++---- vm/src/obj/objmemory.rs | 4 +- vm/src/obj/objnone.rs | 12 +- vm/src/obj/objobject.rs | 38 +++--- vm/src/obj/objproperty.rs | 4 +- vm/src/obj/objset.rs | 86 ++++++------- vm/src/obj/objsuper.rs | 6 +- vm/src/obj/objtuple.rs | 34 ++--- vm/src/obj/objtype.rs | 18 ++- vm/src/pyobject.rs | 254 +++++++++++++++++++++---------------- vm/src/stdlib/io.rs | 16 +-- vm/src/stdlib/json.rs | 9 +- vm/src/vm.rs | 5 +- wasm/lib/src/convert.rs | 2 +- 28 files changed, 409 insertions(+), 380 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 6d3d512a79..8c4d68025c 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -295,7 +295,7 @@ fn builtin_format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn catch_attr_exception(ex: PyObjectRef, default: T, vm: &VirtualMachine) -> PyResult { - if objtype::isinstance(&ex, &vm.ctx.exceptions.attribute_error) { + if objtype::isinstance(&ex, vm.ctx.exceptions.attribute_error.as_object()) { Ok(default) } else { Err(ex) @@ -510,7 +510,7 @@ fn builtin_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { match vm.call_method(iterator, "__next__", vec![]) { Ok(value) => Ok(value), Err(value) => { - if objtype::isinstance(&value, &vm.ctx.exceptions.stop_iteration) { + if objtype::isinstance(&value, vm.ctx.exceptions.stop_iteration.as_object()) { match default_value { None => Err(value), Some(value) => Ok(value.clone()), @@ -780,27 +780,27 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "__import__" => ctx.new_rustfunc(builtin_import), // Constants - "NotImplemented" => ctx.not_implemented.clone(), + "NotImplemented" => ctx.not_implemented(), // Exceptions: - "BaseException" => ctx.exceptions.base_exception_type.clone(), - "Exception" => ctx.exceptions.exception_type.clone(), - "ArithmeticError" => ctx.exceptions.arithmetic_error.clone(), - "AssertionError" => ctx.exceptions.assertion_error.clone(), - "AttributeError" => ctx.exceptions.attribute_error.clone(), - "NameError" => ctx.exceptions.name_error.clone(), - "OverflowError" => ctx.exceptions.overflow_error.clone(), - "RuntimeError" => ctx.exceptions.runtime_error.clone(), - "NotImplementedError" => ctx.exceptions.not_implemented_error.clone(), - "TypeError" => ctx.exceptions.type_error.clone(), - "ValueError" => ctx.exceptions.value_error.clone(), - "IndexError" => ctx.exceptions.index_error.clone(), - "ImportError" => ctx.exceptions.import_error.clone(), - "FileNotFoundError" => ctx.exceptions.file_not_found_error.clone(), - "StopIteration" => ctx.exceptions.stop_iteration.clone(), - "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone(), - "KeyError" => ctx.exceptions.key_error.clone(), - "OSError" => ctx.exceptions.os_error.clone(), + "BaseException" => ctx.exceptions.base_exception_type.clone().into_object(), + "Exception" => ctx.exceptions.exception_type.clone().into_object(), + "ArithmeticError" => ctx.exceptions.arithmetic_error.clone().into_object(), + "AssertionError" => ctx.exceptions.assertion_error.clone().into_object(), + "AttributeError" => ctx.exceptions.attribute_error.clone().into_object(), + "NameError" => ctx.exceptions.name_error.clone().into_object(), + "OverflowError" => ctx.exceptions.overflow_error.clone().into_object(), + "RuntimeError" => ctx.exceptions.runtime_error.clone().into_object(), + "NotImplementedError" => ctx.exceptions.not_implemented_error.clone().into_object(), + "TypeError" => ctx.exceptions.type_error.clone().into_object(), + "ValueError" => ctx.exceptions.value_error.clone().into_object(), + "IndexError" => ctx.exceptions.index_error.clone().into_object(), + "ImportError" => ctx.exceptions.import_error.clone().into_object(), + "FileNotFoundError" => ctx.exceptions.file_not_found_error.clone().into_object(), + "StopIteration" => ctx.exceptions.stop_iteration.clone().into_object(), + "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone().into_object(), + "KeyError" => ctx.exceptions.key_error.clone().into_object(), + "OSError" => ctx.exceptions.os_error.clone().into_object(), }); #[cfg(not(target_arch = "wasm32"))] diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index d37f174717..c08160dd6e 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1,6 +1,7 @@ use crate::function::PyFuncArgs; use crate::obj::objsequence; use crate::obj::objtype; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{create_type, PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; @@ -65,7 +66,10 @@ fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, - required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))] + required = [( + exc, + Some(vm.ctx.exceptions.exception_type.clone().into_object()) + )] ); let type_name = objtype::get_type_name(&exc.typ()); let msg = if let Ok(m) = vm.get_attribute(exc.clone(), "msg") { @@ -82,31 +86,31 @@ fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { #[derive(Debug)] pub struct ExceptionZoo { - pub arithmetic_error: PyObjectRef, - pub assertion_error: PyObjectRef, - pub attribute_error: PyObjectRef, - pub base_exception_type: PyObjectRef, - pub exception_type: PyObjectRef, - pub file_not_found_error: PyObjectRef, - pub import_error: PyObjectRef, - pub index_error: PyObjectRef, - pub key_error: PyObjectRef, - pub module_not_found_error: PyObjectRef, - pub name_error: PyObjectRef, - pub not_implemented_error: PyObjectRef, - pub os_error: PyObjectRef, - pub overflow_error: PyObjectRef, - pub permission_error: PyObjectRef, - pub runtime_error: PyObjectRef, - pub stop_iteration: PyObjectRef, - pub syntax_error: PyObjectRef, - pub type_error: PyObjectRef, - pub value_error: PyObjectRef, - pub zero_division_error: PyObjectRef, + pub arithmetic_error: PyClassRef, + pub assertion_error: PyClassRef, + pub attribute_error: PyClassRef, + pub base_exception_type: PyClassRef, + pub exception_type: PyClassRef, + pub file_not_found_error: PyClassRef, + pub import_error: PyClassRef, + pub index_error: PyClassRef, + pub key_error: PyClassRef, + pub module_not_found_error: PyClassRef, + pub name_error: PyClassRef, + pub not_implemented_error: PyClassRef, + pub os_error: PyClassRef, + pub overflow_error: PyClassRef, + pub permission_error: PyClassRef, + pub runtime_error: PyClassRef, + pub stop_iteration: PyClassRef, + pub syntax_error: PyClassRef, + pub type_error: PyClassRef, + pub value_error: PyClassRef, + pub zero_division_error: PyClassRef, } impl ExceptionZoo { - pub fn new(type_type: &PyObjectRef, object_type: &PyObjectRef) -> Self { + pub fn new(type_type: &PyClassRef, object_type: &PyClassRef) -> Self { // Sorted By Hierarchy then alphabetized. let base_exception_type = create_type("BaseException", &type_type, &object_type); let exception_type = create_type("Exception", &type_type, &base_exception_type); @@ -159,13 +163,13 @@ impl ExceptionZoo { pub fn init(context: &PyContext) { let base_exception_type = &context.exceptions.base_exception_type; context.set_attr( - &base_exception_type, + base_exception_type, "__init__", context.new_rustfunc(exception_init), ); let exception_type = &context.exceptions.exception_type; context.set_attr( - &exception_type, + exception_type, "__str__", context.new_rustfunc(exception_str), ); diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 0015557fec..d0fa668c70 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -253,7 +253,7 @@ impl Frame { // Add an entry in the traceback: assert!(objtype::isinstance( &exception, - &vm.ctx.exceptions.base_exception_type + vm.ctx.exceptions.base_exception_type.as_object() )); let traceback = vm .get_attribute(exception.clone(), "__traceback__") @@ -656,11 +656,17 @@ impl Frame { 0 | 2 | 3 => panic!("Not implemented!"), _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"), }; - if objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { + if objtype::isinstance( + &exception, + vm.ctx.exceptions.base_exception_type.as_object(), + ) { info!("Exception raised: {:?}", exception); Err(exception) } else if objtype::isinstance(&exception, &vm.ctx.type_type()) - && objtype::issubclass(&exception, &vm.ctx.exceptions.base_exception_type) + && objtype::issubclass( + &exception, + vm.ctx.exceptions.base_exception_type.as_object(), + ) { let exception = vm.new_empty_exception(exception)?; info!("Exception raised: {:?}", exception); diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 4d4e6ebe2e..0c205ffc80 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -143,7 +143,7 @@ macro_rules! extend_class { ( $ctx:expr, $class:expr, { $($name:expr => $value:expr),* $(,)* }) => { let class = $class; $( - $ctx.set_attr(&class, $name, $value); + $ctx.set_attr(class, $name, $value); )* } } diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index 4feb1300bd..fc19626a47 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -65,9 +65,9 @@ The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed."; let bool_type = &context.bool_type; - context.set_attr(&bool_type, "__new__", context.new_rustfunc(bool_new)); - context.set_attr(&bool_type, "__repr__", context.new_rustfunc(bool_repr)); - context.set_attr(&bool_type, "__doc__", context.new_str(bool_doc.to_string())); + context.set_attr(bool_type, "__new__", context.new_rustfunc(bool_new)); + context.set_attr(bool_type, "__repr__", context.new_rustfunc(bool_repr)); + context.set_attr(bool_type, "__doc__", context.new_str(bool_doc.to_string())); } pub fn not(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index c7038fe9ac..ff7a6d9382 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -61,84 +61,80 @@ pub fn init(context: &PyContext) { - any object implementing the buffer API.\n \ - an integer"; + context.set_attr(bytearray_type, "__eq__", context.new_rustfunc(bytearray_eq)); context.set_attr( - &bytearray_type, - "__eq__", - context.new_rustfunc(bytearray_eq), - ); - context.set_attr( - &bytearray_type, + bytearray_type, "__new__", context.new_rustfunc(bytearray_new), ); context.set_attr( - &bytearray_type, + bytearray_type, "__repr__", context.new_rustfunc(bytearray_repr), ); context.set_attr( - &bytearray_type, + bytearray_type, "__len__", context.new_rustfunc(bytesarray_len), ); context.set_attr( - &bytearray_type, + bytearray_type, "__doc__", context.new_str(bytearray_doc.to_string()), ); context.set_attr( - &bytearray_type, + bytearray_type, "isalnum", context.new_rustfunc(bytearray_isalnum), ); context.set_attr( - &bytearray_type, + bytearray_type, "isalpha", context.new_rustfunc(bytearray_isalpha), ); context.set_attr( - &bytearray_type, + bytearray_type, "isascii", context.new_rustfunc(bytearray_isascii), ); context.set_attr( - &bytearray_type, + bytearray_type, "isdigit", context.new_rustfunc(bytearray_isdigit), ); context.set_attr( - &bytearray_type, + bytearray_type, "islower", context.new_rustfunc(bytearray_islower), ); context.set_attr( - &bytearray_type, + bytearray_type, "isspace", context.new_rustfunc(bytearray_isspace), ); context.set_attr( - &bytearray_type, + bytearray_type, "isupper", context.new_rustfunc(bytearray_isupper), ); context.set_attr( - &bytearray_type, + bytearray_type, "istitle", context.new_rustfunc(bytearray_istitle), ); context.set_attr( - &bytearray_type, + bytearray_type, "clear", context.new_rustfunc(bytearray_clear), ); - context.set_attr(&bytearray_type, "pop", context.new_rustfunc(bytearray_pop)); + context.set_attr(bytearray_type, "pop", context.new_rustfunc(bytearray_pop)); context.set_attr( - &bytearray_type, + bytearray_type, "lower", context.new_rustfunc(bytearray_lower), ); context.set_attr( - &bytearray_type, + bytearray_type, "upper", context.new_rustfunc(bytearray_upper), ); diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index dc1f930a46..c1fdcf955b 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -43,7 +43,7 @@ impl PyValue for PyBytes { // Fill bytes class methods: pub fn init(context: &PyContext) { - let bytes_type = &context.bytes_type; + let bytes_type = context.bytes_type.as_object(); let bytes_doc = "bytes(iterable_of_ints) -> bytes\n\ diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 21df7f2cc1..6c7f66002b 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -32,7 +32,7 @@ impl PyValue for PyCode { } pub fn init(context: &PyContext) { - let code_type = &context.code_type; + let code_type = context.code_type.as_object(); context.set_attr(code_type, "__new__", context.new_rustfunc(code_new)); context.set_attr(code_type, "__repr__", context.new_rustfunc(code_repr)); diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 7443306512..15baf5df42 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -34,30 +34,22 @@ pub fn init(context: &PyContext) { "Create a complex number from a real part and an optional imaginary part.\n\n\ This is equivalent to (real + imag*1j) where imag defaults to 0."; - context.set_attr(&complex_type, "__abs__", context.new_rustfunc(complex_abs)); - context.set_attr(&complex_type, "__add__", context.new_rustfunc(complex_add)); + context.set_attr(complex_type, "__abs__", context.new_rustfunc(complex_abs)); + context.set_attr(complex_type, "__add__", context.new_rustfunc(complex_add)); + context.set_attr(complex_type, "__radd__", context.new_rustfunc(complex_radd)); + context.set_attr(complex_type, "__eq__", context.new_rustfunc(complex_eq)); + context.set_attr(complex_type, "__neg__", context.new_rustfunc(complex_neg)); + context.set_attr(complex_type, "__new__", context.new_rustfunc(complex_new)); + context.set_attr(complex_type, "real", context.new_property(complex_real)); + context.set_attr(complex_type, "imag", context.new_property(complex_imag)); context.set_attr( - &complex_type, - "__radd__", - context.new_rustfunc(complex_radd), - ); - context.set_attr(&complex_type, "__eq__", context.new_rustfunc(complex_eq)); - context.set_attr(&complex_type, "__neg__", context.new_rustfunc(complex_neg)); - context.set_attr(&complex_type, "__new__", context.new_rustfunc(complex_new)); - context.set_attr(&complex_type, "real", context.new_property(complex_real)); - context.set_attr(&complex_type, "imag", context.new_property(complex_imag)); - context.set_attr( - &complex_type, + complex_type, "__doc__", context.new_str(complex_doc.to_string()), ); + context.set_attr(complex_type, "__repr__", context.new_rustfunc(complex_repr)); context.set_attr( - &complex_type, - "__repr__", - context.new_rustfunc(complex_repr), - ); - context.set_attr( - &complex_type, + complex_type, "conjugate", context.new_rustfunc(complex_conjugate), ); diff --git a/vm/src/obj/objellipsis.rs b/vm/src/obj/objellipsis.rs index 03c9d69e8a..d80dee68e6 100644 --- a/vm/src/obj/objellipsis.rs +++ b/vm/src/obj/objellipsis.rs @@ -3,7 +3,7 @@ use crate::pyobject::{PyContext, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; pub fn init(context: &PyContext) { - let ellipsis_type = &context.ellipsis_type; + let ellipsis_type = context.ellipsis_type.as_object(); context.set_attr(ellipsis_type, "__new__", context.new_rustfunc(ellipsis_new)); context.set_attr( ellipsis_type, diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index af2071184f..cd6a4f33f0 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -133,9 +133,9 @@ impl PyFloatRef { fn floordiv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; - let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { + let v2 = if objtype::isinstance(&other, vm.ctx.float_type.as_object()) { get_value(&other) - } else if objtype::isinstance(&other, &vm.ctx.int_type) { + } else if objtype::isinstance(&other, vm.ctx.int_type.as_object()) { objint::get_value(&other).to_f64().ok_or_else(|| { vm.new_overflow_error("int too large to convert to float".to_string()) })? @@ -193,9 +193,9 @@ impl PyFloatRef { fn mod_(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; - let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { + let v2 = if objtype::isinstance(&other, vm.ctx.float_type.as_object()) { get_value(&other) - } else if objtype::isinstance(&other, &vm.ctx.int_type) { + } else if objtype::isinstance(&other, vm.ctx.int_type.as_object()) { objint::get_value(&other).to_f64().ok_or_else(|| { vm.new_overflow_error("int too large to convert to float".to_string()) })? @@ -258,9 +258,9 @@ impl PyFloatRef { fn truediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; - let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { + let v2 = if objtype::isinstance(&other, vm.ctx.float_type.as_object()) { get_value(&other) - } else if objtype::isinstance(&other, &vm.ctx.int_type) { + } else if objtype::isinstance(&other, vm.ctx.int_type.as_object()) { objint::get_value(&other).to_f64().ok_or_else(|| { vm.new_overflow_error("int too large to convert to float".to_string()) })? @@ -277,9 +277,9 @@ impl PyFloatRef { fn rtruediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; - let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { + let v2 = if objtype::isinstance(&other, vm.ctx.float_type.as_object()) { get_value(&other) - } else if objtype::isinstance(&other, &vm.ctx.int_type) { + } else if objtype::isinstance(&other, vm.ctx.int_type.as_object()) { objint::get_value(&other).to_f64().ok_or_else(|| { vm.new_overflow_error("int too large to convert to float".to_string()) })? @@ -296,9 +296,9 @@ impl PyFloatRef { fn mul(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; - if objtype::isinstance(&other, &vm.ctx.float_type) { + if objtype::isinstance(&other, vm.ctx.float_type.as_object()) { Ok(vm.ctx.new_float(v1 * get_value(&other))) - } else if objtype::isinstance(&other, &vm.ctx.int_type) { + } else if objtype::isinstance(&other, vm.ctx.int_type.as_object()) { Ok(vm .ctx .new_float(v1 * objint::get_value(&other).to_f64().unwrap())) diff --git a/vm/src/obj/objframe.rs b/vm/src/obj/objframe.rs index 2d179fa24d..40007725f6 100644 --- a/vm/src/obj/objframe.rs +++ b/vm/src/obj/objframe.rs @@ -9,10 +9,10 @@ use crate::vm::VirtualMachine; pub fn init(context: &PyContext) { let frame_type = &context.frame_type; - context.set_attr(&frame_type, "__new__", context.new_rustfunc(frame_new)); - context.set_attr(&frame_type, "__repr__", context.new_rustfunc(frame_repr)); - context.set_attr(&frame_type, "f_locals", context.new_property(frame_flocals)); - context.set_attr(&frame_type, "f_code", context.new_property(frame_fcode)); + context.set_attr(frame_type, "__new__", context.new_rustfunc(frame_new)); + context.set_attr(frame_type, "__repr__", context.new_rustfunc(frame_repr)); + context.set_attr(frame_type, "f_locals", context.new_property(frame_flocals)); + context.set_attr(frame_type, "f_code", context.new_property(frame_fcode)); } fn frame_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index 208bcb43f9..0307f35cf4 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -22,20 +22,16 @@ impl PyValue for PyGenerator { pub fn init(context: &PyContext) { let generator_type = &context.generator_type; context.set_attr( - &generator_type, + generator_type, "__iter__", context.new_rustfunc(generator_iter), ); context.set_attr( - &generator_type, + generator_type, "__next__", context.new_rustfunc(generator_next), ); - context.set_attr( - &generator_type, - "send", - context.new_rustfunc(generator_send), - ); + context.set_attr(generator_type, "send", context.new_rustfunc(generator_send)); } pub fn new_generator(frame: PyObjectRef, vm: &VirtualMachine) -> PyGeneratorRef { diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 1b7709d748..2726af1336 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -12,6 +12,7 @@ use super::objbytes::PyBytes; use super::objrange::PyRange; use super::objsequence; use super::objtype; +use crate::obj::objtype::PyClassRef; /* * This helper function is called at multiple places. First, it is called @@ -42,7 +43,7 @@ pub fn get_next_object( Ok(value) => Ok(Some(value)), Err(next_error) => { // Check if we have stopiteration, or something else: - if objtype::isinstance(&next_error, &vm.ctx.exceptions.stop_iteration) { + if objtype::isinstance(&next_error, vm.ctx.exceptions.stop_iteration.as_object()) { Ok(None) } else { Err(next_error) @@ -90,18 +91,18 @@ fn contains(vm: &VirtualMachine, args: PyFuncArgs, iter_type: PyObjectRef) -> Py } /// Common setup for iter types, adds __iter__ and __contains__ methods -pub fn iter_type_init(context: &PyContext, iter_type: &PyObjectRef) { +pub fn iter_type_init(context: &PyContext, iter_type: &PyClassRef) { let contains_func = { - let cloned_iter_type = iter_type.clone(); + let cloned_iter_type = iter_type.clone().into_object(); move |vm: &VirtualMachine, args: PyFuncArgs| contains(vm, args, cloned_iter_type.clone()) }; context.set_attr( - &iter_type, + iter_type, "__contains__", context.new_rustfunc(contains_func), ); let iter_func = { - let cloned_iter_type = iter_type.clone(); + let cloned_iter_type = iter_type.clone().into_object(); move |vm: &VirtualMachine, args: PyFuncArgs| { arg_check!( vm, @@ -112,7 +113,7 @@ pub fn iter_type_init(context: &PyContext, iter_type: &PyObjectRef) { Ok(iter.clone()) } }; - context.set_attr(&iter_type, "__iter__", context.new_rustfunc(iter_func)); + context.set_attr(iter_type, "__iter__", context.new_rustfunc(iter_func)); } // Sequence iterator: @@ -178,7 +179,7 @@ pub fn init(context: &PyContext) { In the second form, the callable is called until it returns the sentinel."; iter_type_init(context, iter_type); - context.set_attr(&iter_type, "__new__", context.new_rustfunc(iter_new)); - context.set_attr(&iter_type, "__next__", context.new_rustfunc(iter_next)); - context.set_attr(&iter_type, "__doc__", context.new_str(iter_doc.to_string())); + context.set_attr(iter_type, "__new__", context.new_rustfunc(iter_new)); + context.set_attr(iter_type, "__next__", context.new_rustfunc(iter_next)); + context.set_attr(iter_type, "__doc__", context.new_str(iter_doc.to_string())); } diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 1b3a9b4fa9..ebc97f7edd 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -420,31 +420,31 @@ pub fn init(context: &PyContext) { If no argument is given, the constructor creates a new empty list.\n\ The argument must be an iterable if specified."; - context.set_attr(&list_type, "__add__", context.new_rustfunc(PyListRef::add)); - context.set_attr(&list_type, "__iadd__", context.new_rustfunc(PyListRef::iadd)); - context.set_attr(&list_type, "__contains__", context.new_rustfunc(PyListRef::contains)); - context.set_attr(&list_type, "__eq__", context.new_rustfunc(PyListRef::eq)); - context.set_attr(&list_type, "__lt__", context.new_rustfunc(PyListRef::lt)); - context.set_attr(&list_type, "__gt__", context.new_rustfunc(PyListRef::gt)); - context.set_attr(&list_type, "__le__", context.new_rustfunc(PyListRef::le)); - context.set_attr(&list_type, "__ge__", context.new_rustfunc(PyListRef::ge)); - context.set_attr(&list_type, "__getitem__", context.new_rustfunc(PyListRef::getitem)); - context.set_attr(&list_type, "__iter__", context.new_rustfunc(PyListRef::iter)); - context.set_attr(&list_type, "__setitem__", context.new_rustfunc(PyListRef::setitem)); - context.set_attr(&list_type, "__mul__", context.new_rustfunc(PyListRef::mul)); - context.set_attr(&list_type, "__len__", context.new_rustfunc(PyListRef::len)); - context.set_attr(&list_type, "__new__", context.new_rustfunc(list_new)); - context.set_attr(&list_type, "__repr__", context.new_rustfunc(PyListRef::repr)); - context.set_attr(&list_type, "__doc__", context.new_str(list_doc.to_string())); - context.set_attr(&list_type, "append", context.new_rustfunc(PyListRef::append)); - context.set_attr(&list_type, "clear", context.new_rustfunc(PyListRef::clear)); - context.set_attr(&list_type, "copy", context.new_rustfunc(PyListRef::copy)); - context.set_attr(&list_type, "count", context.new_rustfunc(PyListRef::count)); - context.set_attr(&list_type, "extend", context.new_rustfunc(PyListRef::extend)); - context.set_attr(&list_type, "index", context.new_rustfunc(PyListRef::index)); - context.set_attr(&list_type, "insert", context.new_rustfunc(PyListRef::insert)); - context.set_attr(&list_type, "reverse", context.new_rustfunc(PyListRef::reverse)); - context.set_attr(&list_type, "sort", context.new_rustfunc(list_sort)); - context.set_attr(&list_type, "pop", context.new_rustfunc(PyListRef::pop)); - context.set_attr(&list_type, "remove", context.new_rustfunc(PyListRef::remove)); + context.set_attr(list_type, "__add__", context.new_rustfunc(PyListRef::add)); + context.set_attr(list_type, "__iadd__", context.new_rustfunc(PyListRef::iadd)); + context.set_attr(list_type, "__contains__", context.new_rustfunc(PyListRef::contains)); + context.set_attr(list_type, "__eq__", context.new_rustfunc(PyListRef::eq)); + context.set_attr(list_type, "__lt__", context.new_rustfunc(PyListRef::lt)); + context.set_attr(list_type, "__gt__", context.new_rustfunc(PyListRef::gt)); + context.set_attr(list_type, "__le__", context.new_rustfunc(PyListRef::le)); + context.set_attr(list_type, "__ge__", context.new_rustfunc(PyListRef::ge)); + context.set_attr(list_type, "__getitem__", context.new_rustfunc(PyListRef::getitem)); + context.set_attr(list_type, "__iter__", context.new_rustfunc(PyListRef::iter)); + context.set_attr(list_type, "__setitem__", context.new_rustfunc(PyListRef::setitem)); + context.set_attr(list_type, "__mul__", context.new_rustfunc(PyListRef::mul)); + context.set_attr(list_type, "__len__", context.new_rustfunc(PyListRef::len)); + context.set_attr(list_type, "__new__", context.new_rustfunc(list_new)); + context.set_attr(list_type, "__repr__", context.new_rustfunc(PyListRef::repr)); + context.set_attr(list_type, "__doc__", context.new_str(list_doc.to_string())); + context.set_attr(list_type, "append", context.new_rustfunc(PyListRef::append)); + context.set_attr(list_type, "clear", context.new_rustfunc(PyListRef::clear)); + context.set_attr(list_type, "copy", context.new_rustfunc(PyListRef::copy)); + context.set_attr(list_type, "count", context.new_rustfunc(PyListRef::count)); + context.set_attr(list_type, "extend", context.new_rustfunc(PyListRef::extend)); + context.set_attr(list_type, "index", context.new_rustfunc(PyListRef::index)); + context.set_attr(list_type, "insert", context.new_rustfunc(PyListRef::insert)); + context.set_attr(list_type, "reverse", context.new_rustfunc(PyListRef::reverse)); + context.set_attr(list_type, "sort", context.new_rustfunc(list_sort)); + context.set_attr(list_type, "pop", context.new_rustfunc(PyListRef::pop)); + context.set_attr(list_type, "remove", context.new_rustfunc(PyListRef::remove)); } diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index aa67985514..ca365afc57 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -15,7 +15,7 @@ impl PyValue for PyMemoryView { pub fn new_memory_view(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(cls, None), (bytes_object, None)]); - vm.ctx.set_attr(&cls, "obj", bytes_object.clone()); + vm.ctx.set_attr(cls, "obj", bytes_object.clone()); Ok(PyObject::new( PyMemoryView { obj: bytes_object.clone(), @@ -27,7 +27,7 @@ pub fn new_memory_view(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn init(ctx: &PyContext) { let memoryview_type = &ctx.memoryview_type; ctx.set_attr( - &memoryview_type, + memoryview_type, "__new__", ctx.new_rustfunc(new_memory_view), ); diff --git a/vm/src/obj/objnone.rs b/vm/src/obj/objnone.rs index b0a790cc16..66ec0e30c5 100644 --- a/vm/src/obj/objnone.rs +++ b/vm/src/obj/objnone.rs @@ -1,7 +1,6 @@ -use crate::function::PyFuncArgs; use crate::obj::objproperty::PyPropertyRef; use crate::obj::objstr::PyStringRef; -use crate::obj::objtype::{class_get_attr, class_has_attr}; +use crate::obj::objtype::{class_get_attr, class_has_attr, PyClassRef}; use crate::pyobject::{ IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; @@ -103,13 +102,8 @@ impl PyNoneRef { } } -fn none_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(_zelf, Some(vm.ctx.type_type.clone()))] - ); - Ok(vm.get_none()) +fn none_new(_: PyClassRef, _vm: &VirtualMachine) -> PyNone { + PyNone } pub fn init(context: &PyContext) { diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 98700aec3a..5405d41c51 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -182,36 +182,36 @@ pub fn init(context: &PyContext) { let object = &context.object; let object_doc = "The most base type"; - context.set_attr(&object, "__new__", context.new_rustfunc(new_instance)); - context.set_attr(&object, "__init__", context.new_rustfunc(object_init)); + context.set_attr(object, "__new__", context.new_rustfunc(new_instance)); + context.set_attr(object, "__init__", context.new_rustfunc(object_init)); context.set_attr( - &object, + object, "__class__", PropertyBuilder::new(context) .add_getter(object_class) .add_setter(object_class_setter) .create(), ); - context.set_attr(&object, "__eq__", context.new_rustfunc(object_eq)); - context.set_attr(&object, "__ne__", context.new_rustfunc(object_ne)); - context.set_attr(&object, "__lt__", context.new_rustfunc(object_lt)); - context.set_attr(&object, "__le__", context.new_rustfunc(object_le)); - context.set_attr(&object, "__gt__", context.new_rustfunc(object_gt)); - context.set_attr(&object, "__ge__", context.new_rustfunc(object_ge)); - context.set_attr(&object, "__setattr__", context.new_rustfunc(object_setattr)); - context.set_attr(&object, "__delattr__", context.new_rustfunc(object_delattr)); - context.set_attr(&object, "__dict__", context.new_property(object_dict)); - context.set_attr(&object, "__dir__", context.new_rustfunc(object_dir)); - context.set_attr(&object, "__hash__", context.new_rustfunc(object_hash)); - context.set_attr(&object, "__str__", context.new_rustfunc(object_str)); - context.set_attr(&object, "__repr__", context.new_rustfunc(object_repr)); - context.set_attr(&object, "__format__", context.new_rustfunc(object_format)); + context.set_attr(object, "__eq__", context.new_rustfunc(object_eq)); + context.set_attr(object, "__ne__", context.new_rustfunc(object_ne)); + context.set_attr(object, "__lt__", context.new_rustfunc(object_lt)); + context.set_attr(object, "__le__", context.new_rustfunc(object_le)); + context.set_attr(object, "__gt__", context.new_rustfunc(object_gt)); + context.set_attr(object, "__ge__", context.new_rustfunc(object_ge)); + context.set_attr(object, "__setattr__", context.new_rustfunc(object_setattr)); + context.set_attr(object, "__delattr__", context.new_rustfunc(object_delattr)); + context.set_attr(object, "__dict__", context.new_property(object_dict)); + context.set_attr(object, "__dir__", context.new_rustfunc(object_dir)); + context.set_attr(object, "__hash__", context.new_rustfunc(object_hash)); + context.set_attr(object, "__str__", context.new_rustfunc(object_str)); + context.set_attr(object, "__repr__", context.new_rustfunc(object_repr)); + context.set_attr(object, "__format__", context.new_rustfunc(object_format)); context.set_attr( - &object, + object, "__getattribute__", context.new_rustfunc(object_getattribute), ); - context.set_attr(&object, "__doc__", context.new_str(object_doc.to_string())); + context.set_attr(object, "__doc__", context.new_str(object_doc.to_string())); } fn object_init(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objproperty.rs b/vm/src/obj/objproperty.rs index d1791f2485..27f5cd964c 100644 --- a/vm/src/obj/objproperty.rs +++ b/vm/src/obj/objproperty.rs @@ -30,7 +30,7 @@ impl PyReadOnlyPropertyRef { _owner: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - if obj.is(&vm.ctx.none) { + if obj.is(vm.ctx.none.as_object()) { Ok(self.into_object()) } else { vm.invoke(self.getter.clone(), obj) @@ -89,7 +89,7 @@ impl PyPropertyRef { vm: &VirtualMachine, ) -> PyResult { if let Some(getter) = self.getter.as_ref() { - if obj.is(&vm.ctx.none) { + if obj.is(vm.ctx.none.as_object()) { Ok(self.into_object()) } else { vm.invoke(getter.clone(), obj) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index b11c21aa39..e98f2434c7 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -552,73 +552,65 @@ pub fn init(context: &PyContext) { set(iterable) -> new set object\n\n\ Build an unordered collection of unique elements."; + context.set_attr(set_type, "__contains__", context.new_rustfunc(set_contains)); + context.set_attr(set_type, "__len__", context.new_rustfunc(set_len)); + context.set_attr(set_type, "__new__", context.new_rustfunc(set_new)); + context.set_attr(set_type, "__repr__", context.new_rustfunc(set_repr)); + context.set_attr(set_type, "__eq__", context.new_rustfunc(set_eq)); + context.set_attr(set_type, "__ge__", context.new_rustfunc(set_ge)); + context.set_attr(set_type, "__gt__", context.new_rustfunc(set_gt)); + context.set_attr(set_type, "__le__", context.new_rustfunc(set_le)); + context.set_attr(set_type, "__lt__", context.new_rustfunc(set_lt)); + context.set_attr(set_type, "issubset", context.new_rustfunc(set_le)); + context.set_attr(set_type, "issuperset", context.new_rustfunc(set_ge)); + context.set_attr(set_type, "union", context.new_rustfunc(set_union)); + context.set_attr(set_type, "__or__", context.new_rustfunc(set_union)); context.set_attr( - &set_type, - "__contains__", - context.new_rustfunc(set_contains), - ); - context.set_attr(&set_type, "__len__", context.new_rustfunc(set_len)); - context.set_attr(&set_type, "__new__", context.new_rustfunc(set_new)); - context.set_attr(&set_type, "__repr__", context.new_rustfunc(set_repr)); - context.set_attr(&set_type, "__eq__", context.new_rustfunc(set_eq)); - context.set_attr(&set_type, "__ge__", context.new_rustfunc(set_ge)); - context.set_attr(&set_type, "__gt__", context.new_rustfunc(set_gt)); - context.set_attr(&set_type, "__le__", context.new_rustfunc(set_le)); - context.set_attr(&set_type, "__lt__", context.new_rustfunc(set_lt)); - context.set_attr(&set_type, "issubset", context.new_rustfunc(set_le)); - context.set_attr(&set_type, "issuperset", context.new_rustfunc(set_ge)); - context.set_attr(&set_type, "union", context.new_rustfunc(set_union)); - context.set_attr(&set_type, "__or__", context.new_rustfunc(set_union)); - context.set_attr( - &set_type, + set_type, "intersection", context.new_rustfunc(set_intersection), ); - context.set_attr(&set_type, "__and__", context.new_rustfunc(set_intersection)); - context.set_attr( - &set_type, - "difference", - context.new_rustfunc(set_difference), - ); - context.set_attr(&set_type, "__sub__", context.new_rustfunc(set_difference)); + context.set_attr(set_type, "__and__", context.new_rustfunc(set_intersection)); + context.set_attr(set_type, "difference", context.new_rustfunc(set_difference)); + context.set_attr(set_type, "__sub__", context.new_rustfunc(set_difference)); context.set_attr( - &set_type, + set_type, "symmetric_difference", context.new_rustfunc(set_symmetric_difference), ); context.set_attr( - &set_type, + set_type, "__xor__", context.new_rustfunc(set_symmetric_difference), ); - context.set_attr(&set_type, "__doc__", context.new_str(set_doc.to_string())); - context.set_attr(&set_type, "add", context.new_rustfunc(set_add)); - context.set_attr(&set_type, "remove", context.new_rustfunc(set_remove)); - context.set_attr(&set_type, "discard", context.new_rustfunc(set_discard)); - context.set_attr(&set_type, "clear", context.new_rustfunc(set_clear)); - context.set_attr(&set_type, "copy", context.new_rustfunc(set_copy)); - context.set_attr(&set_type, "pop", context.new_rustfunc(set_pop)); - context.set_attr(&set_type, "update", context.new_rustfunc(set_update)); - context.set_attr(&set_type, "__ior__", context.new_rustfunc(set_ior)); + context.set_attr(set_type, "__doc__", context.new_str(set_doc.to_string())); + context.set_attr(set_type, "add", context.new_rustfunc(set_add)); + context.set_attr(set_type, "remove", context.new_rustfunc(set_remove)); + context.set_attr(set_type, "discard", context.new_rustfunc(set_discard)); + context.set_attr(set_type, "clear", context.new_rustfunc(set_clear)); + context.set_attr(set_type, "copy", context.new_rustfunc(set_copy)); + context.set_attr(set_type, "pop", context.new_rustfunc(set_pop)); + context.set_attr(set_type, "update", context.new_rustfunc(set_update)); + context.set_attr(set_type, "__ior__", context.new_rustfunc(set_ior)); context.set_attr( - &set_type, + set_type, "intersection_update", context.new_rustfunc(set_intersection_update), ); - context.set_attr(&set_type, "__iand__", context.new_rustfunc(set_iand)); + context.set_attr(set_type, "__iand__", context.new_rustfunc(set_iand)); context.set_attr( - &set_type, + set_type, "difference_update", context.new_rustfunc(set_difference_update), ); - context.set_attr(&set_type, "__isub__", context.new_rustfunc(set_isub)); + context.set_attr(set_type, "__isub__", context.new_rustfunc(set_isub)); context.set_attr( - &set_type, + set_type, "symmetric_difference_update", context.new_rustfunc(set_symmetric_difference_update), ); - context.set_attr(&set_type, "__ixor__", context.new_rustfunc(set_ixor)); - context.set_attr(&set_type, "__iter__", context.new_rustfunc(set_iter)); + context.set_attr(set_type, "__ixor__", context.new_rustfunc(set_ixor)); + context.set_attr(set_type, "__iter__", context.new_rustfunc(set_iter)); let frozenset_type = &context.frozenset_type; @@ -627,18 +619,18 @@ pub fn init(context: &PyContext) { Build an immutable unordered collection of unique elements."; context.set_attr( - &frozenset_type, + frozenset_type, "__contains__", context.new_rustfunc(set_contains), ); - context.set_attr(&frozenset_type, "__len__", context.new_rustfunc(set_len)); + context.set_attr(frozenset_type, "__len__", context.new_rustfunc(set_len)); context.set_attr( - &frozenset_type, + frozenset_type, "__doc__", context.new_str(frozenset_doc.to_string()), ); context.set_attr( - &frozenset_type, + frozenset_type, "__repr__", context.new_rustfunc(frozenset_repr), ); diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 2b2be02414..5c6e53c808 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -46,14 +46,14 @@ pub fn init(context: &PyContext) { def cmeth(cls, arg):\n \ super().cmeth(arg)\n"; - context.set_attr(&super_type, "__new__", context.new_rustfunc(super_new)); + context.set_attr(super_type, "__new__", context.new_rustfunc(super_new)); context.set_attr( - &super_type, + super_type, "__getattribute__", context.new_rustfunc(super_getattribute), ); context.set_attr( - &super_type, + super_type, "__doc__", context.new_str(super_doc.to_string()), ); diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 7438e1c90a..7a3771cbd9 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -233,21 +233,21 @@ pub fn init(context: &PyContext) { tuple(iterable) -> tuple initialized from iterable's items If the argument is a tuple, the return value is the same object."; - context.set_attr(&tuple_type, "__add__", context.new_rustfunc(PyTupleRef::add)); - context.set_attr(&tuple_type, "__eq__", context.new_rustfunc(PyTupleRef::eq)); - context.set_attr(&tuple_type,"__contains__",context.new_rustfunc(PyTupleRef::contains)); - context.set_attr(&tuple_type,"__getitem__",context.new_rustfunc(PyTupleRef::getitem)); - context.set_attr(&tuple_type, "__hash__", context.new_rustfunc(PyTupleRef::hash)); - context.set_attr(&tuple_type, "__iter__", context.new_rustfunc(PyTupleRef::iter)); - context.set_attr(&tuple_type, "__len__", context.new_rustfunc(PyTupleRef::len)); - context.set_attr(&tuple_type, "__new__", context.new_rustfunc(tuple_new)); - context.set_attr(&tuple_type, "__mul__", context.new_rustfunc(PyTupleRef::mul)); - context.set_attr(&tuple_type, "__repr__", context.new_rustfunc(PyTupleRef::repr)); - context.set_attr(&tuple_type, "count", context.new_rustfunc(PyTupleRef::count)); - context.set_attr(&tuple_type, "__lt__", context.new_rustfunc(PyTupleRef::lt)); - context.set_attr(&tuple_type, "__le__", context.new_rustfunc(PyTupleRef::le)); - context.set_attr(&tuple_type, "__gt__", context.new_rustfunc(PyTupleRef::gt)); - context.set_attr(&tuple_type, "__ge__", context.new_rustfunc(PyTupleRef::ge)); - context.set_attr(&tuple_type,"__doc__",context.new_str(tuple_doc.to_string())); - context.set_attr(&tuple_type, "index", context.new_rustfunc(PyTupleRef::index)); + context.set_attr(tuple_type, "__add__", context.new_rustfunc(PyTupleRef::add)); + context.set_attr(tuple_type, "__eq__", context.new_rustfunc(PyTupleRef::eq)); + context.set_attr(tuple_type,"__contains__",context.new_rustfunc(PyTupleRef::contains)); + context.set_attr(tuple_type,"__getitem__",context.new_rustfunc(PyTupleRef::getitem)); + context.set_attr(tuple_type, "__hash__", context.new_rustfunc(PyTupleRef::hash)); + context.set_attr(tuple_type, "__iter__", context.new_rustfunc(PyTupleRef::iter)); + context.set_attr(tuple_type, "__len__", context.new_rustfunc(PyTupleRef::len)); + context.set_attr(tuple_type, "__new__", context.new_rustfunc(tuple_new)); + context.set_attr(tuple_type, "__mul__", context.new_rustfunc(PyTupleRef::mul)); + context.set_attr(tuple_type, "__repr__", context.new_rustfunc(PyTupleRef::repr)); + context.set_attr(tuple_type, "count", context.new_rustfunc(PyTupleRef::count)); + context.set_attr(tuple_type, "__lt__", context.new_rustfunc(PyTupleRef::lt)); + context.set_attr(tuple_type, "__le__", context.new_rustfunc(PyTupleRef::le)); + context.set_attr(tuple_type, "__gt__", context.new_rustfunc(PyTupleRef::gt)); + context.set_attr(tuple_type, "__ge__", context.new_rustfunc(PyTupleRef::ge)); + context.set_attr(tuple_type,"__doc__",context.new_str(tuple_doc.to_string())); + context.set_attr(tuple_type, "index", context.new_rustfunc(PyTupleRef::index)); } diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 27b137fb21..9dbbb913b1 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -408,11 +408,23 @@ mod tests { #[test] fn test_linearise() { let context = PyContext::new(); - let object: PyClassRef = FromPyObjectRef::from_pyobj(&context.object); + let object: PyClassRef = context.object.clone(); let type_type = &context.type_type; - let a = new(type_type.clone(), "A", vec![object.clone()], HashMap::new()).unwrap(); - let b = new(type_type.clone(), "B", vec![object.clone()], HashMap::new()).unwrap(); + let a = new( + type_type.clone().into_object(), + "A", + vec![object.clone()], + HashMap::new(), + ) + .unwrap(); + let b = new( + type_type.clone().into_object(), + "B", + vec![object.clone()], + HashMap::new(), + ) + .unwrap(); let a: PyClassRef = FromPyObjectRef::from_pyobj(&a); let b: PyClassRef = FromPyObjectRef::from_pyobj(&b); diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index d6e84b653b..a1b3078cc6 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -31,13 +31,13 @@ use crate::obj::objfloat::{self, PyFloat}; use crate::obj::objframe; use crate::obj::objfunction::{self, PyFunction, PyMethod}; use crate::obj::objgenerator; -use crate::obj::objint::{self, PyInt}; +use crate::obj::objint::{self, PyInt, PyIntRef}; use crate::obj::objiter; use crate::obj::objlist::{self, PyList}; use crate::obj::objmap; use crate::obj::objmemory; use crate::obj::objmodule::{self, PyModule}; -use crate::obj::objnone; +use crate::obj::objnone::{self, PyNone, PyNoneRef}; use crate::obj::objobject; use crate::obj::objproperty; use crate::obj::objproperty::PropertyBuilder; @@ -106,61 +106,64 @@ impl fmt::Display for PyObject { #[derive(Debug)] pub struct PyContext { - pub bytes_type: PyObjectRef, - pub bytearray_type: PyObjectRef, - pub bool_type: PyObjectRef, - pub classmethod_type: PyObjectRef, - pub code_type: PyObjectRef, - pub dict_type: PyObjectRef, - pub ellipsis_type: PyObjectRef, - pub enumerate_type: PyObjectRef, - pub filter_type: PyObjectRef, - pub float_type: PyObjectRef, - pub frame_type: PyObjectRef, - pub frozenset_type: PyObjectRef, - pub generator_type: PyObjectRef, - pub int_type: PyObjectRef, - pub iter_type: PyObjectRef, - pub complex_type: PyObjectRef, - pub true_value: PyObjectRef, - pub false_value: PyObjectRef, - pub list_type: PyObjectRef, - pub map_type: PyObjectRef, - pub memoryview_type: PyObjectRef, - pub none: PyObjectRef, - pub ellipsis: PyObjectRef, - pub not_implemented: PyObjectRef, - pub tuple_type: PyObjectRef, - pub set_type: PyObjectRef, - pub staticmethod_type: PyObjectRef, - 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, - pub builtin_function_or_method_type: PyObjectRef, - pub property_type: PyObjectRef, - pub readonly_property_type: PyObjectRef, - pub module_type: PyObjectRef, - pub bound_method_type: PyObjectRef, - pub weakref_type: PyObjectRef, - pub object: PyObjectRef, + pub bytes_type: PyClassRef, + pub bytearray_type: PyClassRef, + pub bool_type: PyClassRef, + pub classmethod_type: PyClassRef, + pub code_type: PyClassRef, + pub dict_type: PyClassRef, + pub ellipsis_type: PyClassRef, + pub enumerate_type: PyClassRef, + pub filter_type: PyClassRef, + pub float_type: PyClassRef, + pub frame_type: PyClassRef, + pub frozenset_type: PyClassRef, + pub generator_type: PyClassRef, + pub int_type: PyClassRef, + pub iter_type: PyClassRef, + pub complex_type: PyClassRef, + pub true_value: PyIntRef, + pub false_value: PyIntRef, + pub list_type: PyClassRef, + pub map_type: PyClassRef, + pub memoryview_type: PyClassRef, + pub none: PyNoneRef, + pub ellipsis: PyEllipsisRef, + pub not_implemented: PyNotImplementedRef, + pub tuple_type: PyClassRef, + pub set_type: PyClassRef, + pub staticmethod_type: PyClassRef, + pub super_type: PyClassRef, + pub str_type: PyClassRef, + pub range_type: PyClassRef, + pub slice_type: PyClassRef, + pub type_type: PyClassRef, + pub zip_type: PyClassRef, + pub function_type: PyClassRef, + pub builtin_function_or_method_type: PyClassRef, + pub property_type: PyClassRef, + pub readonly_property_type: PyClassRef, + pub module_type: PyClassRef, + pub bound_method_type: PyClassRef, + pub weakref_type: PyClassRef, + pub object: PyClassRef, pub exceptions: exceptions::ExceptionZoo, } -pub fn create_type(name: &str, type_type: &PyObjectRef, base: &PyObjectRef) -> PyObjectRef { +pub fn create_type(name: &str, type_type: &PyClassRef, base: &PyClassRef) -> PyClassRef { let dict = PyAttributes::new(); - objtype::new( - type_type.clone(), + let new_type = objtype::new( + type_type.clone().into_object(), name, - vec![FromPyObjectRef::from_pyobj(base)], + vec![base.clone()], dict, ) - .unwrap() + .unwrap(); + FromPyObjectRef::from_pyobj(&new_type) } +pub type PyNotImplementedRef = PyRef; + #[derive(Debug)] pub struct PyNotImplemented; @@ -170,16 +173,18 @@ impl PyValue for PyNotImplemented { } } +pub type PyEllipsisRef = PyRef; + #[derive(Debug)] pub struct PyEllipsis; impl PyValue for PyEllipsis { fn class(vm: &VirtualMachine) -> PyObjectRef { - vm.ctx.ellipsis_type.clone() + vm.ctx.ellipsis_type.clone().into_object() } } -fn init_type_hierarchy() -> (PyObjectRef, PyObjectRef) { +fn init_type_hierarchy() -> (PyClassRef, PyClassRef) { // `type` inherits from `object` // and both `type` and `object are instances of `type`. // to produce this circular dependency, we need an unsafe block. @@ -210,7 +215,10 @@ fn init_type_hierarchy() -> (PyObjectRef, PyObjectRef) { ptr::write(&mut (*object_type_ptr).typ, type_type.clone()); ptr::write(&mut (*type_type_ptr).typ, type_type.clone()); - (type_type, object_type) + ( + PyClassRef::from_pyobj(&type_type), + PyClassRef::from_pyobj(&object_type), + ) } } @@ -244,7 +252,6 @@ impl PyContext { let bytearray_type = create_type("bytearray", &type_type, &object_type); let tuple_type = create_type("tuple", &type_type, &object_type); let iter_type = create_type("iter", &type_type, &object_type); - let ellipsis_type = create_type("EllipsisType", &type_type, &object_type); let enumerate_type = create_type("enumerate", &type_type, &object_type); let filter_type = create_type("filter", &type_type, &object_type); let map_type = create_type("map", &type_type, &object_type); @@ -256,20 +263,24 @@ impl PyContext { let slice_type = create_type("slice", &type_type, &object_type); let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type); - let none = PyObject::new( - objnone::PyNone, - create_type("NoneType", &type_type, &object_type), - ); + fn create_object(payload: T, cls: &PyClassRef) -> PyRef { + PyRef { + obj: PyObject::new(payload, cls.clone().into_object()), + _payload: PhantomData, + } + } + + let none_type = create_type("NoneType", &type_type, &object_type); + let none = create_object(PyNone, &none_type); - let ellipsis = PyObject::new(PyEllipsis, ellipsis_type.clone()); + let ellipsis_type = create_type("EllipsisType", &type_type, &object_type); + let ellipsis = create_object(PyEllipsis, &ellipsis_type); - let not_implemented = PyObject::new( - PyNotImplemented, - create_type("NotImplementedType", &type_type, &object_type), - ); + let not_implemented_type = create_type("NotImplementedType", &type_type, &object_type); + let not_implemented = create_object(PyNotImplemented, ¬_implemented_type); - let true_value = PyObject::new(PyInt::new(BigInt::one()), bool_type.clone()); - let false_value = PyObject::new(PyInt::new(BigInt::zero()), bool_type.clone()); + let true_value = create_object(PyInt::new(BigInt::one()), &bool_type); + let false_value = create_object(PyInt::new(BigInt::zero()), &bool_type); let context = PyContext { bool_type, memoryview_type, @@ -353,159 +364,159 @@ impl PyContext { } pub fn bytearray_type(&self) -> PyObjectRef { - self.bytearray_type.clone() + self.bytearray_type.clone().into_object() } pub fn bytes_type(&self) -> PyObjectRef { - self.bytes_type.clone() + self.bytes_type.clone().into_object() } pub fn code_type(&self) -> PyObjectRef { - self.code_type.clone() + self.code_type.clone().into_object() } pub fn complex_type(&self) -> PyObjectRef { - self.complex_type.clone() + self.complex_type.clone().into_object() } pub fn dict_type(&self) -> PyObjectRef { - self.dict_type.clone() + self.dict_type.clone().into_object() } pub fn float_type(&self) -> PyObjectRef { - self.float_type.clone() + self.float_type.clone().into_object() } pub fn frame_type(&self) -> PyObjectRef { - self.frame_type.clone() + self.frame_type.clone().into_object() } pub fn int_type(&self) -> PyObjectRef { - self.int_type.clone() + self.int_type.clone().into_object() } pub fn list_type(&self) -> PyObjectRef { - self.list_type.clone() + self.list_type.clone().into_object() } pub fn module_type(&self) -> PyObjectRef { - self.module_type.clone() + self.module_type.clone().into_object() } pub fn set_type(&self) -> PyObjectRef { - self.set_type.clone() + self.set_type.clone().into_object() } pub fn range_type(&self) -> PyObjectRef { - self.range_type.clone() + self.range_type.clone().into_object() } pub fn slice_type(&self) -> PyObjectRef { - self.slice_type.clone() + self.slice_type.clone().into_object() } pub fn frozenset_type(&self) -> PyObjectRef { - self.frozenset_type.clone() + self.frozenset_type.clone().into_object() } pub fn bool_type(&self) -> PyObjectRef { - self.bool_type.clone() + self.bool_type.clone().into_object() } pub fn memoryview_type(&self) -> PyObjectRef { - self.memoryview_type.clone() + self.memoryview_type.clone().into_object() } pub fn tuple_type(&self) -> PyObjectRef { - self.tuple_type.clone() + self.tuple_type.clone().into_object() } pub fn iter_type(&self) -> PyObjectRef { - self.iter_type.clone() + self.iter_type.clone().into_object() } pub fn enumerate_type(&self) -> PyObjectRef { - self.enumerate_type.clone() + self.enumerate_type.clone().into_object() } pub fn filter_type(&self) -> PyObjectRef { - self.filter_type.clone() + self.filter_type.clone().into_object() } pub fn map_type(&self) -> PyObjectRef { - self.map_type.clone() + self.map_type.clone().into_object() } pub fn zip_type(&self) -> PyObjectRef { - self.zip_type.clone() + self.zip_type.clone().into_object() } pub fn str_type(&self) -> PyObjectRef { - self.str_type.clone() + self.str_type.clone().into_object() } pub fn super_type(&self) -> PyObjectRef { - self.super_type.clone() + self.super_type.clone().into_object() } pub fn function_type(&self) -> PyObjectRef { - self.function_type.clone() + self.function_type.clone().into_object() } pub fn builtin_function_or_method_type(&self) -> PyObjectRef { - self.builtin_function_or_method_type.clone() + self.builtin_function_or_method_type.clone().into_object() } pub fn property_type(&self) -> PyObjectRef { - self.property_type.clone() + self.property_type.clone().into_object() } pub fn readonly_property_type(&self) -> PyObjectRef { - self.readonly_property_type.clone() + self.readonly_property_type.clone().into_object() } pub fn classmethod_type(&self) -> PyObjectRef { - self.classmethod_type.clone() + self.classmethod_type.clone().into_object() } pub fn staticmethod_type(&self) -> PyObjectRef { - self.staticmethod_type.clone() + self.staticmethod_type.clone().into_object() } pub fn generator_type(&self) -> PyObjectRef { - self.generator_type.clone() + self.generator_type.clone().into_object() } pub fn bound_method_type(&self) -> PyObjectRef { - self.bound_method_type.clone() + self.bound_method_type.clone().into_object() } pub fn weakref_type(&self) -> PyObjectRef { - self.weakref_type.clone() + self.weakref_type.clone().into_object() } pub fn type_type(&self) -> PyObjectRef { - self.type_type.clone() + self.type_type.clone().into_object() } pub fn none(&self) -> PyObjectRef { - self.none.clone() + self.none.clone().into_object() } pub fn ellipsis(&self) -> PyObjectRef { - self.ellipsis.clone() + self.ellipsis.clone().into_object() } pub fn not_implemented(&self) -> PyObjectRef { - self.not_implemented.clone() + self.not_implemented.clone().into_object() } pub fn object(&self) -> PyObjectRef { - self.object.clone() + self.object.clone().into_object() } pub fn new_object(&self) -> PyObjectRef { - self.new_instance(self.object(), None) + self.new_instance(self.object.clone(), None) } pub fn new_int>(&self, i: T) -> PyObjectRef { @@ -534,9 +545,9 @@ impl PyContext { pub fn new_bool(&self, b: bool) -> PyObjectRef { if b { - self.true_value.clone() + self.true_value.clone().into_object() } else { - self.false_value.clone() + self.false_value.clone().into_object() } } @@ -578,7 +589,7 @@ impl PyContext { name: name.to_string(), dict, }, - self.module_type.clone(), + self.module_type.clone().into_object(), ) } @@ -623,10 +634,10 @@ impl PyContext { PyObject::new(PyMethod::new(object, function), self.bound_method_type()) } - pub fn new_instance(&self, class: PyObjectRef, dict: Option) -> PyObjectRef { + pub fn new_instance(&self, class: PyClassRef, dict: Option) -> PyObjectRef { let dict = dict.unwrap_or_default(); PyObject { - typ: class, + typ: class.into_object(), dict: Some(RefCell::new(dict)), payload: Box::new(objobject::PyInstance), } @@ -650,7 +661,13 @@ impl PyContext { obj.get_attr(attr_name) } - pub fn set_attr(&self, obj: &PyObjectRef, attr_name: &str, value: PyObjectRef) { + pub fn set_attr<'a, T: Into<&'a PyObjectRef>>( + &'a self, + obj: T, + attr_name: &str, + value: PyObjectRef, + ) { + let obj = obj.into(); if let Some(PyModule { ref dict, .. }) = obj.payload::() { dict.set_item(self, attr_name, value) } else if let Some(ref dict) = obj.dict { @@ -706,13 +723,22 @@ pub struct PyObject { /// A `PyRef` can be directly returned from a built-in function to handle /// situations (such as when implementing in-place methods such as `__iadd__`) /// where a reference to the same object must be returned. -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct PyRef { // invariant: this obj must always have payload of type T obj: PyObjectRef, _payload: PhantomData, } +impl Clone for PyRef { + fn clone(&self) -> Self { + Self { + obj: self.obj.clone(), + _payload: PhantomData, + } + } +} + impl PyRef { pub fn as_object(&self) -> &PyObjectRef { &self.obj @@ -768,6 +794,12 @@ impl IntoPyObject for PyRef { } } +impl<'a, T: PyValue> From<&'a PyRef> for &'a PyObjectRef { + fn from(obj: &'a PyRef) -> Self { + obj.as_object() + } +} + impl fmt::Display for PyRef where T: PyValue + fmt::Display, @@ -1009,8 +1041,8 @@ where match self.vm.call_method(&self.obj, "__next__", vec![]) { Ok(value) => Some(T::try_from_object(self.vm, value)), Err(err) => { - let stop_ex = self.vm.ctx.exceptions.stop_iteration.clone(); - if objtype::isinstance(&err, &stop_ex) { + let stop_ex = &self.vm.ctx.exceptions.stop_iteration; + if objtype::isinstance(&err, stop_ex.as_object()) { None } else { Some(Err(err)) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 1ce303c5f3..dc351bf5ee 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -102,7 +102,7 @@ fn io_base_cm_exit(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn buffered_io_base_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(buffered, None), (raw, None)]); - vm.ctx.set_attr(&buffered, "raw", raw.clone()); + vm.ctx.set_attr(buffered, "raw", raw.clone()); Ok(vm.get_none()) } @@ -151,10 +151,10 @@ fn file_io_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let args = vec![name.clone(), vm.ctx.new_int(os_mode)]; let file_no = os::os_open(vm, PyFuncArgs::new(args, vec![]))?; - vm.ctx.set_attr(&file_io, "name", name.clone()); - vm.ctx.set_attr(&file_io, "fileno", file_no); - vm.ctx.set_attr(&file_io, "closefd", vm.new_bool(false)); - vm.ctx.set_attr(&file_io, "closed", vm.new_bool(false)); + vm.ctx.set_attr(file_io, "name", name.clone()); + vm.ctx.set_attr(file_io, "fileno", file_no); + vm.ctx.set_attr(file_io, "closefd", vm.new_bool(false)); + vm.ctx.set_attr(file_io, "closed", vm.new_bool(false)); Ok(vm.get_none()) } @@ -218,7 +218,7 @@ fn file_io_readinto(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { }; let updated = os::raw_file_number(f.into_inner()); - vm.ctx.set_attr(&file_io, "fileno", vm.ctx.new_int(updated)); + vm.ctx.set_attr(file_io, "fileno", vm.ctx.new_int(updated)); Ok(vm.get_none()) } @@ -244,7 +244,7 @@ fn file_io_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(len) => { //reset raw fd on the FileIO object let updated = os::raw_file_number(handle); - vm.ctx.set_attr(&file_io, "fileno", vm.ctx.new_int(updated)); + vm.ctx.set_attr(file_io, "fileno", vm.ctx.new_int(updated)); //return number of bytes written Ok(vm.ctx.new_int(len)) @@ -276,7 +276,7 @@ fn text_io_wrapper_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(text_io_wrapper, None), (buffer, None)] ); - vm.ctx.set_attr(&text_io_wrapper, "buffer", buffer.clone()); + vm.ctx.set_attr(text_io_wrapper, "buffer", buffer.clone()); Ok(vm.get_none()) } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index da373a955c..314cd85890 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -6,13 +6,15 @@ use serde::ser::{SerializeMap, SerializeSeq}; use serde_json; use crate::function::PyFuncArgs; +use crate::obj::objtype::PyClassRef; use crate::obj::{ objbool, objdict, objfloat, objint, objsequence, objstr::{self, PyString}, objtype, }; use crate::pyobject::{ - create_type, DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol, + create_type, DictProtocol, FromPyObjectRef, IdProtocol, PyContext, PyObjectRef, PyResult, + TypeProtocol, }; use crate::VirtualMachine; use num_traits::cast::ToPrimitive; @@ -184,7 +186,7 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { where E: serde::de::Error, { - Ok(self.vm.ctx.none.clone()) + Ok(self.vm.ctx.none.clone().into_object()) } } @@ -206,6 +208,7 @@ pub fn de_pyobject(vm: &VirtualMachine, s: &str) -> PyResult { .unwrap() .get_item("JSONDecodeError") .unwrap(); + let json_decode_error = PyClassRef::from_pyobj(&json_decode_error); let exc = vm.new_exception(json_decode_error, format!("{}", err)); vm.ctx.set_attr(&exc, "lineno", vm.ctx.new_int(err.line())); vm.ctx.set_attr(&exc, "colno", vm.ctx.new_int(err.column())); @@ -240,6 +243,6 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "json", { "dumps" => ctx.new_rustfunc(json_dumps), "loads" => ctx.new_rustfunc(json_loads), - "JSONDecodeError" => json_decode_error + "JSONDecodeError" => json_decode_error.into_object() }) } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 029588d130..fc7d017451 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -28,6 +28,7 @@ use crate::obj::objsequence; use crate::obj::objstr::{PyString, PyStringRef}; use crate::obj::objtuple::PyTuple; use crate::obj::objtype; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{ DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, TryIntoRef, TypeProtocol, @@ -137,7 +138,7 @@ impl VirtualMachine { self.invoke(exc_type, args) } - pub fn new_exception(&self, exc_type: PyObjectRef, msg: String) -> PyObjectRef { + pub fn new_exception(&self, exc_type: PyClassRef, msg: String) -> PyObjectRef { // TODO: exc_type may be user-defined exception, so we should return PyResult // TODO: maybe there is a clearer way to create an instance: info!("New exception created: {}", msg); @@ -145,7 +146,7 @@ impl VirtualMachine { let args: Vec = vec![pymsg]; // Call function: - self.invoke(exc_type, args).unwrap() + self.invoke(exc_type.into_object(), args).unwrap() } pub fn new_attribute_error(&self, msg: String) -> PyObjectRef { diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 122ff1763e..70d67d88db 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -13,7 +13,7 @@ use crate::vm_class::{AccessibleVM, WASMVirtualMachine}; pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { macro_rules! map_exceptions { ($py_exc:ident, $msg:expr, { $($py_exc_ty:expr => $js_err_new:expr),*$(,)? }) => { - $(if objtype::isinstance($py_exc, $py_exc_ty) { + $(if objtype::isinstance($py_exc, $py_exc_ty.as_object()) { JsValue::from($js_err_new($msg)) } else)* { JsValue::from(js_sys::Error::new($msg)) From b354b86b7b8109a0cf36badbefc72e51359b2d15 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 23 Mar 2019 08:59:05 +1300 Subject: [PATCH 010/884] Fix none_new, return same object that is in PyContext --- vm/src/obj/objnone.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm/src/obj/objnone.rs b/vm/src/obj/objnone.rs index 66ec0e30c5..086bdce87f 100644 --- a/vm/src/obj/objnone.rs +++ b/vm/src/obj/objnone.rs @@ -102,8 +102,8 @@ impl PyNoneRef { } } -fn none_new(_: PyClassRef, _vm: &VirtualMachine) -> PyNone { - PyNone +fn none_new(_: PyClassRef, vm: &VirtualMachine) -> PyNoneRef { + vm.ctx.none.clone() } pub fn init(context: &PyContext) { From bb161a5a670e1b2ff2b429e8da1b1807bf98bed0 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Fri, 22 Mar 2019 18:04:09 -0700 Subject: [PATCH 011/884] Remove Box from PyObject --- vm/src/obj/objtype.rs | 17 +++------ vm/src/obj/objweakref.rs | 4 +-- vm/src/pyobject.rs | 76 ++++++++++++++++++++++++++++------------ vm/src/vm.rs | 2 +- 4 files changed, 61 insertions(+), 38 deletions(-) diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 78c946dbc1..65a145888e 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -35,12 +35,6 @@ impl PyValue for PyClass { } } -impl IdProtocol for PyClassRef { - fn get_id(&self) -> usize { - self.as_object().get_id() - } -} - impl TypeProtocol for PyClassRef { fn type_ref(&self) -> &PyObjectRef { &self.as_object().type_ref() @@ -300,10 +294,7 @@ fn take_next_base(mut bases: Vec>) -> Option<(PyClassRef, Vec>) -> Option<(PyClassRef, Vec, + referent: Weak>, } impl PyWeak { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 5780cf6201..10e398b2a4 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -72,7 +72,7 @@ Basically reference counting, but then done by rust. /// this reference counting is accounted for by this type. Use the `.clone()` /// method to create a new reference and increment the amount of references /// to the python object by 1. -pub type PyObjectRef = Rc; +pub type PyObjectRef = Rc>; /// Use this type for function which return a python object or and exception. /// Both the python object and the python exception are `PyObjectRef` types @@ -83,7 +83,7 @@ pub type PyResult = Result; // A valid value, o /// faster, unordered, and only supports strings as keys. pub type PyAttributes = HashMap; -impl fmt::Display for PyObject { +impl fmt::Display for PyObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::TypeProtocol; if let Some(PyClass { ref name, .. }) = self.payload::() { @@ -188,25 +188,25 @@ fn init_type_hierarchy() -> (PyObjectRef, PyObjectRef) { let object_type = PyObject { typ: mem::uninitialized(), // ! dict: Some(RefCell::new(PyAttributes::new())), - payload: Box::new(PyClass { + payload: PyClass { name: String::from("object"), mro: vec![], - }), + }, } .into_ref(); let type_type = PyObject { typ: mem::uninitialized(), // ! dict: Some(RefCell::new(PyAttributes::new())), - payload: Box::new(PyClass { + payload: PyClass { name: String::from("type"), mro: vec![FromPyObjectRef::from_pyobj(&object_type)], - }), + }, } .into_ref(); - let object_type_ptr = PyObjectRef::into_raw(object_type.clone()) as *mut PyObject; - let type_type_ptr = PyObjectRef::into_raw(type_type.clone()) as *mut PyObject; + let object_type_ptr = PyObjectRef::into_raw(object_type.clone()) as *mut PyObject; + let type_type_ptr = PyObjectRef::into_raw(type_type.clone()) as *mut PyObject; ptr::write(&mut (*object_type_ptr).typ, type_type.clone()); ptr::write(&mut (*type_type_ptr).typ, type_type.clone()); @@ -628,7 +628,7 @@ impl PyContext { PyObject { typ: class, dict: Some(RefCell::new(dict)), - payload: Box::new(objobject::PyInstance), + payload: objobject::PyInstance, } .into_ref() } @@ -691,10 +691,13 @@ impl Default for PyContext { /// This is an actual python object. It consists of a `typ` which is the /// python class, and carries some rust payload optionally. This rust /// payload can be a rust float or rust int in case of float and int objects. -pub struct PyObject { +pub struct PyObject +where + T: ?Sized + PyObjectPayload, +{ pub typ: PyObjectRef, pub dict: Option>, // __dict__ member - pub payload: Box, + pub payload: T, } /// A reference to a Python object. @@ -788,9 +791,30 @@ pub trait IdProtocol { } } -impl IdProtocol for PyObjectRef { +#[derive(Debug)] +enum Never {} + +impl PyValue for Never { + fn class(_vm: &mut VirtualMachine) -> PyObjectRef { + unreachable!() + } +} + +impl IdProtocol for PyObject { + fn get_id(&self) -> usize { + self as *const _ as *const PyObject as usize + } +} + +impl IdProtocol for Rc { + fn get_id(&self) -> usize { + (**self).get_id() + } +} + +impl IdProtocol for PyRef { fn get_id(&self) -> usize { - &*self as &PyObject as *const PyObject as usize + self.obj.get_id() } } @@ -814,7 +838,10 @@ impl TypeProtocol for PyObjectRef { } } -impl TypeProtocol for PyObject { +impl TypeProtocol for PyObject +where + T: ?Sized + PyObjectPayload, +{ fn type_ref(&self) -> &PyObjectRef { &self.typ } @@ -953,9 +980,9 @@ impl BufferProtocol for PyObjectRef { } } -impl fmt::Debug for PyObject { +impl fmt::Debug for PyObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "[PyObj {:?}]", self.payload) + write!(f, "[PyObj {:?}]", &self.payload) } } @@ -1127,21 +1154,24 @@ impl PyValue for PyIteratorValue { } } -impl PyObject { - pub fn new(payload: T, typ: PyObjectRef) -> PyObjectRef { +impl PyObject +where + T: Sized + PyObjectPayload, +{ + pub fn new(payload: T, typ: PyObjectRef) -> PyObjectRef { PyObject { typ, dict: Some(RefCell::new(PyAttributes::new())), - payload: Box::new(payload), + payload, } .into_ref() } - pub fn new_without_dict(payload: T, typ: PyObjectRef) -> PyObjectRef { + pub fn new_without_dict(payload: T, typ: PyObjectRef) -> PyObjectRef { PyObject { typ, dict: None, - payload: Box::new(payload), + payload, } .into_ref() } @@ -1150,7 +1180,9 @@ impl PyObject { pub fn into_ref(self) -> PyObjectRef { Rc::new(self) } +} +impl PyObject { #[inline] pub fn payload(&self) -> Option<&T> { self.payload.as_any().downcast_ref() @@ -1213,7 +1245,7 @@ impl FromPyObjectRef for PyRef { #[cfg(test)] mod tests { - use super::PyContext; + use super::*; #[test] fn test_type_type() { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 7f9b474d0a..491d391cef 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -333,7 +333,7 @@ impl VirtualMachine { } // TODO: is it safe to just invoke __call__ otherwise? - trace!("invoke __call__ for: {:?}", func_ref.payload); + trace!("invoke __call__ for: {:?}", &func_ref.payload); self.call_method(&func_ref, "__call__", args) } From c06b1c70dffac8862d46e7fb996f58bddff8464a Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Fri, 22 Mar 2019 18:05:24 -0700 Subject: [PATCH 012/884] Fix merge issue --- vm/src/pyobject.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index e585c54180..d9622fc8e7 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -795,7 +795,7 @@ pub trait IdProtocol { enum Never {} impl PyValue for Never { - fn class(_vm: &mut VirtualMachine) -> PyObjectRef { + fn class(_vm: &VirtualMachine) -> PyObjectRef { unreachable!() } } From 85ffde738253023c519e00e8e1375e69edabfe52 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Fri, 22 Mar 2019 18:37:06 -0700 Subject: [PATCH 013/884] Fix wasm --- wasm/lib/src/vm_class.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 9d80854f73..cbb35cc95c 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -8,7 +8,7 @@ use wasm_bindgen::prelude::*; use rustpython_vm::compile; use rustpython_vm::frame::{NameProtocol, Scope}; use rustpython_vm::function::PyFuncArgs; -use rustpython_vm::pyobject::{PyContext, PyObjectRef, PyResult}; +use rustpython_vm::pyobject::{PyContext, PyObject, PyObjectPayload, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; use crate::browser_module::setup_browser_module; @@ -24,7 +24,7 @@ pub(crate) struct StoredVirtualMachine { pub scope: RefCell, /// you can put a Rc in here, keep it as a Weak, and it'll be held only for /// as long as the StoredVM is alive - held_rcs: RefCell>>, + held_objects: RefCell>, } impl StoredVirtualMachine { @@ -38,7 +38,7 @@ impl StoredVirtualMachine { StoredVirtualMachine { vm, scope: RefCell::new(scope), - held_rcs: RefCell::new(Vec::new()), + held_objects: RefCell::new(Vec::new()), } } } @@ -178,13 +178,13 @@ impl WASMVirtualMachine { STORED_VMS.with(|cell| cell.borrow().contains_key(&self.id)) } - pub(crate) fn push_held_rc( + pub(crate) fn push_held_rc( &self, - rc: Rc, - ) -> Result, JsValue> { + obj: PyObjectRef, + ) -> Result>, JsValue> { self.with(|stored_vm| { - let weak = Rc::downgrade(&rc); - stored_vm.held_rcs.borrow_mut().push(rc); + let weak = Rc::downgrade(&obj); + stored_vm.held_objects.borrow_mut().push(obj); weak }) } From 050bf0730ec72a1b438d284fed399c18bde441ba Mon Sep 17 00:00:00 2001 From: Joey Date: Fri, 22 Mar 2019 19:10:22 -0700 Subject: [PATCH 014/884] Remove HeldRcInner --- wasm/lib/src/vm_class.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index cbb35cc95c..fd2181182f 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -15,10 +15,6 @@ use crate::browser_module::setup_browser_module; use crate::convert; use crate::wasm_builtins; -pub trait HeldRcInner {} - -impl HeldRcInner for T {} - pub(crate) struct StoredVirtualMachine { pub vm: VirtualMachine, pub scope: RefCell, From c1d5ce715f378d19dd5f72d39bb8bb9118048f86 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 23 Mar 2019 13:34:30 +1300 Subject: [PATCH 015/884] Change isinstance/issubclass to accept PyClassRef instead of PyCObject. Also changed PyValue::class to return a PyClassRef. --- vm/src/builtins.rs | 91 +++++++------- vm/src/compile.rs | 8 +- vm/src/exceptions.rs | 5 +- vm/src/frame.rs | 41 +++---- vm/src/function.rs | 3 +- vm/src/obj/objbuiltinfunc.rs | 5 +- vm/src/obj/objbytearray.rs | 2 +- vm/src/obj/objbytes.rs | 2 +- vm/src/obj/objclassmethod.rs | 2 +- vm/src/obj/objcode.rs | 3 +- vm/src/obj/objcomplex.rs | 2 +- vm/src/obj/objdict.rs | 3 +- vm/src/obj/objenumerate.rs | 2 +- vm/src/obj/objfilter.rs | 3 +- vm/src/obj/objfloat.rs | 23 ++-- vm/src/obj/objfunction.rs | 5 +- vm/src/obj/objgenerator.rs | 3 +- vm/src/obj/objint.rs | 12 +- vm/src/obj/objiter.rs | 8 +- vm/src/obj/objlist.rs | 16 +-- vm/src/obj/objmap.rs | 2 +- vm/src/obj/objmemory.rs | 3 +- vm/src/obj/objmodule.rs | 3 +- vm/src/obj/objnone.rs | 4 +- vm/src/obj/objobject.rs | 3 +- vm/src/obj/objproperty.rs | 8 +- vm/src/obj/objrange.rs | 54 ++++----- vm/src/obj/objset.rs | 8 +- vm/src/obj/objslice.rs | 3 +- vm/src/obj/objstaticmethod.rs | 2 +- vm/src/obj/objstr.rs | 2 +- vm/src/obj/objsuper.rs | 66 +++++----- vm/src/obj/objtuple.rs | 6 +- vm/src/obj/objtype.rs | 20 ++-- vm/src/obj/objweakref.rs | 2 +- vm/src/obj/objzip.rs | 3 +- vm/src/pyobject.rs | 213 +++++++++++++++++---------------- vm/src/stdlib/ast.rs | 6 +- vm/src/stdlib/io.rs | 26 ++-- vm/src/stdlib/re.rs | 9 +- vm/src/stdlib/socket.rs | 5 +- vm/src/stdlib/types.rs | 10 +- vm/src/stdlib/weakref.rs | 2 +- vm/src/vm.rs | 30 ++--- wasm/lib/src/browser_module.rs | 4 +- 45 files changed, 371 insertions(+), 362 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 8c4d68025c..47903bd001 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -15,11 +15,13 @@ use crate::obj::objdict; use crate::obj::objint; use crate::obj::objiter; use crate::obj::objstr::{self, PyStringRef}; -use crate::obj::objtype; +use crate::obj::objtype::{self, PyClassRef}; use crate::frame::Scope; use crate::function::{Args, OptionalArg, PyFuncArgs}; -use crate::pyobject::{DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{ + DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, TypeProtocol, +}; use crate::vm::VirtualMachine; #[cfg(not(target_arch = "wasm32"))] @@ -295,7 +297,7 @@ fn builtin_format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn catch_attr_exception(ex: PyObjectRef, default: T, vm: &VirtualMachine) -> PyResult { - if objtype::isinstance(&ex, vm.ctx.exceptions.attribute_error.as_object()) { + if objtype::isinstance(&ex, &vm.ctx.exceptions.attribute_error) { Ok(default) } else { Err(ex) @@ -357,15 +359,8 @@ fn builtin_id(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // builtin_input -fn builtin_isinstance(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(obj, None), (typ, Some(vm.get_type()))] - ); - - let isinstance = vm.isinstance(obj, typ)?; - Ok(vm.new_bool(isinstance)) +fn builtin_isinstance(obj: PyObjectRef, typ: PyClassRef, vm: &VirtualMachine) -> PyResult { + vm.isinstance(&obj, &typ) } fn builtin_issubclass(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -510,7 +505,7 @@ fn builtin_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { match vm.call_method(iterator, "__next__", vec![]) { Ok(value) => Ok(value), Err(value) => { - if objtype::isinstance(&value, vm.ctx.exceptions.stop_iteration.as_object()) { + if objtype::isinstance(&value, &vm.ctx.exceptions.stop_iteration) { match default_value { None => Err(value), Some(value) => Ok(value.clone()), @@ -719,24 +714,24 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "all" => ctx.new_rustfunc(builtin_all), "any" => ctx.new_rustfunc(builtin_any), "bin" => ctx.new_rustfunc(builtin_bin), - "bool" => ctx.bool_type(), - "bytearray" => ctx.bytearray_type(), - "bytes" => ctx.bytes_type(), + "bool" => ctx.bool_type().into_object(), + "bytearray" => ctx.bytearray_type().into_object(), + "bytes" => ctx.bytes_type().into_object(), "callable" => ctx.new_rustfunc(builtin_callable), "chr" => ctx.new_rustfunc(builtin_chr), - "classmethod" => ctx.classmethod_type(), + "classmethod" => ctx.classmethod_type().into_object(), "compile" => ctx.new_rustfunc(builtin_compile), - "complex" => ctx.complex_type(), + "complex" => ctx.complex_type().into_object(), "delattr" => ctx.new_rustfunc(builtin_delattr), - "dict" => ctx.dict_type(), + "dict" => ctx.dict_type().into_object(), "divmod" => ctx.new_rustfunc(builtin_divmod), "dir" => ctx.new_rustfunc(builtin_dir), - "enumerate" => ctx.enumerate_type(), + "enumerate" => ctx.enumerate_type().into_object(), "eval" => ctx.new_rustfunc(builtin_eval), "exec" => ctx.new_rustfunc(builtin_exec), - "float" => ctx.float_type(), - "frozenset" => ctx.frozenset_type(), - "filter" => ctx.filter_type(), + "float" => ctx.float_type().into_object(), + "frozenset" => ctx.frozenset_type().into_object(), + "filter" => ctx.filter_type().into_object(), "format" => ctx.new_rustfunc(builtin_format), "getattr" => ctx.new_rustfunc(builtin_getattr), "globals" => ctx.new_rustfunc(builtin_globals), @@ -744,39 +739,39 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "hash" => ctx.new_rustfunc(builtin_hash), "hex" => ctx.new_rustfunc(builtin_hex), "id" => ctx.new_rustfunc(builtin_id), - "int" => ctx.int_type(), + "int" => ctx.int_type().into_object(), "isinstance" => ctx.new_rustfunc(builtin_isinstance), "issubclass" => ctx.new_rustfunc(builtin_issubclass), "iter" => ctx.new_rustfunc(builtin_iter), "len" => ctx.new_rustfunc(builtin_len), - "list" => ctx.list_type(), + "list" => ctx.list_type().into_object(), "locals" => ctx.new_rustfunc(builtin_locals), - "map" => ctx.map_type(), + "map" => ctx.map_type().into_object(), "max" => ctx.new_rustfunc(builtin_max), - "memoryview" => ctx.memoryview_type(), + "memoryview" => ctx.memoryview_type().into_object(), "min" => ctx.new_rustfunc(builtin_min), - "object" => ctx.object(), + "object" => ctx.object().into_object(), "oct" => ctx.new_rustfunc(builtin_oct), "ord" => ctx.new_rustfunc(builtin_ord), "next" => ctx.new_rustfunc(builtin_next), "pow" => ctx.new_rustfunc(builtin_pow), "print" => ctx.new_rustfunc(builtin_print), - "property" => ctx.property_type(), - "range" => ctx.range_type(), + "property" => ctx.property_type().into_object(), + "range" => ctx.range_type().into_object(), "repr" => ctx.new_rustfunc(builtin_repr), "reversed" => ctx.new_rustfunc(builtin_reversed), "round" => ctx.new_rustfunc(builtin_round), - "set" => ctx.set_type(), + "set" => ctx.set_type().into_object(), "setattr" => ctx.new_rustfunc(builtin_setattr), "sorted" => ctx.new_rustfunc(builtin_sorted), - "slice" => ctx.slice_type(), - "staticmethod" => ctx.staticmethod_type(), - "str" => ctx.str_type(), + "slice" => ctx.slice_type().into_object(), + "staticmethod" => ctx.staticmethod_type().into_object(), + "str" => ctx.str_type().into_object(), "sum" => ctx.new_rustfunc(builtin_sum), - "super" => ctx.super_type(), - "tuple" => ctx.tuple_type(), - "type" => ctx.type_type(), - "zip" => ctx.zip_type(), + "super" => ctx.super_type().into_object(), + "tuple" => ctx.tuple_type().into_object(), + "type" => ctx.type_type().into_object(), + "zip" => ctx.zip_type().into_object(), "__import__" => ctx.new_rustfunc(builtin_import), // Constants @@ -813,12 +808,16 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu let function = args.shift(); let name_arg = args.shift(); let bases = args.args.clone(); - let mut metaclass = args.get_kwarg("metaclass", vm.get_type()); + let mut metaclass = if let Some(metaclass) = args.get_optional_kwarg("metaclass") { + PyClassRef::try_from_object(vm, metaclass)? + } else { + vm.get_type() + }; for base in bases.clone() { - if objtype::issubclass(&base.typ(), &metaclass) { - metaclass = base.typ(); - } else if !objtype::issubclass(&metaclass, &base.typ()) { + if objtype::issubclass(&base.type_pyref(), &metaclass) { + metaclass = base.type_pyref(); + } else if !objtype::issubclass(&metaclass, &base.type_pyref()) { return Err(vm.new_type_error("metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases".to_string())); } } @@ -826,13 +825,17 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu let bases = vm.context().new_tuple(bases); // Prepare uses full __getattribute__ resolution chain. - let prepare = vm.get_attribute(metaclass.clone(), "__prepare__")?; + let prepare = vm.get_attribute(metaclass.clone().into_object(), "__prepare__")?; let namespace = vm.invoke(prepare, vec![name_arg.clone(), bases.clone()])?; let cells = vm.new_dict(); vm.invoke_with_locals(function, cells.clone(), namespace.clone())?; - let class = vm.call_method(&metaclass, "__call__", vec![name_arg, bases, namespace])?; + let class = vm.call_method( + metaclass.as_object(), + "__call__", + vec![name_arg, bases, namespace], + )?; cells.set_item(&vm.ctx, "__class__", class.clone()); Ok(class) } diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 1742392cdd..46101cdf46 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -8,6 +8,7 @@ use crate::bytecode::{self, CallType, CodeObject, Instruction, Varargs}; use crate::error::CompileError; use crate::obj::objcode; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyObject, PyObjectRef}; use num_complex::Complex64; use rustpython_parser::{ast, parser}; @@ -26,7 +27,7 @@ pub fn compile( source: &str, mode: &Mode, source_path: String, - code_type: PyObjectRef, + code_type: PyClassRef, ) -> Result { let mut compiler = Compiler::new(); compiler.source_path = Some(source_path); @@ -49,7 +50,10 @@ pub fn compile( let code = compiler.pop_code_object(); trace!("Compilation completed: {:?}", code); - Ok(PyObject::new(objcode::PyCode::new(code), code_type)) + Ok(PyObject::new( + objcode::PyCode::new(code), + code_type.into_object(), + )) } pub enum Mode { diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index c08160dd6e..3326299641 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -66,10 +66,7 @@ fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, - required = [( - exc, - Some(vm.ctx.exceptions.exception_type.clone().into_object()) - )] + required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))] ); let type_name = objtype::get_type_name(&exc.typ()); let msg = if let Ok(m) = vm.get_attribute(exc.clone(), "msg") { diff --git a/vm/src/frame.rs b/vm/src/frame.rs index d0fa668c70..60744c8e4e 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -20,6 +20,7 @@ use crate::obj::objlist; use crate::obj::objslice::PySlice; use crate::obj::objstr; use crate::obj::objtype; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{ DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, @@ -195,7 +196,7 @@ pub struct Frame { } impl PyValue for Frame { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.frame_type() } } @@ -253,7 +254,7 @@ impl Frame { // Add an entry in the traceback: assert!(objtype::isinstance( &exception, - vm.ctx.exceptions.base_exception_type.as_object() + &vm.ctx.exceptions.base_exception_type )); let traceback = vm .get_attribute(exception.clone(), "__traceback__") @@ -656,29 +657,25 @@ impl Frame { 0 | 2 | 3 => panic!("Not implemented!"), _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"), }; - if objtype::isinstance( - &exception, - vm.ctx.exceptions.base_exception_type.as_object(), - ) { - info!("Exception raised: {:?}", exception); - Err(exception) - } else if objtype::isinstance(&exception, &vm.ctx.type_type()) - && objtype::issubclass( - &exception, - vm.ctx.exceptions.base_exception_type.as_object(), - ) - { - let exception = vm.new_empty_exception(exception)?; + if objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { info!("Exception raised: {:?}", exception); Err(exception) + } else if let Ok(exception) = PyClassRef::try_from_object(vm, exception) { + if objtype::issubclass(&exception, &vm.ctx.exceptions.base_exception_type) { + let exception = vm.new_empty_exception(exception)?; + info!("Exception raised: {:?}", exception); + Err(exception) + } else { + let msg = format!( + "Can only raise BaseException derived types, not {}", + exception + ); + let type_error_type = vm.ctx.exceptions.type_error.clone(); + let type_error = vm.new_exception(type_error_type, msg); + Err(type_error) + } } else { - let msg = format!( - "Can only raise BaseException derived types, not {}", - exception - ); - let type_error_type = vm.ctx.exceptions.type_error.clone(); - let type_error = vm.new_exception(type_error_type, msg); - Err(type_error) + Err(vm.new_type_error("exceptions must derive from BaseException".to_string())) } } diff --git a/vm/src/function.rs b/vm/src/function.rs index 7159427e56..839cbc9eaa 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -6,6 +6,7 @@ use crate::pyobject::{IntoPyObject, PyObjectRef, PyResult, TryFromObject, TypePr use crate::vm::VirtualMachine; use self::OptionalArg::*; +use crate::obj::objtype::PyClassRef; /// The `PyFuncArgs` struct is one of the most used structs then creating /// a rust function that can be called from python. It holds both positional @@ -89,7 +90,7 @@ impl PyFuncArgs { pub fn get_optional_kwarg_with_type( &self, key: &str, - ty: PyObjectRef, + ty: PyClassRef, vm: &VirtualMachine, ) -> Result, PyObjectRef> { match self.get_optional_kwarg(key) { diff --git a/vm/src/obj/objbuiltinfunc.rs b/vm/src/obj/objbuiltinfunc.rs index 259c94b627..928f240b01 100644 --- a/vm/src/obj/objbuiltinfunc.rs +++ b/vm/src/obj/objbuiltinfunc.rs @@ -1,7 +1,8 @@ use std::fmt; use crate::function::PyNativeFunc; -use crate::pyobject::{PyObjectRef, PyValue}; +use crate::obj::objtype::PyClassRef; +use crate::pyobject::PyValue; use crate::vm::VirtualMachine; pub struct PyBuiltinFunction { @@ -10,7 +11,7 @@ pub struct PyBuiltinFunction { } impl PyValue for PyBuiltinFunction { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.builtin_function_or_method_type() } } diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index ff7a6d9382..3db772a7f2 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -29,7 +29,7 @@ impl PyByteArray { } impl PyValue for PyByteArray { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.bytearray_type() } } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index c1fdcf955b..98f2d6df39 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -34,7 +34,7 @@ impl Deref for PyBytes { } impl PyValue for PyBytes { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.bytes_type() } } diff --git a/vm/src/obj/objclassmethod.rs b/vm/src/obj/objclassmethod.rs index 725f508e98..ffacfdc07a 100644 --- a/vm/src/obj/objclassmethod.rs +++ b/vm/src/obj/objclassmethod.rs @@ -9,7 +9,7 @@ pub struct PyClassMethod { pub type PyClassMethodRef = PyRef; impl PyValue for PyClassMethod { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.classmethod_type() } } diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 6c7f66002b..7c43198c37 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -6,6 +6,7 @@ use std::fmt; use crate::bytecode; use crate::function::PyFuncArgs; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; @@ -26,7 +27,7 @@ impl fmt::Debug for PyCode { } impl PyValue for PyCode { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.code_type() } } diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 15baf5df42..c2ed9178b6 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -16,7 +16,7 @@ pub struct PyComplex { type PyComplexRef = PyRef; impl PyValue for PyComplex { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.complex_type() } } diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index ad134f5aeb..036e26ec0f 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -12,6 +12,7 @@ use crate::vm::{ReprGuard, VirtualMachine}; use super::objiter; use super::objstr::{self, PyStringRef}; use super::objtype; +use crate::obj::objtype::PyClassRef; pub type DictContentType = HashMap; @@ -30,7 +31,7 @@ impl fmt::Debug for PyDict { } impl PyValue for PyDict { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.dict_type() } } diff --git a/vm/src/obj/objenumerate.rs b/vm/src/obj/objenumerate.rs index 5a66b7b5e2..4e6c9b1a7d 100644 --- a/vm/src/obj/objenumerate.rs +++ b/vm/src/obj/objenumerate.rs @@ -20,7 +20,7 @@ pub struct PyEnumerate { type PyEnumerateRef = PyRef; impl PyValue for PyEnumerate { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.enumerate_type() } } diff --git a/vm/src/obj/objfilter.rs b/vm/src/obj/objfilter.rs index d83f70cc5c..6fcf84fb4d 100644 --- a/vm/src/obj/objfilter.rs +++ b/vm/src/obj/objfilter.rs @@ -6,6 +6,7 @@ use crate::vm::VirtualMachine; // Required for arg_check! to use isinstance use super::objbool; use super::objiter; +use crate::obj::objtype::PyClassRef; #[derive(Debug)] pub struct PyFilter { @@ -14,7 +15,7 @@ pub struct PyFilter { } impl PyValue for PyFilter { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.filter_type() } } diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index cd6a4f33f0..ded6876687 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -2,6 +2,7 @@ use super::objbytes; use super::objint; use super::objstr; use super::objtype; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{ IntoPyObject, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; @@ -16,7 +17,7 @@ pub struct PyFloat { } impl PyValue for PyFloat { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.float_type() } } @@ -133,9 +134,9 @@ impl PyFloatRef { fn floordiv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; - let v2 = if objtype::isinstance(&other, vm.ctx.float_type.as_object()) { + let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) - } else if objtype::isinstance(&other, vm.ctx.int_type.as_object()) { + } else if objtype::isinstance(&other, &vm.ctx.int_type) { objint::get_value(&other).to_f64().ok_or_else(|| { vm.new_overflow_error("int too large to convert to float".to_string()) })? @@ -193,9 +194,9 @@ impl PyFloatRef { fn mod_(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; - let v2 = if objtype::isinstance(&other, vm.ctx.float_type.as_object()) { + let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) - } else if objtype::isinstance(&other, vm.ctx.int_type.as_object()) { + } else if objtype::isinstance(&other, &vm.ctx.int_type) { objint::get_value(&other).to_f64().ok_or_else(|| { vm.new_overflow_error("int too large to convert to float".to_string()) })? @@ -258,9 +259,9 @@ impl PyFloatRef { fn truediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; - let v2 = if objtype::isinstance(&other, vm.ctx.float_type.as_object()) { + let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) - } else if objtype::isinstance(&other, vm.ctx.int_type.as_object()) { + } else if objtype::isinstance(&other, &vm.ctx.int_type) { objint::get_value(&other).to_f64().ok_or_else(|| { vm.new_overflow_error("int too large to convert to float".to_string()) })? @@ -277,9 +278,9 @@ impl PyFloatRef { fn rtruediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; - let v2 = if objtype::isinstance(&other, vm.ctx.float_type.as_object()) { + let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) - } else if objtype::isinstance(&other, vm.ctx.int_type.as_object()) { + } else if objtype::isinstance(&other, &vm.ctx.int_type) { objint::get_value(&other).to_f64().ok_or_else(|| { vm.new_overflow_error("int too large to convert to float".to_string()) })? @@ -296,9 +297,9 @@ impl PyFloatRef { fn mul(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; - if objtype::isinstance(&other, vm.ctx.float_type.as_object()) { + if objtype::isinstance(&other, &vm.ctx.float_type) { Ok(vm.ctx.new_float(v1 * get_value(&other))) - } else if objtype::isinstance(&other, vm.ctx.int_type.as_object()) { + } else if objtype::isinstance(&other, &vm.ctx.int_type) { Ok(vm .ctx .new_float(v1 * objint::get_value(&other).to_f64().unwrap())) diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index c4c480d67b..96fed8eec2 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -1,5 +1,6 @@ use crate::frame::Scope; use crate::function::PyFuncArgs; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; @@ -22,7 +23,7 @@ impl PyFunction { } impl PyValue for PyFunction { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.function_type() } } @@ -41,7 +42,7 @@ impl PyMethod { } impl PyValue for PyMethod { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.bound_method_type() } } diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index 0307f35cf4..ccb19a32c2 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -4,6 +4,7 @@ use crate::frame::{ExecutionResult, Frame}; use crate::function::PyFuncArgs; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; @@ -14,7 +15,7 @@ pub struct PyGenerator { type PyGeneratorRef = PyRef; impl PyValue for PyGenerator { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.generator_type() } } diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 1a8b565579..f388aeac67 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -7,7 +7,7 @@ use num_traits::{Pow, Signed, ToPrimitive, Zero}; use crate::format::FormatSpec; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - IntoPyObject, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, + FromPyObjectRef, IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -15,6 +15,7 @@ use crate::vm::VirtualMachine; use super::objfloat; use super::objstr; use super::objtype; +use crate::obj::objtype::PyClassRef; #[derive(Debug)] pub struct PyInt { @@ -37,7 +38,7 @@ impl IntoPyObject for BigInt { } impl PyValue for PyInt { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.int_type() } } @@ -377,9 +378,6 @@ fn int_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(cls, None)], optional = [(val_option, None)] ); - if !objtype::issubclass(cls, &vm.ctx.int_type()) { - return Err(vm.new_type_error(format!("{:?} is not a subtype of int", cls))); - } let base = match args.get_optional_kwarg("base") { Some(argument) => get_value(&argument).to_u32().unwrap(), @@ -389,7 +387,9 @@ fn int_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Some(val) => to_int(vm, val, base)?, None => Zero::zero(), }; - Ok(PyObject::new(PyInt::new(val), cls.clone())) + Ok(PyInt::new(val) + .into_ref_with_type(vm, PyClassRef::from_pyobj(cls))? + .into_object()) } // Casting function: diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 2726af1336..7c2341173b 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -43,7 +43,7 @@ pub fn get_next_object( Ok(value) => Ok(Some(value)), Err(next_error) => { // Check if we have stopiteration, or something else: - if objtype::isinstance(&next_error, vm.ctx.exceptions.stop_iteration.as_object()) { + if objtype::isinstance(&next_error, &vm.ctx.exceptions.stop_iteration) { Ok(None) } else { Err(next_error) @@ -70,7 +70,7 @@ pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef { vm.new_exception(stop_iteration_type, "End of iterator".to_string()) } -fn contains(vm: &VirtualMachine, args: PyFuncArgs, iter_type: PyObjectRef) -> PyResult { +fn contains(vm: &VirtualMachine, args: PyFuncArgs, iter_type: PyClassRef) -> PyResult { arg_check!( vm, args, @@ -93,7 +93,7 @@ fn contains(vm: &VirtualMachine, args: PyFuncArgs, iter_type: PyObjectRef) -> Py /// Common setup for iter types, adds __iter__ and __contains__ methods pub fn iter_type_init(context: &PyContext, iter_type: &PyClassRef) { let contains_func = { - let cloned_iter_type = iter_type.clone().into_object(); + let cloned_iter_type = iter_type.clone(); move |vm: &VirtualMachine, args: PyFuncArgs| contains(vm, args, cloned_iter_type.clone()) }; context.set_attr( @@ -102,7 +102,7 @@ pub fn iter_type_init(context: &PyContext, iter_type: &PyClassRef) { context.new_rustfunc(contains_func), ); let iter_func = { - let cloned_iter_type = iter_type.clone().into_object(); + let cloned_iter_type = iter_type.clone(); move |vm: &VirtualMachine, args: PyFuncArgs| { arg_check!( vm, diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index ebc97f7edd..9100ce0d73 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -5,8 +5,7 @@ use num_traits::ToPrimitive; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - IdProtocol, PyContext, PyIteratorValue, PyObject, PyObjectRef, PyRef, PyResult, PyValue, - TypeProtocol, + IdProtocol, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -17,6 +16,7 @@ use super::objsequence::{ PySliceableSequence, }; use super::objtype; +use crate::obj::objtype::PyClassRef; #[derive(Default)] pub struct PyList { @@ -40,7 +40,7 @@ impl From> for PyList { } impl PyValue for PyList { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.list_type() } } @@ -306,21 +306,17 @@ impl PyListRef { } fn list_new( - cls: PyRef, + cls: PyClassRef, iterable: OptionalArg, vm: &VirtualMachine, -) -> PyResult { - if !objtype::issubclass(cls.as_object(), &vm.ctx.list_type()) { - return Err(vm.new_type_error(format!("{} is not a subtype of list", cls))); - } - +) -> PyResult { let elements = if let OptionalArg::Present(iterable) = iterable { vm.extract_elements(&iterable)? } else { vec![] }; - Ok(PyObject::new(PyList::from(elements), cls.into_object())) + PyList::from(elements).into_ref_with_type(vm, cls) } fn quicksort( diff --git a/vm/src/obj/objmap.rs b/vm/src/obj/objmap.rs index 089c4f94b0..49cc539e58 100644 --- a/vm/src/obj/objmap.rs +++ b/vm/src/obj/objmap.rs @@ -13,7 +13,7 @@ pub struct PyMap { type PyMapRef = PyRef; impl PyValue for PyMap { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.map_type() } } diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index ca365afc57..bc24ca54fa 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -1,4 +1,5 @@ use crate::function::PyFuncArgs; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; @@ -8,7 +9,7 @@ pub struct PyMemoryView { } impl PyValue for PyMemoryView { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.memoryview_type() } } diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs index fe6e3fca1d..2d1d8921e1 100644 --- a/vm/src/obj/objmodule.rs +++ b/vm/src/obj/objmodule.rs @@ -1,4 +1,5 @@ use crate::obj::objstr::PyStringRef; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{DictProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; @@ -10,7 +11,7 @@ pub struct PyModule { pub type PyModuleRef = PyRef; impl PyValue for PyModule { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.module_type() } } diff --git a/vm/src/obj/objnone.rs b/vm/src/obj/objnone.rs index 086bdce87f..7b0eefec21 100644 --- a/vm/src/obj/objnone.rs +++ b/vm/src/obj/objnone.rs @@ -11,8 +11,8 @@ pub struct PyNone; pub type PyNoneRef = PyRef; impl PyValue for PyNone { - fn class(vm: &VirtualMachine) -> PyObjectRef { - vm.ctx.none().typ() + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.none().type_pyref() } } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 5405d41c51..81fdc0b3f9 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -4,6 +4,7 @@ use super::objstr::{self, PyStringRef}; use super::objtype; use crate::function::PyFuncArgs; use crate::obj::objproperty::PropertyBuilder; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{ DictProtocol, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, @@ -14,7 +15,7 @@ use crate::vm::VirtualMachine; pub struct PyInstance; impl PyValue for PyInstance { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.object() } } diff --git a/vm/src/obj/objproperty.rs b/vm/src/obj/objproperty.rs index 27f5cd964c..7a9dd8447e 100644 --- a/vm/src/obj/objproperty.rs +++ b/vm/src/obj/objproperty.rs @@ -16,7 +16,7 @@ pub struct PyReadOnlyProperty { } impl PyValue for PyReadOnlyProperty { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.readonly_property_type() } } @@ -47,7 +47,7 @@ pub struct PyProperty { } impl PyValue for PyProperty { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.property_type() } } @@ -200,7 +200,7 @@ impl<'a> PropertyBuilder<'a> { deleter: None, }; - PyObject::new(payload, self.ctx.property_type()) + PyObject::new(payload, self.ctx.property_type().into_object()) } else { let payload = PyReadOnlyProperty { getter: self.getter.expect( @@ -208,7 +208,7 @@ impl<'a> PropertyBuilder<'a> { ), }; - PyObject::new(payload, self.ctx.readonly_property_type()) + PyObject::new(payload, self.ctx.readonly_property_type().into_object()) } } } diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 8f037bbe19..614bfe5c0f 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -7,13 +7,16 @@ use num_traits::{One, Signed, ToPrimitive, Zero}; use crate::function::PyFuncArgs; use crate::pyobject::{ - PyContext, PyIteratorValue, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, + PyContext, PyIteratorValue, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; use super::objint::{self, PyInt}; use super::objslice::PySlice; use super::objtype; +use crate::obj::objtype::PyClassRef; + +pub type PyRangeRef = PyRef; #[derive(Debug, Clone)] pub struct PyRange { @@ -25,7 +28,7 @@ pub struct PyRange { } impl PyValue for PyRange { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.range_type() } } @@ -218,30 +221,20 @@ fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn range_iter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(range, Some(vm.ctx.range_type()))]); - - Ok(PyObject::new( - PyIteratorValue { - position: Cell::new(0), - iterated_obj: range.clone(), - }, - vm.ctx.iter_type(), - )) +fn range_iter(range: PyRangeRef, _vm: &VirtualMachine) -> PyIteratorValue { + PyIteratorValue { + position: Cell::new(0), + iterated_obj: range.into_object(), + } } -fn range_reversed(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); - - let range = get_value(zelf).reversed(); +fn range_reversed(zelf: PyRangeRef, vm: &VirtualMachine) -> PyIteratorValue { + let range = zelf.reversed(); - Ok(PyObject::new( - PyIteratorValue { - position: Cell::new(0), - iterated_obj: PyObject::new(range, vm.ctx.range_type()), - }, - vm.ctx.iter_type(), - )) + PyIteratorValue { + position: Cell::new(0), + iterated_obj: range.into_ref(vm).into_object(), + } } fn range_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -301,14 +294,13 @@ fn range_getitem(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { range.step }; - Ok(PyObject::new( - PyRange { - start: new_start, - end: new_end, - step: new_step, - }, - vm.ctx.range_type(), - )) + Ok(PyRange { + start: new_start, + end: new_end, + step: new_step, + } + .into_ref(vm) + .into_object()) } else { Err(vm.new_type_error("range indices must be integer or slice".to_string())) } diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index e98f2434c7..33a4455318 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -16,7 +16,7 @@ use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objint; use super::objiter; -use super::objtype::{self, PyClassRef}; +use super::objtype::PyClassRef; #[derive(Default)] pub struct PySet { @@ -32,7 +32,7 @@ impl fmt::Debug for PySet { } impl PyValue for PySet { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.set_type() } } @@ -157,10 +157,6 @@ fn set_new( iterable: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - if !objtype::issubclass(cls.as_object(), &vm.ctx.set_type()) { - return Err(vm.new_type_error(format!("{} is not a subtype of set", cls))); - } - let elements: HashMap = match iterable { OptionalArg::Missing => HashMap::new(), OptionalArg::Present(iterable) => { diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index 751f6fd7f1..4a7124fdfd 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -5,6 +5,7 @@ use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeP use crate::vm::VirtualMachine; use super::objint; +use crate::obj::objtype::PyClassRef; #[derive(Debug)] pub struct PySlice { @@ -15,7 +16,7 @@ pub struct PySlice { } impl PyValue for PySlice { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.slice_type() } } diff --git a/vm/src/obj/objstaticmethod.rs b/vm/src/obj/objstaticmethod.rs index f8430979da..a33acf176d 100644 --- a/vm/src/obj/objstaticmethod.rs +++ b/vm/src/obj/objstaticmethod.rs @@ -9,7 +9,7 @@ pub struct PyStaticMethod { pub type PyStaticMethodRef = PyRef; impl PyValue for PyStaticMethod { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.staticmethod_type() } } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index d608aad6b5..a7cdc1a411 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -605,7 +605,7 @@ impl PyStringRef { } impl PyValue for PyString { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.str_type() } } diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 5c6e53c808..97fdd367e0 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -7,16 +7,18 @@ https://github.com/python/cpython/blob/50b48572d9a90c5bb36e2bef6179548ea927a35a/ */ use crate::frame::NameProtocol; -use crate::function::PyFuncArgs; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::obj::objstr; -use crate::obj::objtype::PyClass; +use crate::obj::objtype::{PyClass, PyClassRef}; use crate::pyobject::{ - DictProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, + DictProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; use super::objtype; +pub type PySuperRef = PyRef; + #[derive(Debug)] pub struct PySuper { obj: PyObjectRef, @@ -24,7 +26,7 @@ pub struct PySuper { } impl PyValue for PySuper { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.super_type() } } @@ -89,25 +91,18 @@ fn super_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn super_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("super.__new__ {:?}", args.args); - arg_check!( - vm, - args, - required = [(cls, None)], - optional = [(py_type, None), (py_obj, None)] - ); - - if !objtype::issubclass(cls, &vm.ctx.super_type()) { - return Err(vm.new_type_error(format!("{:?} is not a subtype of super", cls))); - } - +fn super_new( + cls: PyClassRef, + py_type: OptionalArg, + py_obj: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { // Get the type: - let py_type = if let Some(ty) = py_type { + let py_type = if let OptionalArg::Present(ty) = py_type { ty.clone() } else { match vm.current_scope().load_cell(vm, "__class__") { - Some(obj) => obj.clone(), + Some(obj) => PyClassRef::try_from_object(vm, obj)?, _ => { return Err(vm.new_type_error( "super must be called with 1 argument or from inside class method".to_string(), @@ -117,8 +112,8 @@ fn super_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { }; // Check type argument: - if !objtype::isinstance(&py_type, &vm.get_type()) { - let type_name = objtype::get_type_name(&py_type.typ()); + if !objtype::isinstance(py_type.as_object(), &vm.get_type()) { + let type_name = objtype::get_type_name(py_type.as_object().type_ref()); return Err(vm.new_type_error(format!( "super() argument 1 must be type, not {}", type_name @@ -126,7 +121,7 @@ fn super_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } // Get the bound object: - let py_obj = if let Some(obj) = py_obj { + let py_obj = if let OptionalArg::Present(obj) = py_obj { obj.clone() } else { let frame = vm.current_frame(); @@ -146,17 +141,22 @@ fn super_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { }; // Check obj type: - if !(objtype::isinstance(&py_obj, &py_type) || objtype::issubclass(&py_obj, &py_type)) { - return Err(vm.new_type_error( - "super(type, obj): obj must be an instance or subtype of type".to_string(), - )); + if !objtype::isinstance(&py_obj, &py_type) { + let is_subclass = if let Ok(py_obj) = PyClassRef::try_from_object(vm, py_obj.clone()) { + objtype::issubclass(&py_obj, &py_type) + } else { + false + }; + if !is_subclass { + return Err(vm.new_type_error( + "super(type, obj): obj must be an instance or subtype of type".to_string(), + )); + } } - Ok(PyObject::new( - PySuper { - obj: py_obj, - typ: py_type, - }, - cls.clone(), - )) + PySuper { + obj: py_obj, + typ: py_type.into_object(), + } + .into_ref_with_type(vm, cls) } diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 7a3771cbd9..65a823db42 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -38,7 +38,7 @@ impl From> for PyTuple { } impl PyValue for PyTuple { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.tuple_type() } } @@ -213,10 +213,6 @@ fn tuple_new( iterable: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - if !objtype::issubclass(cls.as_object(), &vm.ctx.tuple_type()) { - return Err(vm.new_type_error(format!("{} is not a subtype of tuple", cls))); - } - let elements = if let OptionalArg::Present(iterable) = iterable { vm.extract_elements(&iterable)? } else { diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 1117a4df98..d2cabe1ff8 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -30,7 +30,7 @@ impl fmt::Display for PyClass { pub type PyClassRef = PyRef; impl PyValue for PyClass { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.type_type() } } @@ -95,11 +95,11 @@ impl PyClassRef { } fn instance_check(self, obj: PyObjectRef, _vm: &VirtualMachine) -> bool { - isinstance(&obj, self.as_object()) + isinstance(&obj, &self) } - fn subclass_check(self, subclass: PyObjectRef, _vm: &VirtualMachine) -> bool { - issubclass(&subclass, self.as_object()) + fn subclass_check(self, subclass: PyClassRef, _vm: &VirtualMachine) -> bool { + issubclass(&subclass, &self) } fn repr(self, _vm: &VirtualMachine) -> String { @@ -180,16 +180,16 @@ fn _mro(cls: &PyClassRef) -> Vec { /// Determines if `obj` actually an instance of `cls`, this doesn't call __instancecheck__, so only /// use this if `cls` is known to have not overridden the base __instancecheck__ magic method. -pub fn isinstance(obj: &PyObjectRef, cls: &PyObjectRef) -> bool { - issubclass(obj.type_ref(), &cls) +pub fn isinstance(obj: &PyObjectRef, cls: &PyClassRef) -> bool { + issubclass(&obj.type_pyref(), &cls) } /// Determines if `subclass` is actually a subclass of `cls`, this doesn't call __subclasscheck__, /// so only use this if `cls` is known to have not overridden the base __subclasscheck__ magic /// method. -pub fn issubclass(subclass: &PyObjectRef, cls: &PyObjectRef) -> bool { - let mro = &subclass.payload::().unwrap().mro; - subclass.is(cls) || mro.iter().any(|c| c.is(cls)) +pub fn issubclass(subclass: &PyClassRef, cls: &PyClassRef) -> bool { + let mro = &subclass.mro; + subclass.is(cls) || mro.iter().any(|c| c.is(cls.as_object())) } pub fn get_type_name(typ: &PyObjectRef) -> String { @@ -238,7 +238,7 @@ pub fn type_new_class( .iter() .map(|x| FromPyObjectRef::from_pyobj(x)) .collect(); - bases.push(FromPyObjectRef::from_pyobj(&vm.ctx.object())); + bases.push(vm.ctx.object()); let name = objstr::get_value(name); new( typ.clone(), diff --git a/vm/src/obj/objweakref.rs b/vm/src/obj/objweakref.rs index 09c17d2284..3a71e3b630 100644 --- a/vm/src/obj/objweakref.rs +++ b/vm/src/obj/objweakref.rs @@ -23,7 +23,7 @@ impl PyWeak { } impl PyValue for PyWeak { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.weakref_type() } } diff --git a/vm/src/obj/objzip.rs b/vm/src/obj/objzip.rs index 0a3c5453a4..63df96f62f 100644 --- a/vm/src/obj/objzip.rs +++ b/vm/src/obj/objzip.rs @@ -3,6 +3,7 @@ use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeP use crate::vm::VirtualMachine; use super::objiter; +use crate::obj::objtype::PyClassRef; #[derive(Debug)] pub struct PyZip { @@ -10,7 +11,7 @@ pub struct PyZip { } impl PyValue for PyZip { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.zip_type() } } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 59d185a48a..00dd7b36b1 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -168,8 +168,8 @@ pub type PyNotImplementedRef = PyRef; pub struct PyNotImplemented; impl PyValue for PyNotImplemented { - fn class(vm: &VirtualMachine) -> PyObjectRef { - vm.ctx.not_implemented().typ() + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.not_implemented().type_pyref() } } @@ -179,8 +179,8 @@ pub type PyEllipsisRef = PyRef; pub struct PyEllipsis; impl PyValue for PyEllipsis { - fn class(vm: &VirtualMachine) -> PyObjectRef { - vm.ctx.ellipsis_type.clone().into_object() + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.ellipsis_type.clone() } } @@ -363,140 +363,140 @@ impl PyContext { context } - pub fn bytearray_type(&self) -> PyObjectRef { - self.bytearray_type.clone().into_object() + pub fn bytearray_type(&self) -> PyClassRef { + self.bytearray_type.clone() } - pub fn bytes_type(&self) -> PyObjectRef { - self.bytes_type.clone().into_object() + pub fn bytes_type(&self) -> PyClassRef { + self.bytes_type.clone() } - pub fn code_type(&self) -> PyObjectRef { - self.code_type.clone().into_object() + pub fn code_type(&self) -> PyClassRef { + self.code_type.clone() } - pub fn complex_type(&self) -> PyObjectRef { - self.complex_type.clone().into_object() + pub fn complex_type(&self) -> PyClassRef { + self.complex_type.clone() } - pub fn dict_type(&self) -> PyObjectRef { - self.dict_type.clone().into_object() + pub fn dict_type(&self) -> PyClassRef { + self.dict_type.clone() } - pub fn float_type(&self) -> PyObjectRef { - self.float_type.clone().into_object() + pub fn float_type(&self) -> PyClassRef { + self.float_type.clone() } - pub fn frame_type(&self) -> PyObjectRef { - self.frame_type.clone().into_object() + pub fn frame_type(&self) -> PyClassRef { + self.frame_type.clone() } - pub fn int_type(&self) -> PyObjectRef { - self.int_type.clone().into_object() + pub fn int_type(&self) -> PyClassRef { + self.int_type.clone() } - pub fn list_type(&self) -> PyObjectRef { - self.list_type.clone().into_object() + pub fn list_type(&self) -> PyClassRef { + self.list_type.clone() } - pub fn module_type(&self) -> PyObjectRef { - self.module_type.clone().into_object() + pub fn module_type(&self) -> PyClassRef { + self.module_type.clone() } - pub fn set_type(&self) -> PyObjectRef { - self.set_type.clone().into_object() + pub fn set_type(&self) -> PyClassRef { + self.set_type.clone() } - pub fn range_type(&self) -> PyObjectRef { - self.range_type.clone().into_object() + pub fn range_type(&self) -> PyClassRef { + self.range_type.clone() } - pub fn slice_type(&self) -> PyObjectRef { - self.slice_type.clone().into_object() + pub fn slice_type(&self) -> PyClassRef { + self.slice_type.clone() } - pub fn frozenset_type(&self) -> PyObjectRef { - self.frozenset_type.clone().into_object() + pub fn frozenset_type(&self) -> PyClassRef { + self.frozenset_type.clone() } - pub fn bool_type(&self) -> PyObjectRef { - self.bool_type.clone().into_object() + pub fn bool_type(&self) -> PyClassRef { + self.bool_type.clone() } - pub fn memoryview_type(&self) -> PyObjectRef { - self.memoryview_type.clone().into_object() + pub fn memoryview_type(&self) -> PyClassRef { + self.memoryview_type.clone() } - pub fn tuple_type(&self) -> PyObjectRef { - self.tuple_type.clone().into_object() + pub fn tuple_type(&self) -> PyClassRef { + self.tuple_type.clone() } - pub fn iter_type(&self) -> PyObjectRef { - self.iter_type.clone().into_object() + pub fn iter_type(&self) -> PyClassRef { + self.iter_type.clone() } - pub fn enumerate_type(&self) -> PyObjectRef { - self.enumerate_type.clone().into_object() + pub fn enumerate_type(&self) -> PyClassRef { + self.enumerate_type.clone() } - pub fn filter_type(&self) -> PyObjectRef { - self.filter_type.clone().into_object() + pub fn filter_type(&self) -> PyClassRef { + self.filter_type.clone() } - pub fn map_type(&self) -> PyObjectRef { - self.map_type.clone().into_object() + pub fn map_type(&self) -> PyClassRef { + self.map_type.clone() } - pub fn zip_type(&self) -> PyObjectRef { - self.zip_type.clone().into_object() + pub fn zip_type(&self) -> PyClassRef { + self.zip_type.clone() } - pub fn str_type(&self) -> PyObjectRef { - self.str_type.clone().into_object() + pub fn str_type(&self) -> PyClassRef { + self.str_type.clone() } - pub fn super_type(&self) -> PyObjectRef { - self.super_type.clone().into_object() + pub fn super_type(&self) -> PyClassRef { + self.super_type.clone() } - pub fn function_type(&self) -> PyObjectRef { - self.function_type.clone().into_object() + pub fn function_type(&self) -> PyClassRef { + self.function_type.clone() } - pub fn builtin_function_or_method_type(&self) -> PyObjectRef { - self.builtin_function_or_method_type.clone().into_object() + pub fn builtin_function_or_method_type(&self) -> PyClassRef { + self.builtin_function_or_method_type.clone() } - pub fn property_type(&self) -> PyObjectRef { - self.property_type.clone().into_object() + pub fn property_type(&self) -> PyClassRef { + self.property_type.clone() } - pub fn readonly_property_type(&self) -> PyObjectRef { - self.readonly_property_type.clone().into_object() + pub fn readonly_property_type(&self) -> PyClassRef { + self.readonly_property_type.clone() } - pub fn classmethod_type(&self) -> PyObjectRef { - self.classmethod_type.clone().into_object() + pub fn classmethod_type(&self) -> PyClassRef { + self.classmethod_type.clone() } - pub fn staticmethod_type(&self) -> PyObjectRef { - self.staticmethod_type.clone().into_object() + pub fn staticmethod_type(&self) -> PyClassRef { + self.staticmethod_type.clone() } - pub fn generator_type(&self) -> PyObjectRef { - self.generator_type.clone().into_object() + pub fn generator_type(&self) -> PyClassRef { + self.generator_type.clone() } - pub fn bound_method_type(&self) -> PyObjectRef { - self.bound_method_type.clone().into_object() + pub fn bound_method_type(&self) -> PyClassRef { + self.bound_method_type.clone() } - pub fn weakref_type(&self) -> PyObjectRef { - self.weakref_type.clone().into_object() + pub fn weakref_type(&self) -> PyClassRef { + self.weakref_type.clone() } - pub fn type_type(&self) -> PyObjectRef { - self.type_type.clone().into_object() + pub fn type_type(&self) -> PyClassRef { + self.type_type.clone() } pub fn none(&self) -> PyObjectRef { @@ -511,8 +511,8 @@ impl PyContext { self.not_implemented.clone().into_object() } - pub fn object(&self) -> PyObjectRef { - self.object.clone().into_object() + pub fn object(&self) -> PyClassRef { + self.object.clone() } pub fn new_object(&self) -> PyObjectRef { @@ -520,27 +520,33 @@ impl PyContext { } pub fn new_int>(&self, i: T) -> PyObjectRef { - PyObject::new(PyInt::new(i), self.int_type()) + PyObject::new(PyInt::new(i), self.int_type().into_object()) } pub fn new_float(&self, value: f64) -> PyObjectRef { - PyObject::new(PyFloat::from(value), self.float_type()) + PyObject::new(PyFloat::from(value), self.float_type().into_object()) } pub fn new_complex(&self, value: Complex64) -> PyObjectRef { - PyObject::new(PyComplex::from(value), self.complex_type()) + PyObject::new(PyComplex::from(value), self.complex_type().into_object()) } pub fn new_str(&self, s: String) -> PyObjectRef { - PyObject::new(objstr::PyString { value: s }, self.str_type()) + PyObject::new(objstr::PyString { value: s }, self.str_type().into_object()) } pub fn new_bytes(&self, data: Vec) -> PyObjectRef { - PyObject::new(objbytes::PyBytes::new(data), self.bytes_type()) + PyObject::new( + objbytes::PyBytes::new(data), + self.bytes_type().into_object(), + ) } pub fn new_bytearray(&self, data: Vec) -> PyObjectRef { - PyObject::new(objbytearray::PyByteArray::new(data), self.bytearray_type()) + PyObject::new( + objbytearray::PyByteArray::new(data), + self.bytearray_type().into_object(), + ) } pub fn new_bool(&self, b: bool) -> PyObjectRef { @@ -552,31 +558,32 @@ impl PyContext { } pub fn new_tuple(&self, elements: Vec) -> PyObjectRef { - PyObject::new(PyTuple::from(elements), self.tuple_type()) + PyObject::new(PyTuple::from(elements), self.tuple_type().into_object()) } pub fn new_list(&self, elements: Vec) -> PyObjectRef { - PyObject::new(PyList::from(elements), self.list_type()) + PyObject::new(PyList::from(elements), self.list_type().into_object()) } pub fn new_set(&self) -> PyObjectRef { // Initialized empty, as calling __hash__ is required for adding each object to the set // which requires a VM context - this is done in the objset code itself. - PyObject::new(PySet::default(), self.set_type()) + PyObject::new(PySet::default(), self.set_type().into_object()) } pub fn new_dict(&self) -> PyObjectRef { - PyObject::new(PyDict::default(), self.dict_type()) + PyObject::new(PyDict::default(), self.dict_type().into_object()) } - pub fn new_class(&self, name: &str, base: PyObjectRef) -> PyObjectRef { - objtype::new( - self.type_type(), + pub fn new_class(&self, name: &str, base: PyClassRef) -> PyClassRef { + let typ = objtype::new( + self.type_type().into_object(), name, - vec![FromPyObjectRef::from_pyobj(&base)], + vec![base], PyAttributes::new(), ) - .unwrap() + .unwrap(); + PyClassRef::from_pyobj(&typ) } pub fn new_scope(&self) -> Scope { @@ -599,12 +606,12 @@ impl PyContext { { PyObject::new( PyBuiltinFunction::new(f.into_func()), - self.builtin_function_or_method_type(), + self.builtin_function_or_method_type().into_object(), ) } pub fn new_frame(&self, code: PyObjectRef, scope: Scope) -> PyObjectRef { - PyObject::new(Frame::new(code, scope), self.frame_type()) + PyObject::new(Frame::new(code, scope), self.frame_type().into_object()) } pub fn new_property(&self, f: F) -> PyObjectRef @@ -615,7 +622,7 @@ impl PyContext { } pub fn new_code_object(&self, code: bytecode::CodeObject) -> PyObjectRef { - PyObject::new(objcode::PyCode::new(code), self.code_type()) + PyObject::new(objcode::PyCode::new(code), self.code_type().into_object()) } pub fn new_function( @@ -626,12 +633,15 @@ impl PyContext { ) -> PyObjectRef { PyObject::new( PyFunction::new(code_obj, scope, defaults), - self.function_type(), + self.function_type().into_object(), ) } pub fn new_bound_method(&self, function: PyObjectRef, object: PyObjectRef) -> PyObjectRef { - PyObject::new(PyMethod::new(object, function), self.bound_method_type()) + PyObject::new( + PyMethod::new(object, function), + self.bound_method_type().into_object(), + ) } pub fn new_instance(&self, class: PyClassRef, dict: Option) -> PyObjectRef { @@ -1068,8 +1078,7 @@ where match self.vm.call_method(&self.obj, "__next__", vec![]) { Ok(value) => Some(T::try_from_object(self.vm, value)), Err(err) => { - let stop_ex = &self.vm.ctx.exceptions.stop_iteration; - if objtype::isinstance(&err, stop_ex.as_object()) { + if objtype::isinstance(&err, &self.vm.ctx.exceptions.stop_iteration) { None } else { Some(Err(err)) @@ -1168,7 +1177,7 @@ where T: PyValue + Sized, { fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new(self, T::class(vm))) + Ok(PyObject::new(self, T::class(vm).into_object())) } } @@ -1181,7 +1190,7 @@ pub struct PyIteratorValue { } impl PyValue for PyIteratorValue { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.iter_type() } } @@ -1227,25 +1236,25 @@ impl PyObject { } pub trait PyValue: fmt::Debug + Sized + 'static { - fn class(vm: &VirtualMachine) -> PyObjectRef; + fn class(vm: &VirtualMachine) -> PyClassRef; fn into_ref(self, vm: &VirtualMachine) -> PyRef { PyRef { - obj: PyObject::new(self, Self::class(vm)), + obj: PyObject::new(self, Self::class(vm).into_object()), _payload: PhantomData, } } fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyClassRef) -> PyResult> { let class = Self::class(vm); - if objtype::issubclass(&cls.obj, &class) { + if objtype::issubclass(&cls, &class) { Ok(PyRef { obj: PyObject::new(self, cls.obj), _payload: PhantomData, }) } else { let subtype = vm.to_pystr(&cls.obj)?; - let basetype = vm.to_pystr(&class)?; + let basetype = vm.to_pystr(&class.obj)?; Err(vm.new_type_error(format!("{} is not a subtype of {}", subtype, basetype))) } } diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index a3010a5199..cd6b6b0644 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -626,8 +626,8 @@ fn ast_parse(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "ast", { "parse" => ctx.new_rustfunc(ast_parse), - "Module" => py_class!(ctx, "_ast.Module", ctx.object(), {}), - "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ctx.object(), {}), - "Call" => py_class!(ctx, "_ast.Call", ctx.object(), {}) + "Module" => py_class!(ctx, "_ast.Module", ctx.object(), {}).into_object(), + "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ctx.object(), {}).into_object(), + "Call" => py_class!(ctx, "_ast.Call", ctx.object(), {}).into_object() }) } diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index dc351bf5ee..4284fbdcef 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -19,6 +19,7 @@ use crate::obj::objbytearray::PyByteArray; use crate::obj::objbytes; use crate::obj::objint; use crate::obj::objstr; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{ BufferProtocol, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; @@ -42,7 +43,7 @@ struct PyStringIO { type PyStringIORef = PyRef; impl PyValue for PyStringIO { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.class("io", "StringIO") } } @@ -388,9 +389,10 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { }); // RawBaseIO Subclasses + // TODO Fix name? let file_io = py_class!(ctx, "FileIO", raw_io_base.clone(), { "__init__" => ctx.new_rustfunc(file_io_init), - "name" => ctx.str_type(), + "name" => ctx.str_type().into_object(), "read" => ctx.new_rustfunc(file_io_read), "readinto" => ctx.new_rustfunc(file_io_readinto), "write" => ctx.new_rustfunc(file_io_write) @@ -425,15 +427,15 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "io", { "open" => ctx.new_rustfunc(io_open), - "IOBase" => io_base.clone(), - "RawIOBase" => raw_io_base.clone(), - "BufferedIOBase" => buffered_io_base.clone(), - "TextIOBase" => text_io_base.clone(), - "FileIO" => file_io.clone(), - "BufferedReader" => buffered_reader.clone(), - "BufferedWriter" => buffered_writer.clone(), - "TextIOWrapper" => text_io_wrapper.clone(), - "StringIO" => string_io, - "BytesIO" => bytes_io, + "IOBase" => io_base.into_object(), + "RawIOBase" => raw_io_base.into_object(), + "BufferedIOBase" => buffered_io_base.into_object(), + "TextIOBase" => text_io_base.into_object(), + "FileIO" => file_io.into_object(), + "BufferedReader" => buffered_reader.into_object(), + "BufferedWriter" => buffered_writer.into_object(), + "TextIOWrapper" => text_io_wrapper.into_object(), + "StringIO" => string_io.into_object(), + "BytesIO" => bytes_io.into_object(), }) } diff --git a/vm/src/stdlib/re.rs b/vm/src/stdlib/re.rs index 863e24c406..fec63adf74 100644 --- a/vm/src/stdlib/re.rs +++ b/vm/src/stdlib/re.rs @@ -12,11 +12,12 @@ use regex::{Match, Regex}; use crate::function::PyFuncArgs; use crate::import; use crate::obj::objstr; +use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; impl PyValue for Regex { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.class("re", "Pattern") } } @@ -35,9 +36,9 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "re", { "compile" => ctx.new_rustfunc(re_compile), - "Match" => match_type, + "Match" => match_type.into_object(), "match" => ctx.new_rustfunc(re_match), - "Pattern" => pattern_type, + "Pattern" => pattern_type.into_object(), "search" => ctx.new_rustfunc(re_search) }) } @@ -109,7 +110,7 @@ struct PyMatch { } impl PyValue for PyMatch { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.class("re", "Match") } } diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 4e75cfa417..3c03f4bd8f 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -13,6 +13,7 @@ use crate::obj::objstr; use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; +use crate::obj::objtype::PyClassRef; use num_traits::ToPrimitive; #[derive(Debug, Copy, Clone)] @@ -118,7 +119,7 @@ pub struct Socket { } impl PyValue for Socket { - fn class(_vm: &VirtualMachine) -> PyObjectRef { + fn class(_vm: &VirtualMachine) -> PyClassRef { // TODO unimplemented!() } @@ -439,6 +440,6 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "AF_INET" => ctx.new_int(AddressFamily::Inet as i32), "SOCK_STREAM" => ctx.new_int(SocketKind::Stream as i32), "SOCK_DGRAM" => ctx.new_int(SocketKind::Dgram as i32), - "socket" => socket.clone(), + "socket" => socket.into_object(), }) } diff --git a/vm/src/stdlib/types.rs b/vm/src/stdlib/types.rs index 279cdbdf9e..5bc1ab3840 100644 --- a/vm/src/stdlib/types.rs +++ b/vm/src/stdlib/types.rs @@ -20,15 +20,15 @@ fn types_new_class(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { None => vm.ctx.new_tuple(vec![]), }; let dict = vm.ctx.new_dict(); - objtype::type_new_class(vm, &vm.ctx.type_type(), name, &bases, &dict) + objtype::type_new_class(vm, &vm.ctx.type_type().into_object(), name, &bases, &dict) } pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "types", { "new_class" => ctx.new_rustfunc(types_new_class), - "FunctionType" => ctx.function_type(), - "LambdaType" => ctx.function_type(), - "CodeType" => ctx.code_type(), - "FrameType" => ctx.frame_type() + "FunctionType" => ctx.function_type().into_object(), + "LambdaType" => ctx.function_type().into_object(), + "CodeType" => ctx.code_type().into_object(), + "FrameType" => ctx.frame_type().into_object() }) } diff --git a/vm/src/stdlib/weakref.rs b/vm/src/stdlib/weakref.rs index 5616c3ddfe..58cccb5ab9 100644 --- a/vm/src/stdlib/weakref.rs +++ b/vm/src/stdlib/weakref.rs @@ -9,6 +9,6 @@ use super::super::pyobject::{PyContext, PyObjectRef}; pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "_weakref", { - "ref" => ctx.weakref_type() + "ref" => ctx.weakref_type().into_object() }) } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 8268b10581..a0e0eaf786 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -30,8 +30,8 @@ use crate::obj::objtuple::PyTuple; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, TryIntoRef, - TypeProtocol, + DictProtocol, FromPyObjectRef, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, + TryIntoRef, TypeProtocol, }; use crate::stdlib; use crate::sysmodule; @@ -105,12 +105,14 @@ impl VirtualMachine { Ref::map(frame, |f| &f.scope) } - pub fn class(&self, module: &str, class: &str) -> PyObjectRef { + pub fn class(&self, module: &str, class: &str) -> PyClassRef { let module = self .import(module) .unwrap_or_else(|_| panic!("unable to import {}", module)); - self.get_attribute(module.clone(), class) - .unwrap_or_else(|_| panic!("module {} has no class {}", module, class)) + let class = self + .get_attribute(module.clone(), class) + .unwrap_or_else(|_| panic!("module {} has no class {}", module, class)); + PyClassRef::from_pyobj(&class) } /// Create a new python string object. @@ -132,10 +134,10 @@ impl VirtualMachine { self.ctx.new_dict() } - pub fn new_empty_exception(&self, exc_type: PyObjectRef) -> PyResult { + pub fn new_empty_exception(&self, exc_type: PyClassRef) -> PyResult { info!("New exception created: no msg"); let args = PyFuncArgs::default(); - self.invoke(exc_type, args) + self.invoke(exc_type.into_object(), args) } pub fn new_exception(&self, exc_type: PyClassRef, msg: String) -> PyObjectRef { @@ -214,11 +216,11 @@ impl VirtualMachine { self.ctx.none() } - pub fn get_type(&self) -> PyObjectRef { + pub fn get_type(&self) -> PyClassRef { self.ctx.type_type() } - pub fn get_object(&self) -> PyObjectRef { + pub fn get_object(&self) -> PyClassRef { self.ctx.object() } @@ -236,8 +238,8 @@ impl VirtualMachine { TryFromObject::try_from_object(self, str) } - pub fn to_pystr(&self, obj: &PyObjectRef) -> Result { - let py_str_obj = self.to_str(obj)?; + pub fn to_pystr<'a, T: Into<&'a PyObjectRef>>(&'a self, obj: T) -> Result { + let py_str_obj = self.to_str(obj.into())?; Ok(py_str_obj.value.clone()) } @@ -259,13 +261,13 @@ impl VirtualMachine { /// Determines if `obj` is an instance of `cls`, either directly, indirectly or virtually via /// the __instancecheck__ magic method. - pub fn isinstance(&self, obj: &PyObjectRef, cls: &PyObjectRef) -> PyResult { + pub fn isinstance(&self, obj: &PyObjectRef, cls: &PyClassRef) -> PyResult { // cpython first does an exact check on the type, although documentation doesn't state that // https://github.com/python/cpython/blob/a24107b04c1277e3c1105f98aff5bfa3a98b33a0/Objects/abstract.c#L2408 - if Rc::ptr_eq(&obj.typ(), cls) { + if Rc::ptr_eq(&obj.typ(), cls.as_object()) { Ok(true) } else { - let ret = self.call_method(cls, "__instancecheck__", vec![obj.clone()])?; + let ret = self.call_method(cls.as_object(), "__instancecheck__", vec![obj.clone()])?; objbool::boolval(self, ret) } } diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 7fc2a2476e..d55ed0c32b 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -164,7 +164,7 @@ pub struct PyPromise { } impl PyValue for PyPromise { - fn class(vm: &VirtualMachine) -> PyObjectRef { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.class(BROWSER_NAME, "Promise") } } @@ -327,7 +327,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "fetch" => ctx.new_rustfunc(browser_fetch), "request_animation_frame" => ctx.new_rustfunc(browser_request_animation_frame), "cancel_animation_frame" => ctx.new_rustfunc(browser_cancel_animation_frame), - "Promise" => promise, + "Promise" => promise.into_object(), "alert" => ctx.new_rustfunc(browser_alert), "confirm" => ctx.new_rustfunc(browser_confirm), "prompt" => ctx.new_rustfunc(browser_prompt), From ad584df1205bff9bd0ab22ca09537d6e2e1f0b32 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 23 Mar 2019 16:05:04 +1300 Subject: [PATCH 016/884] Allow context.set_attr to accept PyRef values in addition to PyObjectRef --- vm/src/builtins.rs | 86 +++++++++++++++++----------------- vm/src/pyobject.rs | 15 ++++-- vm/src/stdlib/ast.rs | 6 +-- vm/src/stdlib/io.rs | 22 ++++----- vm/src/stdlib/json.rs | 2 +- vm/src/stdlib/re.rs | 4 +- vm/src/stdlib/socket.rs | 2 +- vm/src/stdlib/types.rs | 8 ++-- vm/src/stdlib/weakref.rs | 2 +- wasm/lib/src/browser_module.rs | 2 +- 10 files changed, 78 insertions(+), 71 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 47903bd001..5ac00561c6 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -714,24 +714,24 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "all" => ctx.new_rustfunc(builtin_all), "any" => ctx.new_rustfunc(builtin_any), "bin" => ctx.new_rustfunc(builtin_bin), - "bool" => ctx.bool_type().into_object(), - "bytearray" => ctx.bytearray_type().into_object(), - "bytes" => ctx.bytes_type().into_object(), + "bool" => ctx.bool_type(), + "bytearray" => ctx.bytearray_type(), + "bytes" => ctx.bytes_type(), "callable" => ctx.new_rustfunc(builtin_callable), "chr" => ctx.new_rustfunc(builtin_chr), - "classmethod" => ctx.classmethod_type().into_object(), + "classmethod" => ctx.classmethod_type(), "compile" => ctx.new_rustfunc(builtin_compile), - "complex" => ctx.complex_type().into_object(), + "complex" => ctx.complex_type(), "delattr" => ctx.new_rustfunc(builtin_delattr), - "dict" => ctx.dict_type().into_object(), + "dict" => ctx.dict_type(), "divmod" => ctx.new_rustfunc(builtin_divmod), "dir" => ctx.new_rustfunc(builtin_dir), - "enumerate" => ctx.enumerate_type().into_object(), + "enumerate" => ctx.enumerate_type(), "eval" => ctx.new_rustfunc(builtin_eval), "exec" => ctx.new_rustfunc(builtin_exec), - "float" => ctx.float_type().into_object(), - "frozenset" => ctx.frozenset_type().into_object(), - "filter" => ctx.filter_type().into_object(), + "float" => ctx.float_type(), + "frozenset" => ctx.frozenset_type(), + "filter" => ctx.filter_type(), "format" => ctx.new_rustfunc(builtin_format), "getattr" => ctx.new_rustfunc(builtin_getattr), "globals" => ctx.new_rustfunc(builtin_globals), @@ -739,63 +739,63 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "hash" => ctx.new_rustfunc(builtin_hash), "hex" => ctx.new_rustfunc(builtin_hex), "id" => ctx.new_rustfunc(builtin_id), - "int" => ctx.int_type().into_object(), + "int" => ctx.int_type(), "isinstance" => ctx.new_rustfunc(builtin_isinstance), "issubclass" => ctx.new_rustfunc(builtin_issubclass), "iter" => ctx.new_rustfunc(builtin_iter), "len" => ctx.new_rustfunc(builtin_len), - "list" => ctx.list_type().into_object(), + "list" => ctx.list_type(), "locals" => ctx.new_rustfunc(builtin_locals), - "map" => ctx.map_type().into_object(), + "map" => ctx.map_type(), "max" => ctx.new_rustfunc(builtin_max), - "memoryview" => ctx.memoryview_type().into_object(), + "memoryview" => ctx.memoryview_type(), "min" => ctx.new_rustfunc(builtin_min), - "object" => ctx.object().into_object(), + "object" => ctx.object(), "oct" => ctx.new_rustfunc(builtin_oct), "ord" => ctx.new_rustfunc(builtin_ord), "next" => ctx.new_rustfunc(builtin_next), "pow" => ctx.new_rustfunc(builtin_pow), "print" => ctx.new_rustfunc(builtin_print), - "property" => ctx.property_type().into_object(), - "range" => ctx.range_type().into_object(), + "property" => ctx.property_type(), + "range" => ctx.range_type(), "repr" => ctx.new_rustfunc(builtin_repr), "reversed" => ctx.new_rustfunc(builtin_reversed), "round" => ctx.new_rustfunc(builtin_round), - "set" => ctx.set_type().into_object(), + "set" => ctx.set_type(), "setattr" => ctx.new_rustfunc(builtin_setattr), "sorted" => ctx.new_rustfunc(builtin_sorted), - "slice" => ctx.slice_type().into_object(), - "staticmethod" => ctx.staticmethod_type().into_object(), - "str" => ctx.str_type().into_object(), + "slice" => ctx.slice_type(), + "staticmethod" => ctx.staticmethod_type(), + "str" => ctx.str_type(), "sum" => ctx.new_rustfunc(builtin_sum), - "super" => ctx.super_type().into_object(), - "tuple" => ctx.tuple_type().into_object(), - "type" => ctx.type_type().into_object(), - "zip" => ctx.zip_type().into_object(), + "super" => ctx.super_type(), + "tuple" => ctx.tuple_type(), + "type" => ctx.type_type(), + "zip" => ctx.zip_type(), "__import__" => ctx.new_rustfunc(builtin_import), // Constants "NotImplemented" => ctx.not_implemented(), // Exceptions: - "BaseException" => ctx.exceptions.base_exception_type.clone().into_object(), - "Exception" => ctx.exceptions.exception_type.clone().into_object(), - "ArithmeticError" => ctx.exceptions.arithmetic_error.clone().into_object(), - "AssertionError" => ctx.exceptions.assertion_error.clone().into_object(), - "AttributeError" => ctx.exceptions.attribute_error.clone().into_object(), - "NameError" => ctx.exceptions.name_error.clone().into_object(), - "OverflowError" => ctx.exceptions.overflow_error.clone().into_object(), - "RuntimeError" => ctx.exceptions.runtime_error.clone().into_object(), - "NotImplementedError" => ctx.exceptions.not_implemented_error.clone().into_object(), - "TypeError" => ctx.exceptions.type_error.clone().into_object(), - "ValueError" => ctx.exceptions.value_error.clone().into_object(), - "IndexError" => ctx.exceptions.index_error.clone().into_object(), - "ImportError" => ctx.exceptions.import_error.clone().into_object(), - "FileNotFoundError" => ctx.exceptions.file_not_found_error.clone().into_object(), - "StopIteration" => ctx.exceptions.stop_iteration.clone().into_object(), - "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone().into_object(), - "KeyError" => ctx.exceptions.key_error.clone().into_object(), - "OSError" => ctx.exceptions.os_error.clone().into_object(), + "BaseException" => ctx.exceptions.base_exception_type.clone(), + "Exception" => ctx.exceptions.exception_type.clone(), + "ArithmeticError" => ctx.exceptions.arithmetic_error.clone(), + "AssertionError" => ctx.exceptions.assertion_error.clone(), + "AttributeError" => ctx.exceptions.attribute_error.clone(), + "NameError" => ctx.exceptions.name_error.clone(), + "OverflowError" => ctx.exceptions.overflow_error.clone(), + "RuntimeError" => ctx.exceptions.runtime_error.clone(), + "NotImplementedError" => ctx.exceptions.not_implemented_error.clone(), + "TypeError" => ctx.exceptions.type_error.clone(), + "ValueError" => ctx.exceptions.value_error.clone(), + "IndexError" => ctx.exceptions.index_error.clone(), + "ImportError" => ctx.exceptions.import_error.clone(), + "FileNotFoundError" => ctx.exceptions.file_not_found_error.clone(), + "StopIteration" => ctx.exceptions.stop_iteration.clone(), + "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone(), + "KeyError" => ctx.exceptions.key_error.clone(), + "OSError" => ctx.exceptions.os_error.clone(), }); #[cfg(not(target_arch = "wasm32"))] diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 00dd7b36b1..aec9823664 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -671,17 +671,18 @@ impl PyContext { obj.get_attr(attr_name) } - pub fn set_attr<'a, T: Into<&'a PyObjectRef>>( + pub fn set_attr<'a, T: Into<&'a PyObjectRef>, V: Into>( &'a self, obj: T, attr_name: &str, - value: PyObjectRef, + value: V, ) { let obj = obj.into(); if let Some(PyModule { ref dict, .. }) = obj.payload::() { - dict.set_item(self, attr_name, value) + dict.set_item(self, attr_name, value.into()) } else if let Some(ref dict) = obj.dict { - dict.borrow_mut().insert(attr_name.to_string(), value); + dict.borrow_mut() + .insert(attr_name.to_string(), value.into()); } else { unimplemented!("set_attr unimplemented for: {:?}", obj); }; @@ -813,6 +814,12 @@ impl<'a, T: PyValue> From<&'a PyRef> for &'a PyObjectRef { } } +impl From> for PyObjectRef { + fn from(obj: PyRef) -> Self { + obj.into_object() + } +} + impl fmt::Display for PyRef where T: PyValue + fmt::Display, diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index cd6b6b0644..a3010a5199 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -626,8 +626,8 @@ fn ast_parse(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "ast", { "parse" => ctx.new_rustfunc(ast_parse), - "Module" => py_class!(ctx, "_ast.Module", ctx.object(), {}).into_object(), - "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ctx.object(), {}).into_object(), - "Call" => py_class!(ctx, "_ast.Call", ctx.object(), {}).into_object() + "Module" => py_class!(ctx, "_ast.Module", ctx.object(), {}), + "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ctx.object(), {}), + "Call" => py_class!(ctx, "_ast.Call", ctx.object(), {}) }) } diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 4284fbdcef..207402e8ff 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -392,7 +392,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { // TODO Fix name? let file_io = py_class!(ctx, "FileIO", raw_io_base.clone(), { "__init__" => ctx.new_rustfunc(file_io_init), - "name" => ctx.str_type().into_object(), + "name" => ctx.str_type(), "read" => ctx.new_rustfunc(file_io_read), "readinto" => ctx.new_rustfunc(file_io_readinto), "write" => ctx.new_rustfunc(file_io_write) @@ -427,15 +427,15 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "io", { "open" => ctx.new_rustfunc(io_open), - "IOBase" => io_base.into_object(), - "RawIOBase" => raw_io_base.into_object(), - "BufferedIOBase" => buffered_io_base.into_object(), - "TextIOBase" => text_io_base.into_object(), - "FileIO" => file_io.into_object(), - "BufferedReader" => buffered_reader.into_object(), - "BufferedWriter" => buffered_writer.into_object(), - "TextIOWrapper" => text_io_wrapper.into_object(), - "StringIO" => string_io.into_object(), - "BytesIO" => bytes_io.into_object(), + "IOBase" => io_base, + "RawIOBase" => raw_io_base, + "BufferedIOBase" => buffered_io_base, + "TextIOBase" => text_io_base, + "FileIO" => file_io, + "BufferedReader" => buffered_reader, + "BufferedWriter" => buffered_writer, + "TextIOWrapper" => text_io_wrapper, + "StringIO" => string_io, + "BytesIO" => bytes_io, }) } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 314cd85890..e7e6d3b6f9 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -243,6 +243,6 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "json", { "dumps" => ctx.new_rustfunc(json_dumps), "loads" => ctx.new_rustfunc(json_loads), - "JSONDecodeError" => json_decode_error.into_object() + "JSONDecodeError" => json_decode_error }) } diff --git a/vm/src/stdlib/re.rs b/vm/src/stdlib/re.rs index fec63adf74..6858f48d5d 100644 --- a/vm/src/stdlib/re.rs +++ b/vm/src/stdlib/re.rs @@ -36,9 +36,9 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "re", { "compile" => ctx.new_rustfunc(re_compile), - "Match" => match_type.into_object(), + "Match" => match_type, "match" => ctx.new_rustfunc(re_match), - "Pattern" => pattern_type.into_object(), + "Pattern" => pattern_type, "search" => ctx.new_rustfunc(re_search) }) } diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 3c03f4bd8f..4af7e32a13 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -440,6 +440,6 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "AF_INET" => ctx.new_int(AddressFamily::Inet as i32), "SOCK_STREAM" => ctx.new_int(SocketKind::Stream as i32), "SOCK_DGRAM" => ctx.new_int(SocketKind::Dgram as i32), - "socket" => socket.into_object(), + "socket" => socket, }) } diff --git a/vm/src/stdlib/types.rs b/vm/src/stdlib/types.rs index 5bc1ab3840..09aeb0dacc 100644 --- a/vm/src/stdlib/types.rs +++ b/vm/src/stdlib/types.rs @@ -26,9 +26,9 @@ fn types_new_class(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "types", { "new_class" => ctx.new_rustfunc(types_new_class), - "FunctionType" => ctx.function_type().into_object(), - "LambdaType" => ctx.function_type().into_object(), - "CodeType" => ctx.code_type().into_object(), - "FrameType" => ctx.frame_type().into_object() + "FunctionType" => ctx.function_type(), + "LambdaType" => ctx.function_type(), + "CodeType" => ctx.code_type(), + "FrameType" => ctx.frame_type() }) } diff --git a/vm/src/stdlib/weakref.rs b/vm/src/stdlib/weakref.rs index 58cccb5ab9..5616c3ddfe 100644 --- a/vm/src/stdlib/weakref.rs +++ b/vm/src/stdlib/weakref.rs @@ -9,6 +9,6 @@ use super::super::pyobject::{PyContext, PyObjectRef}; pub fn make_module(ctx: &PyContext) -> PyObjectRef { py_module!(ctx, "_weakref", { - "ref" => ctx.weakref_type().into_object() + "ref" => ctx.weakref_type() }) } diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index d55ed0c32b..e78779bf2c 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -327,7 +327,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "fetch" => ctx.new_rustfunc(browser_fetch), "request_animation_frame" => ctx.new_rustfunc(browser_request_animation_frame), "cancel_animation_frame" => ctx.new_rustfunc(browser_cancel_animation_frame), - "Promise" => promise.into_object(), + "Promise" => promise, "alert" => ctx.new_rustfunc(browser_alert), "confirm" => ctx.new_rustfunc(browser_confirm), "prompt" => ctx.new_rustfunc(browser_prompt), From 4e7e5037859dc9ecd05e4ecd00512360272ed200 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 23 Mar 2019 19:05:56 +1300 Subject: [PATCH 017/884] Fix wasm build, to reflect that isinstance now takes a PyClassRef --- wasm/lib/src/browser_module.rs | 12 +++++++----- wasm/lib/src/convert.rs | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index e78779bf2c..566edc8efb 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -9,9 +9,11 @@ use wasm_bindgen_futures::{future_to_promise, JsFuture}; use rustpython_vm::function::PyFuncArgs; use rustpython_vm::import::import_module; +use rustpython_vm::obj::objtype::PyClassRef; use rustpython_vm::obj::{objint, objstr}; use rustpython_vm::pyobject::{ - AttributeProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, + AttributeProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TryFromObject, + TypeProtocol, }; use rustpython_vm::VirtualMachine; @@ -170,8 +172,8 @@ impl PyValue for PyPromise { } impl PyPromise { - pub fn new_obj(promise_type: PyObjectRef, value: Promise) -> PyObjectRef { - PyObject::new(PyPromise { value }, promise_type) + pub fn new_obj(promise_type: PyClassRef, value: Promise) -> PyObjectRef { + PyObject::new(PyPromise { value }, promise_type.into_object()) } } @@ -182,9 +184,9 @@ pub fn get_promise_value(obj: &PyObjectRef) -> Promise { panic!("Inner error getting promise") } -pub fn import_promise_type(vm: &VirtualMachine) -> PyResult { +pub fn import_promise_type(vm: &VirtualMachine) -> PyResult { match import_module(vm, PathBuf::default(), BROWSER_NAME)?.get_attr("Promise") { - Some(promise) => Ok(promise), + Some(promise) => PyClassRef::try_from_object(vm, promise), None => Err(vm.new_not_implemented_error("No Promise".to_string())), } } diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 70d67d88db..122ff1763e 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -13,7 +13,7 @@ use crate::vm_class::{AccessibleVM, WASMVirtualMachine}; pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { macro_rules! map_exceptions { ($py_exc:ident, $msg:expr, { $($py_exc_ty:expr => $js_err_new:expr),*$(,)? }) => { - $(if objtype::isinstance($py_exc, $py_exc_ty.as_object()) { + $(if objtype::isinstance($py_exc, $py_exc_ty) { JsValue::from($js_err_new($msg)) } else)* { JsValue::from(js_sys::Error::new($msg)) From 983cb9e8863e5339f5b1c06eca009bd1cbd68abf Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 23 Mar 2019 19:35:42 +1300 Subject: [PATCH 018/884] Fix Never::class signature --- vm/src/pyobject.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index aec9823664..fdb569ed66 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -844,7 +844,7 @@ pub trait IdProtocol { enum Never {} impl PyValue for Never { - fn class(_vm: &VirtualMachine) -> PyObjectRef { + fn class(_vm: &VirtualMachine) -> PyClassRef { unreachable!() } } From b93f96d491a4306de6e90b3ba25e66f7597b613c Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 23 Mar 2019 09:16:32 +0100 Subject: [PATCH 019/884] Increase usage of extend_class macro. --- vm/src/obj/objbool.rs | 8 ++-- vm/src/obj/objbytearray.rs | 96 ++++++++------------------------------ vm/src/obj/objbytes.rs | 28 ++++++----- vm/src/obj/objcode.rs | 6 ++- vm/src/obj/objcomplex.rs | 32 ++++++------- vm/src/obj/objellipsis.rs | 10 ++-- vm/src/obj/objenumerate.rs | 14 ++---- vm/src/obj/objframe.rs | 10 ++-- vm/src/obj/objlist.rs | 56 +++++++++++----------- vm/src/obj/objmemory.rs | 8 ++-- 10 files changed, 100 insertions(+), 168 deletions(-) diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index fc19626a47..f584c0d382 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -65,9 +65,11 @@ The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed."; let bool_type = &context.bool_type; - context.set_attr(bool_type, "__new__", context.new_rustfunc(bool_new)); - context.set_attr(bool_type, "__repr__", context.new_rustfunc(bool_repr)); - context.set_attr(bool_type, "__doc__", context.new_str(bool_doc.to_string())); + extend_class!(context, bool_type, { + "__new__" => context.new_rustfunc(bool_new), + "__repr__" => context.new_rustfunc(bool_repr), + "__doc__" => context.new_str(bool_doc.to_string()) + }); } pub fn not(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index ff7a6d9382..43c57fee8c 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -61,83 +61,25 @@ pub fn init(context: &PyContext) { - any object implementing the buffer API.\n \ - an integer"; - context.set_attr(bytearray_type, "__eq__", context.new_rustfunc(bytearray_eq)); - context.set_attr( - bytearray_type, - "__new__", - context.new_rustfunc(bytearray_new), - ); - context.set_attr( - bytearray_type, - "__repr__", - context.new_rustfunc(bytearray_repr), - ); - context.set_attr( - bytearray_type, - "__len__", - context.new_rustfunc(bytesarray_len), - ); - context.set_attr( - bytearray_type, - "__doc__", - context.new_str(bytearray_doc.to_string()), - ); - context.set_attr( - bytearray_type, - "isalnum", - context.new_rustfunc(bytearray_isalnum), - ); - context.set_attr( - bytearray_type, - "isalpha", - context.new_rustfunc(bytearray_isalpha), - ); - context.set_attr( - bytearray_type, - "isascii", - context.new_rustfunc(bytearray_isascii), - ); - context.set_attr( - bytearray_type, - "isdigit", - context.new_rustfunc(bytearray_isdigit), - ); - context.set_attr( - bytearray_type, - "islower", - context.new_rustfunc(bytearray_islower), - ); - context.set_attr( - bytearray_type, - "isspace", - context.new_rustfunc(bytearray_isspace), - ); - context.set_attr( - bytearray_type, - "isupper", - context.new_rustfunc(bytearray_isupper), - ); - context.set_attr( - bytearray_type, - "istitle", - context.new_rustfunc(bytearray_istitle), - ); - context.set_attr( - bytearray_type, - "clear", - context.new_rustfunc(bytearray_clear), - ); - context.set_attr(bytearray_type, "pop", context.new_rustfunc(bytearray_pop)); - context.set_attr( - bytearray_type, - "lower", - context.new_rustfunc(bytearray_lower), - ); - context.set_attr( - bytearray_type, - "upper", - context.new_rustfunc(bytearray_upper), - ); + extend_class!(context, bytearray_type, { + "__doc__" => context.new_str(bytearray_doc.to_string()), + "__eq__" => context.new_rustfunc(bytearray_eq), + "__len__" => context.new_rustfunc(bytesarray_len), + "__new__" => context.new_rustfunc(bytearray_new), + "__repr__" => context.new_rustfunc(bytearray_repr), + "clear" => context.new_rustfunc(bytearray_clear), + "isalnum" => context.new_rustfunc(bytearray_isalnum), + "isalpha" => context.new_rustfunc(bytearray_isalpha), + "isascii" => context.new_rustfunc(bytearray_isascii), + "isdigit" => context.new_rustfunc(bytearray_isdigit), + "islower" => context.new_rustfunc(bytearray_islower), + "isspace" => context.new_rustfunc(bytearray_isspace), + "istitle" =>context.new_rustfunc(bytearray_istitle), + "isupper" => context.new_rustfunc(bytearray_isupper), + "lower" => context.new_rustfunc(bytearray_lower), + "pop" => context.new_rustfunc(bytearray_pop), + "upper" => context.new_rustfunc(bytearray_upper) + }); } fn bytearray_new( diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index c1fdcf955b..401b720701 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -56,21 +56,19 @@ pub fn init(context: &PyContext) { - any object implementing the buffer API.\n \ - an integer"; - context.set_attr(bytes_type, "__eq__", context.new_rustfunc(bytes_eq)); - context.set_attr(bytes_type, "__lt__", context.new_rustfunc(bytes_lt)); - context.set_attr(bytes_type, "__le__", context.new_rustfunc(bytes_le)); - context.set_attr(bytes_type, "__gt__", context.new_rustfunc(bytes_gt)); - context.set_attr(bytes_type, "__ge__", context.new_rustfunc(bytes_ge)); - context.set_attr(bytes_type, "__hash__", context.new_rustfunc(bytes_hash)); - context.set_attr(bytes_type, "__new__", context.new_rustfunc(bytes_new)); - context.set_attr(bytes_type, "__repr__", context.new_rustfunc(bytes_repr)); - context.set_attr(bytes_type, "__len__", context.new_rustfunc(bytes_len)); - context.set_attr(bytes_type, "__iter__", context.new_rustfunc(bytes_iter)); - context.set_attr( - bytes_type, - "__doc__", - context.new_str(bytes_doc.to_string()), - ); + extend_class!(context, bytes_type, { + "__eq__" => context.new_rustfunc(bytes_eq), + "__lt__" => context.new_rustfunc(bytes_lt), + "__le__" => context.new_rustfunc(bytes_le), + "__gt__" => context.new_rustfunc(bytes_gt), + "__ge__" => context.new_rustfunc(bytes_ge), + "__hash__" => context.new_rustfunc(bytes_hash), + "__new__" => context.new_rustfunc(bytes_new), + "__repr__" => context.new_rustfunc(bytes_repr), + "__len__" => context.new_rustfunc(bytes_len), + "__iter__" => context.new_rustfunc(bytes_iter), + "__doc__" => context.new_str(bytes_doc.to_string()) + }); } fn bytes_new( diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 6c7f66002b..864ad39fef 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -33,8 +33,10 @@ impl PyValue for PyCode { pub fn init(context: &PyContext) { let code_type = context.code_type.as_object(); - context.set_attr(code_type, "__new__", context.new_rustfunc(code_new)); - context.set_attr(code_type, "__repr__", context.new_rustfunc(code_repr)); + extend_class!(context, code_type, { + "__new__" => context.new_rustfunc(code_new), + "__repr__" => context.new_rustfunc(code_repr) + }); for (name, f) in &[ ( diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 15baf5df42..0f81ae942e 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -34,25 +34,19 @@ pub fn init(context: &PyContext) { "Create a complex number from a real part and an optional imaginary part.\n\n\ This is equivalent to (real + imag*1j) where imag defaults to 0."; - context.set_attr(complex_type, "__abs__", context.new_rustfunc(complex_abs)); - context.set_attr(complex_type, "__add__", context.new_rustfunc(complex_add)); - context.set_attr(complex_type, "__radd__", context.new_rustfunc(complex_radd)); - context.set_attr(complex_type, "__eq__", context.new_rustfunc(complex_eq)); - context.set_attr(complex_type, "__neg__", context.new_rustfunc(complex_neg)); - context.set_attr(complex_type, "__new__", context.new_rustfunc(complex_new)); - context.set_attr(complex_type, "real", context.new_property(complex_real)); - context.set_attr(complex_type, "imag", context.new_property(complex_imag)); - context.set_attr( - complex_type, - "__doc__", - context.new_str(complex_doc.to_string()), - ); - context.set_attr(complex_type, "__repr__", context.new_rustfunc(complex_repr)); - context.set_attr( - complex_type, - "conjugate", - context.new_rustfunc(complex_conjugate), - ); + extend_class!(context, complex_type, { + "__abs__" => context.new_rustfunc(complex_abs), + "__add__" => context.new_rustfunc(complex_add), + "__doc__" => context.new_str(complex_doc.to_string()), + "__eq__" => context.new_rustfunc(complex_eq), + "__neg__" => context.new_rustfunc(complex_neg), + "__new__" => context.new_rustfunc(complex_new), + "__radd__" => context.new_rustfunc(complex_radd), + "__repr__" => context.new_rustfunc(complex_repr), + "conjugate" => context.new_rustfunc(complex_conjugate), + "imag" => context.new_property(complex_imag), + "real" => context.new_property(complex_real) + }); } pub fn get_value(obj: &PyObjectRef) -> Complex64 { diff --git a/vm/src/obj/objellipsis.rs b/vm/src/obj/objellipsis.rs index d80dee68e6..8ce843352f 100644 --- a/vm/src/obj/objellipsis.rs +++ b/vm/src/obj/objellipsis.rs @@ -4,12 +4,10 @@ use crate::vm::VirtualMachine; pub fn init(context: &PyContext) { let ellipsis_type = context.ellipsis_type.as_object(); - context.set_attr(ellipsis_type, "__new__", context.new_rustfunc(ellipsis_new)); - context.set_attr( - ellipsis_type, - "__repr__", - context.new_rustfunc(ellipsis_repr), - ); + extend_class!(context, ellipsis_type, { + "__new__" => context.new_rustfunc(ellipsis_new), + "__repr__" => context.new_rustfunc(ellipsis_repr) + }); } fn ellipsis_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objenumerate.rs b/vm/src/obj/objenumerate.rs index 5a66b7b5e2..fd3fd7df9c 100644 --- a/vm/src/obj/objenumerate.rs +++ b/vm/src/obj/objenumerate.rs @@ -72,14 +72,8 @@ fn enumerate_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn init(context: &PyContext) { let enumerate_type = &context.enumerate_type; objiter::iter_type_init(context, enumerate_type); - context.set_attr( - enumerate_type, - "__new__", - context.new_rustfunc(enumerate_new), - ); - context.set_attr( - enumerate_type, - "__next__", - context.new_rustfunc(enumerate_next), - ); + extend_class!(context, enumerate_type, { + "__new__" => context.new_rustfunc(enumerate_new), + "__next__" => context.new_rustfunc(enumerate_next) + }); } diff --git a/vm/src/obj/objframe.rs b/vm/src/obj/objframe.rs index 40007725f6..8657dc2800 100644 --- a/vm/src/obj/objframe.rs +++ b/vm/src/obj/objframe.rs @@ -9,10 +9,12 @@ use crate::vm::VirtualMachine; pub fn init(context: &PyContext) { let frame_type = &context.frame_type; - context.set_attr(frame_type, "__new__", context.new_rustfunc(frame_new)); - context.set_attr(frame_type, "__repr__", context.new_rustfunc(frame_repr)); - context.set_attr(frame_type, "f_locals", context.new_property(frame_flocals)); - context.set_attr(frame_type, "f_code", context.new_property(frame_fcode)); + extend_class!(context, frame_type, { + "__new__" => context.new_rustfunc(frame_new), + "__repr__" => context.new_rustfunc(frame_repr), + "f_locals" => context.new_property(frame_flocals), + "f_code" => context.new_property(frame_fcode) + }); } fn frame_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index ebc97f7edd..7bf263a927 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -420,31 +420,33 @@ pub fn init(context: &PyContext) { If no argument is given, the constructor creates a new empty list.\n\ The argument must be an iterable if specified."; - context.set_attr(list_type, "__add__", context.new_rustfunc(PyListRef::add)); - context.set_attr(list_type, "__iadd__", context.new_rustfunc(PyListRef::iadd)); - context.set_attr(list_type, "__contains__", context.new_rustfunc(PyListRef::contains)); - context.set_attr(list_type, "__eq__", context.new_rustfunc(PyListRef::eq)); - context.set_attr(list_type, "__lt__", context.new_rustfunc(PyListRef::lt)); - context.set_attr(list_type, "__gt__", context.new_rustfunc(PyListRef::gt)); - context.set_attr(list_type, "__le__", context.new_rustfunc(PyListRef::le)); - context.set_attr(list_type, "__ge__", context.new_rustfunc(PyListRef::ge)); - context.set_attr(list_type, "__getitem__", context.new_rustfunc(PyListRef::getitem)); - context.set_attr(list_type, "__iter__", context.new_rustfunc(PyListRef::iter)); - context.set_attr(list_type, "__setitem__", context.new_rustfunc(PyListRef::setitem)); - context.set_attr(list_type, "__mul__", context.new_rustfunc(PyListRef::mul)); - context.set_attr(list_type, "__len__", context.new_rustfunc(PyListRef::len)); - context.set_attr(list_type, "__new__", context.new_rustfunc(list_new)); - context.set_attr(list_type, "__repr__", context.new_rustfunc(PyListRef::repr)); - context.set_attr(list_type, "__doc__", context.new_str(list_doc.to_string())); - context.set_attr(list_type, "append", context.new_rustfunc(PyListRef::append)); - context.set_attr(list_type, "clear", context.new_rustfunc(PyListRef::clear)); - context.set_attr(list_type, "copy", context.new_rustfunc(PyListRef::copy)); - context.set_attr(list_type, "count", context.new_rustfunc(PyListRef::count)); - context.set_attr(list_type, "extend", context.new_rustfunc(PyListRef::extend)); - context.set_attr(list_type, "index", context.new_rustfunc(PyListRef::index)); - context.set_attr(list_type, "insert", context.new_rustfunc(PyListRef::insert)); - context.set_attr(list_type, "reverse", context.new_rustfunc(PyListRef::reverse)); - context.set_attr(list_type, "sort", context.new_rustfunc(list_sort)); - context.set_attr(list_type, "pop", context.new_rustfunc(PyListRef::pop)); - context.set_attr(list_type, "remove", context.new_rustfunc(PyListRef::remove)); + extend_class!(context, list_type, { + "__add__" => context.new_rustfunc(PyListRef::add), + "__iadd__" => context.new_rustfunc(PyListRef::iadd), + "__contains__" => context.new_rustfunc(PyListRef::contains), + "__eq__" => context.new_rustfunc(PyListRef::eq), + "__lt__" => context.new_rustfunc(PyListRef::lt), + "__gt__" => context.new_rustfunc(PyListRef::gt), + "__le__" => context.new_rustfunc(PyListRef::le), + "__ge__" => context.new_rustfunc(PyListRef::ge), + "__getitem__" => context.new_rustfunc(PyListRef::getitem), + "__iter__" => context.new_rustfunc(PyListRef::iter), + "__setitem__" => context.new_rustfunc(PyListRef::setitem), + "__mul__" => context.new_rustfunc(PyListRef::mul), + "__len__" => context.new_rustfunc(PyListRef::len), + "__new__" => context.new_rustfunc(list_new), + "__repr__" => context.new_rustfunc(PyListRef::repr), + "__doc__" => context.new_str(list_doc.to_string()), + "append" => context.new_rustfunc(PyListRef::append), + "clear" => context.new_rustfunc(PyListRef::clear), + "copy" => context.new_rustfunc(PyListRef::copy), + "count" => context.new_rustfunc(PyListRef::count), + "extend" => context.new_rustfunc(PyListRef::extend), + "index" => context.new_rustfunc(PyListRef::index), + "insert" => context.new_rustfunc(PyListRef::insert), + "reverse" => context.new_rustfunc(PyListRef::reverse), + "sort" => context.new_rustfunc(list_sort), + "pop" => context.new_rustfunc(PyListRef::pop), + "remove" => context.new_rustfunc(PyListRef::remove) + }); } diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index ca365afc57..48e068d9bc 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -26,9 +26,7 @@ pub fn new_memory_view(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn init(ctx: &PyContext) { let memoryview_type = &ctx.memoryview_type; - ctx.set_attr( - memoryview_type, - "__new__", - ctx.new_rustfunc(new_memory_view), - ); + extend_class!(ctx, memoryview_type, { + "__new__" => ctx.new_rustfunc(new_memory_view) + }); } From 9ebbde812691a6c9303543cda8bf3fd22bb96fcd Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 23 Mar 2019 09:31:42 +0000 Subject: [PATCH 020/884] Replace special cases in boolval with __bool__ method on types. --- vm/src/obj/objbool.rs | 24 ------------------------ vm/src/obj/objdict.rs | 5 +++++ vm/src/obj/objfloat.rs | 5 +++++ vm/src/obj/objlist.rs | 5 +++++ vm/src/obj/objstr.rs | 5 +++++ vm/src/obj/objtuple.rs | 5 +++++ 6 files changed, 25 insertions(+), 24 deletions(-) diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index f584c0d382..e21a3a3296 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -6,12 +6,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objdict::PyDict; -use super::objfloat::PyFloat; use super::objint::PyInt; -use super::objlist::PyList; -use super::objstr::PyString; -use super::objtuple::PyTuple; use super::objtype; impl IntoPyObject for bool { @@ -27,25 +22,6 @@ impl TryFromObject for bool { } pub fn boolval(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - if let Some(s) = obj.payload::() { - return Ok(!s.value.is_empty()); - } - if let Some(value) = obj.payload::() { - return Ok(*value != PyFloat::from(0.0)); - } - if let Some(dict) = obj.payload::() { - return Ok(!dict.entries.borrow().is_empty()); - } - if let Some(i) = obj.payload::() { - return Ok(!i.value.is_zero()); - } - if let Some(list) = obj.payload::() { - return Ok(!list.elements.borrow().is_empty()); - } - if let Some(tuple) = obj.payload::() { - return Ok(!tuple.elements.borrow().is_empty()); - } - Ok(if let Ok(f) = vm.get_method(obj.clone(), "__bool__") { let bool_res = vm.invoke(f, PyFuncArgs::default())?; match bool_res.payload::() { diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 036e26ec0f..0c8d5fe5ec 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -175,6 +175,10 @@ fn dict_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } impl PyDictRef { + fn bool(self, _vm: &VirtualMachine) -> bool { + !self.entries.borrow().is_empty() + } + fn len(self, _vm: &VirtualMachine) -> usize { self.entries.borrow().len() } @@ -300,6 +304,7 @@ impl PyDictRef { pub fn init(context: &PyContext) { extend_class!(context, &context.dict_type, { + "__bool__" => context.new_rustfunc(PyDictRef::bool), "__len__" => context.new_rustfunc(PyDictRef::len), "__contains__" => context.new_rustfunc(PyDictRef::contains), "__delitem__" => context.new_rustfunc(PyDictRef::delitem), diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index ded6876687..1abeabba2a 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -120,6 +120,10 @@ impl PyFloatRef { } } + fn bool(self, _vm: &VirtualMachine) -> bool { + self.value != 0.0 + } + fn divmod(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.float_type()) || objtype::isinstance(&other, &vm.ctx.int_type()) @@ -366,6 +370,7 @@ pub fn init(context: &PyContext) { "__abs__" => context.new_rustfunc(PyFloatRef::abs), "__add__" => context.new_rustfunc(PyFloatRef::add), "__radd__" => context.new_rustfunc(PyFloatRef::add), + "__bool__" => context.new_rustfunc(PyFloatRef::bool), "__divmod__" => context.new_rustfunc(PyFloatRef::divmod), "__floordiv__" => context.new_rustfunc(PyFloatRef::floordiv), "__new__" => context.new_rustfunc(PyFloatRef::new_float), diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 9a91d051b5..dd5eb055bd 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -94,6 +94,10 @@ impl PyListRef { } } + fn bool(self, _vm: &VirtualMachine) -> bool { + !self.elements.borrow().is_empty() + } + fn clear(self, _vm: &VirtualMachine) { self.elements.borrow_mut().clear(); } @@ -419,6 +423,7 @@ pub fn init(context: &PyContext) { extend_class!(context, list_type, { "__add__" => context.new_rustfunc(PyListRef::add), "__iadd__" => context.new_rustfunc(PyListRef::iadd), + "__bool__" => context.new_rustfunc(PyListRef::bool), "__contains__" => context.new_rustfunc(PyListRef::contains), "__eq__" => context.new_rustfunc(PyListRef::eq), "__lt__" => context.new_rustfunc(PyListRef::lt), diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index a7cdc1a411..67ee8e5e7e 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -57,6 +57,10 @@ impl PyStringRef { } } + fn bool(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() + } + fn eq(self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { self.value == get_value(&rhs) @@ -632,6 +636,7 @@ pub fn init(context: &PyContext) { extend_class!(context, str_type, { "__add__" => context.new_rustfunc(PyStringRef::add), + "__bool__" => context.new_rustfunc(PyStringRef::bool), "__contains__" => context.new_rustfunc(PyStringRef::contains), "__doc__" => context.new_str(str_doc.to_string()), "__eq__" => context.new_rustfunc(PyStringRef::eq), diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 65a823db42..92a90913a3 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -101,6 +101,10 @@ impl PyTupleRef { } } + fn bool(self, _vm: &VirtualMachine) -> bool { + !self.elements.borrow().is_empty() + } + fn count(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { let mut count: usize = 0; for element in self.elements.borrow().iter() { @@ -230,6 +234,7 @@ tuple(iterable) -> tuple initialized from iterable's items If the argument is a tuple, the return value is the same object."; context.set_attr(tuple_type, "__add__", context.new_rustfunc(PyTupleRef::add)); + context.set_attr(tuple_type, "__bool__", context.new_rustfunc(PyTupleRef::bool)); context.set_attr(tuple_type, "__eq__", context.new_rustfunc(PyTupleRef::eq)); context.set_attr(tuple_type,"__contains__",context.new_rustfunc(PyTupleRef::contains)); context.set_attr(tuple_type,"__getitem__",context.new_rustfunc(PyTupleRef::getitem)); From c8bdb249b08f7f31bf30d632efd6f0459c5a3da2 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 23 Mar 2019 10:11:39 +0000 Subject: [PATCH 021/884] Remove attribute protocol from wasm. --- vm/src/pyobject.rs | 2 +- wasm/lib/src/browser_module.rs | 12 +++++++----- wasm/lib/src/convert.rs | 9 +++++---- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index fdb569ed66..76fa09f7b4 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -896,7 +896,7 @@ where } } -pub trait AttributeProtocol { +trait AttributeProtocol { fn get_attr(&self, attr_name: &str) -> Option; fn has_attr(&self, attr_name: &str) -> bool; } diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 566edc8efb..710b18e639 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -12,8 +12,7 @@ use rustpython_vm::import::import_module; use rustpython_vm::obj::objtype::PyClassRef; use rustpython_vm::obj::{objint, objstr}; use rustpython_vm::pyobject::{ - AttributeProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TryFromObject, - TypeProtocol, + PyContext, PyObject, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use rustpython_vm::VirtualMachine; @@ -185,9 +184,12 @@ pub fn get_promise_value(obj: &PyObjectRef) -> Promise { } pub fn import_promise_type(vm: &VirtualMachine) -> PyResult { - match import_module(vm, PathBuf::default(), BROWSER_NAME)?.get_attr("Promise") { - Some(promise) => PyClassRef::try_from_object(vm, promise), - None => Err(vm.new_not_implemented_error("No Promise".to_string())), + match vm.get_attribute( + import_module(vm, PathBuf::default(), BROWSER_NAME)?, + "Promise", + ) { + Ok(promise) => PyClassRef::try_from_object(vm, promise), + Err(_) => Err(vm.new_not_implemented_error("No Promise".to_string())), } } diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 122ff1763e..c7ba1c6161 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -4,7 +4,7 @@ use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; use rustpython_vm::function::PyFuncArgs; use rustpython_vm::obj::{objbytes, objint, objsequence, objtype}; -use rustpython_vm::pyobject::{AttributeProtocol, DictProtocol, PyObjectRef, PyResult}; +use rustpython_vm::pyobject::{DictProtocol, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; use crate::browser_module; @@ -20,8 +20,9 @@ pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { } }; } - let msg = match py_err - .get_attr("msg") + let msg = match vm + .get_attribute(py_err.clone(), "msg") + .ok() .and_then(|msg| vm.to_pystr(&msg).ok()) { Some(msg) => msg, @@ -37,7 +38,7 @@ pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { &vm.ctx.exceptions.name_error => js_sys::ReferenceError::new, &vm.ctx.exceptions.syntax_error => js_sys::SyntaxError::new, }); - if let Some(tb) = py_err.get_attr("__traceback__") { + if let Ok(tb) = vm.get_attribute(py_err.clone(), "__traceback__") { if objtype::isinstance(&tb, &vm.ctx.list_type()) { let elements = objsequence::get_elements(&tb).to_vec(); if let Some(top) = elements.get(0) { From 386f90fa25ab3aff6e2adf109f75485a2858c8cb Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 23 Mar 2019 10:19:42 +0000 Subject: [PATCH 022/884] Remove PyContext.get_attr. --- vm/src/frame.rs | 2 +- vm/src/pyobject.rs | 7 ------- vm/src/stdlib/io.rs | 14 +++++++------- vm/src/stdlib/re.rs | 4 ++-- 4 files changed, 10 insertions(+), 17 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 60744c8e4e..79fdfc1f57 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -707,7 +707,7 @@ impl Frame { if !expr.is(&vm.get_none()) { let repr = vm.to_repr(&expr)?; // TODO: implement sys.displayhook - if let Some(print) = vm.ctx.get_attr(&vm.builtins, "print") { + if let Ok(print) = vm.get_attribute(vm.builtins.clone(), "print") { vm.invoke(print, vec![repr.into_object()])?; } } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 76fa09f7b4..c46bb95df7 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -664,13 +664,6 @@ impl PyContext { }; } - pub fn get_attr(&self, obj: &PyObjectRef, attr_name: &str) -> Option { - // This does not need to be on the PyContext. - // We do not require to make a new key as string for this function - // (yet)... - obj.get_attr(attr_name) - } - pub fn set_attr<'a, T: Into<&'a PyObjectRef>, V: Into>( &'a self, obj: T, diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 207402e8ff..9c6c275161 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -116,7 +116,7 @@ fn buffered_reader_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let mut result = vec![]; let mut length = buff_size; - let raw = vm.ctx.get_attr(&buffered, "raw").unwrap(); + let raw = vm.get_attribute(buffered.clone(), "raw").unwrap(); //Iterates through the raw class, invoking the readinto method //to obtain buff_size many bytes. Exit when less than buff_size many @@ -264,7 +264,7 @@ fn buffered_writer_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(buffered, None), (obj, Some(vm.ctx.bytes_type()))] ); - let raw = vm.ctx.get_attr(&buffered, "raw").unwrap(); + let raw = vm.get_attribute(buffered.clone(), "raw").unwrap(); //This should be replaced with a more appropriate chunking implementation vm.call_method(&raw, "write", vec![obj.clone()]) @@ -284,7 +284,7 @@ fn text_io_wrapper_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn text_io_base_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(text_io_base, None)]); - let raw = vm.ctx.get_attr(&text_io_base, "buffer").unwrap(); + let raw = vm.get_attribute(text_io_base.clone(), "buffer").unwrap(); if let Ok(bytes) = vm.call_method(&raw, "read", PyFuncArgs::default()) { let value = objbytes::get_value(&bytes).to_vec(); @@ -335,10 +335,10 @@ pub fn io_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { //RawIO: FileIO //Buffered: BufferedWriter, BufferedReader //Text: TextIOWrapper - let file_io_class = vm.ctx.get_attr(&module, "FileIO").unwrap(); - let buffered_writer_class = vm.ctx.get_attr(&module, "BufferedWriter").unwrap(); - let buffered_reader_class = vm.ctx.get_attr(&module, "BufferedReader").unwrap(); - let text_io_wrapper_class = vm.ctx.get_attr(&module, "TextIOWrapper").unwrap(); + let file_io_class = vm.get_attribute(module.clone(), "FileIO").unwrap(); + let buffered_writer_class = vm.get_attribute(module.clone(), "BufferedWriter").unwrap(); + let buffered_reader_class = vm.get_attribute(module.clone(), "BufferedReader").unwrap(); + let text_io_wrapper_class = vm.get_attribute(module, "TextIOWrapper").unwrap(); //Construct a FileIO (subclass of RawIOBase) //This is subsequently consumed by a Buffered Class. diff --git a/vm/src/stdlib/re.rs b/vm/src/stdlib/re.rs index 6858f48d5d..57db3f0a80 100644 --- a/vm/src/stdlib/re.rs +++ b/vm/src/stdlib/re.rs @@ -121,7 +121,7 @@ fn create_match(vm: &VirtualMachine, match_value: &Match) -> PyResult { // TODO: implement match object // TODO: how to refer to match object defined in this let module = import::import_module(vm, PathBuf::default(), "re").unwrap(); - let match_class = vm.ctx.get_attr(&module, "Match").unwrap(); + let match_class = vm.get_attribute(module, "Match").unwrap(); // let mo = vm.invoke(match_class, PyFuncArgs::default())?; // let txt = vm.ctx.new_str(result.as_str().to_string()); @@ -147,7 +147,7 @@ fn re_compile(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let regex = make_regex(vm, pattern)?; // TODO: retrieval of this module is akward: let module = import::import_module(vm, PathBuf::default(), "re").unwrap(); - let pattern_class = vm.ctx.get_attr(&module, "Pattern").unwrap(); + let pattern_class = vm.get_attribute(module, "Pattern").unwrap(); Ok(PyObject::new(regex, pattern_class.clone())) } From 441560b69142ec0e5b50c10d6e569f4c618cccf6 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 23 Mar 2019 10:23:24 +0000 Subject: [PATCH 023/884] Delete Attribute Protocol. --- vm/src/pyobject.rs | 64 ---------------------------------------------- 1 file changed, 64 deletions(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index c46bb95df7..83b2deab99 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -889,70 +889,6 @@ where } } -trait AttributeProtocol { - fn get_attr(&self, attr_name: &str) -> Option; - fn has_attr(&self, attr_name: &str) -> bool; -} - -fn class_get_item(class: &PyObjectRef, attr_name: &str) -> Option { - if let Some(ref dict) = class.dict { - dict.borrow().get(attr_name).cloned() - } else { - panic!("Only classes should be in MRO!"); - } -} - -fn class_has_item(class: &PyObjectRef, attr_name: &str) -> bool { - if let Some(ref dict) = class.dict { - dict.borrow().contains_key(attr_name) - } else { - panic!("Only classes should be in MRO!"); - } -} - -impl AttributeProtocol for PyObjectRef { - fn get_attr(&self, attr_name: &str) -> Option { - if let Some(PyClass { ref mro, .. }) = self.payload::() { - if let Some(item) = class_get_item(self, attr_name) { - return Some(item); - } - for class in mro { - if let Some(item) = class_get_item(class.as_object(), attr_name) { - return Some(item); - } - } - return None; - } - - if let Some(PyModule { ref dict, .. }) = self.payload::() { - return dict.get_item(attr_name); - } - - if let Some(ref dict) = self.dict { - dict.borrow().get(attr_name).cloned() - } else { - None - } - } - - fn has_attr(&self, attr_name: &str) -> bool { - if let Some(PyClass { ref mro, .. }) = self.payload::() { - return class_has_item(self, attr_name) - || mro.iter().any(|d| class_has_item(d.as_object(), attr_name)); - } - - if let Some(PyModule { ref dict, .. }) = self.payload::() { - return dict.contains_key(attr_name); - } - - if let Some(ref dict) = self.dict { - dict.borrow().contains_key(attr_name) - } else { - false - } - } -} - pub trait DictProtocol { fn contains_key(&self, k: &str) -> bool; fn get_item(&self, k: &str) -> Option; From 430bc4bac2217c3fe5dc6c06e6eda28272cb93d6 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 23 Mar 2019 09:35:53 -0500 Subject: [PATCH 024/884] Fix vm.import when there are no frames on stack --- vm/src/builtins.rs | 11 ++++++++--- vm/src/obj/objsuper.rs | 8 +++++--- vm/src/vm.rs | 30 +++++++++++++++++++----------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 6d3d512a79..a81273e60c 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -700,9 +700,14 @@ fn builtin_import(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { ] ); let current_path = { - let mut source_pathbuf = PathBuf::from(&vm.current_frame().code.source_path); - source_pathbuf.pop(); - source_pathbuf + match vm.current_frame() { + Some(frame) => { + let mut source_pathbuf = PathBuf::from(&frame.code.source_path); + source_pathbuf.pop(); + source_pathbuf + } + None => PathBuf::new(), + } }; import_module(vm, current_path, &objstr::get_value(name)) diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 2b2be02414..43161ae6b7 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -129,13 +129,15 @@ fn super_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let py_obj = if let Some(obj) = py_obj { obj.clone() } else { - let frame = vm.current_frame(); + let frame = vm.current_frame().expect("no current frame for super()"); if let Some(first_arg) = frame.code.arg_names.get(0) { match vm.get_locals().get_item(first_arg) { Some(obj) => obj.clone(), _ => { - return Err(vm - .new_type_error(format!("super arguement {} was not supplied", first_arg))); + return Err(vm.new_type_error(format!( + "super arguement {} was not supplied", + first_arg + ))); } } } else { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 029588d130..05a115732b 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -91,19 +91,28 @@ impl VirtualMachine { result } - pub fn current_frame(&self) -> Ref { - Ref::map(self.frames.borrow(), |frames| { - let index = frames.len() - 1; - let current_frame = &frames[index]; - objframe::get_value(current_frame) - }) + pub fn current_frame(&self) -> Option> { + let frames = self.frames.borrow(); + if frames.is_empty() { + None + } else { + Some(Ref::map(self.frames.borrow(), |frames| { + objframe::get_value(frames.last().unwrap()) + })) + } } pub fn current_scope(&self) -> Ref { - let frame = self.current_frame(); + let frame = self + .current_frame() + .expect("called current_scope but no frames on the stack"); Ref::map(frame, |f| &f.scope) } + pub fn try_class(&self, module: &str, class: &str) -> PyResult { + self.get_attribute(self.import(module)?, class) + } + pub fn class(&self, module: &str, class: &str) -> PyObjectRef { let module = self .import(module) @@ -246,10 +255,9 @@ impl VirtualMachine { } pub fn import(&self, module: &str) -> PyResult { - let builtins_import = self.builtins.get_item("__import__"); - match builtins_import { - Some(func) => self.invoke(func, vec![self.ctx.new_str(module.to_string())]), - None => Err(self.new_exception( + match self.get_attribute(self.builtins.clone(), "__import__") { + Ok(func) => self.invoke(func, vec![self.ctx.new_str(module.to_string())]), + Err(_) => Err(self.new_exception( self.ctx.exceptions.import_error.clone(), "__import__ not found".to_string(), )), From a830d8e131bf5d4f5debbc054814bbbbe4777535 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 23 Mar 2019 09:36:15 -0500 Subject: [PATCH 025/884] Add PyDocument and PyElement --- vm/src/function.rs | 1 + wasm/lib/src/browser_module.rs | 113 ++++++++++++++++++++++++++------- wasm/lib/src/convert.rs | 4 +- wasm/lib/src/vm_class.rs | 1 - 4 files changed, 94 insertions(+), 25 deletions(-) diff --git a/vm/src/function.rs b/vm/src/function.rs index 7159427e56..3093b8c5f5 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -304,6 +304,7 @@ pub enum OptionalArg { } impl OptionalArg { + #[inline] pub fn into_option(self) -> Option { match self { Present(value) => Some(value), diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 7fc2a2476e..13f1404ab2 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use futures::Future; use js_sys::Promise; use num_traits::cast::ToPrimitive; @@ -7,11 +5,13 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::{future_to_promise, JsFuture}; -use rustpython_vm::function::PyFuncArgs; -use rustpython_vm::import::import_module; -use rustpython_vm::obj::{objint, objstr}; +use rustpython_vm::function::{OptionalArg, PyFuncArgs}; +use rustpython_vm::obj::{ + objint, + objstr::{self, PyStringRef}, +}; use rustpython_vm::pyobject::{ - AttributeProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, + PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use rustpython_vm::VirtualMachine; @@ -44,7 +44,7 @@ impl FetchResponseFormat { fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(url, Some(vm.ctx.str_type()))]); - let promise_type = import_promise_type(vm)?; + let promise_type = vm.try_class("browser", "Promise")?; let response_format = args.get_optional_kwarg_with_type("response_format", vm.ctx.str_type(), vm)?; @@ -165,7 +165,7 @@ pub struct PyPromise { impl PyValue for PyPromise { fn class(vm: &VirtualMachine) -> PyObjectRef { - vm.class(BROWSER_NAME, "Promise") + vm.class("browser", "Promise") } } @@ -182,15 +182,8 @@ pub fn get_promise_value(obj: &PyObjectRef) -> Promise { panic!("Inner error getting promise") } -pub fn import_promise_type(vm: &VirtualMachine) -> PyResult { - match import_module(vm, PathBuf::default(), BROWSER_NAME)?.get_attr("Promise") { - Some(promise) => Ok(promise), - None => Err(vm.new_not_implemented_error("No Promise".to_string())), - } -} - fn promise_then(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let promise_type = import_promise_type(vm)?; + let promise_type = vm.try_class("browser", "Promise")?; arg_check!( vm, args, @@ -236,7 +229,7 @@ fn promise_then(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn promise_catch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let promise_type = import_promise_type(vm)?; + let promise_type = vm.try_class("browser", "Promise")?; arg_check!( vm, args, @@ -270,6 +263,65 @@ fn promise_catch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(PyPromise::new_obj(promise_type, ret_promise)) } +#[derive(Debug)] +struct PyDocument { + doc: web_sys::Document, +} +type PyDocumentRef = PyRef; + +impl PyValue for PyDocument { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.class("browser", "Document") + } +} + +fn document_query(zelf: PyDocumentRef, query: PyStringRef, vm: &VirtualMachine) -> PyResult { + let elem = zelf + .doc + .query_selector(&query.value) + .map_err(|err| convert::js_py_typeerror(vm, err))?; + let elem = match elem { + Some(elem) => PyElement { elem }.into_ref(vm).into_object(), + None => vm.get_none(), + }; + Ok(elem) +} + +#[derive(Debug)] +struct PyElement { + elem: web_sys::Element, +} +type PyElementRef = PyRef; + +impl PyValue for PyElement { + fn class(vm: &VirtualMachine) -> PyObjectRef { + vm.class("browser", "Element") + } +} + +fn elem_get_attr( + zelf: PyElementRef, + attr: PyStringRef, + default: OptionalArg, + vm: &VirtualMachine, +) -> PyObjectRef { + match zelf.elem.get_attribute(&attr.value) { + Some(s) => vm.new_str(s), + None => default.into_option().unwrap_or_else(|| vm.get_none()), + } +} + +fn elem_set_attr( + zelf: PyElementRef, + attr: PyStringRef, + value: PyStringRef, + vm: &VirtualMachine, +) -> PyResult<()> { + zelf.elem + .set_attribute(&attr.value, &value.value) + .map_err(|err| convert::js_to_py(vm, err)) +} + fn browser_alert(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(message, Some(vm.ctx.str_type()))]); @@ -315,19 +367,36 @@ fn browser_prompt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(result) } -const BROWSER_NAME: &str = "browser"; - pub fn make_module(ctx: &PyContext) -> PyObjectRef { let promise = py_class!(ctx, "Promise", ctx.object(), { "then" => ctx.new_rustfunc(promise_then), - "catch" => ctx.new_rustfunc(promise_catch) + "catch" => ctx.new_rustfunc(promise_catch), + }); + + let document_class = py_class!(ctx, "Document", ctx.object(), { + "query" => ctx.new_rustfunc(document_query), + }); + + let document = PyObject::new( + PyDocument { + doc: window().document().expect("Document missing from window"), + }, + document_class.clone(), + ); + + let element = py_class!(ctx, "Element", ctx.object(), { + "get_attr" => ctx.new_rustfunc(elem_get_attr), + "set_attr" => ctx.new_rustfunc(elem_set_attr), }); - py_module!(ctx, BROWSER_NAME, { + py_module!(ctx, "browser", { "fetch" => ctx.new_rustfunc(browser_fetch), "request_animation_frame" => ctx.new_rustfunc(browser_request_animation_frame), "cancel_animation_frame" => ctx.new_rustfunc(browser_cancel_animation_frame), "Promise" => promise, + "Document" => document_class, + "document" => document, + "Element" => element, "alert" => ctx.new_rustfunc(browser_alert), "confirm" => ctx.new_rustfunc(browser_confirm), "prompt" => ctx.new_rustfunc(browser_prompt), @@ -337,5 +406,5 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { pub fn setup_browser_module(vm: &VirtualMachine) { vm.stdlib_inits .borrow_mut() - .insert(BROWSER_NAME.to_string(), Box::new(make_module)); + .insert("browser".to_string(), Box::new(make_module)); } diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 122ff1763e..b7f9048e91 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -114,7 +114,7 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { } } // the browser module might not be injected - if let Ok(promise_type) = browser_module::import_promise_type(vm) { + if let Ok(promise_type) = vm.try_class("browser", "Promise") { if objtype::isinstance(&py_obj, &promise_type) { return browser_module::get_promise_value(&py_obj).into(); } @@ -158,7 +158,7 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { if js_val.is_object() { if let Some(promise) = js_val.dyn_ref::() { // the browser module might not be injected - if let Ok(promise_type) = browser_module::import_promise_type(vm) { + if let Ok(promise_type) = vm.try_class("browser", "Promise") { return browser_module::PyPromise::new_obj(promise_type, promise.clone()); } } diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 9d80854f73..77822147a8 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -49,7 +49,6 @@ impl StoredVirtualMachine { thread_local! { static STORED_VMS: RefCell>> = RefCell::default(); - static ACTIVE_VMS: RefCell> = RefCell::default(); } #[wasm_bindgen(js_name = vmStore)] From 2626a800ab486c74ae3a28e19a0ac1757e19fe64 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 23 Mar 2019 11:04:40 +0200 Subject: [PATCH 026/884] Support frozenset --- tests/snippets/set.py | 85 ++++++++++++++++++ vm/src/obj/objset.rs | 204 ++++++++++++++++++++++++++++++------------ 2 files changed, 233 insertions(+), 56 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index a7a20b0efb..9e49dff89e 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -144,3 +144,88 @@ def __hash__(self): assert a == set([1,2,4,5]) with assertRaises(TypeError): a ^= 1 + +# frozen set + +assert frozenset([1,2]) == frozenset([1,2]) +assert not frozenset([1,2,3]) == frozenset([1,2]) + +assert frozenset([1,2,3]) >= frozenset([1,2]) +assert frozenset([1,2]) >= frozenset([1,2]) +assert not frozenset([1,3]) >= frozenset([1,2]) + +assert frozenset([1,2,3]).issuperset(frozenset([1,2])) +assert frozenset([1,2]).issuperset(frozenset([1,2])) +assert not frozenset([1,3]).issuperset(frozenset([1,2])) + +assert frozenset([1,2,3]) > frozenset([1,2]) +assert not frozenset([1,2]) > frozenset([1,2]) +assert not frozenset([1,3]) > frozenset([1,2]) + +assert frozenset([1,2]) <= frozenset([1,2,3]) +assert frozenset([1,2]) <= frozenset([1,2]) +assert not frozenset([1,3]) <= frozenset([1,2]) + +assert frozenset([1,2]).issubset(frozenset([1,2,3])) +assert frozenset([1,2]).issubset(frozenset([1,2])) +assert not frozenset([1,3]).issubset(frozenset([1,2])) + +assert frozenset([1,2]) < frozenset([1,2,3]) +assert not frozenset([1,2]) < frozenset([1,2]) +assert not frozenset([1,3]) < frozenset([1,2]) + +a = frozenset([1, 2, 3]) +assert len(a) == 3 +b = a.copy() +assert b == a + +assert frozenset([1,2,3]).union(frozenset([4,5])) == frozenset([1,2,3,4,5]) +assert frozenset([1,2,3]).union(frozenset([1,2,3,4,5])) == frozenset([1,2,3,4,5]) + +assert frozenset([1,2,3]) | frozenset([4,5]) == frozenset([1,2,3,4,5]) +assert frozenset([1,2,3]) | frozenset([1,2,3,4,5]) == frozenset([1,2,3,4,5]) + +assert frozenset([1,2,3]).intersection(frozenset([1,2])) == frozenset([1,2]) +assert frozenset([1,2,3]).intersection(frozenset([5,6])) == frozenset([]) + +assert frozenset([1,2,3]) & frozenset([4,5]) == frozenset([]) +assert frozenset([1,2,3]) & frozenset([1,2,3,4,5]) == frozenset([1,2,3]) + +assert frozenset([1,2,3]).difference(frozenset([1,2])) == frozenset([3]) +assert frozenset([1,2,3]).difference(frozenset([5,6])) == frozenset([1,2,3]) + +assert frozenset([1,2,3]) - frozenset([4,5]) == frozenset([1,2,3]) +assert frozenset([1,2,3]) - frozenset([1,2,3,4,5]) == frozenset([]) + +assert frozenset([1,2,3]).symmetric_difference(frozenset([1,2])) == frozenset([3]) +assert frozenset([1,2,3]).symmetric_difference(frozenset([5,6])) == frozenset([1,2,3,5,6]) + +assert frozenset([1,2,3]) ^ frozenset([4,5]) == frozenset([1,2,3,4,5]) +assert frozenset([1,2,3]) ^ frozenset([1,2,3,4,5]) == frozenset([4,5]) + +assert_raises(TypeError, lambda: frozenset([[]])) + +# set and frozen set +assert frozenset([1,2,3]).union(set([4,5])) == frozenset([1,2,3,4,5]) +assert set([1,2,3]).union(frozenset([4,5])) == set([1,2,3,4,5]) + +assert frozenset([1,2,3]) | set([4,5]) == frozenset([1,2,3,4,5]) +assert set([1,2,3]) | frozenset([4,5]) == set([1,2,3,4,5]) + +assert frozenset([1,2,3]).intersection(set([5,6])) == frozenset([]) +assert set([1,2,3]).intersection(frozenset([5,6])) == set([]) + +assert frozenset([1,2,3]) & set([1,2,3,4,5]) == frozenset([1,2,3]) +assert set([1,2,3]) & frozenset([1,2,3,4,5]) == set([1,2,3]) + +assert frozenset([1,2,3]).difference(set([5,6])) == frozenset([1,2,3]) +assert set([1,2,3]).difference(frozenset([5,6])) == set([1,2,3]) + +assert frozenset([1,2,3]) - set([4,5]) == frozenset([1,2,3]) +assert set([1,2,3]) - frozenset([4,5]) == frozenset([1,2,3]) + +assert frozenset([1,2,3]).symmetric_difference(set([1,2])) == frozenset([3]) +assert set([1,2,3]).symmetric_difference(frozenset([1,2])) == set([3]) + +assert frozenset([1,2,3]) ^ set([4,5]) == frozenset([1,2,3,4,5]) +assert set([1,2,3]) ^ frozenset([4,5]) == set([1,2,3,4,5]) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 33a4455318..8bb338d407 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -9,13 +9,14 @@ use std::hash::{Hash, Hasher}; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + PyContext, PyIteratorValue, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objint; use super::objiter; +use super::objtype; use super::objtype::PyClassRef; #[derive(Default)] @@ -24,6 +25,12 @@ pub struct PySet { } pub type PySetRef = PyRef; +#[derive(Default)] +pub struct PyFrozenSet { + elements: RefCell>, +} +pub type PyFrozenSetRef = PyRef; + impl fmt::Debug for PySet { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // TODO: implement more detailed, non-recursive Debug formatter @@ -31,14 +38,69 @@ impl fmt::Debug for PySet { } } +impl fmt::Debug for PyFrozenSet { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: implement more detailed, non-recursive Debug formatter + f.write_str("frozenset") + } +} + impl PyValue for PySet { fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.set_type() } } +impl PyValue for PyFrozenSet { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.frozenset_type() + } +} + pub fn get_elements(obj: &PyObjectRef) -> HashMap { - obj.payload::().unwrap().elements.borrow().clone() + if let Some(set) = obj.payload::() { + return set.elements.borrow().clone(); + } else if let Some(frozenset) = obj.payload::() { + return frozenset.elements.borrow().clone(); + } + panic!("Not frozenset or set"); +} + +macro_rules! validate_set_or_frozenset { + ( $vm:ident, $obj:expr ) => { + if !(objtype::issubclass(&$obj, &$vm.ctx.set_type()) + || objtype::issubclass(&$obj, &$vm.ctx.frozenset_type())) + { + return Err($vm.new_type_error(format!( + "{} is not a subtype of set or frozenset a", + $obj.typ() + ))); + } + }; +} + +fn create_set( + vm: &VirtualMachine, + elements: HashMap, + cls: PyClassRef, +) -> PyResult { + if objtype::issubclass(&cls, &vm.ctx.set_type()) { + Ok(PyObject::new( + PySet { + elements: RefCell::new(elements), + }, + PySet::class(vm).into_object(), + )) + } else if objtype::issubclass(&cls, &vm.ctx.frozenset_type()) { + Ok(PyObject::new( + PyFrozenSet { + elements: RefCell::new(elements), + }, + PyFrozenSet::class(vm).into_object(), + )) + } else { + Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", cls))) + } } fn perform_action_with_hash( @@ -152,11 +214,9 @@ fn set_clear(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } /* Create a new object of sub-type of set */ -fn set_new( - cls: PyClassRef, - iterable: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { +fn set_new(cls: PyClassRef, iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { + validate_set_or_frozenset!(vm, cls); + let elements: HashMap = match iterable { OptionalArg::Missing => HashMap::new(), OptionalArg::Present(iterable) => { @@ -169,25 +229,22 @@ fn set_new( } }; - PySet { - elements: RefCell::new(elements), - } - .into_ref_with_type(vm, cls) + create_set(vm, elements, cls) } fn set_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("set.len called with: {:?}", args); - arg_check!(vm, args, required = [(s, Some(vm.ctx.set_type()))]); + arg_check!(vm, args, required = [(s, None)]); + validate_set_or_frozenset!(vm, s.type_pyref()); let elements = get_elements(s); Ok(vm.context().new_int(elements.len())) } -fn set_copy(obj: PySetRef, _vm: &VirtualMachine) -> PySet { +fn set_copy(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { trace!("set.copy called with: {:?}", obj); - let elements = obj.elements.borrow().clone(); - PySet { - elements: RefCell::new(elements), - } + validate_set_or_frozenset!(vm, obj.type_pyref()); + let elements = get_elements(&obj).clone(); + create_set(vm, elements, obj.type_pyref()) } fn set_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -211,11 +268,8 @@ fn set_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn set_contains(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(set, Some(vm.ctx.set_type())), (needle, None)] - ); + arg_check!(vm, args, required = [(set, None), (needle, None)]); + validate_set_or_frozenset!(vm, &set.type_pyref()); for element in get_elements(set).iter() { match vm._eq(needle.clone(), element.1.clone()) { Ok(value) => { @@ -281,14 +335,10 @@ fn set_compare_inner( size_func: &Fn(usize, usize) -> bool, swap: bool, ) -> PyResult { - arg_check!( - vm, - args, - required = [ - (zelf, Some(vm.ctx.set_type())), - (other, Some(vm.ctx.set_type())) - ] - ); + arg_check!(vm, args, required = [(zelf, None), (other, None)]); + + validate_set_or_frozenset!(vm, zelf.type_pyref()); + validate_set_or_frozenset!(vm, other.type_pyref()); let get_zelf = |swap: bool| -> &PyObjectRef { if swap { @@ -323,47 +373,48 @@ fn set_compare_inner( Ok(vm.new_bool(true)) } -fn set_union(zelf: PySetRef, other: PySetRef, _vm: &VirtualMachine) -> PySet { - let mut elements = zelf.elements.borrow().clone(); - elements.extend(other.elements.borrow().clone()); +fn set_union(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + validate_set_or_frozenset!(vm, zelf.type_pyref()); + validate_set_or_frozenset!(vm, other.type_pyref()); - PySet { - elements: RefCell::new(elements), - } + let mut elements = get_elements(&zelf).clone(); + elements.extend(get_elements(&other).clone()); + + create_set(vm, elements, zelf.type_pyref()) } -fn set_intersection(zelf: PySetRef, other: PySetRef, vm: &VirtualMachine) -> PyResult { +fn set_intersection(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { set_combine_inner(zelf, other, vm, SetCombineOperation::Intersection) } -fn set_difference(zelf: PySetRef, other: PySetRef, vm: &VirtualMachine) -> PyResult { +fn set_difference(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { set_combine_inner(zelf, other, vm, SetCombineOperation::Difference) } fn set_symmetric_difference( - zelf: PySetRef, - other: PySetRef, + zelf: PyObjectRef, + other: PyObjectRef, vm: &VirtualMachine, -) -> PyResult { +) -> PyResult { + validate_set_or_frozenset!(vm, zelf.type_pyref()); + validate_set_or_frozenset!(vm, other.type_pyref()); let mut elements = HashMap::new(); - for element in zelf.elements.borrow().iter() { - let value = vm.call_method(other.as_object(), "__contains__", vec![element.1.clone()])?; + for element in get_elements(&zelf).iter() { + let value = vm.call_method(&other, "__contains__", vec![element.1.clone()])?; if !objbool::get_value(&value) { elements.insert(element.0.clone(), element.1.clone()); } } - for element in other.elements.borrow().iter() { - let value = vm.call_method(zelf.as_object(), "__contains__", vec![element.1.clone()])?; + for element in get_elements(&other).iter() { + let value = vm.call_method(&zelf, "__contains__", vec![element.1.clone()])?; if !objbool::get_value(&value) { elements.insert(element.0.clone(), element.1.clone()); } } - Ok(PySet { - elements: RefCell::new(elements), - }) + create_set(vm, elements, zelf.type_pyref()) } enum SetCombineOperation { @@ -372,15 +423,17 @@ enum SetCombineOperation { } fn set_combine_inner( - zelf: PySetRef, - other: PySetRef, + zelf: PyObjectRef, + other: PyObjectRef, vm: &VirtualMachine, op: SetCombineOperation, -) -> PyResult { +) -> PyResult { + validate_set_or_frozenset!(vm, zelf.type_pyref()); + validate_set_or_frozenset!(vm, other.type_pyref()); let mut elements = HashMap::new(); - for element in zelf.elements.borrow().iter() { - let value = vm.call_method(other.as_object(), "__contains__", vec![element.1.clone()])?; + for element in get_elements(&zelf).iter() { + let value = vm.call_method(&other, "__contains__", vec![element.1.clone()])?; let should_add = match op { SetCombineOperation::Intersection => objbool::get_value(&value), SetCombineOperation::Difference => !objbool::get_value(&value), @@ -390,9 +443,7 @@ fn set_combine_inner( } } - Ok(PySet { - elements: RefCell::new(elements), - }) + create_set(vm, elements, zelf.type_pyref()) } fn set_pop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -614,6 +665,46 @@ pub fn init(context: &PyContext) { frozenset(iterable) -> frozenset object\n\n\ Build an immutable unordered collection of unique elements."; + context.set_attr(frozenset_type, "__new__", context.new_rustfunc(set_new)); + context.set_attr(frozenset_type, "__eq__", context.new_rustfunc(set_eq)); + context.set_attr(frozenset_type, "__ge__", context.new_rustfunc(set_ge)); + context.set_attr(frozenset_type, "__gt__", context.new_rustfunc(set_gt)); + context.set_attr(frozenset_type, "__le__", context.new_rustfunc(set_le)); + context.set_attr(frozenset_type, "__lt__", context.new_rustfunc(set_lt)); + context.set_attr(frozenset_type, "issubset", context.new_rustfunc(set_le)); + context.set_attr(frozenset_type, "issuperset", context.new_rustfunc(set_ge)); + context.set_attr(frozenset_type, "union", context.new_rustfunc(set_union)); + context.set_attr(frozenset_type, "__or__", context.new_rustfunc(set_union)); + context.set_attr( + frozenset_type, + "intersection", + context.new_rustfunc(set_intersection), + ); + context.set_attr( + frozenset_type, + "__and__", + context.new_rustfunc(set_intersection), + ); + context.set_attr( + frozenset_type, + "difference", + context.new_rustfunc(set_difference), + ); + context.set_attr( + frozenset_type, + "__sub__", + context.new_rustfunc(set_difference), + ); + context.set_attr( + frozenset_type, + "symmetric_difference", + context.new_rustfunc(set_symmetric_difference), + ); + context.set_attr( + frozenset_type, + "__xor__", + context.new_rustfunc(set_symmetric_difference), + ); context.set_attr( frozenset_type, "__contains__", @@ -630,4 +721,5 @@ pub fn init(context: &PyContext) { "__repr__", context.new_rustfunc(frozenset_repr), ); + context.set_attr(frozenset_type, "copy", context.new_rustfunc(set_copy)); } From dc05d5f94b43985245e5af0a505d0d0ce4e2a350 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 23 Mar 2019 11:06:18 +0200 Subject: [PATCH 027/884] Remove Refcell around PyFrozenSet elements --- vm/src/obj/objset.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 8bb338d407..1d69d3bdef 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -27,7 +27,7 @@ pub type PySetRef = PyRef; #[derive(Default)] pub struct PyFrozenSet { - elements: RefCell>, + elements: HashMap, } pub type PyFrozenSetRef = PyRef; @@ -61,7 +61,7 @@ pub fn get_elements(obj: &PyObjectRef) -> HashMap { if let Some(set) = obj.payload::() { return set.elements.borrow().clone(); } else if let Some(frozenset) = obj.payload::() { - return frozenset.elements.borrow().clone(); + return frozenset.elements.clone(); } panic!("Not frozenset or set"); } @@ -93,9 +93,7 @@ fn create_set( )) } else if objtype::issubclass(&cls, &vm.ctx.frozenset_type()) { Ok(PyObject::new( - PyFrozenSet { - elements: RefCell::new(elements), - }, + PyFrozenSet { elements: elements }, PyFrozenSet::class(vm).into_object(), )) } else { From 2aee2981e3bafb0f8d77c2cffa7b76c5ac2cee40 Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 23 Mar 2019 09:38:00 -0700 Subject: [PATCH 028/884] Remove FromPyObjectRef, replace with downcast --- vm/src/obj/objint.rs | 5 ++--- vm/src/obj/objtype.rs | 6 +++--- vm/src/pyobject.rs | 44 +++++++++++++++++++++---------------------- vm/src/stdlib/json.rs | 6 ++---- vm/src/vm.rs | 6 +++--- 5 files changed, 31 insertions(+), 36 deletions(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index f388aeac67..7c19968da3 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -7,8 +7,7 @@ use num_traits::{Pow, Signed, ToPrimitive, Zero}; use crate::format::FormatSpec; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - FromPyObjectRef, IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, - TypeProtocol, + IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -388,7 +387,7 @@ fn int_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { None => Zero::zero(), }; Ok(PyInt::new(val) - .into_ref_with_type(vm, PyClassRef::from_pyobj(cls))? + .into_ref_with_type(vm, cls.clone().downcast().unwrap())? .into_object()) } diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index d2cabe1ff8..284b304de4 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -4,8 +4,8 @@ use std::fmt; use crate::function::{Args, KwArgs, PyFuncArgs}; use crate::pyobject::{ - FromPyObjectRef, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyRef, PyResult, - PyValue, TypeProtocol, + IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, + TypeProtocol, }; use crate::vm::VirtualMachine; @@ -236,7 +236,7 @@ pub fn type_new_class( let mut bases: Vec = vm .extract_elements(bases)? .iter() - .map(|x| FromPyObjectRef::from_pyobj(x)) + .map(|x| x.clone().downcast().unwrap()) .collect(); bases.push(vm.ctx.object()); let name = objstr::get_value(name); diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 83b2deab99..41a1ca6f34 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -159,7 +159,7 @@ pub fn create_type(name: &str, type_type: &PyClassRef, base: &PyClassRef) -> PyC dict, ) .unwrap(); - FromPyObjectRef::from_pyobj(&new_type) + new_type.downcast().unwrap() } pub type PyNotImplementedRef = PyRef; @@ -205,7 +205,7 @@ fn init_type_hierarchy() -> (PyClassRef, PyClassRef) { dict: Some(RefCell::new(PyAttributes::new())), payload: PyClass { name: String::from("type"), - mro: vec![FromPyObjectRef::from_pyobj(&object_type)], + mro: vec![object_type.clone().downcast().unwrap()], }, } .into_ref(); @@ -216,8 +216,8 @@ fn init_type_hierarchy() -> (PyClassRef, PyClassRef) { ptr::write(&mut (*type_type_ptr).typ, type_type.clone()); ( - PyClassRef::from_pyobj(&type_type), - PyClassRef::from_pyobj(&object_type), + type_type.downcast().unwrap(), + object_type.downcast().unwrap(), ) } } @@ -583,7 +583,7 @@ impl PyContext { PyAttributes::new(), ) .unwrap(); - PyClassRef::from_pyobj(&typ) + typ.downcast().unwrap() } pub fn new_scope(&self) -> Scope { @@ -721,6 +721,21 @@ where pub payload: T, } +impl PyObject { + pub fn downcast(self: Rc) -> Option> { + if self.payload_is::() { + Some({ + PyRef { + obj: self, + _payload: PhantomData, + } + }) + } else { + None + } + } +} + /// A reference to a Python object. /// /// Note that a `PyRef` can only deref to a shared / immutable reference. @@ -860,16 +875,12 @@ impl IdProtocol for PyRef { } } -pub trait FromPyObjectRef { - fn from_pyobj(obj: &PyObjectRef) -> Self; -} - pub trait TypeProtocol { fn typ(&self) -> PyObjectRef { self.type_ref().clone() } fn type_pyref(&self) -> PyClassRef { - FromPyObjectRef::from_pyobj(self.type_ref()) + self.typ().downcast().unwrap() } fn type_ref(&self) -> &PyObjectRef; } @@ -1207,19 +1218,6 @@ impl PyObjectPayload for T { } } -impl FromPyObjectRef for PyRef { - fn from_pyobj(obj: &PyObjectRef) -> Self { - if obj.payload_is::() { - PyRef { - obj: obj.clone(), - _payload: PhantomData, - } - } else { - panic!("Error getting inner type: {:?}", obj.typ) - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index e7e6d3b6f9..e1cfbd93b5 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -6,15 +6,13 @@ use serde::ser::{SerializeMap, SerializeSeq}; use serde_json; use crate::function::PyFuncArgs; -use crate::obj::objtype::PyClassRef; use crate::obj::{ objbool, objdict, objfloat, objint, objsequence, objstr::{self, PyString}, objtype, }; use crate::pyobject::{ - create_type, DictProtocol, FromPyObjectRef, IdProtocol, PyContext, PyObjectRef, PyResult, - TypeProtocol, + create_type, DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol, }; use crate::VirtualMachine; use num_traits::cast::ToPrimitive; @@ -208,7 +206,7 @@ pub fn de_pyobject(vm: &VirtualMachine, s: &str) -> PyResult { .unwrap() .get_item("JSONDecodeError") .unwrap(); - let json_decode_error = PyClassRef::from_pyobj(&json_decode_error); + let json_decode_error = json_decode_error.downcast().unwrap(); let exc = vm.new_exception(json_decode_error, format!("{}", err)); vm.ctx.set_attr(&exc, "lineno", vm.ctx.new_int(err.line())); vm.ctx.set_attr(&exc, "colno", vm.ctx.new_int(err.column())); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index a0e0eaf786..4a3cc3abc0 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -30,8 +30,8 @@ use crate::obj::objtuple::PyTuple; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - DictProtocol, FromPyObjectRef, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, - TryIntoRef, TypeProtocol, + DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, TryIntoRef, + TypeProtocol, }; use crate::stdlib; use crate::sysmodule; @@ -112,7 +112,7 @@ impl VirtualMachine { let class = self .get_attribute(module.clone(), class) .unwrap_or_else(|_| panic!("module {} has no class {}", module, class)); - PyClassRef::from_pyobj(&class) + class.downcast().unwrap() } /// Create a new python string object. From fa8d3524bfd341ab2f44408f75fb2c61632b9214 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 23 Mar 2019 17:49:49 +0100 Subject: [PATCH 029/884] Use extend_class macro even more --- vm/src/exceptions.rs | 17 ++++++-------- vm/src/obj/objgenerator.rs | 16 ++++--------- vm/src/obj/objiter.rs | 20 ++++++++-------- vm/src/obj/objobject.rs | 47 +++++++++++++++++--------------------- vm/src/obj/objsuper.rs | 16 ++++--------- vm/src/obj/objtuple.rs | 38 +++++++++++++++--------------- 6 files changed, 69 insertions(+), 85 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 3326299641..3eb962edfe 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -159,15 +159,12 @@ impl ExceptionZoo { pub fn init(context: &PyContext) { let base_exception_type = &context.exceptions.base_exception_type; - context.set_attr( - base_exception_type, - "__init__", - context.new_rustfunc(exception_init), - ); + extend_class!(context, base_exception_type, { + "__init__" => context.new_rustfunc(exception_init) + }); + let exception_type = &context.exceptions.exception_type; - context.set_attr( - exception_type, - "__str__", - context.new_rustfunc(exception_str), - ); + extend_class!(context, exception_type, { + "__str__" => context.new_rustfunc(exception_str) + }); } diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index ccb19a32c2..b766aafd4d 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -22,17 +22,11 @@ impl PyValue for PyGenerator { pub fn init(context: &PyContext) { let generator_type = &context.generator_type; - context.set_attr( - generator_type, - "__iter__", - context.new_rustfunc(generator_iter), - ); - context.set_attr( - generator_type, - "__next__", - context.new_rustfunc(generator_next), - ); - context.set_attr(generator_type, "send", context.new_rustfunc(generator_send)); + extend_class!(context, generator_type, { + "__iter__" => context.new_rustfunc(generator_iter), + "__next__" => context.new_rustfunc(generator_next), + "send" => context.new_rustfunc(generator_send) + }); } pub fn new_generator(frame: PyObjectRef, vm: &VirtualMachine) -> PyGeneratorRef { diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 7c2341173b..282ccf146d 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -96,11 +96,7 @@ pub fn iter_type_init(context: &PyContext, iter_type: &PyClassRef) { let cloned_iter_type = iter_type.clone(); move |vm: &VirtualMachine, args: PyFuncArgs| contains(vm, args, cloned_iter_type.clone()) }; - context.set_attr( - iter_type, - "__contains__", - context.new_rustfunc(contains_func), - ); + let iter_func = { let cloned_iter_type = iter_type.clone(); move |vm: &VirtualMachine, args: PyFuncArgs| { @@ -113,7 +109,11 @@ pub fn iter_type_init(context: &PyContext, iter_type: &PyClassRef) { Ok(iter.clone()) } }; - context.set_attr(iter_type, "__iter__", context.new_rustfunc(iter_func)); + + extend_class!(context, iter_type, { + "__contains__" => context.new_rustfunc(contains_func), + "__iter__" => context.new_rustfunc(iter_func) + }); } // Sequence iterator: @@ -179,7 +179,9 @@ pub fn init(context: &PyContext) { In the second form, the callable is called until it returns the sentinel."; iter_type_init(context, iter_type); - context.set_attr(iter_type, "__new__", context.new_rustfunc(iter_new)); - context.set_attr(iter_type, "__next__", context.new_rustfunc(iter_next)); - context.set_attr(iter_type, "__doc__", context.new_str(iter_doc.to_string())); + extend_class!(context, iter_type, { + "__new__" => context.new_rustfunc(iter_new), + "__next__" => context.new_rustfunc(iter_next), + "__doc__" => context.new_str(iter_doc.to_string()) + }); } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 81fdc0b3f9..e0ff9b20cd 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -183,36 +183,31 @@ pub fn init(context: &PyContext) { let object = &context.object; let object_doc = "The most base type"; - context.set_attr(object, "__new__", context.new_rustfunc(new_instance)); - context.set_attr(object, "__init__", context.new_rustfunc(object_init)); - context.set_attr( - object, - "__class__", + extend_class!(context, object, { + "__new__" => context.new_rustfunc(new_instance), + "__init__" => context.new_rustfunc(object_init), + "__class__" => PropertyBuilder::new(context) .add_getter(object_class) .add_setter(object_class_setter) .create(), - ); - context.set_attr(object, "__eq__", context.new_rustfunc(object_eq)); - context.set_attr(object, "__ne__", context.new_rustfunc(object_ne)); - context.set_attr(object, "__lt__", context.new_rustfunc(object_lt)); - context.set_attr(object, "__le__", context.new_rustfunc(object_le)); - context.set_attr(object, "__gt__", context.new_rustfunc(object_gt)); - context.set_attr(object, "__ge__", context.new_rustfunc(object_ge)); - context.set_attr(object, "__setattr__", context.new_rustfunc(object_setattr)); - context.set_attr(object, "__delattr__", context.new_rustfunc(object_delattr)); - context.set_attr(object, "__dict__", context.new_property(object_dict)); - context.set_attr(object, "__dir__", context.new_rustfunc(object_dir)); - context.set_attr(object, "__hash__", context.new_rustfunc(object_hash)); - context.set_attr(object, "__str__", context.new_rustfunc(object_str)); - context.set_attr(object, "__repr__", context.new_rustfunc(object_repr)); - context.set_attr(object, "__format__", context.new_rustfunc(object_format)); - context.set_attr( - object, - "__getattribute__", - context.new_rustfunc(object_getattribute), - ); - context.set_attr(object, "__doc__", context.new_str(object_doc.to_string())); + "__eq__" => context.new_rustfunc(object_eq), + "__ne__" => context.new_rustfunc(object_ne), + "__lt__" => context.new_rustfunc(object_lt), + "__le__" => context.new_rustfunc(object_le), + "__gt__" => context.new_rustfunc(object_gt), + "__ge__" => context.new_rustfunc(object_ge), + "__setattr__" => context.new_rustfunc(object_setattr), + "__delattr__" => context.new_rustfunc(object_delattr), + "__dict__" => context.new_property(object_dict), + "__dir__" => context.new_rustfunc(object_dir), + "__hash__" => context.new_rustfunc(object_hash), + "__str__" => context.new_rustfunc(object_str), + "__repr__" => context.new_rustfunc(object_repr), + "__format__" => context.new_rustfunc(object_format), + "__getattribute__" => context.new_rustfunc(object_getattribute), + "__doc__" => context.new_str(object_doc.to_string()) + }); } fn object_init(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 97fdd367e0..c3fba5eeb8 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -48,17 +48,11 @@ pub fn init(context: &PyContext) { def cmeth(cls, arg):\n \ super().cmeth(arg)\n"; - context.set_attr(super_type, "__new__", context.new_rustfunc(super_new)); - context.set_attr( - super_type, - "__getattribute__", - context.new_rustfunc(super_getattribute), - ); - context.set_attr( - super_type, - "__doc__", - context.new_str(super_doc.to_string()), - ); + extend_class!(context, super_type, { + "__new__" => context.new_rustfunc(super_new), + "__getattribute__" => context.new_rustfunc(super_getattribute), + "__doc__" => context.new_str(super_doc.to_string()), + }); } fn super_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 92a90913a3..e5bf5c1eec 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -233,22 +233,24 @@ pub fn init(context: &PyContext) { tuple(iterable) -> tuple initialized from iterable's items If the argument is a tuple, the return value is the same object."; - context.set_attr(tuple_type, "__add__", context.new_rustfunc(PyTupleRef::add)); - context.set_attr(tuple_type, "__bool__", context.new_rustfunc(PyTupleRef::bool)); - context.set_attr(tuple_type, "__eq__", context.new_rustfunc(PyTupleRef::eq)); - context.set_attr(tuple_type,"__contains__",context.new_rustfunc(PyTupleRef::contains)); - context.set_attr(tuple_type,"__getitem__",context.new_rustfunc(PyTupleRef::getitem)); - context.set_attr(tuple_type, "__hash__", context.new_rustfunc(PyTupleRef::hash)); - context.set_attr(tuple_type, "__iter__", context.new_rustfunc(PyTupleRef::iter)); - context.set_attr(tuple_type, "__len__", context.new_rustfunc(PyTupleRef::len)); - context.set_attr(tuple_type, "__new__", context.new_rustfunc(tuple_new)); - context.set_attr(tuple_type, "__mul__", context.new_rustfunc(PyTupleRef::mul)); - context.set_attr(tuple_type, "__repr__", context.new_rustfunc(PyTupleRef::repr)); - context.set_attr(tuple_type, "count", context.new_rustfunc(PyTupleRef::count)); - context.set_attr(tuple_type, "__lt__", context.new_rustfunc(PyTupleRef::lt)); - context.set_attr(tuple_type, "__le__", context.new_rustfunc(PyTupleRef::le)); - context.set_attr(tuple_type, "__gt__", context.new_rustfunc(PyTupleRef::gt)); - context.set_attr(tuple_type, "__ge__", context.new_rustfunc(PyTupleRef::ge)); - context.set_attr(tuple_type,"__doc__",context.new_str(tuple_doc.to_string())); - context.set_attr(tuple_type, "index", context.new_rustfunc(PyTupleRef::index)); + extend_class!(context, tuple_type, { + "__add__" => context.new_rustfunc(PyTupleRef::add), + "__bool__" => context.new_rustfunc(PyTupleRef::bool), + "__eq__" => context.new_rustfunc(PyTupleRef::eq), + "__contains__" => context.new_rustfunc(PyTupleRef::contains), + "__getitem__" => context.new_rustfunc(PyTupleRef::getitem), + "__hash__" => context.new_rustfunc(PyTupleRef::hash), + "__iter__" => context.new_rustfunc(PyTupleRef::iter), + "__len__" => context.new_rustfunc(PyTupleRef::len), + "__new__" => context.new_rustfunc(tuple_new), + "__mul__" => context.new_rustfunc(PyTupleRef::mul), + "__repr__" => context.new_rustfunc(PyTupleRef::repr), + "count" => context.new_rustfunc(PyTupleRef::count), + "__lt__" => context.new_rustfunc(PyTupleRef::lt), + "__le__" => context.new_rustfunc(PyTupleRef::le), + "__gt__" => context.new_rustfunc(PyTupleRef::gt), + "__ge__" => context.new_rustfunc(PyTupleRef::ge), + "__doc__" => context.new_str(tuple_doc.to_string()), + "index" => context.new_rustfunc(PyTupleRef::index) + }); } From f413beb05115550253d42d13989848112b0647c5 Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 23 Mar 2019 10:03:40 -0700 Subject: [PATCH 030/884] Fix test --- vm/src/obj/objtype.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 284b304de4..81f04603bb 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -385,7 +385,6 @@ pub fn new( #[cfg(test)] mod tests { - use super::FromPyObjectRef; use super::{linearise_mro, new}; use super::{HashMap, IdProtocol, PyClassRef, PyContext}; @@ -417,8 +416,8 @@ mod tests { ) .unwrap(); - let a: PyClassRef = FromPyObjectRef::from_pyobj(&a); - let b: PyClassRef = FromPyObjectRef::from_pyobj(&b); + let a: PyClassRef = a.downcast().unwrap(); + let b: PyClassRef = b.downcast().unwrap(); assert_eq!( map_ids(linearise_mro(vec![ From 2f9a49d07789365cd43719d16a3d3633efccb664 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 23 Mar 2019 19:56:00 +0200 Subject: [PATCH 031/884] Support classmethod super --- tests/snippets/class.py | 10 ++++++++++ vm/src/obj/objsuper.rs | 5 +++++ 2 files changed, 15 insertions(+) diff --git a/tests/snippets/class.py b/tests/snippets/class.py index 35d043a6d4..06e125f217 100644 --- a/tests/snippets/class.py +++ b/tests/snippets/class.py @@ -64,6 +64,10 @@ class B(): def test1(self): return 200 + @classmethod + def test3(cls): + return 300 + class C(A,B): def test(self): return super().test() @@ -71,9 +75,15 @@ def test(self): def test1(self): return super().test1() + @classmethod + def test3(cls): + return super().test3() + c = C() assert c.test() == 100 assert c.test1() == 200 +assert c.test3() == 300 +assert C.test3() == 300 class Me(): diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 97fdd367e0..8c6b835890 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -8,6 +8,7 @@ https://github.com/python/cpython/blob/50b48572d9a90c5bb36e2bef6179548ea927a35a/ use crate::frame::NameProtocol; use crate::function::{OptionalArg, PyFuncArgs}; +use crate::obj::objfunction::PyMethod; use crate::obj::objstr; use crate::obj::objtype::{PyClass, PyClassRef}; use crate::pyobject::{ @@ -78,6 +79,10 @@ fn super_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Some(PyClass { ref mro, .. }) => { for class in mro { if let Ok(item) = vm.get_attribute(class.as_object().clone(), name_str.clone()) { + if let Some(PyMethod { .. }) = item.payload() { + // This is a classmethod + return Ok(item); + } return Ok(vm.ctx.new_bound_method(item, inst.clone())); } } From 1d820a9586c395acb350bcd6b9118fa085d397af Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 23 Mar 2019 20:01:11 +0200 Subject: [PATCH 032/884] Use payload_is --- vm/src/obj/objsuper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 8c6b835890..da24418915 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -79,7 +79,7 @@ fn super_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Some(PyClass { ref mro, .. }) => { for class in mro { if let Ok(item) = vm.get_attribute(class.as_object().clone(), name_str.clone()) { - if let Some(PyMethod { .. }) = item.payload() { + if item.payload_is::() { // This is a classmethod return Ok(item); } From 0aabe88c5dc1aceea51a579e3bb9c5551bd17949 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 23 Mar 2019 14:04:14 -0500 Subject: [PATCH 033/884] Put `browser` class methods into `impl` blocks --- vm/src/vm.rs | 7 +- wasm/lib/src/browser_module.rs | 235 +++++++++++++++------------------ wasm/lib/src/convert.rs | 14 +- 3 files changed, 121 insertions(+), 135 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 76b06ddfcd..9cf4b98e09 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -111,7 +111,10 @@ impl VirtualMachine { } pub fn try_class(&self, module: &str, class: &str) -> PyResult { - let class = self.get_attribute(self.import(module)?, class)?.downcast().expect("not a class"); + let class = self + .get_attribute(self.import(module)?, class)? + .downcast() + .expect("not a class"); Ok(class) } @@ -122,7 +125,7 @@ impl VirtualMachine { let class = self .get_attribute(module.clone(), class) .unwrap_or_else(|_| panic!("module {} has no class {}", module, class)); - class.downcast().expect("not a class"); + class.downcast().expect("not a class") } /// Create a new python string object. diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 0be52c43d6..ce2d3d6c14 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -7,6 +7,7 @@ use wasm_bindgen_futures::{future_to_promise, JsFuture}; use rustpython_vm::function::{OptionalArg, PyFuncArgs}; use rustpython_vm::obj::{ + objfunction::PyFunction, objint, objstr::{self, PyStringRef}, objtype::PyClassRef, @@ -45,8 +46,6 @@ impl FetchResponseFormat { fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(url, Some(vm.ctx.str_type()))]); - let promise_type = vm.try_class("browser", "Promise")?; - let response_format = args.get_optional_kwarg_with_type("response_format", vm.ctx.str_type(), vm)?; let method = args.get_optional_kwarg_with_type("method", vm.ctx.str_type(), vm)?; @@ -102,7 +101,9 @@ fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { }) .and_then(JsFuture::from); - Ok(PyPromise::new_obj(promise_type, future_to_promise(future))) + Ok(PyPromise::new(future_to_promise(future)) + .into_ref(vm) + .into_object()) } fn browser_request_animation_frame(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -163,6 +164,7 @@ fn browser_cancel_animation_frame(vm: &VirtualMachine, args: PyFuncArgs) -> PyRe pub struct PyPromise { value: Promise, } +pub type PyPromiseRef = PyRef; impl PyValue for PyPromise { fn class(vm: &VirtualMachine) -> PyClassRef { @@ -171,156 +173,135 @@ impl PyValue for PyPromise { } impl PyPromise { - pub fn new_obj(promise_type: PyClassRef, value: Promise) -> PyObjectRef { - PyObject::new(PyPromise { value }, promise_type.into_object()) + pub fn new(value: Promise) -> PyPromise { + PyPromise { value } } -} - -pub fn get_promise_value(obj: &PyObjectRef) -> Promise { - if let Some(promise) = obj.payload::() { - return promise.value.clone(); + pub fn value(&self) -> Promise { + self.value.clone() } - panic!("Inner error getting promise") -} -fn promise_then(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let promise_type = vm.try_class("browser", "Promise")?; - arg_check!( - vm, - args, - required = [ - (zelf, Some(promise_type.clone())), - (on_fulfill, Some(vm.ctx.function_type())) - ], - optional = [(on_reject, Some(vm.ctx.function_type()))] - ); - - let on_fulfill = on_fulfill.clone(); - let on_reject = on_reject.cloned(); - - let acc_vm = AccessibleVM::from(vm); - - let promise = get_promise_value(zelf); + fn then( + zelf: PyPromiseRef, + on_fulfill: PyRef, + on_reject: OptionalArg>, + vm: &VirtualMachine, + ) -> PyResult { + let acc_vm = AccessibleVM::from(vm); - let ret_future = JsFuture::from(promise).then(move |res| { - let stored_vm = &acc_vm - .upgrade() - .expect("that the vm is valid when the promise resolves"); - let vm = &stored_vm.vm; - let ret = match res { - Ok(val) => { - let val = convert::js_to_py(vm, val); - vm.invoke(on_fulfill, PyFuncArgs::new(vec![val], vec![])) - } - Err(err) => { - if let Some(on_reject) = on_reject { - let err = convert::js_to_py(vm, err); - vm.invoke(on_reject, PyFuncArgs::new(vec![err], vec![])) - } else { - return Err(err); + let ret_future = JsFuture::from(zelf.value.clone()).then(move |res| { + let stored_vm = &acc_vm + .upgrade() + .expect("that the vm is valid when the promise resolves"); + let vm = &stored_vm.vm; + let ret = match res { + Ok(val) => { + let val = convert::js_to_py(vm, val); + vm.invoke(on_fulfill.into_object(), PyFuncArgs::new(vec![val], vec![])) } - } - }; - convert::pyresult_to_jsresult(vm, ret) - }); - - let ret_promise = future_to_promise(ret_future); - - Ok(PyPromise::new_obj(promise_type, ret_promise)) -} - -fn promise_catch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let promise_type = vm.try_class("browser", "Promise")?; - arg_check!( - vm, - args, - required = [ - (zelf, Some(promise_type.clone())), - (on_reject, Some(vm.ctx.function_type())) - ] - ); + Err(err) => { + if let OptionalArg::Present(on_reject) = on_reject { + let err = convert::js_to_py(vm, err); + vm.invoke(on_reject.into_object(), PyFuncArgs::new(vec![err], vec![])) + } else { + return Err(err); + } + } + }; + convert::pyresult_to_jsresult(vm, ret) + }); - let on_reject = on_reject.clone(); + let ret_promise = future_to_promise(ret_future); - let acc_vm = AccessibleVM::from(vm); + Ok(PyPromise::new(ret_promise).into_ref(vm)) + } - let promise = get_promise_value(zelf); + fn catch( + zelf: PyPromiseRef, + on_reject: PyRef, + vm: &VirtualMachine, + ) -> PyResult { + let acc_vm = AccessibleVM::from(vm); - let ret_future = JsFuture::from(promise).then(move |res| match res { - Ok(val) => Ok(val), - Err(err) => { - let stored_vm = acc_vm - .upgrade() - .expect("that the vm is valid when the promise resolves"); - let vm = &stored_vm.vm; - let err = convert::js_to_py(vm, err); - let res = vm.invoke(on_reject, PyFuncArgs::new(vec![err], vec![])); - convert::pyresult_to_jsresult(vm, res) - } - }); + let ret_future = JsFuture::from(zelf.value.clone()).then(move |res| match res { + Ok(val) => Ok(val), + Err(err) => { + let stored_vm = acc_vm + .upgrade() + .expect("that the vm is valid when the promise resolves"); + let vm = &stored_vm.vm; + let err = convert::js_to_py(vm, err); + let res = vm.invoke(on_reject.into_object(), PyFuncArgs::new(vec![err], vec![])); + convert::pyresult_to_jsresult(vm, res) + } + }); - let ret_promise = future_to_promise(ret_future); + let ret_promise = future_to_promise(ret_future); - Ok(PyPromise::new_obj(promise_type, ret_promise)) + Ok(PyPromise::new(ret_promise).into_ref(vm)) + } } #[derive(Debug)] -struct PyDocument { +struct Document { doc: web_sys::Document, } -type PyDocumentRef = PyRef; +type DocumentRef = PyRef; -impl PyValue for PyDocument { - fn class(vm: &VirtualMachine) -> PyObjectRef { +impl PyValue for Document { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.class("browser", "Document") } } -fn document_query(zelf: PyDocumentRef, query: PyStringRef, vm: &VirtualMachine) -> PyResult { - let elem = zelf - .doc - .query_selector(&query.value) - .map_err(|err| convert::js_py_typeerror(vm, err))?; - let elem = match elem { - Some(elem) => PyElement { elem }.into_ref(vm).into_object(), - None => vm.get_none(), - }; - Ok(elem) +impl Document { + fn query(zelf: DocumentRef, query: PyStringRef, vm: &VirtualMachine) -> PyResult { + let elem = zelf + .doc + .query_selector(&query.value) + .map_err(|err| convert::js_py_typeerror(vm, err))?; + let elem = match elem { + Some(elem) => Element { elem }.into_ref(vm).into_object(), + None => vm.get_none(), + }; + Ok(elem) + } } #[derive(Debug)] -struct PyElement { +struct Element { elem: web_sys::Element, } -type PyElementRef = PyRef; +type ElementRef = PyRef; -impl PyValue for PyElement { - fn class(vm: &VirtualMachine) -> PyObjectRef { +impl PyValue for Element { + fn class(vm: &VirtualMachine) -> PyClassRef { vm.class("browser", "Element") } } -fn elem_get_attr( - zelf: PyElementRef, - attr: PyStringRef, - default: OptionalArg, - vm: &VirtualMachine, -) -> PyObjectRef { - match zelf.elem.get_attribute(&attr.value) { - Some(s) => vm.new_str(s), - None => default.into_option().unwrap_or_else(|| vm.get_none()), +impl Element { + fn get_attr( + zelf: ElementRef, + attr: PyStringRef, + default: OptionalArg, + vm: &VirtualMachine, + ) -> PyObjectRef { + match zelf.elem.get_attribute(&attr.value) { + Some(s) => vm.new_str(s), + None => default.into_option().unwrap_or_else(|| vm.get_none()), + } } -} -fn elem_set_attr( - zelf: PyElementRef, - attr: PyStringRef, - value: PyStringRef, - vm: &VirtualMachine, -) -> PyResult<()> { - zelf.elem - .set_attribute(&attr.value, &value.value) - .map_err(|err| convert::js_to_py(vm, err)) + fn set_attr( + zelf: ElementRef, + attr: PyStringRef, + value: PyStringRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + zelf.elem + .set_attribute(&attr.value, &value.value) + .map_err(|err| convert::js_to_py(vm, err)) + } } fn browser_alert(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -370,24 +351,24 @@ fn browser_prompt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn make_module(ctx: &PyContext) -> PyObjectRef { let promise = py_class!(ctx, "Promise", ctx.object(), { - "then" => ctx.new_rustfunc(promise_then), - "catch" => ctx.new_rustfunc(promise_catch), + "then" => ctx.new_rustfunc(PyPromise::then), + "catch" => ctx.new_rustfunc(PyPromise::catch), }); let document_class = py_class!(ctx, "Document", ctx.object(), { - "query" => ctx.new_rustfunc(document_query), + "query" => ctx.new_rustfunc(Document::query), }); let document = PyObject::new( - PyDocument { + Document { doc: window().document().expect("Document missing from window"), }, - document_class.clone(), + document_class.clone().into_object(), ); let element = py_class!(ctx, "Element", ctx.object(), { - "get_attr" => ctx.new_rustfunc(elem_get_attr), - "set_attr" => ctx.new_rustfunc(elem_set_attr), + "get_attr" => ctx.new_rustfunc(Element::get_attr), + "set_attr" => ctx.new_rustfunc(Element::set_attr), }); py_module!(ctx, "browser", { diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 9c9e9f902e..ecdb4b1102 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -4,7 +4,7 @@ use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; use rustpython_vm::function::PyFuncArgs; use rustpython_vm::obj::{objbytes, objint, objsequence, objtype}; -use rustpython_vm::pyobject::{DictProtocol, PyObjectRef, PyResult}; +use rustpython_vm::pyobject::{DictProtocol, PyObjectRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; use crate::browser_module; @@ -115,9 +115,9 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { } } // the browser module might not be injected - if let Ok(promise_type) = vm.try_class("browser", "Promise") { - if objtype::isinstance(&py_obj, &promise_type) { - return browser_module::get_promise_value(&py_obj).into(); + if vm.try_class("browser", "Promise").is_ok() { + if let Some(py_prom) = py_obj.payload::() { + return py_prom.value().into(); } } @@ -159,8 +159,10 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { if js_val.is_object() { if let Some(promise) = js_val.dyn_ref::() { // the browser module might not be injected - if let Ok(promise_type) = vm.try_class("browser", "Promise") { - return browser_module::PyPromise::new_obj(promise_type, promise.clone()); + if vm.try_class("browser", "Promise").is_ok() { + return browser_module::PyPromise::new(promise.clone()) + .into_ref(vm) + .into_object(); } } if Array::is_array(&js_val) { From e4e7da3d54f2bc3a800f33c3e71f36fd6f9a225b Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 23 Mar 2019 12:21:55 -0700 Subject: [PATCH 034/884] Some socket cleanups --- vm/src/stdlib/io.rs | 16 +++++-------- vm/src/stdlib/socket.rs | 53 +++++++++++++++++------------------------ 2 files changed, 28 insertions(+), 41 deletions(-) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 9c6c275161..9daaf95d2e 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -21,7 +21,7 @@ use crate::obj::objint; use crate::obj::objstr; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - BufferProtocol, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + BufferProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -59,15 +59,11 @@ impl PyStringIORef { } } -fn string_io_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(cls, None)]); - - Ok(PyObject::new( - PyStringIO { - data: RefCell::new(String::default()), - }, - cls.clone(), - )) +fn string_io_new(cls: PyClassRef, vm: &VirtualMachine) -> PyResult { + PyStringIO { + data: RefCell::new(String::default()), + } + .into_ref_with_type(vm, cls) } fn bytes_io_init(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 4af7e32a13..01268bdef4 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -10,7 +10,9 @@ use crate::obj::objbytes; use crate::obj::objint; use crate::obj::objsequence::get_elements; use crate::obj::objstr; -use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{ + PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, +}; use crate::vm::VirtualMachine; use crate::obj::objtype::PyClassRef; @@ -23,13 +25,13 @@ enum AddressFamily { Inet6 = 3, } -impl AddressFamily { - fn from_i32(vm: &VirtualMachine, value: i32) -> Result { - match value { +impl TryFromObject for AddressFamily { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + match i32::try_from_object(vm, obj)? { 1 => Ok(AddressFamily::Unix), 2 => Ok(AddressFamily::Inet), 3 => Ok(AddressFamily::Inet6), - _ => Err(vm.new_os_error(format!("Unknown address family value: {}", value))), + value => Err(vm.new_os_error(format!("Unknown address family value: {}", value))), } } } @@ -40,12 +42,12 @@ enum SocketKind { Dgram = 2, } -impl SocketKind { - fn from_i32(vm: &VirtualMachine, value: i32) -> Result { - match value { +impl TryFromObject for SocketKind { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + match i32::try_from_object(vm, obj)? { 1 => Ok(SocketKind::Stream), 2 => Ok(SocketKind::Dgram), - _ => Err(vm.new_os_error(format!("Unknown socket kind value: {}", value))), + value => Err(vm.new_os_error(format!("Unknown socket kind value: {}", value))), } } } @@ -139,25 +141,15 @@ fn get_socket<'a>(obj: &'a PyObjectRef) -> impl Deref + 'a { obj.payload::().unwrap() } -fn socket_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (cls, None), - (family_int, Some(vm.ctx.int_type())), - (kind_int, Some(vm.ctx.int_type())) - ] - ); +type SocketRef = PyRef; - let address_family = - AddressFamily::from_i32(vm, objint::get_value(family_int).to_i32().unwrap())?; - let kind = SocketKind::from_i32(vm, objint::get_value(kind_int).to_i32().unwrap())?; - - Ok(PyObject::new( - Socket::new(address_family, kind), - cls.clone(), - )) +fn socket_new( + cls: PyClassRef, + family: AddressFamily, + kind: SocketKind, + vm: &VirtualMachine, +) -> PyResult { + Socket::new(family, kind).into_ref_with_type(vm, cls) } fn socket_connect(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -279,13 +271,12 @@ fn socket_accept(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { address_family: socket.address_family, socket_kind: socket.socket_kind, con: RefCell::new(Some(Connection::TcpStream(tcp_stream))), - }; - - let sock_obj = PyObject::new(socket, zelf.typ()); + } + .into_ref(vm); let addr_tuple = get_addr_tuple(vm, addr)?; - Ok(vm.ctx.new_tuple(vec![sock_obj, addr_tuple])) + Ok(vm.ctx.new_tuple(vec![socket.into_object(), addr_tuple])) } fn socket_recv(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { From 18ed00a65354db3faddd85a5d0baac7215babe23 Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 23 Mar 2019 12:48:28 -0700 Subject: [PATCH 035/884] Range cleanups --- vm/src/function.rs | 2 +- vm/src/obj/objrange.rs | 117 +++++++++++++++++++++-------------------- 2 files changed, 62 insertions(+), 57 deletions(-) diff --git a/vm/src/function.rs b/vm/src/function.rs index 839cbc9eaa..a7581d3f25 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -143,7 +143,7 @@ impl PyFuncArgs { /// /// If the given `FromArgs` includes any conversions, exceptions raised /// during the conversion will halt the binding and return the error. - fn bind(mut self, vm: &VirtualMachine) -> PyResult { + pub fn bind(mut self, vm: &VirtualMachine) -> PyResult { let given_args = self.args.len(); let bound = match T::from_args(vm, &mut self) { Ok(args) => args, diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 614bfe5c0f..db4ded48dd 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -5,25 +5,23 @@ use num_bigint::{BigInt, Sign}; use num_integer::Integer; use num_traits::{One, Signed, ToPrimitive, Zero}; -use crate::function::PyFuncArgs; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - PyContext, PyIteratorValue, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; -use super::objint::{self, PyInt}; +use super::objint::{self, PyInt, PyIntRef}; use super::objslice::PySlice; use super::objtype; -use crate::obj::objtype::PyClassRef; - -pub type PyRangeRef = PyRef; +use super::objtype::PyClassRef; #[derive(Debug, Clone)] pub struct PyRange { // Unfortunately Rust's built in range type doesn't support things like indexing // or ranges where start > end so we need to roll our own. pub start: BigInt, - pub end: BigInt, + pub stop: BigInt, pub step: BigInt, } @@ -37,11 +35,11 @@ impl PyRange { #[inline] pub fn try_len(&self) -> Option { match self.step.sign() { - Sign::Plus if self.start < self.end => ((&self.end - &self.start - 1usize) + Sign::Plus if self.start < self.stop => ((&self.stop - &self.start - 1usize) / &self.step) .to_usize() .map(|sz| sz + 1), - Sign::Minus if self.start > self.end => ((&self.start - &self.end - 1usize) + Sign::Minus if self.start > self.stop => ((&self.start - &self.stop - 1usize) / (-&self.step)) .to_usize() .map(|sz| sz + 1), @@ -57,8 +55,8 @@ impl PyRange { #[inline] fn offset(&self, value: &BigInt) -> Option { match self.step.sign() { - Sign::Plus if *value >= self.start && *value < self.end => Some(value - &self.start), - Sign::Minus if *value <= self.start && *value > self.end => Some(&self.start - value), + Sign::Plus if *value >= self.start && *value < self.stop => Some(value - &self.start), + Sign::Minus if *value <= self.start && *value > self.stop => Some(&self.start - value), _ => None, } } @@ -92,13 +90,13 @@ impl PyRange { #[inline] pub fn is_empty(&self) -> bool { - (self.start <= self.end && self.step.is_negative()) - || (self.start >= self.end && self.step.is_positive()) + (self.start <= self.stop && self.step.is_negative()) + || (self.start >= self.stop && self.step.is_positive()) } #[inline] pub fn forward(&self) -> bool { - self.start < self.end + self.start < self.stop } #[inline] @@ -108,8 +106,8 @@ impl PyRange { { let result = &self.start + &self.step * index; - if (self.forward() && !self.is_empty() && result < self.end) - || (!self.forward() && !self.is_empty() && result > self.end) + if (self.forward() && !self.is_empty() && result < self.stop) + || (!self.forward() && !self.is_empty() && result > self.stop) { Some(result) } else { @@ -121,22 +119,22 @@ impl PyRange { pub fn reversed(&self) -> Self { // compute the last element that is actually contained within the range // this is the new start - let remainder = ((&self.end - &self.start) % &self.step).abs(); + let remainder = ((&self.stop - &self.start) % &self.step).abs(); let start = if remainder.is_zero() { - &self.end - &self.step + &self.stop - &self.step } else { - &self.end - &remainder + &self.stop - &remainder }; match self.step.sign() { Sign::Plus => PyRange { start, - end: &self.start - 1, + stop: &self.start - 1, step: -&self.step, }, Sign::Minus => PyRange { start, - end: &self.start + 1, + stop: &self.start + 1, step: -&self.step, }, Sign::NoSign => unreachable!(), @@ -145,9 +143,9 @@ impl PyRange { pub fn repr(&self) -> String { if self.step == BigInt::one() { - format!("range({}, {})", self.start, self.end) + format!("range({}, {})", self.start, self.stop) } else { - format!("range({}, {}, {})", self.start, self.end, self.step) + format!("range({}, {}, {})", self.start, self.stop, self.step) } } } @@ -185,40 +183,47 @@ pub fn init(context: &PyContext) { }); } -fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, None), (first, Some(vm.ctx.int_type()))], - optional = [ - (second, Some(vm.ctx.int_type())), - (step, Some(vm.ctx.int_type())) - ] - ); +type PyRangeRef = PyRef; - let start = if second.is_some() { - objint::get_value(first).clone() - } else { - BigInt::zero() - }; +impl PyRangeRef { + fn new(cls: PyClassRef, stop: PyIntRef, vm: &VirtualMachine) -> PyResult { + PyRange { + start: Zero::zero(), + stop: stop.value.clone(), + step: One::one(), + } + .into_ref_with_type(vm, cls) + } - let end = if let Some(pyint) = second { - objint::get_value(pyint).clone() - } else { - objint::get_value(first).clone() - }; + fn new_from( + cls: PyClassRef, + start: PyIntRef, + stop: PyIntRef, + step: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + PyRange { + start: start.value.clone(), + stop: stop.value.clone(), + step: step + .into_option() + .map(|i| i.value.clone()) + .unwrap_or_else(One::one), + } + .into_ref_with_type(vm, cls) + } +} - let step = if let Some(pyint) = step { - objint::get_value(pyint).clone() +fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + let range = if args.args.len() <= 2 { + let (cls, stop) = args.bind(vm)?; + PyRangeRef::new(cls, stop, vm) } else { - BigInt::one() - }; + let (cls, start, stop, step) = args.bind(vm)?; + PyRangeRef::new_from(cls, start, stop, step, vm) + }?; - if step.is_zero() { - Err(vm.new_value_error("range with 0 step size".to_string())) - } else { - Ok(PyObject::new(PyRange { start, end, step }, cls.clone())) - } + Ok(range.into_object()) } fn range_iter(range: PyRangeRef, _vm: &VirtualMachine) -> PyIteratorValue { @@ -282,10 +287,10 @@ fn range_getitem(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if let Some(i) = range.get(int) { i } else { - range.end + range.stop } } else { - range.end + range.stop }; let new_step = if let Some(int) = step { @@ -296,7 +301,7 @@ fn range_getitem(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(PyRange { start: new_start, - end: new_end, + stop: new_end, step: new_step, } .into_ref(vm) @@ -384,7 +389,7 @@ fn range_start(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn range_stop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); - Ok(vm.ctx.new_int(get_value(zelf).end)) + Ok(vm.ctx.new_int(get_value(zelf).stop)) } fn range_step(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { From 3177824474a3a4ef043c063e651d2637b402ebcb Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 23 Mar 2019 12:57:17 -0700 Subject: [PATCH 036/884] Fix test- --- vm/src/stdlib/socket.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 01268bdef4..fe0ce29a06 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -121,9 +121,8 @@ pub struct Socket { } impl PyValue for Socket { - fn class(_vm: &VirtualMachine) -> PyClassRef { - // TODO - unimplemented!() + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("socket", "socket") } } From 590b659548a090d08bb51bca46a4457cc560e9a8 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 23 Mar 2019 23:24:57 +0200 Subject: [PATCH 037/884] Change validate_set_or_frozenset to a function --- vm/src/obj/objset.rs | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 1d69d3bdef..300dc9bca9 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -66,17 +66,13 @@ pub fn get_elements(obj: &PyObjectRef) -> HashMap { panic!("Not frozenset or set"); } -macro_rules! validate_set_or_frozenset { - ( $vm:ident, $obj:expr ) => { - if !(objtype::issubclass(&$obj, &$vm.ctx.set_type()) - || objtype::issubclass(&$obj, &$vm.ctx.frozenset_type())) - { - return Err($vm.new_type_error(format!( - "{} is not a subtype of set or frozenset a", - $obj.typ() - ))); - } - }; +fn validate_set_or_frozenset(vm: &VirtualMachine, cls: PyClassRef) -> PyResult<()> { + if !(objtype::issubclass(&cls, &vm.ctx.set_type()) + || objtype::issubclass(&cls, &vm.ctx.frozenset_type())) + { + return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", cls))); + } + Ok(()) } fn create_set( @@ -213,7 +209,7 @@ fn set_clear(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { /* Create a new object of sub-type of set */ fn set_new(cls: PyClassRef, iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { - validate_set_or_frozenset!(vm, cls); + validate_set_or_frozenset(vm, cls.clone())?; let elements: HashMap = match iterable { OptionalArg::Missing => HashMap::new(), @@ -227,20 +223,20 @@ fn set_new(cls: PyClassRef, iterable: OptionalArg, vm: &VirtualMach } }; - create_set(vm, elements, cls) + create_set(vm, elements, cls.clone()) } fn set_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("set.len called with: {:?}", args); arg_check!(vm, args, required = [(s, None)]); - validate_set_or_frozenset!(vm, s.type_pyref()); + validate_set_or_frozenset(vm, s.type_pyref())?; let elements = get_elements(s); Ok(vm.context().new_int(elements.len())) } fn set_copy(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { trace!("set.copy called with: {:?}", obj); - validate_set_or_frozenset!(vm, obj.type_pyref()); + validate_set_or_frozenset(vm, obj.type_pyref())?; let elements = get_elements(&obj).clone(); create_set(vm, elements, obj.type_pyref()) } @@ -267,7 +263,7 @@ fn set_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn set_contains(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(set, None), (needle, None)]); - validate_set_or_frozenset!(vm, &set.type_pyref()); + validate_set_or_frozenset(vm, set.type_pyref())?; for element in get_elements(set).iter() { match vm._eq(needle.clone(), element.1.clone()) { Ok(value) => { @@ -335,8 +331,8 @@ fn set_compare_inner( ) -> PyResult { arg_check!(vm, args, required = [(zelf, None), (other, None)]); - validate_set_or_frozenset!(vm, zelf.type_pyref()); - validate_set_or_frozenset!(vm, other.type_pyref()); + validate_set_or_frozenset(vm, zelf.type_pyref())?; + validate_set_or_frozenset(vm, other.type_pyref())?; let get_zelf = |swap: bool| -> &PyObjectRef { if swap { @@ -372,8 +368,8 @@ fn set_compare_inner( } fn set_union(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - validate_set_or_frozenset!(vm, zelf.type_pyref()); - validate_set_or_frozenset!(vm, other.type_pyref()); + validate_set_or_frozenset(vm, zelf.type_pyref())?; + validate_set_or_frozenset(vm, other.type_pyref())?; let mut elements = get_elements(&zelf).clone(); elements.extend(get_elements(&other).clone()); @@ -394,8 +390,8 @@ fn set_symmetric_difference( other: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - validate_set_or_frozenset!(vm, zelf.type_pyref()); - validate_set_or_frozenset!(vm, other.type_pyref()); + validate_set_or_frozenset(vm, zelf.type_pyref())?; + validate_set_or_frozenset(vm, other.type_pyref())?; let mut elements = HashMap::new(); for element in get_elements(&zelf).iter() { @@ -426,8 +422,8 @@ fn set_combine_inner( vm: &VirtualMachine, op: SetCombineOperation, ) -> PyResult { - validate_set_or_frozenset!(vm, zelf.type_pyref()); - validate_set_or_frozenset!(vm, other.type_pyref()); + validate_set_or_frozenset(vm, zelf.type_pyref())?; + validate_set_or_frozenset(vm, other.type_pyref())?; let mut elements = HashMap::new(); for element in get_elements(&zelf).iter() { From 6fa059fd6c00eb4095c834a39dcfe43965b9a1b6 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 23 Mar 2019 18:52:53 +1300 Subject: [PATCH 038/884] Make PyObject.typ a PyClassRef --- src/main.rs | 22 +++------- vm/src/builtins.rs | 56 +++++++++++-------------- vm/src/compile.rs | 14 +++---- vm/src/eval.rs | 7 +--- vm/src/function.rs | 7 ++++ vm/src/import.rs | 2 +- vm/src/obj/objcode.rs | 4 +- vm/src/obj/objfilter.rs | 33 ++++++++------- vm/src/obj/objfloat.rs | 6 +-- vm/src/obj/objmemory.rs | 21 +++++----- vm/src/obj/objobject.rs | 4 +- vm/src/obj/objproperty.rs | 4 +- vm/src/obj/objsequence.rs | 4 +- vm/src/obj/objslice.rs | 6 ++- vm/src/obj/objtype.rs | 31 ++++---------- vm/src/obj/objzip.rs | 17 ++++---- vm/src/pyobject.rs | 86 +++++++++++++++------------------------ vm/src/stdlib/re.rs | 49 +++++++--------------- vm/src/vm.rs | 5 ++- wasm/lib/src/vm_class.rs | 3 +- 20 files changed, 154 insertions(+), 227 deletions(-) diff --git a/src/main.rs b/src/main.rs index a0683ec3ed..70adf10080 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,16 +65,11 @@ fn main() { } fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResult { - let code_obj = compile::compile( - source, - &compile::Mode::Exec, - source_path, - vm.ctx.code_type(), - ) - .map_err(|err| { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - vm.new_exception(syntax_error, err.to_string()) - })?; + let code_obj = + compile::compile(vm, source, &compile::Mode::Exec, source_path).map_err(|err| { + let syntax_error = vm.context().exceptions.syntax_error.clone(); + vm.new_exception(syntax_error, err.to_string()) + })?; // trace!("Code object: {:?}", code_obj.borrow()); let vars = vm.ctx.new_scope(); // Keep track of local variables vm.run_code_obj(code_obj, vars) @@ -115,12 +110,7 @@ fn run_script(vm: &VirtualMachine, script_file: &str) -> PyResult { } fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), CompileError> { - match compile::compile( - source, - &compile::Mode::Single, - "".to_string(), - vm.ctx.code_type(), - ) { + match compile::compile(vm, source, &compile::Mode::Single, "".to_string()) { Ok(code) => { if let Err(err) = vm.run_code_obj(code, scope) { print_exception(vm, &err); diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 5ac00561c6..c81ee79e92 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -24,6 +24,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; +use crate::obj::objcode::PyCodeRef; #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::io::io_open; @@ -112,22 +113,17 @@ fn builtin_chr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(txt)) } -fn builtin_compile(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (source, None), - (filename, Some(vm.ctx.str_type())), - (mode, Some(vm.ctx.str_type())) - ] - ); - let source = objstr::get_value(source); +fn builtin_compile( + source: PyStringRef, + filename: PyStringRef, + mode: PyStringRef, + vm: &VirtualMachine, +) -> PyResult { // TODO: fix this newline bug: - let source = format!("{}\n", source); + let source = format!("{}\n", &source.value); let mode = { - let mode = objstr::get_value(mode); + let mode = &mode.value; if mode == "exec" { compile::Mode::Exec } else if mode == "eval" { @@ -141,9 +137,7 @@ fn builtin_compile(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } }; - let filename = objstr::get_value(filename); - - compile::compile(&source, &mode, filename, vm.ctx.code_type()).map_err(|err| { + compile::compile(vm, &source, &mode, filename.value.to_string()).map_err(|err| { let syntax_error = vm.context().exceptions.syntax_error.clone(); vm.new_exception(syntax_error, err.to_string()) }) @@ -190,25 +184,23 @@ fn builtin_eval(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let scope = make_scope(vm, globals, locals)?; // Determine code object: - let code_obj = if objtype::isinstance(source, &vm.ctx.code_type()) { - source.clone() + let code_obj = if let Ok(code_obj) = PyCodeRef::try_from_object(vm, source.clone()) { + code_obj } else if objtype::isinstance(source, &vm.ctx.str_type()) { let mode = compile::Mode::Eval; let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - compile::compile(&source, &mode, "".to_string(), vm.ctx.code_type()).map_err( - |err| { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - vm.new_exception(syntax_error, err.to_string()) - }, - )? + compile::compile(vm, &source, &mode, "".to_string()).map_err(|err| { + let syntax_error = vm.context().exceptions.syntax_error.clone(); + vm.new_exception(syntax_error, err.to_string()) + })? } else { return Err(vm.new_type_error("code argument must be str or code object".to_string())); }; // Run the source: - vm.run_code_obj(code_obj.clone(), scope) + vm.run_code_obj(code_obj, scope) } /// Implements `exec` @@ -229,14 +221,12 @@ fn builtin_exec(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - compile::compile(&source, &mode, "".to_string(), vm.ctx.code_type()).map_err( - |err| { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - vm.new_exception(syntax_error, err.to_string()) - }, - )? - } else if objtype::isinstance(source, &vm.ctx.code_type()) { - source.clone() + compile::compile(vm, &source, &mode, "".to_string()).map_err(|err| { + let syntax_error = vm.context().exceptions.syntax_error.clone(); + vm.new_exception(syntax_error, err.to_string()) + })? + } else if let Ok(code_obj) = PyCodeRef::try_from_object(vm, source.clone()) { + code_obj } else { return Err(vm.new_type_error("source argument must be str or code object".to_string())); }; diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 46101cdf46..00bafe7278 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -8,8 +8,9 @@ use crate::bytecode::{self, CallType, CodeObject, Instruction, Varargs}; use crate::error::CompileError; use crate::obj::objcode; -use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyObject, PyObjectRef}; +use crate::obj::objcode::PyCodeRef; +use crate::pyobject::PyValue; +use crate::VirtualMachine; use num_complex::Complex64; use rustpython_parser::{ast, parser}; @@ -24,11 +25,11 @@ struct Compiler { /// Compile a given sourcecode into a bytecode object. pub fn compile( + vm: &VirtualMachine, source: &str, mode: &Mode, source_path: String, - code_type: PyClassRef, -) -> Result { +) -> Result { let mut compiler = Compiler::new(); compiler.source_path = Some(source_path); compiler.push_new_code_object("".to_string()); @@ -50,10 +51,7 @@ pub fn compile( let code = compiler.pop_code_object(); trace!("Compilation completed: {:?}", code); - Ok(PyObject::new( - objcode::PyCode::new(code), - code_type.into_object(), - )) + Ok(objcode::PyCode::new(code).into_ref(vm)) } pub enum Mode { diff --git a/vm/src/eval.rs b/vm/src/eval.rs index 202286eb13..6c7b3ca549 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -8,12 +8,7 @@ use crate::pyobject::PyResult; use crate::vm::VirtualMachine; pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str) -> PyResult { - match compile::compile( - source, - &compile::Mode::Eval, - source_path.to_string(), - vm.ctx.code_type(), - ) { + match compile::compile(vm, source, &compile::Mode::Eval, source_path.to_string()) { Ok(bytecode) => { debug!("Code object: {:?}", bytecode); vm.run_code_obj(bytecode, scope) diff --git a/vm/src/function.rs b/vm/src/function.rs index a7581d3f25..17e7678233 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -305,6 +305,13 @@ pub enum OptionalArg { } impl OptionalArg { + pub fn is_present(&self) -> bool { + match self { + Present(_) => true, + Missing => false, + } + } + pub fn into_option(self) -> Option { match self { Present(value) => Some(value), diff --git a/vm/src/import.rs b/vm/src/import.rs index ed5a6d094b..936b896805 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -27,10 +27,10 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s let source = util::read_file(file_path.as_path()) .map_err(|e| vm.new_exception(import_error.clone(), e.description().to_string()))?; let code_obj = compile::compile( + vm, &source, &compile::Mode::Exec, file_path.to_str().unwrap().to_string(), - vm.ctx.code_type(), ) .map_err(|err| { let syntax_error = vm.context().exceptions.syntax_error.clone(); diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index fda5603f00..7fe54bf513 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -7,9 +7,11 @@ use std::fmt; use crate::bytecode; use crate::function::PyFuncArgs; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; +pub type PyCodeRef = PyRef; + pub struct PyCode { code: bytecode::CodeObject, } diff --git a/vm/src/obj/objfilter.rs b/vm/src/obj/objfilter.rs index 6fcf84fb4d..e547d2c43e 100644 --- a/vm/src/obj/objfilter.rs +++ b/vm/src/obj/objfilter.rs @@ -1,13 +1,13 @@ use crate::function::PyFuncArgs; -use crate::pyobject::{ - IdProtocol, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, -}; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; // Required for arg_check! to use isinstance use super::objbool; use super::objiter; use crate::obj::objtype::PyClassRef; +pub type PyFilterRef = PyRef; + #[derive(Debug)] pub struct PyFilter { predicate: PyObjectRef, @@ -20,20 +20,19 @@ impl PyValue for PyFilter { } } -fn filter_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, None), (function, None), (iterable, None)] - ); - let iterator = objiter::get_iter(vm, iterable)?; - Ok(PyObject::new( - PyFilter { - predicate: function.clone(), - iterator, - }, - cls.clone(), - )) +fn filter_new( + cls: PyClassRef, + function: PyObjectRef, + iterable: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + let iterator = objiter::get_iter(vm, &iterable)?; + + PyFilter { + predicate: function.clone(), + iterator, + } + .into_ref_with_type(vm, cls) } fn filter_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 1abeabba2a..958d368a89 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -4,7 +4,7 @@ use super::objstr; use super::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - IntoPyObject, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; use num_bigint::ToBigInt; @@ -155,7 +155,7 @@ impl PyFloatRef { } } - fn new_float(cls: PyObjectRef, arg: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn new_float(cls: PyClassRef, arg: PyObjectRef, vm: &VirtualMachine) -> PyResult { let value = if objtype::isinstance(&arg, &vm.ctx.float_type()) { get_value(&arg) } else if objtype::isinstance(&arg, &vm.ctx.int_type()) { @@ -193,7 +193,7 @@ impl PyFloatRef { let type_name = objtype::get_type_name(&arg.typ()); return Err(vm.new_type_error(format!("can't convert {} to float", type_name))); }; - Ok(PyObject::new(PyFloat { value }, cls.clone())) + PyFloat { value }.into_ref_with_type(vm, cls) } fn mod_(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index c5926c4abd..7f53fb5ae6 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -1,8 +1,9 @@ -use crate::function::PyFuncArgs; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; +pub type PyMemoryViewRef = PyRef; + #[derive(Debug)] pub struct PyMemoryView { obj: PyObjectRef, @@ -14,15 +15,13 @@ impl PyValue for PyMemoryView { } } -pub fn new_memory_view(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(cls, None), (bytes_object, None)]); - vm.ctx.set_attr(cls, "obj", bytes_object.clone()); - Ok(PyObject::new( - PyMemoryView { - obj: bytes_object.clone(), - }, - cls.clone(), - )) +pub fn new_memory_view( + cls: PyClassRef, + bytes_object: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + vm.ctx.set_attr(&cls, "obj", bytes_object.clone()); + PyMemoryView { obj: bytes_object }.into_ref_with_type(vm, cls) } pub fn init(ctx: &PyContext) { diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index e0ff9b20cd..84ba73fe68 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -7,7 +7,7 @@ use crate::obj::objproperty::PropertyBuilder; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ DictProtocol, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyResult, PyValue, - TypeProtocol, + TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -22,7 +22,7 @@ impl PyValue for PyInstance { pub fn new_instance(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { // more or less __new__ operator - let cls = args.shift(); + let cls = PyClassRef::try_from_object(vm, args.shift())?; Ok(if cls.is(&vm.ctx.object) { PyObject::new_without_dict(PyInstance, cls) } else { diff --git a/vm/src/obj/objproperty.rs b/vm/src/obj/objproperty.rs index 7a9dd8447e..c5459a9fb2 100644 --- a/vm/src/obj/objproperty.rs +++ b/vm/src/obj/objproperty.rs @@ -200,7 +200,7 @@ impl<'a> PropertyBuilder<'a> { deleter: None, }; - PyObject::new(payload, self.ctx.property_type().into_object()) + PyObject::new(payload, self.ctx.property_type()) } else { let payload = PyReadOnlyProperty { getter: self.getter.expect( @@ -208,7 +208,7 @@ impl<'a> PropertyBuilder<'a> { ), }; - PyObject::new(payload, self.ctx.readonly_property_type().into_object()) + PyObject::new(payload, self.ctx.readonly_property_type()) } } } diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index ad9c7bf361..83d0f9f9f8 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -163,12 +163,12 @@ pub fn get_item( if sequence.payload::().is_some() { Ok(PyObject::new( PyList::from(elements.to_vec().get_slice_items(vm, &subscript)?), - sequence.typ(), + sequence.type_pyref(), )) } else if sequence.payload::().is_some() { Ok(PyObject::new( PyTuple::from(elements.to_vec().get_slice_items(vm, &subscript)?), - sequence.typ(), + sequence.type_pyref(), )) } else { panic!("sequence get_item called for non-sequence") diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index 4a7124fdfd..9df435290f 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -1,7 +1,9 @@ use num_bigint::BigInt; use crate::function::PyFuncArgs; -use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{ + FromPyObjectRef, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, +}; use crate::vm::VirtualMachine; use super::objint; @@ -61,7 +63,7 @@ fn slice_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { stop: stop.map(|x| objint::get_value(x).clone()), step: step.map(|x| objint::get_value(x).clone()), }, - cls.clone(), + PyClassRef::from_pyobj(&cls), )) } diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 81f04603bb..1b53226c9d 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -241,11 +241,12 @@ pub fn type_new_class( bases.push(vm.ctx.object()); let name = objstr::get_value(name); new( - typ.clone(), + PyClassRef::from_pyobj(typ), &name, bases, objdict::py_dict_to_attributes(dict), ) + .map(|x| x.into_object()) } pub fn type_call(class: PyClassRef, args: Args, kwargs: KwArgs, vm: &VirtualMachine) -> PyResult { @@ -365,14 +366,14 @@ fn linearise_mro(mut bases: Vec>) -> Option> { } pub fn new( - typ: PyObjectRef, + typ: PyClassRef, name: &str, bases: Vec, dict: HashMap, -) -> PyResult { +) -> PyResult { let mros = bases.into_iter().map(|x| _mro(&x)).collect(); let mro = linearise_mro(mros).unwrap(); - Ok(PyObject { + let new_type = PyObject { payload: PyClass { name: String::from(name), mro, @@ -380,7 +381,8 @@ pub fn new( dict: Some(RefCell::new(dict)), typ, } - .into_ref()) + .into_ref(); + Ok(PyClassRef::from_pyobj(&new_type)) } #[cfg(test)] @@ -401,23 +403,8 @@ mod tests { let object: PyClassRef = context.object.clone(); let type_type = &context.type_type; - let a = new( - type_type.clone().into_object(), - "A", - vec![object.clone()], - HashMap::new(), - ) - .unwrap(); - let b = new( - type_type.clone().into_object(), - "B", - vec![object.clone()], - HashMap::new(), - ) - .unwrap(); - - let a: PyClassRef = a.downcast().unwrap(); - let b: PyClassRef = b.downcast().unwrap(); + let a = new(type_type.clone(), "A", vec![object.clone()], HashMap::new()).unwrap(); + let b = new(type_type.clone(), "B", vec![object.clone()], HashMap::new()).unwrap(); assert_eq!( map_ids(linearise_mro(vec![ diff --git a/vm/src/obj/objzip.rs b/vm/src/obj/objzip.rs index 63df96f62f..e628cb218a 100644 --- a/vm/src/obj/objzip.rs +++ b/vm/src/obj/objzip.rs @@ -1,10 +1,12 @@ -use crate::function::PyFuncArgs; -use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::function::{Args, PyFuncArgs}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; use super::objiter; use crate::obj::objtype::PyClassRef; +pub type PyZipRef = PyRef; + #[derive(Debug)] pub struct PyZip { iterators: Vec, @@ -16,15 +18,12 @@ impl PyValue for PyZip { } } -fn zip_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - no_kwargs!(vm, args); - let cls = &args.args[0]; - let iterables = &args.args[1..]; +fn zip_new(cls: PyClassRef, iterables: Args, vm: &VirtualMachine) -> PyResult { let iterators = iterables - .iter() - .map(|iterable| objiter::get_iter(vm, iterable)) + .into_iter() + .map(|iterable| objiter::get_iter(vm, &iterable)) .collect::, _>>()?; - Ok(PyObject::new(PyZip { iterators }, cls.clone())) + PyZip { iterators }.into_ref_with_type(vm, cls) } fn zip_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 41a1ca6f34..deeb4f370c 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -152,14 +152,7 @@ pub struct PyContext { pub fn create_type(name: &str, type_type: &PyClassRef, base: &PyClassRef) -> PyClassRef { let dict = PyAttributes::new(); - let new_type = objtype::new( - type_type.clone().into_object(), - name, - vec![base.clone()], - dict, - ) - .unwrap(); - new_type.downcast().unwrap() + objtype::new(type_type.clone(), name, vec![base.clone()], dict).unwrap() } pub type PyNotImplementedRef = PyRef; @@ -212,13 +205,14 @@ fn init_type_hierarchy() -> (PyClassRef, PyClassRef) { let object_type_ptr = PyObjectRef::into_raw(object_type.clone()) as *mut PyObject; let type_type_ptr = PyObjectRef::into_raw(type_type.clone()) as *mut PyObject; + + let type_type = PyClassRef::from_pyobj(&type_type); + let object_type = PyClassRef::from_pyobj(&object_type); + ptr::write(&mut (*object_type_ptr).typ, type_type.clone()); ptr::write(&mut (*type_type_ptr).typ, type_type.clone()); - ( - type_type.downcast().unwrap(), - object_type.downcast().unwrap(), - ) + (type_type, object_type) } } @@ -265,7 +259,7 @@ impl PyContext { fn create_object(payload: T, cls: &PyClassRef) -> PyRef { PyRef { - obj: PyObject::new(payload, cls.clone().into_object()), + obj: PyObject::new(payload, cls.clone()), _payload: PhantomData, } } @@ -520,33 +514,27 @@ impl PyContext { } pub fn new_int>(&self, i: T) -> PyObjectRef { - PyObject::new(PyInt::new(i), self.int_type().into_object()) + PyObject::new(PyInt::new(i), self.int_type()) } pub fn new_float(&self, value: f64) -> PyObjectRef { - PyObject::new(PyFloat::from(value), self.float_type().into_object()) + PyObject::new(PyFloat::from(value), self.float_type()) } pub fn new_complex(&self, value: Complex64) -> PyObjectRef { - PyObject::new(PyComplex::from(value), self.complex_type().into_object()) + PyObject::new(PyComplex::from(value), self.complex_type()) } pub fn new_str(&self, s: String) -> PyObjectRef { - PyObject::new(objstr::PyString { value: s }, self.str_type().into_object()) + PyObject::new(objstr::PyString { value: s }, self.str_type()) } pub fn new_bytes(&self, data: Vec) -> PyObjectRef { - PyObject::new( - objbytes::PyBytes::new(data), - self.bytes_type().into_object(), - ) + PyObject::new(objbytes::PyBytes::new(data), self.bytes_type()) } pub fn new_bytearray(&self, data: Vec) -> PyObjectRef { - PyObject::new( - objbytearray::PyByteArray::new(data), - self.bytearray_type().into_object(), - ) + PyObject::new(objbytearray::PyByteArray::new(data), self.bytearray_type()) } pub fn new_bool(&self, b: bool) -> PyObjectRef { @@ -558,32 +546,25 @@ impl PyContext { } pub fn new_tuple(&self, elements: Vec) -> PyObjectRef { - PyObject::new(PyTuple::from(elements), self.tuple_type().into_object()) + PyObject::new(PyTuple::from(elements), self.tuple_type()) } pub fn new_list(&self, elements: Vec) -> PyObjectRef { - PyObject::new(PyList::from(elements), self.list_type().into_object()) + PyObject::new(PyList::from(elements), self.list_type()) } pub fn new_set(&self) -> PyObjectRef { // Initialized empty, as calling __hash__ is required for adding each object to the set // which requires a VM context - this is done in the objset code itself. - PyObject::new(PySet::default(), self.set_type().into_object()) + PyObject::new(PySet::default(), self.set_type()) } pub fn new_dict(&self) -> PyObjectRef { - PyObject::new(PyDict::default(), self.dict_type().into_object()) + PyObject::new(PyDict::default(), self.dict_type()) } pub fn new_class(&self, name: &str, base: PyClassRef) -> PyClassRef { - let typ = objtype::new( - self.type_type().into_object(), - name, - vec![base], - PyAttributes::new(), - ) - .unwrap(); - typ.downcast().unwrap() + objtype::new(self.type_type(), name, vec![base], PyAttributes::new()).unwrap() } pub fn new_scope(&self) -> Scope { @@ -596,7 +577,7 @@ impl PyContext { name: name.to_string(), dict, }, - self.module_type.clone().into_object(), + self.module_type.clone(), ) } @@ -606,12 +587,12 @@ impl PyContext { { PyObject::new( PyBuiltinFunction::new(f.into_func()), - self.builtin_function_or_method_type().into_object(), + self.builtin_function_or_method_type(), ) } pub fn new_frame(&self, code: PyObjectRef, scope: Scope) -> PyObjectRef { - PyObject::new(Frame::new(code, scope), self.frame_type().into_object()) + PyObject::new(Frame::new(code, scope), self.frame_type()) } pub fn new_property(&self, f: F) -> PyObjectRef @@ -622,7 +603,7 @@ impl PyContext { } pub fn new_code_object(&self, code: bytecode::CodeObject) -> PyObjectRef { - PyObject::new(objcode::PyCode::new(code), self.code_type().into_object()) + PyObject::new(objcode::PyCode::new(code), self.code_type()) } pub fn new_function( @@ -633,21 +614,18 @@ impl PyContext { ) -> PyObjectRef { PyObject::new( PyFunction::new(code_obj, scope, defaults), - self.function_type().into_object(), + self.function_type(), ) } pub fn new_bound_method(&self, function: PyObjectRef, object: PyObjectRef) -> PyObjectRef { - PyObject::new( - PyMethod::new(object, function), - self.bound_method_type().into_object(), - ) + PyObject::new(PyMethod::new(object, function), self.bound_method_type()) } pub fn new_instance(&self, class: PyClassRef, dict: Option) -> PyObjectRef { let dict = dict.unwrap_or_default(); PyObject { - typ: class.into_object(), + typ: class, dict: Some(RefCell::new(dict)), payload: objobject::PyInstance, } @@ -716,7 +694,7 @@ pub struct PyObject where T: ?Sized + PyObjectPayload, { - pub typ: PyObjectRef, + pub typ: PyClassRef, pub dict: Option>, // __dict__ member pub payload: T, } @@ -896,7 +874,7 @@ where T: ?Sized + PyObjectPayload, { fn type_ref(&self) -> &PyObjectRef { - &self.typ + self.typ.as_object() } } @@ -1124,7 +1102,7 @@ where T: PyValue + Sized, { fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new(self, T::class(vm).into_object())) + Ok(PyObject::new(self, T::class(vm))) } } @@ -1146,7 +1124,7 @@ impl PyObject where T: Sized + PyObjectPayload, { - pub fn new(payload: T, typ: PyObjectRef) -> PyObjectRef { + pub fn new(payload: T, typ: PyClassRef) -> PyObjectRef { PyObject { typ, dict: Some(RefCell::new(PyAttributes::new())), @@ -1155,7 +1133,7 @@ where .into_ref() } - pub fn new_without_dict(payload: T, typ: PyObjectRef) -> PyObjectRef { + pub fn new_without_dict(payload: T, typ: PyClassRef) -> PyObjectRef { PyObject { typ, dict: None, @@ -1187,7 +1165,7 @@ pub trait PyValue: fmt::Debug + Sized + 'static { fn into_ref(self, vm: &VirtualMachine) -> PyRef { PyRef { - obj: PyObject::new(self, Self::class(vm).into_object()), + obj: PyObject::new(self, Self::class(vm)), _payload: PhantomData, } } @@ -1196,7 +1174,7 @@ pub trait PyValue: fmt::Debug + Sized + 'static { let class = Self::class(vm); if objtype::issubclass(&cls, &class) { Ok(PyRef { - obj: PyObject::new(self, cls.obj), + obj: PyObject::new(self, cls), _payload: PhantomData, }) } else { diff --git a/vm/src/stdlib/re.rs b/vm/src/stdlib/re.rs index 57db3f0a80..4dab1b0c3f 100644 --- a/vm/src/stdlib/re.rs +++ b/vm/src/stdlib/re.rs @@ -4,16 +4,13 @@ * This module fits the python re interface onto the rust regular expression * system. */ - -use std::path::PathBuf; - use regex::{Match, Regex}; use crate::function::PyFuncArgs; -use crate::import; use crate::obj::objstr; +use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; impl PyValue for Regex { @@ -55,7 +52,8 @@ fn re_match(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { (string, Some(vm.ctx.str_type())) ] ); - let regex = make_regex(vm, pattern)?; + let pattern_str = objstr::get_value(&pattern); + let regex = make_regex(vm, &pattern_str)?; let search_text = objstr::get_value(string); do_match(vm, ®ex, search_text) @@ -74,8 +72,8 @@ fn re_search(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { ] ); - // let pattern_str = objstr::get_value(&pattern); - let regex = make_regex(vm, pattern)?; + let pattern_str = objstr::get_value(&pattern); + let regex = make_regex(vm, &pattern_str)?; let search_text = objstr::get_value(string); do_search(vm, ®ex, search_text) @@ -93,10 +91,8 @@ fn do_search(vm: &VirtualMachine, regex: &Regex, search_text: String) -> PyResul } } -fn make_regex(vm: &VirtualMachine, pattern: &PyObjectRef) -> PyResult { - let pattern_str = objstr::get_value(pattern); - - match Regex::new(&pattern_str) { +fn make_regex(vm: &VirtualMachine, pattern: &str) -> PyResult { + match Regex::new(pattern) { Ok(regex) => Ok(regex), Err(err) => Err(vm.new_value_error(format!("Error in regex: {:?}", err))), } @@ -117,39 +113,24 @@ impl PyValue for PyMatch { /// Take a found regular expression and convert it to proper match object. fn create_match(vm: &VirtualMachine, match_value: &Match) -> PyResult { - // Return match object: - // TODO: implement match object - // TODO: how to refer to match object defined in this - let module = import::import_module(vm, PathBuf::default(), "re").unwrap(); - let match_class = vm.get_attribute(module, "Match").unwrap(); - // let mo = vm.invoke(match_class, PyFuncArgs::default())?; // let txt = vm.ctx.new_str(result.as_str().to_string()); // vm.ctx.set_attr(&mo, "str", txt); - let match_value = PyMatch { + Ok(PyMatch { start: match_value.start(), end: match_value.end(), - }; - - Ok(PyObject::new(match_value, match_class.clone())) + } + .into_ref(vm) + .into_object()) } /// Compile a regular expression into a Pattern object. /// See also: /// https://docs.python.org/3/library/re.html#re.compile -fn re_compile(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(pattern, Some(vm.ctx.str_type()))] // TODO: flags=0 - ); - - let regex = make_regex(vm, pattern)?; - // TODO: retrieval of this module is akward: - let module = import::import_module(vm, PathBuf::default(), "re").unwrap(); - let pattern_class = vm.get_attribute(module, "Pattern").unwrap(); +fn re_compile(pattern: PyStringRef, vm: &VirtualMachine) -> PyResult> { + let regex = make_regex(vm, &pattern.value)?; - Ok(PyObject::new(regex, pattern_class.clone())) + Ok(regex.into_ref(vm)) } fn pattern_match(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 4a3cc3abc0..cde574fbcd 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -19,6 +19,7 @@ use crate::function::PyFuncArgs; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode; +use crate::obj::objcode::PyCodeRef; use crate::obj::objframe; use crate::obj::objfunction::{PyFunction, PyMethod}; use crate::obj::objgenerator; @@ -72,8 +73,8 @@ impl VirtualMachine { } } - pub fn run_code_obj(&self, code: PyObjectRef, scope: Scope) -> PyResult { - let frame = self.ctx.new_frame(code, scope); + pub fn run_code_obj(&self, code: PyCodeRef, scope: Scope) -> PyResult { + let frame = self.ctx.new_frame(code.into_object(), scope); self.run_frame_full(frame) } diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index fd2181182f..2d20bc37a5 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -285,8 +285,7 @@ impl WASMVirtualMachine { ref vm, ref scope, .. }| { source.push('\n'); - let code = - compile::compile(&source, &mode, "".to_string(), vm.ctx.code_type()); + let code = compile::compile(vm, &source, &mode, "".to_string()); let code = code.map_err(|err| { let js_err = SyntaxError::new(&format!("Error parsing Python code: {}", err)); if let rustpython_vm::error::CompileError::Parse(ref parse_error) = err { From 00540dec35c130a0aeff0841728cbe26c85bd55b Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 24 Mar 2019 08:12:07 +1300 Subject: [PATCH 039/884] Fix wasm, to reflect that PyObject.typ is now a PyClassRef --- wasm/lib/src/browser_module.rs | 72 ++++++++++++---------------------- wasm/lib/src/convert.rs | 8 ++-- 2 files changed, 31 insertions(+), 49 deletions(-) diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 710b18e639..a48cf3d342 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -7,12 +7,12 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::{future_to_promise, JsFuture}; -use rustpython_vm::function::PyFuncArgs; +use rustpython_vm::function::{OptionalArg, PyFuncArgs}; use rustpython_vm::import::import_module; use rustpython_vm::obj::objtype::PyClassRef; use rustpython_vm::obj::{objint, objstr}; use rustpython_vm::pyobject::{ - PyContext, PyObject, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, + PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use rustpython_vm::VirtualMachine; @@ -45,8 +45,6 @@ impl FetchResponseFormat { fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(url, Some(vm.ctx.str_type()))]); - let promise_type = import_promise_type(vm)?; - let response_format = args.get_optional_kwarg_with_type("response_format", vm.ctx.str_type(), vm)?; let method = args.get_optional_kwarg_with_type("method", vm.ctx.str_type(), vm)?; @@ -102,7 +100,7 @@ fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { }) .and_then(JsFuture::from); - Ok(PyPromise::new_obj(promise_type, future_to_promise(future))) + Ok(PyPromise::from_future(future).into_ref(vm).into_object()) } fn browser_request_animation_frame(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -159,6 +157,8 @@ fn browser_cancel_animation_frame(vm: &VirtualMachine, args: PyFuncArgs) -> PyRe Ok(vm.get_none()) } +pub type PyPromiseRef = PyRef; + #[derive(Debug)] pub struct PyPromise { value: Promise, @@ -171,8 +171,15 @@ impl PyValue for PyPromise { } impl PyPromise { - pub fn new_obj(promise_type: PyClassRef, value: Promise) -> PyObjectRef { - PyObject::new(PyPromise { value }, promise_type.into_object()) + pub fn new(promise: Promise) -> PyPromise { + PyPromise { value: promise } + } + + pub fn from_future(future: F) -> PyPromise + where + F: Future + 'static, + { + PyPromise::new(future_to_promise(future)) } } @@ -193,26 +200,17 @@ pub fn import_promise_type(vm: &VirtualMachine) -> PyResult { } } -fn promise_then(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let promise_type = import_promise_type(vm)?; - arg_check!( - vm, - args, - required = [ - (zelf, Some(promise_type.clone())), - (on_fulfill, Some(vm.ctx.function_type())) - ], - optional = [(on_reject, Some(vm.ctx.function_type()))] - ); - - let on_fulfill = on_fulfill.clone(); - let on_reject = on_reject.cloned(); +fn promise_then( + zelf: PyPromiseRef, + on_fulfill: PyObjectRef, + on_reject: OptionalArg, + vm: &VirtualMachine, +) -> PyPromise { + let on_reject = on_reject.into_option(); let acc_vm = AccessibleVM::from(vm); - let promise = get_promise_value(zelf); - - let ret_future = JsFuture::from(promise).then(move |res| { + let ret_future = JsFuture::from(zelf.value.clone()).then(move |res| { let stored_vm = &acc_vm .upgrade() .expect("that the vm is valid when the promise resolves"); @@ -234,29 +232,13 @@ fn promise_then(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { convert::pyresult_to_jsresult(vm, ret) }); - let ret_promise = future_to_promise(ret_future); - - Ok(PyPromise::new_obj(promise_type, ret_promise)) + PyPromise::from_future(ret_future) } -fn promise_catch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let promise_type = import_promise_type(vm)?; - arg_check!( - vm, - args, - required = [ - (zelf, Some(promise_type.clone())), - (on_reject, Some(vm.ctx.function_type())) - ] - ); - - let on_reject = on_reject.clone(); - +fn promise_catch(zelf: PyPromiseRef, on_reject: PyObjectRef, vm: &VirtualMachine) -> PyPromise { let acc_vm = AccessibleVM::from(vm); - let promise = get_promise_value(zelf); - - let ret_future = JsFuture::from(promise).then(move |res| match res { + let ret_future = JsFuture::from(zelf.value.clone()).then(move |res| match res { Ok(val) => Ok(val), Err(err) => { let stored_vm = acc_vm @@ -269,9 +251,7 @@ fn promise_catch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } }); - let ret_promise = future_to_promise(ret_future); - - Ok(PyPromise::new_obj(promise_type, ret_promise)) + PyPromise::from_future(ret_future) } fn browser_alert(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index c7ba1c6161..41f43778fa 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -4,7 +4,7 @@ use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; use rustpython_vm::function::PyFuncArgs; use rustpython_vm::obj::{objbytes, objint, objsequence, objtype}; -use rustpython_vm::pyobject::{DictProtocol, PyObjectRef, PyResult}; +use rustpython_vm::pyobject::{DictProtocol, PyObjectRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; use crate::browser_module; @@ -159,8 +159,10 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { if js_val.is_object() { if let Some(promise) = js_val.dyn_ref::() { // the browser module might not be injected - if let Ok(promise_type) = browser_module::import_promise_type(vm) { - return browser_module::PyPromise::new_obj(promise_type, promise.clone()); + if let Ok(_) = browser_module::import_promise_type(vm) { + return browser_module::PyPromise::new(promise.clone()) + .into_ref(vm) + .into_object(); } } if Array::is_array(&js_val) { From faf1925a25f64c4158942ded1e4db5253bc3bd44 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 24 Mar 2019 09:08:03 +1300 Subject: [PATCH 040/884] Remove usages of PyClassRef::from_pyobj --- vm/src/obj/objslice.rs | 19 ++++++++----------- vm/src/obj/objtype.rs | 4 ++-- vm/src/pyobject.rs | 4 ++-- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index 9df435290f..d68ae2255f 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -1,9 +1,7 @@ use num_bigint::BigInt; use crate::function::PyFuncArgs; -use crate::pyobject::{ - FromPyObjectRef, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol, -}; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; use super::objint; @@ -57,14 +55,13 @@ fn slice_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok((cls, Some(start), Some(stop), step)) } }?; - Ok(PyObject::new( - PySlice { - start: start.map(|x| objint::get_value(x).clone()), - stop: stop.map(|x| objint::get_value(x).clone()), - step: step.map(|x| objint::get_value(x).clone()), - }, - PyClassRef::from_pyobj(&cls), - )) + PySlice { + start: start.map(|x| objint::get_value(x).clone()), + stop: stop.map(|x| objint::get_value(x).clone()), + step: step.map(|x| objint::get_value(x).clone()), + } + .into_ref_with_type(vm, cls.clone().downcast().unwrap()) + .map(|x| x.into_object()) } fn get_property_value(vm: &VirtualMachine, value: &Option) -> PyResult { diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 1b53226c9d..b48ba99413 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -241,7 +241,7 @@ pub fn type_new_class( bases.push(vm.ctx.object()); let name = objstr::get_value(name); new( - PyClassRef::from_pyobj(typ), + typ.clone().downcast().unwrap(), &name, bases, objdict::py_dict_to_attributes(dict), @@ -382,7 +382,7 @@ pub fn new( typ, } .into_ref(); - Ok(PyClassRef::from_pyobj(&new_type)) + Ok(new_type.downcast().unwrap()) } #[cfg(test)] diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index deeb4f370c..80eba36da3 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -206,8 +206,8 @@ fn init_type_hierarchy() -> (PyClassRef, PyClassRef) { let object_type_ptr = PyObjectRef::into_raw(object_type.clone()) as *mut PyObject; let type_type_ptr = PyObjectRef::into_raw(type_type.clone()) as *mut PyObject; - let type_type = PyClassRef::from_pyobj(&type_type); - let object_type = PyClassRef::from_pyobj(&object_type); + let type_type: PyClassRef = type_type.downcast().unwrap(); + let object_type: PyClassRef = object_type.downcast().unwrap(); ptr::write(&mut (*object_type_ptr).typ, type_type.clone()); ptr::write(&mut (*type_type_ptr).typ, type_type.clone()); From a5558e0e32971c8b6c2a5754250e9a81152e31fa Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 23 Mar 2019 15:05:12 -0700 Subject: [PATCH 041/884] Introduce Either extractor and convert range.__getitem__ --- vm/src/obj/objrange.rs | 115 ++++++++++++++++++----------------------- vm/src/obj/objslice.rs | 4 +- vm/src/pyobject.rs | 44 ++++++++++++++++ 3 files changed, 98 insertions(+), 65 deletions(-) diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index db4ded48dd..8d526227a3 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -7,14 +7,13 @@ use num_traits::{One, Signed, ToPrimitive, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + Either2, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; use super::objint::{self, PyInt, PyIntRef}; -use super::objslice::PySlice; -use super::objtype; -use super::objtype::PyClassRef; +use super::objslice::PySliceRef; +use super::objtype::{self, PyClassRef}; #[derive(Debug, Clone)] pub struct PyRange { @@ -169,7 +168,7 @@ pub fn init(context: &PyContext) { "__bool__" => context.new_rustfunc(range_bool), "__contains__" => context.new_rustfunc(range_contains), "__doc__" => context.new_str(range_doc.to_string()), - "__getitem__" => context.new_rustfunc(range_getitem), + "__getitem__" => context.new_rustfunc(PyRangeRef::getitem), "__iter__" => context.new_rustfunc(range_iter), "__len__" => context.new_rustfunc(range_len), "__new__" => context.new_rustfunc(range_new), @@ -212,6 +211,53 @@ impl PyRangeRef { } .into_ref_with_type(vm, cls) } + + fn getitem(self, subscript: Either2, vm: &VirtualMachine) -> PyResult { + match subscript { + Either2::A(index) => { + if let Some(value) = self.get(index.value.clone()) { + Ok(PyInt::new(value).into_ref(vm).into_object()) + } else { + Err(vm.new_index_error("range object index out of range".to_string())) + } + } + Either2::B(slice) => { + let new_start = if let Some(int) = slice.start.clone() { + if let Some(i) = self.get(int) { + i + } else { + self.start.clone() + } + } else { + self.start.clone() + }; + + let new_end = if let Some(int) = slice.stop.clone() { + if let Some(i) = self.get(int) { + i + } else { + self.stop.clone() + } + } else { + self.stop.clone() + }; + + let new_step = if let Some(int) = slice.step.clone() { + int * self.step.clone() + } else { + self.step.clone() + }; + + Ok(PyRange { + start: new_start, + stop: new_end, + step: new_step, + } + .into_ref(vm) + .into_object()) + } + } + } } fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -252,65 +298,6 @@ fn range_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn range_getitem(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.range_type())), (subscript, None)] - ); - - let range = get_value(zelf); - - if let Some(i) = subscript.payload::() { - if let Some(int) = range.get(i.value.clone()) { - Ok(vm.ctx.new_int(int)) - } else { - Err(vm.new_index_error("range object index out of range".to_string())) - } - } else if let Some(PySlice { - ref start, - ref stop, - ref step, - }) = subscript.payload() - { - let new_start = if let Some(int) = start { - if let Some(i) = range.get(int) { - i - } else { - range.start.clone() - } - } else { - range.start.clone() - }; - - let new_end = if let Some(int) = stop { - if let Some(i) = range.get(int) { - i - } else { - range.stop - } - } else { - range.stop - }; - - let new_step = if let Some(int) = step { - int * range.step - } else { - range.step - }; - - Ok(PyRange { - start: new_start, - stop: new_end, - step: new_step, - } - .into_ref(vm) - .into_object()) - } else { - Err(vm.new_type_error("range indices must be integer or slice".to_string())) - } -} - fn range_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index 4a7124fdfd..d171b2527b 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -1,7 +1,7 @@ use num_bigint::BigInt; use crate::function::PyFuncArgs; -use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; use super::objint; @@ -21,6 +21,8 @@ impl PyValue for PySlice { } } +pub type PySliceRef = PyRef; + fn slice_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { no_kwargs!(vm, args); let (cls, start, stop, step): ( diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 41a1ca6f34..a90a4ebc08 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1218,6 +1218,50 @@ impl PyObjectPayload for T { } } +pub enum Either2 { + A(A), + B(B), +} + +/// This allows a builtin method to accept arguments that may be one of two +/// types, raising a `TypeError` if it is neither. +/// +/// # Example +/// +/// ``` +/// fn do_something(arg: Either2, vm: &VirtualMachine) { +/// match arg { +/// Either2::A(int)=> { +/// // do something with int +/// } +/// Either2::B(string) => { +/// // do something with string +/// } +/// } +/// } +/// ``` +impl TryFromObject for Either2, PyRef> +where + A: PyValue, + B: PyValue, +{ + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + // TODO: downcast could probably be reworked a bit to make these clones not necessary + obj.clone() + .downcast::() + .map(Either2::A) + .or_else(|| obj.clone().downcast::().map(Either2::B)) + .ok_or_else(|| { + vm.new_type_error(format!( + "must be {} or {}, not {}", + A::class(vm), + B::class(vm), + obj.type_pyref() + )) + }) + } +} + #[cfg(test)] mod tests { use super::*; From b6f1ecdb4be116d6242ed05541725adedf9a175c Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 23 Mar 2019 15:49:31 -0700 Subject: [PATCH 042/884] Fix example --- vm/src/pyobject.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index a90a4ebc08..40a2d56e25 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1229,6 +1229,10 @@ pub enum Either2 { /// # Example /// /// ``` +/// use rustpython_vm::VirtualMachine; +/// use rustpython_vm::obj::{objstr::PyStringRef, objint::PyIntRef}; +/// use rustpython_vm::pyobject::Either2; +/// /// fn do_something(arg: Either2, vm: &VirtualMachine) { /// match arg { /// Either2::A(int)=> { From 3c15d892c5b99f903c5389dc1e5beb6e76b488c3 Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 23 Mar 2019 15:57:06 -0700 Subject: [PATCH 043/884] Avoid some cloning --- vm/src/pyobject.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index aec210e7a8..befa798926 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -700,16 +700,23 @@ where } impl PyObject { - pub fn downcast(self: Rc) -> Option> { + /// Attempt to downcast this reference to a subclass. + /// + /// If the downcast fails, the original ref is returned in as `Err` so + /// another downcast can be attempted without unnecessary cloning. + /// + /// Note: The returned `Result` is _not_ a `PyResult`, even though the + /// types are compatible. + pub fn downcast(self: Rc) -> Result, PyObjectRef> { if self.payload_is::() { - Some({ + Ok({ PyRef { obj: self, _payload: PhantomData, } }) } else { - None + Err(self) } } } @@ -1228,12 +1235,10 @@ where B: PyValue, { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - // TODO: downcast could probably be reworked a bit to make these clones not necessary - obj.clone() - .downcast::() + obj.downcast::() .map(Either2::A) - .or_else(|| obj.clone().downcast::().map(Either2::B)) - .ok_or_else(|| { + .or_else(|obj| obj.clone().downcast::().map(Either2::B)) + .map_err(|obj| { vm.new_type_error(format!( "must be {} or {}, not {}", A::class(vm), From dc681015771d32d3b9b3e08c12d6942f4e5591b8 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 24 Mar 2019 12:07:29 +1300 Subject: [PATCH 044/884] Refactor type_new_class to use more specific ref types --- vm/src/obj/objtype.rs | 51 +++++++++++++----------------------------- vm/src/stdlib/types.rs | 30 +++++++++++++------------ 2 files changed, 32 insertions(+), 49 deletions(-) diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index b48ba99413..9d3c9fe490 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -4,16 +4,17 @@ use std::fmt; use crate::function::{Args, KwArgs, PyFuncArgs}; use crate::pyobject::{ - IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, - TypeProtocol, + IdProtocol, PyAttributes, PyContext, PyIterable, PyObject, PyObjectRef, PyRef, PyResult, + PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; use super::objdict; use super::objlist::PyList; use super::objproperty::PropertyBuilder; -use super::objstr::{self, PyStringRef}; +use super::objstr::PyStringRef; use super::objtuple::PyTuple; +use crate::obj::objdict::PyDictRef; #[derive(Clone, Debug)] pub struct PyClass { @@ -203,24 +204,10 @@ pub fn get_type_name(typ: &PyObjectRef) -> String { pub fn type_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { debug!("type.__new__ {:?}", args); if args.args.len() == 2 { - arg_check!( - vm, - args, - required = [(_typ, Some(vm.ctx.type_type())), (obj, None)] - ); - Ok(obj.typ()) + Ok(args.args[1].typ()) } else if args.args.len() == 4 { - arg_check!( - vm, - args, - required = [ - (typ, Some(vm.ctx.type_type())), - (name, Some(vm.ctx.str_type())), - (bases, None), - (dict, Some(vm.ctx.dict_type())) - ] - ); - type_new_class(vm, typ, name, bases, dict) + let (typ, name, bases, dict) = args.bind(vm)?; + type_new_class(vm, typ, name, bases, dict).map(|x| x.into_object()) } else { Err(vm.new_type_error(format!(": type_new: {:?}", args))) } @@ -228,25 +215,19 @@ pub fn type_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn type_new_class( vm: &VirtualMachine, - typ: &PyObjectRef, - name: &PyObjectRef, - bases: &PyObjectRef, - dict: &PyObjectRef, -) -> PyResult { - let mut bases: Vec = vm - .extract_elements(bases)? - .iter() - .map(|x| x.clone().downcast().unwrap()) - .collect(); + typ: PyClassRef, + name: PyStringRef, + bases: PyIterable, + dict: PyDictRef, +) -> PyResult { + let mut bases: Vec = bases.iter(vm)?.collect::, _>>()?; bases.push(vm.ctx.object()); - let name = objstr::get_value(name); new( - typ.clone().downcast().unwrap(), - &name, + typ.clone(), + &name.value, bases, - objdict::py_dict_to_attributes(dict), + objdict::py_dict_to_attributes(dict.as_object()), ) - .map(|x| x.into_object()) } pub fn type_call(class: PyClassRef, args: Args, kwargs: KwArgs, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/stdlib/types.rs b/vm/src/stdlib/types.rs index 09aeb0dacc..8a5ab2939c 100644 --- a/vm/src/stdlib/types.rs +++ b/vm/src/stdlib/types.rs @@ -2,25 +2,27 @@ * Dynamic type creation and names for built in types. */ -use crate::function::PyFuncArgs; +use crate::function::OptionalArg; +use crate::obj::objdict::PyDict; +use crate::obj::objstr::PyStringRef; use crate::obj::objtype; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::obj::objtype::PyClassRef; +use crate::pyobject::{PyContext, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject}; use crate::VirtualMachine; -fn types_new_class(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(name, Some(vm.ctx.str_type()))], - optional = [(bases, None), (_kwds, None), (_exec_body, None)] - ); +fn types_new_class( + name: PyStringRef, + bases: OptionalArg>, + vm: &VirtualMachine, +) -> PyResult { + // TODO kwds and exec_body parameter - let bases: PyObjectRef = match bases { - Some(bases) => bases.clone(), - None => vm.ctx.new_tuple(vec![]), + let bases = match bases { + OptionalArg::Present(bases) => bases, + OptionalArg::Missing => PyIterable::try_from_object(vm, vm.ctx.new_tuple(vec![]))?, }; - let dict = vm.ctx.new_dict(); - objtype::type_new_class(vm, &vm.ctx.type_type().into_object(), name, &bases, &dict) + let dict = PyDict::default().into_ref(vm); + objtype::type_new_class(vm, vm.ctx.type_type(), name, bases, dict) } pub fn make_module(ctx: &PyContext) -> PyObjectRef { From db8e6486469b5d10e05155af1894db29e71db041 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 24 Mar 2019 13:41:37 +1300 Subject: [PATCH 045/884] Make PyFunction.code a PyCodeRef, PyGenerator.frame a FrameRef, and other improvements to increase use of specific ref types. --- vm/src/frame.rs | 12 +++--- vm/src/obj/objcode.rs | 83 +++++++++++++------------------------- vm/src/obj/objfunction.rs | 24 ++++++----- vm/src/obj/objgenerator.rs | 72 +++++++++++++-------------------- vm/src/pyobject.rs | 9 ++--- vm/src/stdlib/dis.rs | 19 ++++----- vm/src/stdlib/json.rs | 2 +- vm/src/sysmodule.rs | 32 ++++----------- vm/src/vm.rs | 35 +++++++--------- 9 files changed, 107 insertions(+), 181 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 79fdfc1f57..f2a54a6325 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -11,7 +11,7 @@ use crate::bytecode; use crate::function::PyFuncArgs; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; -use crate::obj::objcode; +use crate::obj::objcode::PyCodeRef; use crate::obj::objdict; use crate::obj::objdict::PyDict; use crate::obj::objint::PyInt; @@ -22,7 +22,7 @@ use crate::obj::objstr; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TryFromObject, + DictProtocol, IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -186,6 +186,8 @@ enum BlockType { }, } +pub type FrameRef = PyRef; + pub struct Frame { pub code: bytecode::CodeObject, // We need 1 stack per frame @@ -211,7 +213,7 @@ pub enum ExecutionResult { pub type FrameResult = Result, PyObjectRef>; impl Frame { - pub fn new(code: PyObjectRef, scope: Scope) -> Frame { + pub fn new(code: PyCodeRef, scope: Scope) -> Frame { //populate the globals and locals //TODO: This is wrong, check https://github.com/nedbat/byterun/blob/31e6c4a8212c35b5157919abff43a7daa0f377c6/byterun/pyvm2.py#L95 /* @@ -224,7 +226,7 @@ impl Frame { // locals.extend(callargs); Frame { - code: objcode::get_value(&code), + code: code.code.clone(), stack: RefCell::new(vec![]), blocks: RefCell::new(vec![]), // save the callargs as locals @@ -562,7 +564,7 @@ impl Frame { } bytecode::Instruction::MakeFunction { flags } => { let _qualified_name = self.pop_value(); - let code_obj = self.pop_value(); + let code_obj = PyCodeRef::try_from_object(vm, self.pop_value())?; let annotations = if flags.contains(bytecode::FunctionOpArg::HAS_ANNOTATIONS) { self.pop_value() diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 7fe54bf513..fd1511766d 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -13,7 +13,7 @@ use crate::vm::VirtualMachine; pub type PyCodeRef = PyRef; pub struct PyCode { - code: bytecode::CodeObject, + pub code: bytecode::CodeObject, } impl PyCode { @@ -38,30 +38,15 @@ pub fn init(context: &PyContext) { let code_type = context.code_type.as_object(); extend_class!(context, code_type, { "__new__" => context.new_rustfunc(code_new), - "__repr__" => context.new_rustfunc(code_repr) + "__repr__" => context.new_rustfunc(code_repr), + + "co_argcount" => context.new_property(code_co_argcount), + "co_consts" => context.new_property(code_co_consts), + "co_filename" => context.new_property(code_co_filename), + "co_firstlineno" => context.new_property(code_co_firstlineno), + "co_kwonlyargcount" => context.new_property(code_co_kwonlyargcount), + "co_name" => context.new_property(code_co_name), }); - - for (name, f) in &[ - ( - "co_argcount", - code_co_argcount as fn(&VirtualMachine, PyFuncArgs) -> PyResult, - ), - ("co_consts", code_co_consts), - ("co_filename", code_co_filename), - ("co_firstlineno", code_co_firstlineno), - ("co_kwonlyargcount", code_co_kwonlyargcount), - ("co_name", code_co_name), - ] { - context.set_attr(code_type, name, context.new_property(f)) - } -} - -pub fn get_value(obj: &PyObjectRef) -> bytecode::CodeObject { - if let Some(code) = obj.payload::() { - code.code.clone() - } else { - panic!("Inner error getting code {:?}", obj) - } } fn code_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -69,56 +54,42 @@ fn code_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Err(vm.new_type_error("Cannot directly create code object".to_string())) } -fn code_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(o, Some(vm.ctx.code_type()))]); - - let code = get_value(o); - let repr = format!( +fn code_repr(o: PyCodeRef, _vm: &VirtualMachine) -> String { + let code = &o.code; + format!( "", code.obj_name, o.get_id(), code.source_path, code.first_line_number - ); - Ok(vm.new_str(repr)) -} - -fn member_code_obj(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.code_type()))]); - Ok(get_value(zelf)) + ) } -fn code_co_argcount(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let code_obj = member_code_obj(vm, args)?; - Ok(vm.ctx.new_int(code_obj.arg_names.len())) +fn code_co_argcount(code: PyCodeRef, _vm: &VirtualMachine) -> usize { + code.code.arg_names.len() } -fn code_co_filename(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let code_obj = member_code_obj(vm, args)?; - let source_path = code_obj.source_path; - Ok(vm.new_str(source_path)) +fn code_co_filename(code: PyCodeRef, _vm: &VirtualMachine) -> String { + code.code.source_path.clone() } -fn code_co_firstlineno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let code_obj = member_code_obj(vm, args)?; - Ok(vm.ctx.new_int(code_obj.first_line_number)) +fn code_co_firstlineno(code: PyCodeRef, _vm: &VirtualMachine) -> usize { + code.code.first_line_number } -fn code_co_kwonlyargcount(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let code_obj = member_code_obj(vm, args)?; - Ok(vm.ctx.new_int(code_obj.kwonlyarg_names.len())) +fn code_co_kwonlyargcount(code: PyCodeRef, _vm: &VirtualMachine) -> usize { + code.code.kwonlyarg_names.len() } -fn code_co_consts(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let code_obj = member_code_obj(vm, args)?; - let consts = code_obj +fn code_co_consts(code: PyCodeRef, vm: &VirtualMachine) -> PyObjectRef { + let consts = code + .code .get_constants() .map(|x| vm.ctx.unwrap_constant(x)) .collect(); - Ok(vm.ctx.new_tuple(consts)) + vm.ctx.new_tuple(consts) } -fn code_co_name(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let code_obj = member_code_obj(vm, args)?; - Ok(vm.new_str(code_obj.obj_name)) +fn code_co_name(code: PyCodeRef, _vm: &VirtualMachine) -> String { + code.code.obj_name.clone() } diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index 96fed8eec2..1488ee79c7 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -1,19 +1,22 @@ use crate::frame::Scope; use crate::function::PyFuncArgs; +use crate::obj::objcode::PyCodeRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; +pub type PyFunctionRef = PyRef; + #[derive(Debug)] pub struct PyFunction { // TODO: these shouldn't be public - pub code: PyObjectRef, + pub code: PyCodeRef, pub scope: Scope, pub defaults: PyObjectRef, } impl PyFunction { - pub fn new(code: PyObjectRef, scope: Scope, defaults: PyObjectRef) -> Self { + pub fn new(code: PyCodeRef, scope: Scope, defaults: PyObjectRef) -> Self { PyFunction { code, scope, @@ -28,6 +31,12 @@ impl PyValue for PyFunction { } } +impl PyFunctionRef { + fn code(self, _vm: &VirtualMachine) -> PyCodeRef { + self.code.clone() + } +} + #[derive(Debug)] pub struct PyMethod { // TODO: these shouldn't be public @@ -51,7 +60,7 @@ pub fn init(context: &PyContext) { let function_type = &context.function_type; extend_class!(context, function_type, { "__get__" => context.new_rustfunc(bind_method), - "__code__" => context.new_property(function_code) + "__code__" => context.new_property(PyFunctionRef::code) }); let builtin_function_or_method_type = &context.builtin_function_or_method_type; @@ -73,10 +82,3 @@ fn bind_method(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bound_method(function.clone(), obj.clone())) } } - -fn function_code(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - match args.args[0].payload() { - Some(PyFunction { ref code, .. }) => Ok(code.clone()), - None => Err(vm.new_type_error("no code".to_string())), - } -} diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index b766aafd4d..9718d29a44 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -2,17 +2,17 @@ * The mythical generator. */ -use crate::frame::{ExecutionResult, Frame}; -use crate::function::PyFuncArgs; +use crate::frame::{ExecutionResult, FrameRef}; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; +pub type PyGeneratorRef = PyRef; + #[derive(Debug)] pub struct PyGenerator { - frame: PyObjectRef, + frame: FrameRef, } -type PyGeneratorRef = PyRef; impl PyValue for PyGenerator { fn class(vm: &VirtualMachine) -> PyClassRef { @@ -20,48 +20,23 @@ impl PyValue for PyGenerator { } } -pub fn init(context: &PyContext) { - let generator_type = &context.generator_type; - extend_class!(context, generator_type, { - "__iter__" => context.new_rustfunc(generator_iter), - "__next__" => context.new_rustfunc(generator_next), - "send" => context.new_rustfunc(generator_send) - }); -} - -pub fn new_generator(frame: PyObjectRef, vm: &VirtualMachine) -> PyGeneratorRef { - PyGenerator { frame }.into_ref(vm) -} - -fn generator_iter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(o, Some(vm.ctx.generator_type()))]); - Ok(o.clone()) -} +impl PyGeneratorRef { + pub fn new(frame: FrameRef, vm: &VirtualMachine) -> PyGeneratorRef { + PyGenerator { frame }.into_ref(vm) + } -fn generator_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(o, Some(vm.ctx.generator_type()))]); - let value = vm.get_none(); - send(vm, o, &value) -} + fn iter(self, _vm: &VirtualMachine) -> PyGeneratorRef { + self + } -fn generator_send(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(o, Some(vm.ctx.generator_type())), (value, None)] - ); - send(vm, o, value) -} + fn next(self, vm: &VirtualMachine) -> PyResult { + self.send(vm.get_none(), vm) + } -fn send(vm: &VirtualMachine, gen: &PyObjectRef, value: &PyObjectRef) -> PyResult { - if let Some(PyGenerator { ref frame }) = gen.payload() { - if let Some(frame) = frame.payload::() { - frame.push_value(value.clone()); - } else { - panic!("Generator frame isn't a frame."); - } + fn send(self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.frame.push_value(value.clone()); - match vm.run_frame(frame.clone())? { + match vm.run_frame(self.frame.clone())? { ExecutionResult::Yield(value) => Ok(value), ExecutionResult::Return(_value) => { // Stop iteration! @@ -69,7 +44,14 @@ fn send(vm: &VirtualMachine, gen: &PyObjectRef, value: &PyObjectRef) -> PyResult Err(vm.new_exception(stop_iteration, "End of generator".to_string())) } } - } else { - panic!("Cannot extract frame from non-generator"); } } + +pub fn init(context: &PyContext) { + let generator_type = &context.generator_type; + extend_class!(context, generator_type, { + "__iter__" => context.new_rustfunc(PyGeneratorRef::iter), + "__next__" => context.new_rustfunc(PyGeneratorRef::next), + "send" => context.new_rustfunc(PyGeneratorRef::send) + }); +} diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 80eba36da3..f5b8e06ed6 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -14,7 +14,7 @@ use num_traits::{One, Zero}; use crate::bytecode; use crate::exceptions; -use crate::frame::{Frame, Scope}; +use crate::frame::Scope; use crate::function::{IntoPyNativeFunc, PyFuncArgs}; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; @@ -22,6 +22,7 @@ use crate::obj::objbytearray; use crate::obj::objbytes; use crate::obj::objclassmethod; use crate::obj::objcode; +use crate::obj::objcode::PyCodeRef; use crate::obj::objcomplex::{self, PyComplex}; use crate::obj::objdict::{self, PyDict}; use crate::obj::objellipsis; @@ -591,10 +592,6 @@ impl PyContext { ) } - pub fn new_frame(&self, code: PyObjectRef, scope: Scope) -> PyObjectRef { - PyObject::new(Frame::new(code, scope), self.frame_type()) - } - pub fn new_property(&self, f: F) -> PyObjectRef where F: IntoPyNativeFunc, @@ -608,7 +605,7 @@ impl PyContext { pub fn new_function( &self, - code_obj: PyObjectRef, + code_obj: PyCodeRef, scope: Scope, defaults: PyObjectRef, ) -> PyObjectRef { diff --git a/vm/src/stdlib/dis.rs b/vm/src/stdlib/dis.rs index 04209d8b3b..222f23383d 100644 --- a/vm/src/stdlib/dis.rs +++ b/vm/src/stdlib/dis.rs @@ -1,23 +1,18 @@ -use crate::function::PyFuncArgs; -use crate::obj::objcode; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::obj::objcode::PyCodeRef; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TryFromObject}; use crate::vm::VirtualMachine; -fn dis_dis(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, None)]); - +fn dis_dis(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { // Method or function: if let Ok(co) = vm.get_attribute(obj.clone(), "__code__") { - return dis_disassemble(vm, PyFuncArgs::new(vec![co], vec![])); + return dis_disassemble(co, vm); } - dis_disassemble(vm, PyFuncArgs::new(vec![obj.clone()], vec![])) + dis_disassemble(obj, vm) } -fn dis_disassemble(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(co, Some(vm.ctx.code_type()))]); - - let code = objcode::get_value(co); +fn dis_disassemble(co: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let code = &PyCodeRef::try_from_object(vm, co)?.code; print!("{}", code); Ok(vm.get_none()) } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index e1cfbd93b5..2c5c95d34b 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -184,7 +184,7 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { where E: serde::de::Error, { - Ok(self.vm.ctx.none.clone().into_object()) + Ok(self.vm.get_none()) } } diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 8a45bc1c7e..a4a37b350c 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -1,10 +1,8 @@ use std::rc::Rc; use std::{env, mem}; -use num_traits::ToPrimitive; - -use crate::function::PyFuncArgs; -use crate::obj::objint; +use crate::frame::FrameRef; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{DictProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; @@ -18,28 +16,12 @@ fn argv(ctx: &PyContext) -> PyObjectRef { ctx.new_list(argv) } -fn frame_idx(vm: &VirtualMachine, offset: Option<&PyObjectRef>) -> Result { - if let Some(int) = offset { - if let Some(offset) = objint::get_value(&int).to_usize() { - if offset > vm.frames.borrow().len() - 1 { - return Err(vm.new_value_error("call stack is not deep enough".to_string())); - } - return Ok(offset); - } +fn getframe(offset: OptionalArg, vm: &VirtualMachine) -> PyResult { + let offset = offset.into_option().unwrap_or(0); + if offset > vm.frames.borrow().len() - 1 { + return Err(vm.new_value_error("call stack is not deep enough".to_string())); } - Ok(0) -} - -fn getframe(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [], - optional = [(offset, Some(vm.ctx.int_type()))] - ); - - let idx = frame_idx(vm, offset)?; - let idx = vm.frames.borrow().len() - idx - 1; + let idx = vm.frames.borrow().len() - offset - 1; let frame = &vm.frames.borrow()[idx]; Ok(frame.clone()) } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index cde574fbcd..87d22069c2 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -14,15 +14,13 @@ use std::sync::{Mutex, MutexGuard}; use crate::builtins; use crate::bytecode; -use crate::frame::{ExecutionResult, Frame, Scope}; +use crate::frame::{ExecutionResult, Frame, FrameRef, Scope}; use crate::function::PyFuncArgs; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; -use crate::obj::objcode; use crate::obj::objcode::PyCodeRef; -use crate::obj::objframe; use crate::obj::objfunction::{PyFunction, PyMethod}; -use crate::obj::objgenerator; +use crate::obj::objgenerator::PyGeneratorRef; use crate::obj::objiter; use crate::obj::objlist::PyList; use crate::obj::objsequence; @@ -31,7 +29,7 @@ use crate::obj::objtuple::PyTuple; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, TryIntoRef, + DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TryFromObject, TryIntoRef, TypeProtocol, }; use crate::stdlib; @@ -49,7 +47,7 @@ pub struct VirtualMachine { pub sys_module: PyObjectRef, pub stdlib_inits: RefCell>, pub ctx: PyContext, - pub frames: RefCell>, + pub frames: RefCell>, pub wasm_id: Option, } @@ -74,30 +72,28 @@ impl VirtualMachine { } pub fn run_code_obj(&self, code: PyCodeRef, scope: Scope) -> PyResult { - let frame = self.ctx.new_frame(code.into_object(), scope); + let frame = Frame::new(code, scope).into_ref(self); self.run_frame_full(frame) } - pub fn run_frame_full(&self, frame: PyObjectRef) -> PyResult { + pub fn run_frame_full(&self, frame: FrameRef) -> PyResult { match self.run_frame(frame)? { ExecutionResult::Return(value) => Ok(value), _ => panic!("Got unexpected result from function"), } } - pub fn run_frame(&self, frame: PyObjectRef) -> PyResult { + pub fn run_frame(&self, frame: FrameRef) -> PyResult { self.frames.borrow_mut().push(frame.clone()); - let frame = objframe::get_value(&frame); let result = frame.run(self); self.frames.borrow_mut().pop(); result } - pub fn current_frame(&self) -> Ref { + pub fn current_frame(&self) -> Ref { Ref::map(self.frames.borrow(), |frames| { let index = frames.len() - 1; - let current_frame = &frames[index]; - objframe::get_value(current_frame) + &frames[index] }) } @@ -344,21 +340,20 @@ impl VirtualMachine { fn invoke_python_function( &self, - code: &PyObjectRef, + code: &PyCodeRef, scope: &Scope, defaults: &PyObjectRef, args: PyFuncArgs, ) -> PyResult { - let code_object = objcode::get_value(code); let scope = scope.child_scope(&self.ctx); - self.fill_locals_from_args(&code_object, &scope.get_locals(), args, defaults)?; + self.fill_locals_from_args(&code.code, &scope.get_locals(), args, defaults)?; // Construct frame: - let frame = self.ctx.new_frame(code.clone(), scope); + let frame = Frame::new(code.clone(), scope).into_ref(self); // If we have a generator, create a new generator - if code_object.is_generator { - Ok(objgenerator::new_generator(frame, self).into_object()) + if code.code.is_generator { + Ok(PyGeneratorRef::new(frame, self).into_object()) } else { self.run_frame_full(frame) } @@ -379,7 +374,7 @@ impl VirtualMachine { let scope = scope .child_scope_with_locals(cells) .child_scope_with_locals(locals); - let frame = self.ctx.new_frame(code.clone(), scope); + let frame = Frame::new(code.clone(), scope).into_ref(self); return self.run_frame_full(frame); } panic!( From 0f2889a0e5b861c57e97cec3fafc451818e2f1f7 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 23 Mar 2019 19:46:46 -0500 Subject: [PATCH 046/884] Fix weird rustfmt --- vm/src/obj/objsuper.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 1920a73fef..17d015a5fe 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -123,10 +123,8 @@ fn super_new( match vm.get_locals().get_item(first_arg) { Some(obj) => obj.clone(), _ => { - return Err(vm.new_type_error(format!( - "super arguement {} was not supplied", - first_arg - ))); + return Err(vm + .new_type_error(format!("super argument {} was not supplied", first_arg))); } } } else { From 6eb93a7000a97318fb0c3a380d03fdc3818d304b Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 24 Mar 2019 13:50:50 +1300 Subject: [PATCH 047/884] Move code methods into impl PyCodeRef --- vm/src/obj/objcode.rs | 102 +++++++++++++++++++++--------------------- 1 file changed, 51 insertions(+), 51 deletions(-) diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index fd1511766d..a26645b913 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -5,9 +5,8 @@ use std::fmt; use crate::bytecode; -use crate::function::PyFuncArgs; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; pub type PyCodeRef = PyRef; @@ -34,62 +33,63 @@ impl PyValue for PyCode { } } -pub fn init(context: &PyContext) { - let code_type = context.code_type.as_object(); - extend_class!(context, code_type, { - "__new__" => context.new_rustfunc(code_new), - "__repr__" => context.new_rustfunc(code_repr), - - "co_argcount" => context.new_property(code_co_argcount), - "co_consts" => context.new_property(code_co_consts), - "co_filename" => context.new_property(code_co_filename), - "co_firstlineno" => context.new_property(code_co_firstlineno), - "co_kwonlyargcount" => context.new_property(code_co_kwonlyargcount), - "co_name" => context.new_property(code_co_name), - }); -} +impl PyCodeRef { + fn new(_cls: PyClassRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("Cannot directly create code object".to_string())) + } -fn code_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(_cls, None)]); - Err(vm.new_type_error("Cannot directly create code object".to_string())) -} + fn repr(self, _vm: &VirtualMachine) -> String { + let code = &self.code; + format!( + "", + code.obj_name, + self.get_id(), + code.source_path, + code.first_line_number + ) + } -fn code_repr(o: PyCodeRef, _vm: &VirtualMachine) -> String { - let code = &o.code; - format!( - "", - code.obj_name, - o.get_id(), - code.source_path, - code.first_line_number - ) -} + fn co_argcount(self, _vm: &VirtualMachine) -> usize { + self.code.arg_names.len() + } -fn code_co_argcount(code: PyCodeRef, _vm: &VirtualMachine) -> usize { - code.code.arg_names.len() -} + fn co_filename(self, _vm: &VirtualMachine) -> String { + self.code.source_path.clone() + } -fn code_co_filename(code: PyCodeRef, _vm: &VirtualMachine) -> String { - code.code.source_path.clone() -} + fn co_firstlineno(self, _vm: &VirtualMachine) -> usize { + self.code.first_line_number + } -fn code_co_firstlineno(code: PyCodeRef, _vm: &VirtualMachine) -> usize { - code.code.first_line_number -} + fn co_kwonlyargcount(self, _vm: &VirtualMachine) -> usize { + self.code.kwonlyarg_names.len() + } -fn code_co_kwonlyargcount(code: PyCodeRef, _vm: &VirtualMachine) -> usize { - code.code.kwonlyarg_names.len() -} + fn co_consts(self, vm: &VirtualMachine) -> PyObjectRef { + let consts = self + .code + .get_constants() + .map(|x| vm.ctx.unwrap_constant(x)) + .collect(); + vm.ctx.new_tuple(consts) + } -fn code_co_consts(code: PyCodeRef, vm: &VirtualMachine) -> PyObjectRef { - let consts = code - .code - .get_constants() - .map(|x| vm.ctx.unwrap_constant(x)) - .collect(); - vm.ctx.new_tuple(consts) + fn co_name(self, _vm: &VirtualMachine) -> String { + self.code.obj_name.clone() + } } -fn code_co_name(code: PyCodeRef, _vm: &VirtualMachine) -> String { - code.code.obj_name.clone() +pub fn init(context: &PyContext) { + let code_type = context.code_type.as_object(); + extend_class!(context, code_type, { + "__new__" => context.new_rustfunc(PyCodeRef::new), + "__repr__" => context.new_rustfunc(PyCodeRef::repr), + + "co_argcount" => context.new_property(PyCodeRef::co_argcount), + "co_consts" => context.new_property(PyCodeRef::co_consts), + "co_filename" => context.new_property(PyCodeRef::co_filename), + "co_firstlineno" => context.new_property(PyCodeRef::co_firstlineno), + "co_kwonlyargcount" => context.new_property(PyCodeRef::co_kwonlyargcount), + "co_name" => context.new_property(PyCodeRef::co_name), + }); } From 84d47d21cf74aec990dbb5e4ddc4fa2f449d1298 Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 23 Mar 2019 17:51:12 -0700 Subject: [PATCH 048/884] Rename to just Either --- vm/src/obj/objrange.rs | 8 ++++---- vm/src/pyobject.rs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 8d526227a3..468490eeaa 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -7,7 +7,7 @@ use num_traits::{One, Signed, ToPrimitive, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - Either2, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + Either, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -212,16 +212,16 @@ impl PyRangeRef { .into_ref_with_type(vm, cls) } - fn getitem(self, subscript: Either2, vm: &VirtualMachine) -> PyResult { + fn getitem(self, subscript: Either, vm: &VirtualMachine) -> PyResult { match subscript { - Either2::A(index) => { + Either::A(index) => { if let Some(value) = self.get(index.value.clone()) { Ok(PyInt::new(value).into_ref(vm).into_object()) } else { Err(vm.new_index_error("range object index out of range".to_string())) } } - Either2::B(slice) => { + Either::B(slice) => { let new_start = if let Some(int) = slice.start.clone() { if let Some(i) = self.get(int) { i diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index befa798926..19a1eee9f7 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1203,7 +1203,7 @@ impl PyObjectPayload for T { } } -pub enum Either2 { +pub enum Either { A(A), B(B), } @@ -1216,28 +1216,28 @@ pub enum Either2 { /// ``` /// use rustpython_vm::VirtualMachine; /// use rustpython_vm::obj::{objstr::PyStringRef, objint::PyIntRef}; -/// use rustpython_vm::pyobject::Either2; +/// use rustpython_vm::pyobject::Either; /// -/// fn do_something(arg: Either2, vm: &VirtualMachine) { +/// fn do_something(arg: Either, vm: &VirtualMachine) { /// match arg { -/// Either2::A(int)=> { +/// Either::A(int)=> { /// // do something with int /// } -/// Either2::B(string) => { +/// Either::B(string) => { /// // do something with string /// } /// } /// } /// ``` -impl TryFromObject for Either2, PyRef> +impl TryFromObject for Either, PyRef> where A: PyValue, B: PyValue, { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { obj.downcast::() - .map(Either2::A) - .or_else(|obj| obj.clone().downcast::().map(Either2::B)) + .map(Either::A) + .or_else(|obj| obj.clone().downcast::().map(Either::B)) .map_err(|obj| { vm.new_type_error(format!( "must be {} or {}, not {}", From 3f363585e8242387fd1b95d83757b7e187e7c3c0 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 24 Mar 2019 16:08:17 +1300 Subject: [PATCH 049/884] Panic if value poped of stack isn't a code object. --- vm/src/frame.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index f2a54a6325..2a0951ab39 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -564,7 +564,10 @@ impl Frame { } bytecode::Instruction::MakeFunction { flags } => { let _qualified_name = self.pop_value(); - let code_obj = PyCodeRef::try_from_object(vm, self.pop_value())?; + let code_obj = self + .pop_value() + .downcast() + .expect("Second to top value on the stack must be a code object"); let annotations = if flags.contains(bytecode::FunctionOpArg::HAS_ANNOTATIONS) { self.pop_value() From ec433cdbb91aa9babca4369906237e2db59eb3ff Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 23 Mar 2019 22:17:21 -0500 Subject: [PATCH 050/884] Fix rustfmt again (?) --- vm/src/obj/objsuper.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 53e347b9fe..c41cf183e3 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -128,8 +128,9 @@ fn super_new( match vm.get_locals().get_item(first_arg) { Some(obj) => obj.clone(), _ => { - return Err(vm - .new_type_error(format!("super argument {} was not supplied", first_arg))); + return Err( + vm.new_type_error(format!("super arguement {} was not supplied", first_arg)) + ); } } } else { From 12564da369a9b3388b12d01043cda43bb56027c3 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 23 Mar 2019 22:46:21 -0500 Subject: [PATCH 051/884] Rustfmt? --- vm/src/obj/objsuper.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index c41cf183e3..f04fb66058 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -128,9 +128,8 @@ fn super_new( match vm.get_locals().get_item(first_arg) { Some(obj) => obj.clone(), _ => { - return Err( - vm.new_type_error(format!("super arguement {} was not supplied", first_arg)) - ); + return Err(vm + .new_type_error(format!("super arguement {} was not supplied", first_arg))); } } } else { From 9a6f7aa8a1f58e346e1da4027a608c67d4dfc7ba Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Fri, 22 Mar 2019 19:12:33 +0100 Subject: [PATCH 052/884] Make PyRef::clone not require T implementing Clone It seems to be a weird consequence of using PhantomData, so I just rolled a custom Clone. --- vm/src/obj/objmodule.rs | 2 +- vm/src/obj/objnone.rs | 2 +- vm/src/obj/objobject.rs | 2 +- vm/src/obj/objtuple.rs | 1 - vm/src/obj/objtype.rs | 2 +- 5 files changed, 4 insertions(+), 5 deletions(-) diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs index 2d1d8921e1..58d8688db2 100644 --- a/vm/src/obj/objmodule.rs +++ b/vm/src/obj/objmodule.rs @@ -3,7 +3,7 @@ use crate::obj::objtype::PyClassRef; use crate::pyobject::{DictProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct PyModule { pub name: String, pub dict: PyObjectRef, diff --git a/vm/src/obj/objnone.rs b/vm/src/obj/objnone.rs index 7b0eefec21..840b00eccc 100644 --- a/vm/src/obj/objnone.rs +++ b/vm/src/obj/objnone.rs @@ -6,7 +6,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct PyNone; pub type PyNoneRef = PyRef; diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 84ba73fe68..46a90114dc 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -11,7 +11,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct PyInstance; impl PyValue for PyInstance { diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index e5bf5c1eec..a199e34b18 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -15,7 +15,6 @@ use super::objsequence::{ }; use super::objtype::{self, PyClassRef}; -#[derive(Default)] pub struct PyTuple { // TODO: shouldn't be public // TODO: tuples are immutable, remove this RefCell diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 9d3c9fe490..a8dd3289a6 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -16,7 +16,7 @@ use super::objstr::PyStringRef; use super::objtuple::PyTuple; use crate::obj::objdict::PyDictRef; -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct PyClass { pub name: String, pub mro: Vec, From a895ab35aeadcdc3b6504b531104765221212e18 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Fri, 22 Mar 2019 19:13:26 +0100 Subject: [PATCH 053/884] Make PyWeak::downgrade take a reference --- vm/src/obj/objweakref.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/src/obj/objweakref.rs b/vm/src/obj/objweakref.rs index 3a71e3b630..685e32b704 100644 --- a/vm/src/obj/objweakref.rs +++ b/vm/src/obj/objweakref.rs @@ -11,9 +11,9 @@ pub struct PyWeak { } impl PyWeak { - pub fn downgrade(obj: PyObjectRef) -> PyWeak { + pub fn downgrade(obj: &PyObjectRef) -> PyWeak { PyWeak { - referent: Rc::downgrade(&obj), + referent: Rc::downgrade(obj), } } @@ -33,7 +33,7 @@ pub type PyWeakRef = PyRef; impl PyWeakRef { // TODO callbacks fn create(cls: PyClassRef, referent: PyObjectRef, vm: &VirtualMachine) -> PyResult { - PyWeak::downgrade(referent).into_ref_with_type(vm, cls) + PyWeak::downgrade(&referent).into_ref_with_type(vm, cls) } fn call(self, vm: &VirtualMachine) -> PyObjectRef { From e39df6daefb3ba1dbdb4e3a75770da57c2572d99 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Fri, 22 Mar 2019 19:18:33 +0100 Subject: [PATCH 054/884] Basic implementation of T.__subclasses__(). Ideally, the weak list should be updated whenever a type is removed, and the list shouldn't be a Vec, but this should be good enough for starters. --- tests/snippets/types_snippet.py | 23 +++++++++++++++++++++++ vm/src/obj/objtype.rs | 26 ++++++++++++++++++++++++-- vm/src/pyobject.rs | 13 +++++++++++-- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/tests/snippets/types_snippet.py b/tests/snippets/types_snippet.py index ac715751ef..f441552d11 100644 --- a/tests/snippets/types_snippet.py +++ b/tests/snippets/types_snippet.py @@ -26,3 +26,26 @@ assert isinstance(type, type) assert issubclass(type, type) + +class A: pass +class B(A): pass +class C(A): pass +class D(B, C): pass + +assert A.__subclasses__() == [B, C] +assert B.__subclasses__() == [D] +assert C.__subclasses__() == [D] +assert D.__subclasses__() == [] + +del D + +try: # gc sweep is needed here for CPython... + import gc; gc.collect() +except: # ...while RustPython doesn't have `gc` yet. + pass + +assert B.__subclasses__() == [] +assert C.__subclasses__() == [] + +assert type in object.__subclasses__() + diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index a8dd3289a6..89bd8f34a5 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -10,16 +10,18 @@ use crate::pyobject::{ use crate::vm::VirtualMachine; use super::objdict; +use super::objdict::PyDictRef; use super::objlist::PyList; use super::objproperty::PropertyBuilder; use super::objstr::PyStringRef; use super::objtuple::PyTuple; -use crate::obj::objdict::PyDictRef; +use super::objweakref::PyWeak; #[derive(Debug)] pub struct PyClass { pub name: String, pub mro: Vec, + pub subclasses: RefCell>, } impl fmt::Display for PyClass { @@ -146,6 +148,17 @@ impl PyClassRef { Err(vm.new_attribute_error(format!("{} has no attribute '{}'", self, name))) } } + + fn subclasses(self, _vm: &VirtualMachine) -> PyList { + let mut subclasses = self.subclasses.borrow_mut(); + subclasses.retain(|x| x.upgrade().is_some()); + PyList::from( + subclasses + .iter() + .map(|x| x.upgrade().unwrap()) + .collect::>(), + ) + } } /* @@ -168,6 +181,8 @@ pub fn init(ctx: &PyContext) { "__repr__" => ctx.new_rustfunc(PyClassRef::repr), "__prepare__" => ctx.new_rustfunc(PyClassRef::prepare), "__getattribute__" => ctx.new_rustfunc(PyClassRef::getattribute), + "__subclasses__" => ctx.new_rustfunc(PyClassRef::subclasses), + "__getattribute__" => ctx.new_rustfunc(PyClassRef::getattribute), "__instancecheck__" => ctx.new_rustfunc(PyClassRef::instance_check), "__subclasscheck__" => ctx.new_rustfunc(PyClassRef::subclass_check), "__doc__" => ctx.new_str(type_doc.to_string()), @@ -352,17 +367,24 @@ pub fn new( bases: Vec, dict: HashMap, ) -> PyResult { - let mros = bases.into_iter().map(|x| _mro(&x)).collect(); + let mros = bases.iter().map(|x| _mro(&x)).collect(); let mro = linearise_mro(mros).unwrap(); let new_type = PyObject { payload: PyClass { name: String::from(name), mro, + subclasses: RefCell::new(vec![]), }, dict: Some(RefCell::new(dict)), typ, } .into_ref(); + for base in bases { + base.subclasses + .borrow_mut() + .push(PyWeak::downgrade(&new_type)); + } + Ok(new_type.downcast().unwrap()) } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index da16e1ac86..e4488bdb49 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -183,13 +183,14 @@ fn init_type_hierarchy() -> (PyClassRef, PyClassRef) { // and both `type` and `object are instances of `type`. // to produce this circular dependency, we need an unsafe block. // (and yes, this will never get dropped. TODO?) - unsafe { + let (type_type, object_type) = unsafe { let object_type = PyObject { typ: mem::uninitialized(), // ! dict: Some(RefCell::new(PyAttributes::new())), payload: PyClass { name: String::from("object"), mro: vec![], + subclasses: RefCell::new(vec![]), }, } .into_ref(); @@ -200,6 +201,7 @@ fn init_type_hierarchy() -> (PyClassRef, PyClassRef) { payload: PyClass { name: String::from("type"), mro: vec![object_type.clone().downcast().unwrap()], + subclasses: RefCell::new(vec![]), }, } .into_ref(); @@ -214,7 +216,14 @@ fn init_type_hierarchy() -> (PyClassRef, PyClassRef) { ptr::write(&mut (*type_type_ptr).typ, type_type.clone()); (type_type, object_type) - } + }; + + object_type + .subclasses + .borrow_mut() + .push(objweakref::PyWeak::downgrade(&type_type.as_object())); + + (type_type, object_type) } // Basic objects: From d0c4fcb2fa65260c3eb72120cb303c8d1cbf7668 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 24 Mar 2019 13:14:38 +0100 Subject: [PATCH 055/884] Doing what is needed for all our non-latin friends. --- Cargo.lock | 1 + parser/Cargo.toml | 2 +- parser/src/lexer.rs | 595 +++++++++++++++++++---------------- src/main.rs | 2 +- tests/snippets/unicode_fu.py | 13 + 5 files changed, 333 insertions(+), 280 deletions(-) create mode 100644 tests/snippets/unicode_fu.py diff --git a/Cargo.lock b/Cargo.lock index 818d0af89e..57dadb57b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -760,6 +760,7 @@ dependencies = [ "num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/parser/Cargo.toml b/parser/Cargo.toml index e57d421406..7545a27d44 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -14,4 +14,4 @@ log="0.4.1" regex="0.2.2" num-bigint = "0.2" num-traits = "0.2" - +unicode-xid = "0.1.0" diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 113f7afb1a..db8fee2c70 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -1,12 +1,15 @@ //! This module takes care of lexing python source text. This means source //! code is translated into separate tokens. +extern crate unicode_xid; + pub use super::token::Tok; use num_bigint::BigInt; use num_traits::Num; use std::cmp::Ordering; use std::collections::HashMap; use std::str::FromStr; +use unicode_xid::UnicodeXID; #[derive(Clone, Copy, PartialEq, Debug)] struct IndentationLevel { @@ -300,7 +303,7 @@ where } } - while self.is_char() { + while self.is_identifier_continuation() { name.push(self.next_char().unwrap()); } let end_pos = self.get_pos(); @@ -540,10 +543,21 @@ where Ok((start_pos, tok, end_pos)) } - fn is_char(&self) -> bool { - match self.chr0 { - Some('a'..='z') | Some('A'..='Z') | Some('_') | Some('0'..='9') => true, - _ => false, + fn is_identifier_start(&self, c: char) -> bool { + match c { + 'a'..='z' | 'A'..='Z' | '_' => true, + c => UnicodeXID::is_xid_start(c), + } + } + + fn is_identifier_continuation(&self) -> bool { + if let Some(c) = self.chr0 { + match c { + 'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => true, + c => UnicodeXID::is_xid_continue(c), + } + } else { + false } } @@ -686,347 +700,372 @@ where } } - match self.chr0 { - Some('0'..='9') => return Some(self.lex_number()), - Some('_') | Some('a'..='z') | Some('A'..='Z') => { + // Check if we have some character: + if let Some(c) = self.chr0 { + // First check identifier: + if self.is_identifier_start(c) { return Some(self.lex_identifier()); - } - Some('#') => { - self.lex_comment(); - continue; - } - Some('"') => { - return Some(self.lex_string(false, false, false, false)); - } - Some('\'') => { - return Some(self.lex_string(false, false, false, false)); - } - Some('=') => { - let tok_start = self.get_pos(); - self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::EqEqual, tok_end))); + } else { + match c { + '0'..='9' => return Some(self.lex_number()), + '#' => { + self.lex_comment(); + continue; } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Equal, tok_end))); + '"' => { + return Some(self.lex_string(false, false, false, false)); } - } - } - Some('+') => { - let tok_start = self.get_pos(); - self.next_char(); - if let Some('=') = self.chr0 { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::PlusEqual, tok_end))); - } else { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Plus, tok_end))); - } - } - Some('*') => { - let tok_start = self.get_pos(); - self.next_char(); - match self.chr0 { - Some('=') => { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::StarEqual, tok_end))); + '\'' => { + return Some(self.lex_string(false, false, false, false)); } - Some('*') => { + '=' => { + let tok_start = self.get_pos(); self.next_char(); match self.chr0 { Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::DoubleStarEqual, tok_end))); + return Some(Ok((tok_start, Tok::EqEqual, tok_end))); } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::DoubleStar, tok_end))); + return Some(Ok((tok_start, Tok::Equal, tok_end))); } } } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Star, tok_end))); - } - } - } - Some('/') => { - let tok_start = self.get_pos(); - self.next_char(); - match self.chr0 { - Some('=') => { + '+' => { + let tok_start = self.get_pos(); self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::SlashEqual, tok_end))); + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::PlusEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Plus, tok_end))); + } } - Some('/') => { + '*' => { + let tok_start = self.get_pos(); self.next_char(); match self.chr0 { Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::DoubleSlashEqual, tok_end))); + return Some(Ok((tok_start, Tok::StarEqual, tok_end))); + } + Some('*') => { + self.next_char(); + match self.chr0 { + Some('=') => { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok(( + tok_start, + Tok::DoubleStarEqual, + tok_end, + ))); + } + _ => { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::DoubleStar, tok_end))); + } + } } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::DoubleSlash, tok_end))); + return Some(Ok((tok_start, Tok::Star, tok_end))); } } } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Slash, tok_end))); + '/' => { + let tok_start = self.get_pos(); + self.next_char(); + match self.chr0 { + Some('=') => { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::SlashEqual, tok_end))); + } + Some('/') => { + self.next_char(); + match self.chr0 { + Some('=') => { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok(( + tok_start, + Tok::DoubleSlashEqual, + tok_end, + ))); + } + _ => { + let tok_end = self.get_pos(); + return Some(Ok(( + tok_start, + Tok::DoubleSlash, + tok_end, + ))); + } + } + } + _ => { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Slash, tok_end))); + } + } } - } - } - Some('%') => { - let tok_start = self.get_pos(); - self.next_char(); - if let Some('=') = self.chr0 { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::PercentEqual, tok_end))); - } else { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Percent, tok_end))); - } - } - Some('|') => { - let tok_start = self.get_pos(); - self.next_char(); - if let Some('=') = self.chr0 { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::VbarEqual, tok_end))); - } else { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Vbar, tok_end))); - } - } - Some('^') => { - let tok_start = self.get_pos(); - self.next_char(); - if let Some('=') = self.chr0 { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::CircumflexEqual, tok_end))); - } else { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::CircumFlex, tok_end))); - } - } - Some('&') => { - let tok_start = self.get_pos(); - self.next_char(); - if let Some('=') = self.chr0 { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::AmperEqual, tok_end))); - } else { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Amper, tok_end))); - } - } - Some('-') => { - let tok_start = self.get_pos(); - self.next_char(); - match self.chr0 { - Some('=') => { + '%' => { + let tok_start = self.get_pos(); self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::MinusEqual, tok_end))); + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::PercentEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Percent, tok_end))); + } } - Some('>') => { + '|' => { + let tok_start = self.get_pos(); self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Rarrow, tok_end))); + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::VbarEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Vbar, tok_end))); + } } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Minus, tok_end))); + '^' => { + let tok_start = self.get_pos(); + self.next_char(); + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::CircumflexEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::CircumFlex, tok_end))); + } } - } - } - Some('@') => { - let tok_start = self.get_pos(); - self.next_char(); - if let Some('=') = self.chr0 { - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::AtEqual, tok_end))); - } else { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::At, tok_end))); - } - } - Some('!') => { - let tok_start = self.get_pos(); - self.next_char(); - match self.chr0 { - Some('=') => { + '&' => { + let tok_start = self.get_pos(); self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::NotEqual, tok_end))); + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::AmperEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Amper, tok_end))); + } } - _ => panic!("Invalid token '!'"), - } - } - Some('~') => { - return Some(self.eat_single_char(Tok::Tilde)); - } - Some('(') => { - let result = self.eat_single_char(Tok::Lpar); - self.nesting += 1; - return Some(result); - } - Some(')') => { - let result = self.eat_single_char(Tok::Rpar); - if self.nesting == 0 { - return Some(Err(LexicalError::NestingError)); - } - self.nesting -= 1; - return Some(result); - } - Some('[') => { - let result = self.eat_single_char(Tok::Lsqb); - self.nesting += 1; - return Some(result); - } - Some(']') => { - let result = self.eat_single_char(Tok::Rsqb); - if self.nesting == 0 { - return Some(Err(LexicalError::NestingError)); - } - self.nesting -= 1; - return Some(result); - } - Some('{') => { - let result = self.eat_single_char(Tok::Lbrace); - self.nesting += 1; - return Some(result); - } - Some('}') => { - let result = self.eat_single_char(Tok::Rbrace); - if self.nesting == 0 { - return Some(Err(LexicalError::NestingError)); - } - self.nesting -= 1; - return Some(result); - } - Some(':') => { - return Some(self.eat_single_char(Tok::Colon)); - } - Some(';') => { - return Some(self.eat_single_char(Tok::Semi)); - } - Some('<') => { - let tok_start = self.get_pos(); - self.next_char(); - match self.chr0 { - Some('<') => { + '-' => { + let tok_start = self.get_pos(); self.next_char(); match self.chr0 { Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::LeftShiftEqual, tok_end))); + return Some(Ok((tok_start, Tok::MinusEqual, tok_end))); + } + Some('>') => { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Rarrow, tok_end))); } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::LeftShift, tok_end))); + return Some(Ok((tok_start, Tok::Minus, tok_end))); } } } - Some('=') => { + '@' => { + let tok_start = self.get_pos(); self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::LessEqual, tok_end))); + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::AtEqual, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::At, tok_end))); + } } - _ => { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Less, tok_end))); + '!' => { + let tok_start = self.get_pos(); + self.next_char(); + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::NotEqual, tok_end))); + } else { + return Some(Err(LexicalError::UnrecognizedToken { tok: '!' })); + } } - } - } - Some('>') => { - let tok_start = self.get_pos(); - self.next_char(); - match self.chr0 { - Some('>') => { + '~' => { + return Some(self.eat_single_char(Tok::Tilde)); + } + '(' => { + let result = self.eat_single_char(Tok::Lpar); + self.nesting += 1; + return Some(result); + } + ')' => { + let result = self.eat_single_char(Tok::Rpar); + if self.nesting == 0 { + return Some(Err(LexicalError::NestingError)); + } + self.nesting -= 1; + return Some(result); + } + '[' => { + let result = self.eat_single_char(Tok::Lsqb); + self.nesting += 1; + return Some(result); + } + ']' => { + let result = self.eat_single_char(Tok::Rsqb); + if self.nesting == 0 { + return Some(Err(LexicalError::NestingError)); + } + self.nesting -= 1; + return Some(result); + } + '{' => { + let result = self.eat_single_char(Tok::Lbrace); + self.nesting += 1; + return Some(result); + } + '}' => { + let result = self.eat_single_char(Tok::Rbrace); + if self.nesting == 0 { + return Some(Err(LexicalError::NestingError)); + } + self.nesting -= 1; + return Some(result); + } + ':' => { + return Some(self.eat_single_char(Tok::Colon)); + } + ';' => { + return Some(self.eat_single_char(Tok::Semi)); + } + '<' => { + let tok_start = self.get_pos(); self.next_char(); match self.chr0 { + Some('<') => { + self.next_char(); + match self.chr0 { + Some('=') => { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok(( + tok_start, + Tok::LeftShiftEqual, + tok_end, + ))); + } + _ => { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::LeftShift, tok_end))); + } + } + } Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::RightShiftEqual, tok_end))); + return Some(Ok((tok_start, Tok::LessEqual, tok_end))); } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::RightShift, tok_end))); + return Some(Ok((tok_start, Tok::Less, tok_end))); } } } - Some('=') => { + '>' => { + let tok_start = self.get_pos(); + self.next_char(); + match self.chr0 { + Some('>') => { + self.next_char(); + match self.chr0 { + Some('=') => { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok(( + tok_start, + Tok::RightShiftEqual, + tok_end, + ))); + } + _ => { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::RightShift, tok_end))); + } + } + } + Some('=') => { + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::GreaterEqual, tok_end))); + } + _ => { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Greater, tok_end))); + } + } + } + ',' => { + let tok_start = self.get_pos(); self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::GreaterEqual, tok_end))); + return Some(Ok((tok_start, Tok::Comma, tok_end))); } - _ => { + '.' => { + let tok_start = self.get_pos(); + self.next_char(); + if let (Some('.'), Some('.')) = (&self.chr0, &self.chr1) { + self.next_char(); + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Ellipsis, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Dot, tok_end))); + } + } + '\n' => { + let tok_start = self.get_pos(); + self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Greater, tok_end))); + self.new_line(); + + // Depending on the nesting level, we emit newline or not: + if self.nesting == 0 { + self.at_begin_of_line = true; + return Some(Ok((tok_start, Tok::Newline, tok_end))); + } else { + continue; + } } + ' ' => { + // Skip whitespaces + self.next_char(); + continue; + } + _ => { + let c = self.next_char(); + return Some(Err(LexicalError::UnrecognizedToken { tok: c.unwrap() })); + } // Ignore all the rest.. } } - Some(',') => { - let tok_start = self.get_pos(); - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Comma, tok_end))); - } - Some('.') => { - let tok_start = self.get_pos(); - self.next_char(); - if let (Some('.'), Some('.')) = (&self.chr0, &self.chr1) { - self.next_char(); - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Ellipsis, tok_end))); - } else { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Dot, tok_end))); - } - } - Some('\n') => { - let tok_start = self.get_pos(); - self.next_char(); - let tok_end = self.get_pos(); - self.new_line(); - - // Depending on the nesting level, we emit newline or not: - if self.nesting == 0 { - self.at_begin_of_line = true; - return Some(Ok((tok_start, Tok::Newline, tok_end))); - } else { - continue; - } - } - Some(' ') => { - // Skip whitespaces - self.next_char(); - continue; - } - None => return None, - _ => { - let c = self.next_char(); - return Some(Err(LexicalError::UnrecognizedToken { tok: c.unwrap() })); - } // Ignore all the rest.. + } else { + return None; } } } diff --git a/src/main.rs b/src/main.rs index 70adf10080..750faf98a8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -153,7 +153,7 @@ fn get_prompt(vm: &VirtualMachine, prompt_name: &str) -> String { fn run_shell(vm: &VirtualMachine) -> PyResult { println!( - "Welcome to the magnificent Rust Python {} interpreter", + "Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}", crate_version!() ); let vars = vm.ctx.new_scope(); diff --git a/tests/snippets/unicode_fu.py b/tests/snippets/unicode_fu.py new file mode 100644 index 0000000000..96d5bf9770 --- /dev/null +++ b/tests/snippets/unicode_fu.py @@ -0,0 +1,13 @@ + +# Test the unicode support! 👋 + + +ᚴ=2 + +assert ᚴ*8 == 16 + +ᚴ="👋" + +c = ᚴ*3 + +assert c == '👋👋👋' From 0ac1c1f19c1a7b4e7e085abef9bcc91be0ac29b2 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 24 Mar 2019 08:42:18 -0500 Subject: [PATCH 056/884] Add line continuation for the WASM demo terminal --- wasm/demo/src/main.js | 84 ++++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/wasm/demo/src/main.js b/wasm/demo/src/main.js index f5034f62a1..9b0fca8ef8 100644 --- a/wasm/demo/src/main.js +++ b/wasm/demo/src/main.js @@ -82,12 +82,6 @@ snippets.addEventListener('change', updateSnippet); // option selected for the `select`, but the textarea won't be updated) updateSnippet(); -const prompt = '>>>>> '; - -const term = new Terminal(); -term.open(document.getElementById('terminal')); -term.write(prompt); - function removeNonAscii(str) { if (str === null || str === '') return false; else str = str.toString(); @@ -99,38 +93,80 @@ function printToConsole(data) { term.write(removeNonAscii(data) + '\r\n'); } +const term = new Terminal(); +term.open(document.getElementById('terminal')); + const terminalVM = rp.vmStore.init('term_vm'); terminalVM.setStdout(printToConsole); -var input = ''; +function getPrompt(name = 'ps1') { + terminalVM.exec(` +try: + import sys as __sys + __prompt = __sys.${name} +except: + __prompt = '' +finally: + del __sys +`); + return String(terminalVM.eval('__prompt')); +} + +term.write(getPrompt()); + +function resetInput() { + continuedInput = []; + input = ''; + continuing = false; +} + +let continuedInput, input, continuing; +resetInput(); + +let ps2; + term.on('data', data => { const code = data.charCodeAt(0); if (code == 13) { // CR - if (input[input.length - 1] == ':') { - input += data; - term.write('\r\n.....'); - } else { - term.write('\r\n'); - try { - terminalVM.execSingle(input); - } catch (err) { - if (err instanceof WebAssembly.RuntimeError) { - err = window.__RUSTPYTHON_ERROR || err; - } - printToConsole(err); + term.write('\r\n'); + continuedInput.push(input); + if (continuing) { + if (input === '') { + continuing = false; + } else { + input = ''; + term.write(ps2); + return; + } + } + try { + terminalVM.execSingle(continuedInput.join('\n')); + } catch (err) { + if (err instanceof SyntaxError && err.message.includes('EOF')) { + ps2 = getPrompt('ps2'); + term.write(ps2); + continuing = true; + input = ''; + return; + } else if (err instanceof WebAssembly.RuntimeError) { + err = window.__RUSTPYTHON_ERROR || err; } - term.write(prompt); - input = ''; + printToConsole(err); } - } else if (code == 127) { + resetInput(); + term.write(getPrompt()); + } else if (code == 127 || code == 8) { + // Backspace if (input.length > 0) { term.write('\b \b'); input = input.slice(0, -1); } - } else if (code < 32 || code == 127) { + } else if (code < 32) { // Control - return; + term.write('\r\n' + getPrompt()); + input = ''; + continuedInput = []; } else { // Visible term.write(data); From 565719dcdacaf83cfcaf7781b5b9437acb983270 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 24 Mar 2019 11:26:41 -0500 Subject: [PATCH 057/884] Update cargo dependencies --- Cargo.lock | 724 ++++++++++++++++++++++------------------ wasm/lib/src/convert.rs | 13 +- 2 files changed, 395 insertions(+), 342 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 818d0af89e..f3ea4ba4dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,10 +2,10 @@ # It is not intended for manual editing. [[package]] name = "aho-corasick" -version = "0.6.4" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -13,7 +13,7 @@ name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -27,15 +27,15 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "arrayvec" -version = "0.4.7" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -52,39 +52,45 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "atty" -version = "0.2.10" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "autocfg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "backtrace" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "backtrace-sys" -version = "0.1.24" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -110,7 +116,7 @@ name = "blake2-rfc" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -119,10 +125,15 @@ name = "block-buffer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bumpalo" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byte-tools" version = "0.2.0" @@ -130,7 +141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "byteorder" -version = "1.2.6" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -138,30 +149,30 @@ name = "caseless" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cc" -version = "1.0.25" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" -version = "0.1.3" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clap" -version = "2.31.2" +version = "2.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -186,7 +197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "digest" -version = "0.7.2" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -194,12 +205,12 @@ dependencies = [ [[package]] name = "dirs" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -207,16 +218,16 @@ name = "docopt" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "either" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -226,46 +237,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "env_logger" -version = "0.5.10" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "env_logger" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "failure" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "failure_derive" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -279,17 +290,8 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" +name = "fuchsia-cprng" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -307,7 +309,7 @@ dependencies = [ [[package]] name = "heck" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -315,7 +317,7 @@ dependencies = [ [[package]] name = "humantime" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -323,23 +325,23 @@ dependencies = [ [[package]] name = "itertools" -version = "0.7.8" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "itoa" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "js-sys" -version = "0.3.6" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -360,17 +362,17 @@ dependencies = [ "atty 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", "ena 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", "lalrpop-snap 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -387,7 +389,7 @@ dependencies = [ "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "ena 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", "lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", @@ -404,24 +406,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lazy_static" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "lexical" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "lexical-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "lexical-core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "lexical-core" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", "stackvector 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -429,7 +431,7 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.42" +version = "0.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -437,32 +439,26 @@ name = "log" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "log" -version = "0.4.2" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "memchr" -version = "2.0.1" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "new_debug_unreachable" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] [[package]] name = "nix" @@ -470,28 +466,29 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "nodrop" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "nom" -version = "4.1.1" +version = "4.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-bigint" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", @@ -519,7 +516,7 @@ name = "num-rational" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -545,19 +542,19 @@ dependencies = [ [[package]] name = "phf_generator" -version = "0.7.22" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "phf_shared" -version = "0.7.22" +version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -565,14 +562,6 @@ name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "proc-macro2" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "proc-macro2" version = "0.4.27" @@ -588,58 +577,131 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "quote" -version = "0.5.2" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "quote" -version = "0.6.11" +name = "rand" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand" -version = "0.4.2" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "rand" -version = "0.5.5" +name = "rand_chacha" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_core" -version = "0.2.2" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rand_core" -version = "0.3.0" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "rand_hc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_isaac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_jitter" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_os" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_pcg" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_xorshift" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rdrand" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "redox_syscall" -version = "0.1.40" +version = "0.1.51" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -647,18 +709,18 @@ name = "redox_termios" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "redox_users" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -666,23 +728,23 @@ name = "regex" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex" -version = "1.0.3" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -695,20 +757,20 @@ name = "regex-syntax" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex-syntax" -version = "0.6.0" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rustc-demangle" -version = "0.1.9" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -732,9 +794,9 @@ dependencies = [ name = "rustpython" version = "0.0.1" dependencies = [ - "clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", "rustyline 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -756,8 +818,8 @@ version = "0.0.1" dependencies = [ "lalrpop 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -767,24 +829,24 @@ name = "rustpython_vm" version = "0.1.0" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lexical 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", "num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_derive 0.1.0", "rustpython_parser 0.0.1", - "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -793,15 +855,15 @@ dependencies = [ name = "rustpython_wasm" version = "0.1.0-pre-alpha.1" dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", - "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-futures 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "web-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -809,15 +871,15 @@ name = "rustyline" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -845,27 +907,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.66" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde_derive" -version = "1.0.66" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_json" -version = "1.0.26" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -875,13 +937,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)", "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "siphasher" -version = "0.2.2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "smallvec" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -907,7 +974,7 @@ name = "statrs" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -915,24 +982,24 @@ name = "string_cache" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", - "string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "string_cache_codegen" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", - "phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -951,16 +1018,6 @@ name = "strsim" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "syn" -version = "0.14.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "syn" version = "0.15.29" @@ -973,12 +1030,12 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.9.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -991,14 +1048,6 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "termcolor" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "termcolor" version = "1.0.4" @@ -1012,14 +1061,14 @@ name = "termion" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "textwrap" -version = "0.9.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1030,7 +1079,7 @@ name = "thread_local" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1040,13 +1089,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ucd-util" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "unicode-normalization" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "unicode-segmentation" @@ -1073,7 +1125,7 @@ dependencies = [ [[package]] name = "utf8-ranges" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1086,6 +1138,11 @@ name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "void" version = "1.0.2" @@ -1093,87 +1150,88 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "wasm-bindgen" -version = "0.2.29" +version = "0.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "wasm-bindgen-macro 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.29" +version = "0.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bumpalo 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-futures" -version = "0.3.6" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.29" +version = "0.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro-support 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro-support 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.29" +version = "0.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-backend 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.29" +version = "0.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "wasm-bindgen-webidl" -version = "0.2.23" +version = "0.2.40" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-backend 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "web-sys" -version = "0.3.6" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-webidl 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-webidl 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1181,7 +1239,7 @@ name = "weedle" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "nom 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1191,7 +1249,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1210,10 +1268,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1221,21 +1279,13 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "wincolor" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "wincolor" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1244,93 +1294,100 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] -"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" +"checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" -"checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f" -"checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" +"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" +"checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum ascii-canvas 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b385d69402821a1c254533a011a312531cbcc0e3e24f19bbb4747a5a2daf37e2" "checksum atty 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d0fd4c0631f06448cc45a6bbb3b710ebb7ff8ccb96a0800c994afe23a70d5df2" -"checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1" -"checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a" -"checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" +"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" +"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" +"checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4" +"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" "checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c" "checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" "checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" +"checksum bumpalo 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "20bd7761c869bd69b5bd9dbbb1bd84de2f3970b764172efc41e67a06b508c56e" "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" -"checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781" +"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" "checksum caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "808dab3318747be122cb31d36de18d4d1c81277a76f8332a02b81a3d73463d7f" -"checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" -"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" -"checksum clap 2.31.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0f16b89cbb9ee36d87483dc939fe9f1e13c05898d56d7b230a0d4dff033a536" +"checksum cc 1.0.31 (registry+https://github.com/rust-lang/crates.io-index)" = "c9ce8bb087aacff865633f0bd5aeaed910fe2fe55b55f4739527f2e023a2e53d" +"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" -"checksum digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00a49051fef47a72c9623101b19bd71924a45cca838826caae3eaa4d00772603" -"checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a" +"checksum digest 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "03b072242a8cbaf9c145665af9d250c59af3b958f83ed6824e13533cf76d5b90" +"checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" "checksum docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d8acd393692c503b168471874953a2531df0e9ab77d0b6bbc582395743300a4a" -"checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" +"checksum either 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c67353c641dc847124ea1902d69bd753dee9bb3beff9aa3662ecf86c971d1fac" "checksum ena 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe5a5078ac8c506d3e4430763b1ba9b609b1286913e7d08e581d1c2de9b7e5" -"checksum env_logger 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0e6e40ebb0e66918a37b38c7acab4e10d299e0463fe2af5d29b9cc86710cfd2a" -"checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e" -"checksum failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7efb22686e4a466b1ec1a15c2898f91fa9cb340452496dca654032de20ff95b9" -"checksum failure_derive 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "946d0e98a50d9831f5d589038d2ca7f8f455b1c21028c0db0e84116a12696426" +"checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" +"checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" +"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" +"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" -"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" "checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" -"checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" -"checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e" -"checksum itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "f58856976b776fedd95533137617a02fb25719f40e7d9b01c7043cd65474f450" -"checksum itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5adb58558dcd1d786b5f0bd15f3226ee23486e24b7b58304b60f64dc68e62606" -"checksum js-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58cfec35fd4a94f3cf357d5cb7da71c71cd52720c2f2a7320090a8db5f06f655" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" +"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" +"checksum js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "d4bda84b98977341bb6ba1086ecbd9eab8bc929de0f2923c118baf76c21dd0c8" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lalrpop 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ba451f7bd819b7afc99d4cf4bdcd5a4861e64955ba9680ac70df3a50625ad6cf" "checksum lalrpop-snap 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60013fd6be14317d43f47658b1440956a9ca48a9ed0257e0e0a59aac13e43a1f" "checksum lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60c6c48ba857cd700673ce88907cadcdd7e2cd7783ed02378537c5ffd4f6460c" -"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" -"checksum lexical 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4fac65df7e751b57bb3a334c346239cb4ce2601907d698726ceeb82a54ba4ef" -"checksum lexical-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "025babf624c0c2b4bed1373efd684d5d0b2eecd61138d26ec3eec77bf0f2e33d" -"checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c106ed999697325a540c43d66a8d5175668cd96d7eb0bdba03a3bd98256cd698" +"checksum lexical-core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e82e023e062f1d25f807ad182008fba1b46538e999f908a08cc0c29e084462e" +"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" -"checksum log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6fddaa003a65722a7fb9e26b0ce95921fe4ba590542ced664d8ce2fa26f9f3ac" -"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" -"checksum new_debug_unreachable 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0cdc457076c78ab54d5e0d6fa7c47981757f1e34dc39ff92787f217dede586c4" +"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" +"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" +"checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30" "checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17" -"checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" -"checksum nom 4.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9c349f68f25f596b9f44cf0e7c69752a5c633b0550c3ff849518bfba0233774a" -"checksum num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "10b8423ea72ec64751198856a853e07b37087cfc9b53a87ecb19bff67b6d1320" +"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" +"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +"checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" "checksum num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "107b9be86cd2481930688277b675b0114578227f034674726605b8a482d8baf8" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" "checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" "checksum petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" -"checksum phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "05a079dd052e7b674d21cb31cbb6c05efd56a2cd2827db7692e2f1a507ebd998" -"checksum phf_shared 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "c2261d544c2bb6aa3b10022b0be371b9c7c64f762ef28c6f5d4f1ef6d97b5930" +"checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" +"checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -"checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7" "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" -"checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" -"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" -"checksum rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e464cd887e869cddcae8792a4ee31d23c7edd516700695608f5b98c67ee0131c" -"checksum rand_core 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1961a422c4d189dfb50ffa9320bf1f2a9bd54ecb92792fb9477f99a1045f3372" -"checksum rand_core 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0905b6b7079ec73b314d4c748701f6931eb79fd97c668caa3f1899b22b32c6db" -"checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" +"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" +"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" +"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" +"checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" +"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" +"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" +"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" +"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" +"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" +"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" +"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" +"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" -"checksum redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "214a97e49be64fd2c86f568dd0cb2c757d2cc53de95b273b6ad0a1c908482f26" +"checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828" "checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" -"checksum regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3d8c9f33201f46669484bacc312b00e7541bed6aaf296dffe2bb4e0ac6b8ce2a" +"checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f" "checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" -"checksum regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1ac0f60d675cc6cf13a20ec076568254472551051ad5dd050364d70671bf6b" -"checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" +"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" +"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6de8ecd7fad7731f306f69b6e10ec5a3178c61e464dcc06979427aa4cc891145" "checksum rustyline 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6010155119d53aac4f5b987cb8f6ea913d0d64d9b237da36f8f96a90cb3f5385" @@ -1338,55 +1395,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "e9a2d9a9ac5120e0f768801ca2b58ad6eec929dc9d1d616c162f208869c2ce95" -"checksum serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "0a90213fa7e0f5eac3f7afe2d5ff6b088af515052cc7303bd68c7e3b91a3fb79" -"checksum serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "44dd2cfde475037451fa99b7e5df77aa3cfd1536575fa8e7a538ab36dcde49ae" +"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560" +"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" +"checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" -"checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537" +"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +"checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" "checksum stackvector 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c049c77bf85fbc036484c97b008276d539d9ebff9dfbde37b632ebcd5b8746b6" "checksum static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" "checksum statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "10102ac8d55e35db2b3fafc26f81ba8647da2e15879ab686a67e6d19af2685e8" "checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423" -"checksum string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35293b05cf1494e8ddd042a7df6756bf18d07f42d234f32e71dce8a7aabb0191" +"checksum string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eea1eee654ef80933142157fdad9dd8bc43cf7c74e999e369263496f04ff4da" "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" "checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" -"checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" "checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" -"checksum synstructure 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "85bb9b7550d063ea184027c9b8c20ac167cd36d3e06b3a40bceb9d746dc1a7b7" +"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" -"checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83" "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" "checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" -"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" -"checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" -"checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" +"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" +"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" "checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum wasm-bindgen 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "91f95b8f30407b9ca0c2de157281d3828bbed1fc1f55bea6eb54f40c52ec75ec" -"checksum wasm-bindgen-backend 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "ab7c242ebcb45bae45340986c48d1853eb2c1c52ff551f7724951b62a2c51429" -"checksum wasm-bindgen-futures 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d1784e7401a90119b2a4e8ec9c8d37c3594c3e3bb9ba24533ee1969eebaf0485" -"checksum wasm-bindgen-macro 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "6e353f83716dec9a3597b5719ef88cb6c9e461ec16528f38aa023d3224b4e569" -"checksum wasm-bindgen-macro-support 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "3cc90b65fe69c3dd5a09684517dc79f42b847baa2d479c234d125e0a629d9b0a" -"checksum wasm-bindgen-shared 0.2.29 (registry+https://github.com/rust-lang/crates.io-index)" = "a71a37df4f5845025f96f279d20bbe5b19cbcb77f5410a3a90c6c544d889a162" -"checksum wasm-bindgen-webidl 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)" = "b064b8b2336b5a6bf5f31bc95fc1310842395df29877d910cb6f8f791070f319" -"checksum web-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d7c588c2e5a091bc4532c5a87032955f9133b644e868b54d08ead0185dcc5b9" +"checksum wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "9742fc4860f47bede1090a5e4b0cfc33afcd70cfdf45dd28f2cfb02d4662b0dd" +"checksum wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d7f35ecbb4180513cdb9b298543321bcb278670730415cbb3205ff2c66a477" +"checksum wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "67bfbec85e48e8915b6acfa1c4d90b770ce033c96dc2067688f3bccd00d3b9e1" +"checksum wasm-bindgen-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "e3c86b06bcd28e92e87d2c2ad208889b2f69ea33f79810b91ef660cc3de65a4c" +"checksum wasm-bindgen-macro-support 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "81d7338dd8c67e193d8ef18e5802dc03d8710456baa792c1c2e66847e57fd389" +"checksum wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "0d57c3b66f2f3e4d96b50f49b7b7e2f4cfcddc88b15744433c98c5c105b26672" +"checksum wasm-bindgen-webidl 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "fc7052a577eefce17a439edfce5c2eedc8432fe7457bbe949624cdf946233fa7" +"checksum web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "72d02003924c77af871ad74216f828b6e32af776cbb3f5dab3dcde2f2713c012" "checksum weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26a4c67f132386d965390b8a734d5d10adbcd30eb5cc74bd9229af8b83f10044" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" +"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -"checksum winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "afc5508759c5bf4285e61feb862b6083c8480aec864fa17a81fdec6f69b461ab" +"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb06499a3a4d44302791052df005d5232b927ed1a9658146d842165c4de7767" "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" "checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index ecdb4b1102..909955b6df 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -125,12 +125,10 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { || objtype::isinstance(&py_obj, &vm.ctx.bytearray_type()) { let bytes = objbytes::get_value(&py_obj); - let arr = Uint8Array::new_with_length(bytes.len() as u32); - for (i, byte) in bytes.iter().enumerate() { - Reflect::set(&arr, &(i as u32).into(), &(*byte).into()) - .expect("setting Uint8Array value failed"); + unsafe { + let view = Uint8Array::view(&bytes); + view.slice(0, bytes.len() as u32).into() } - arr.into() } else { match vm.serialize(&py_obj) { Ok(json) => js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED), @@ -182,9 +180,8 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { .cloned() .unwrap_or_else(|| js_val.unchecked_ref::().buffer()), ); - let mut vec = Vec::with_capacity(u8_array.length() as usize); - // TODO: use Uint8Array::copy_to once updating js_sys doesn't break everything - u8_array.for_each(&mut |byte, _, _| vec.push(byte)); + let mut vec = vec![0; u8_array.length() as usize]; + u8_array.copy_to(&mut vec); vm.ctx.new_bytes(vec) } else { let dict = vm.new_dict(); From c2e1e9b0dc0a4180a1ccb8c8bb5a4c7e5dee27c2 Mon Sep 17 00:00:00 2001 From: Joey Date: Sun, 24 Mar 2019 08:51:26 -0700 Subject: [PATCH 058/884] Convert range to new args style --- vm/src/obj/objint.rs | 7 + vm/src/obj/objrange.rs | 322 ++++++++++++++++------------------------- 2 files changed, 129 insertions(+), 200 deletions(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 7c19968da3..345fe90c31 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -1,3 +1,4 @@ +use std::fmt; use std::hash::{Hash, Hasher}; use num_bigint::{BigInt, ToBigInt}; @@ -22,6 +23,12 @@ pub struct PyInt { pub value: BigInt, } +impl fmt::Display for PyInt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + BigInt::fmt(&self.value, f) + } +} + pub type PyIntRef = PyRef; impl PyInt { diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 468490eeaa..9ae2fcd541 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -3,17 +3,15 @@ use std::ops::Mul; use num_bigint::{BigInt, Sign}; use num_integer::Integer; -use num_traits::{One, Signed, ToPrimitive, Zero}; +use num_traits::{One, Signed, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{ - Either, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, -}; +use crate::pyobject::{Either, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; -use super::objint::{self, PyInt, PyIntRef}; +use super::objint::{PyInt, PyIntRef}; use super::objslice::PySliceRef; -use super::objtype::{self, PyClassRef}; +use super::objtype::PyClassRef; #[derive(Debug, Clone)] pub struct PyRange { @@ -31,26 +29,6 @@ impl PyValue for PyRange { } impl PyRange { - #[inline] - pub fn try_len(&self) -> Option { - match self.step.sign() { - Sign::Plus if self.start < self.stop => ((&self.stop - &self.start - 1usize) - / &self.step) - .to_usize() - .map(|sz| sz + 1), - Sign::Minus if self.start > self.stop => ((&self.start - &self.stop - 1usize) - / (-&self.step)) - .to_usize() - .map(|sz| sz + 1), - _ => Some(0), - } - } - - #[inline] - pub fn len(&self) -> usize { - self.try_len().unwrap() - } - #[inline] fn offset(&self, value: &BigInt) -> Option { match self.step.sign() { @@ -60,14 +38,6 @@ impl PyRange { } } - #[inline] - pub fn contains(&self, value: &BigInt) -> bool { - match self.offset(value) { - Some(ref offset) => offset.is_multiple_of(&self.step), - None => false, - } - } - #[inline] pub fn index_of(&self, value: &BigInt) -> Option { match self.offset(value) { @@ -78,15 +48,6 @@ impl PyRange { } } - #[inline] - pub fn count(&self, value: &BigInt) -> usize { - if self.index_of(value).is_some() { - 1 - } else { - 0 - } - } - #[inline] pub fn is_empty(&self) -> bool { (self.start <= self.stop && self.step.is_negative()) @@ -113,40 +74,6 @@ impl PyRange { None } } - - #[inline] - pub fn reversed(&self) -> Self { - // compute the last element that is actually contained within the range - // this is the new start - let remainder = ((&self.stop - &self.start) % &self.step).abs(); - let start = if remainder.is_zero() { - &self.stop - &self.step - } else { - &self.stop - &remainder - }; - - match self.step.sign() { - Sign::Plus => PyRange { - start, - stop: &self.start - 1, - step: -&self.step, - }, - Sign::Minus => PyRange { - start, - stop: &self.start + 1, - step: -&self.step, - }, - Sign::NoSign => unreachable!(), - } - } - - pub fn repr(&self) -> String { - if self.step == BigInt::one() { - format!("range({}, {})", self.start, self.stop) - } else { - format!("range({}, {}, {})", self.start, self.stop, self.step) - } - } } pub fn get_value(obj: &PyObjectRef) -> PyRange { @@ -165,20 +92,20 @@ pub fn init(context: &PyContext) { When step is given, it specifies the increment (or decrement)."; extend_class!(context, range_type, { - "__bool__" => context.new_rustfunc(range_bool), - "__contains__" => context.new_rustfunc(range_contains), + "__bool__" => context.new_rustfunc(PyRangeRef::bool), + "__contains__" => context.new_rustfunc(PyRangeRef::contains), "__doc__" => context.new_str(range_doc.to_string()), "__getitem__" => context.new_rustfunc(PyRangeRef::getitem), - "__iter__" => context.new_rustfunc(range_iter), - "__len__" => context.new_rustfunc(range_len), + "__iter__" => context.new_rustfunc(PyRangeRef::iter), + "__len__" => context.new_rustfunc(PyRangeRef::len), "__new__" => context.new_rustfunc(range_new), - "__repr__" => context.new_rustfunc(range_repr), - "__reversed__" => context.new_rustfunc(range_reversed), - "count" => context.new_rustfunc(range_count), - "index" => context.new_rustfunc(range_index), - "start" => context.new_property(range_start), - "step" => context.new_property(range_step), - "stop" => context.new_property(range_stop) + "__repr__" => context.new_rustfunc(PyRangeRef::repr), + "__reversed__" => context.new_rustfunc(PyRangeRef::reversed), + "count" => context.new_rustfunc(PyRangeRef::count), + "index" => context.new_rustfunc(PyRangeRef::index), + "start" => context.new_property(PyRangeRef::start), + "stop" => context.new_property(PyRangeRef::stop), + "step" => context.new_property(PyRangeRef::step), }); } @@ -212,6 +139,113 @@ impl PyRangeRef { .into_ref_with_type(vm, cls) } + fn start(self, _vm: &VirtualMachine) -> BigInt { + self.start.clone() + } + + fn stop(self, _vm: &VirtualMachine) -> BigInt { + self.stop.clone() + } + + fn step(self, _vm: &VirtualMachine) -> BigInt { + self.step.clone() + } + + fn iter(self: PyRangeRef, _vm: &VirtualMachine) -> PyIteratorValue { + PyIteratorValue { + position: Cell::new(0), + iterated_obj: self.into_object(), + } + } + + fn reversed(self: PyRangeRef, vm: &VirtualMachine) -> PyIteratorValue { + // compute the last element that is actually contained within the range + // this is the new start + let remainder = ((&self.stop - &self.start) % &self.step).abs(); + let start = if remainder.is_zero() { + &self.stop - &self.step + } else { + &self.stop - &remainder + }; + + let reversed = match self.step.sign() { + Sign::Plus => PyRange { + start, + stop: &self.start - 1, + step: -&self.step, + }, + Sign::Minus => PyRange { + start, + stop: &self.start + 1, + step: -&self.step, + }, + Sign::NoSign => unreachable!(), + }; + PyIteratorValue { + position: Cell::new(0), + iterated_obj: reversed.into_ref(vm).into_object(), + } + } + + fn len(self, _vm: &VirtualMachine) -> PyInt { + match self.step.sign() { + Sign::Plus if self.start < self.stop => { + PyInt::new((&self.stop - &self.start - 1usize) / &self.step + 1) + } + Sign::Minus if self.start > self.stop => { + PyInt::new((&self.start - &self.stop - 1usize) / (-&self.step) + 1) + } + Sign::Plus | Sign::Minus => PyInt::new(0), + Sign::NoSign => unreachable!(), + } + } + + fn repr(self, _vm: &VirtualMachine) -> String { + if self.step.is_one() { + format!("range({}, {})", self.start, self.stop) + } else { + format!("range({}, {}, {})", self.start, self.stop, self.step) + } + } + + fn bool(self, _vm: &VirtualMachine) -> bool { + !self.is_empty() + } + + fn contains(self, needle: PyObjectRef, _vm: &VirtualMachine) -> bool { + if let Ok(int) = needle.downcast::() { + match self.offset(&int.value) { + Some(ref offset) => offset.is_multiple_of(&self.step), + None => false, + } + } else { + false + } + } + + fn index(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Ok(int) = needle.downcast::() { + match self.index_of(&int.value) { + Some(idx) => Ok(PyInt::new(idx)), + None => Err(vm.new_value_error(format!("{} is not in range", int))), + } + } else { + Err(vm.new_value_error("sequence.index(x): x not in sequence".to_string())) + } + } + + fn count(self, item: PyObjectRef, _vm: &VirtualMachine) -> PyInt { + if let Ok(int) = item.downcast::() { + if self.index_of(&int.value).is_some() { + PyInt::new(1) + } else { + PyInt::new(0) + } + } else { + PyInt::new(0) + } + } + fn getitem(self, subscript: Either, vm: &VirtualMachine) -> PyResult { match subscript { Either::A(index) => { @@ -271,115 +305,3 @@ fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(range.into_object()) } - -fn range_iter(range: PyRangeRef, _vm: &VirtualMachine) -> PyIteratorValue { - PyIteratorValue { - position: Cell::new(0), - iterated_obj: range.into_object(), - } -} - -fn range_reversed(zelf: PyRangeRef, vm: &VirtualMachine) -> PyIteratorValue { - let range = zelf.reversed(); - - PyIteratorValue { - position: Cell::new(0), - iterated_obj: range.into_ref(vm).into_object(), - } -} - -fn range_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); - - if let Some(len) = get_value(zelf).try_len() { - Ok(vm.ctx.new_int(len)) - } else { - Err(vm.new_overflow_error("Python int too large to convert to Rust usize".to_string())) - } -} - -fn range_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); - - let repr = get_value(zelf).repr(); - - Ok(vm.ctx.new_str(repr)) -} - -fn range_bool(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); - - let len = get_value(zelf).len(); - - Ok(vm.ctx.new_bool(len > 0)) -} - -fn range_contains(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.range_type())), (needle, None)] - ); - - let range = get_value(zelf); - - let result = if objtype::isinstance(needle, &vm.ctx.int_type()) { - range.contains(&objint::get_value(needle)) - } else { - false - }; - - Ok(vm.ctx.new_bool(result)) -} - -fn range_index(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.range_type())), (needle, None)] - ); - - let range = get_value(zelf); - - if objtype::isinstance(needle, &vm.ctx.int_type()) { - let needle = objint::get_value(needle); - - match range.index_of(&needle) { - Some(idx) => Ok(vm.ctx.new_int(idx)), - None => Err(vm.new_value_error(format!("{} is not in range", needle))), - } - } else { - Err(vm.new_value_error("sequence.index(x): x not in sequence".to_string())) - } -} - -fn range_count(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.range_type())), (item, None)] - ); - - let range = get_value(zelf); - - if objtype::isinstance(item, &vm.ctx.int_type()) { - Ok(vm.ctx.new_int(range.count(&objint::get_value(item)))) - } else { - Ok(vm.ctx.new_int(0)) - } -} - -fn range_start(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); - Ok(vm.ctx.new_int(get_value(zelf).start)) -} - -fn range_stop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); - Ok(vm.ctx.new_int(get_value(zelf).stop)) -} - -fn range_step(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.range_type()))]); - Ok(vm.ctx.new_int(get_value(zelf).step)) -} From 9a0113deedde20c7791f6c07936dc3e0c4b21e40 Mon Sep 17 00:00:00 2001 From: Joey Date: Sun, 24 Mar 2019 09:57:26 -0700 Subject: [PATCH 059/884] range: represent w/ int refs --- tests/snippets/builtin_range.py | 4 ++ vm/src/obj/objint.rs | 4 ++ vm/src/obj/objrange.rs | 122 +++++++++++++++++--------------- 3 files changed, 73 insertions(+), 57 deletions(-) diff --git a/tests/snippets/builtin_range.py b/tests/snippets/builtin_range.py index 89014880ce..2ea71f7a7f 100644 --- a/tests/snippets/builtin_range.py +++ b/tests/snippets/builtin_range.py @@ -50,3 +50,7 @@ assert list(reversed(range(5))) == [4, 3, 2, 1, 0] assert list(reversed(range(5, 0, -1))) == [1, 2, 3, 4, 5] assert list(reversed(range(1,10,5))) == [6, 1] + +# range retains the original int refs +i = 2**64 +assert range(i).stop is i diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 345fe90c31..fddc759bc4 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -35,6 +35,10 @@ impl PyInt { pub fn new>(i: T) -> Self { PyInt { value: i.into() } } + + pub fn as_bigint(&self) -> &BigInt { + &self.value + } } impl IntoPyObject for BigInt { diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 9ae2fcd541..380754b5f6 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -15,11 +15,9 @@ use super::objtype::PyClassRef; #[derive(Debug, Clone)] pub struct PyRange { - // Unfortunately Rust's built in range type doesn't support things like indexing - // or ranges where start > end so we need to roll our own. - pub start: BigInt, - pub stop: BigInt, - pub step: BigInt, + pub start: PyIntRef, + pub stop: PyIntRef, + pub step: PyIntRef, } impl PyValue for PyRange { @@ -31,32 +29,36 @@ impl PyValue for PyRange { impl PyRange { #[inline] fn offset(&self, value: &BigInt) -> Option { - match self.step.sign() { - Sign::Plus if *value >= self.start && *value < self.stop => Some(value - &self.start), - Sign::Minus if *value <= self.start && *value > self.stop => Some(&self.start - value), + let start = self.start.as_bigint(); + let stop = self.stop.as_bigint(); + let step = self.step.as_bigint(); + match step.sign() { + Sign::Plus if value >= start && value < stop => Some(value - start), + Sign::Minus if value <= self.start.as_bigint() && value > stop => Some(start - value), _ => None, } } #[inline] pub fn index_of(&self, value: &BigInt) -> Option { + let step = self.step.as_bigint(); match self.offset(value) { - Some(ref offset) if offset.is_multiple_of(&self.step) => { - Some((offset / &self.step).abs()) - } + Some(ref offset) if offset.is_multiple_of(step) => Some((offset / step).abs()), Some(_) | None => None, } } #[inline] pub fn is_empty(&self) -> bool { - (self.start <= self.stop && self.step.is_negative()) - || (self.start >= self.stop && self.step.is_positive()) + let start = self.start.as_bigint(); + let stop = self.stop.as_bigint(); + let step = self.step.as_bigint(); + (start <= stop && step.is_negative()) || (start >= stop && step.is_positive()) } #[inline] pub fn forward(&self) -> bool { - self.start < self.stop + self.start.as_bigint() < self.stop.as_bigint() } #[inline] @@ -64,10 +66,14 @@ impl PyRange { where &'a BigInt: Mul, { - let result = &self.start + &self.step * index; + let start = self.start.as_bigint(); + let stop = self.stop.as_bigint(); + let step = self.step.as_bigint(); + + let result = start + step * index; - if (self.forward() && !self.is_empty() && result < self.stop) - || (!self.forward() && !self.is_empty() && result > self.stop) + if (self.forward() && !self.is_empty() && &result < stop) + || (!self.forward() && !self.is_empty() && &result > stop) { Some(result) } else { @@ -114,9 +120,9 @@ type PyRangeRef = PyRef; impl PyRangeRef { fn new(cls: PyClassRef, stop: PyIntRef, vm: &VirtualMachine) -> PyResult { PyRange { - start: Zero::zero(), - stop: stop.value.clone(), - step: One::one(), + start: PyInt::new(BigInt::zero()).into_ref(vm), + stop: stop.clone(), + step: PyInt::new(BigInt::one()).into_ref(vm), } .into_ref_with_type(vm, cls) } @@ -129,25 +135,24 @@ impl PyRangeRef { vm: &VirtualMachine, ) -> PyResult { PyRange { - start: start.value.clone(), - stop: stop.value.clone(), + start: start, + stop: stop, step: step .into_option() - .map(|i| i.value.clone()) - .unwrap_or_else(One::one), + .unwrap_or_else(|| PyInt::new(BigInt::one()).into_ref(vm)), } .into_ref_with_type(vm, cls) } - fn start(self, _vm: &VirtualMachine) -> BigInt { + fn start(self, _vm: &VirtualMachine) -> PyIntRef { self.start.clone() } - fn stop(self, _vm: &VirtualMachine) -> BigInt { + fn stop(self, _vm: &VirtualMachine) -> PyIntRef { self.stop.clone() } - fn step(self, _vm: &VirtualMachine) -> BigInt { + fn step(self, _vm: &VirtualMachine) -> PyIntRef { self.step.clone() } @@ -159,28 +164,31 @@ impl PyRangeRef { } fn reversed(self: PyRangeRef, vm: &VirtualMachine) -> PyIteratorValue { + let start = self.start.as_bigint(); + let stop = self.stop.as_bigint(); + let step = self.step.as_bigint(); + // compute the last element that is actually contained within the range // this is the new start - let remainder = ((&self.stop - &self.start) % &self.step).abs(); - let start = if remainder.is_zero() { - &self.stop - &self.step + let remainder = ((stop - start) % step).abs(); + let new_start = if remainder.is_zero() { + stop - step } else { - &self.stop - &remainder + stop - &remainder }; - let reversed = match self.step.sign() { - Sign::Plus => PyRange { - start, - stop: &self.start - 1, - step: -&self.step, - }, - Sign::Minus => PyRange { - start, - stop: &self.start + 1, - step: -&self.step, - }, + let new_stop: BigInt = match step.sign() { + Sign::Plus => start - 1, + Sign::Minus => start + 1, Sign::NoSign => unreachable!(), }; + + let reversed = PyRange { + start: PyInt::new(new_start).into_ref(vm), + stop: PyInt::new(new_stop).into_ref(vm), + step: PyInt::new(-step).into_ref(vm), + }; + PyIteratorValue { position: Cell::new(0), iterated_obj: reversed.into_ref(vm).into_object(), @@ -188,20 +196,20 @@ impl PyRangeRef { } fn len(self, _vm: &VirtualMachine) -> PyInt { - match self.step.sign() { - Sign::Plus if self.start < self.stop => { - PyInt::new((&self.stop - &self.start - 1usize) / &self.step + 1) - } - Sign::Minus if self.start > self.stop => { - PyInt::new((&self.start - &self.stop - 1usize) / (-&self.step) + 1) - } + let start = self.start.as_bigint(); + let stop = self.stop.as_bigint(); + let step = self.step.as_bigint(); + + match step.sign() { + Sign::Plus if start < stop => PyInt::new((stop - start - 1usize) / step + 1), + Sign::Minus if start > stop => PyInt::new((start - stop - 1usize) / (-step) + 1), Sign::Plus | Sign::Minus => PyInt::new(0), Sign::NoSign => unreachable!(), } } fn repr(self, _vm: &VirtualMachine) -> String { - if self.step.is_one() { + if self.step.as_bigint().is_one() { format!("range({}, {})", self.start, self.stop) } else { format!("range({}, {}, {})", self.start, self.stop, self.step) @@ -215,7 +223,7 @@ impl PyRangeRef { fn contains(self, needle: PyObjectRef, _vm: &VirtualMachine) -> bool { if let Ok(int) = needle.downcast::() { match self.offset(&int.value) { - Some(ref offset) => offset.is_multiple_of(&self.step), + Some(ref offset) => offset.is_multiple_of(self.step.as_bigint()), None => false, } } else { @@ -256,9 +264,9 @@ impl PyRangeRef { } } Either::B(slice) => { - let new_start = if let Some(int) = slice.start.clone() { + let new_start = if let Some(int) = slice.start.as_ref() { if let Some(i) = self.get(int) { - i + PyInt::new(i).into_ref(vm) } else { self.start.clone() } @@ -266,9 +274,9 @@ impl PyRangeRef { self.start.clone() }; - let new_end = if let Some(int) = slice.stop.clone() { + let new_end = if let Some(int) = slice.stop.as_ref() { if let Some(i) = self.get(int) { - i + PyInt::new(i).into_ref(vm) } else { self.stop.clone() } @@ -276,8 +284,8 @@ impl PyRangeRef { self.stop.clone() }; - let new_step = if let Some(int) = slice.step.clone() { - int * self.step.clone() + let new_step = if let Some(int) = slice.step.as_ref() { + PyInt::new(int * self.step.as_bigint()).into_ref(vm) } else { self.step.clone() }; From f2d562a4ccf4b3ae4d6bf6bf3f9e6d157d95ce29 Mon Sep 17 00:00:00 2001 From: Joey Date: Sun, 24 Mar 2019 10:05:25 -0700 Subject: [PATCH 060/884] pyint: use as_bigint() everywhere --- vm/src/frame.rs | 2 +- vm/src/obj/objbool.rs | 4 ++-- vm/src/obj/objenumerate.rs | 2 +- vm/src/obj/objint.rs | 3 +-- vm/src/obj/objrange.rs | 8 ++++---- vm/src/obj/objsequence.rs | 2 +- 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 2a0951ab39..b9e6cf9653 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -413,7 +413,7 @@ impl Frame { if x.is(&vm.ctx.none()) { None } else if let Some(i) = x.payload::() { - Some(i.value.clone()) + Some(i.as_bigint().clone()) } else { panic!("Expect Int or None as BUILD_SLICE arguments") } diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index e21a3a3296..d4ef4b0fbc 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -25,7 +25,7 @@ pub fn boolval(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { Ok(if let Ok(f) = vm.get_method(obj.clone(), "__bool__") { let bool_res = vm.invoke(f, PyFuncArgs::default())?; match bool_res.payload::() { - Some(i) => !i.value.is_zero(), + Some(i) => !i.as_bigint().is_zero(), None => return Err(vm.new_type_error(String::from("TypeError"))), } } else { @@ -59,7 +59,7 @@ pub fn not(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { // Retrieve inner int value: pub fn get_value(obj: &PyObjectRef) -> bool { - !obj.payload::().unwrap().value.is_zero() + !obj.payload::().unwrap().as_bigint().is_zero() } fn bool_repr(vm: &VirtualMachine, args: PyFuncArgs) -> Result { diff --git a/vm/src/obj/objenumerate.rs b/vm/src/obj/objenumerate.rs index 0b0e5d4488..68d1e49f6b 100644 --- a/vm/src/obj/objenumerate.rs +++ b/vm/src/obj/objenumerate.rs @@ -32,7 +32,7 @@ fn enumerate_new( vm: &VirtualMachine, ) -> PyResult { let counter = match start { - OptionalArg::Present(start) => start.value.clone(), + OptionalArg::Present(start) => start.as_bigint().clone(), OptionalArg::Missing => BigInt::zero(), }; diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index fddc759bc4..dd40f06a42 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -19,8 +19,7 @@ use crate::obj::objtype::PyClassRef; #[derive(Debug)] pub struct PyInt { - // TODO: shouldn't be public - pub value: BigInt, + value: BigInt, } impl fmt::Display for PyInt { diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 380754b5f6..04c9c273b1 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -222,7 +222,7 @@ impl PyRangeRef { fn contains(self, needle: PyObjectRef, _vm: &VirtualMachine) -> bool { if let Ok(int) = needle.downcast::() { - match self.offset(&int.value) { + match self.offset(int.as_bigint()) { Some(ref offset) => offset.is_multiple_of(self.step.as_bigint()), None => false, } @@ -233,7 +233,7 @@ impl PyRangeRef { fn index(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { if let Ok(int) = needle.downcast::() { - match self.index_of(&int.value) { + match self.index_of(int.as_bigint()) { Some(idx) => Ok(PyInt::new(idx)), None => Err(vm.new_value_error(format!("{} is not in range", int))), } @@ -244,7 +244,7 @@ impl PyRangeRef { fn count(self, item: PyObjectRef, _vm: &VirtualMachine) -> PyInt { if let Ok(int) = item.downcast::() { - if self.index_of(&int.value).is_some() { + if self.index_of(int.as_bigint()).is_some() { PyInt::new(1) } else { PyInt::new(0) @@ -257,7 +257,7 @@ impl PyRangeRef { fn getitem(self, subscript: Either, vm: &VirtualMachine) -> PyResult { match subscript { Either::A(index) => { - if let Some(value) = self.get(index.value.clone()) { + if let Some(value) = self.get(index.as_bigint()) { Ok(PyInt::new(value).into_ref(vm).into_object()) } else { Err(vm.new_index_error("range object index out of range".to_string())) diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index 83d0f9f9f8..b8d91e5909 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -144,7 +144,7 @@ pub fn get_item( subscript: PyObjectRef, ) -> PyResult { if let Some(i) = subscript.payload::() { - return match i.value.to_i32() { + return match i.as_bigint().to_i32() { Some(value) => { if let Some(pos_index) = elements.to_vec().get_pos(value) { let obj = elements[pos_index].clone(); From ca11e7dea79ed413138ee85acdd06b1070801ae6 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 24 Mar 2019 20:16:38 +0100 Subject: [PATCH 061/884] Add support for emoji as name. --- Cargo.lock | 42 ++++++++++++++++++++++++++++++++++++++++++ parser/Cargo.toml | 1 + parser/src/lexer.rs | 24 +++++++++++++++++------- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 57dadb57b3..6193772c40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -760,6 +760,7 @@ dependencies = [ "num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-emoji-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1044,6 +1045,42 @@ name = "ucd-util" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unic-emoji-char" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-common 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-normalization" version = "0.1.7" @@ -1364,6 +1401,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum ucd-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fd2be2d6639d0f8fe6cdda291ad456e23629558d466e2789d2c3e9892bda285d" +"checksum unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +"checksum unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" +"checksum unic-common 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" +"checksum unic-emoji-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d" +"checksum unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" "checksum unicode-normalization 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "6a0180bc61fc5a987082bfa111f4cc95c4caff7f9799f3e46df09163a937aa25" "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 7545a27d44..f2f8e5a6b4 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -15,3 +15,4 @@ regex="0.2.2" num-bigint = "0.2" num-traits = "0.2" unicode-xid = "0.1.0" +unic-emoji-char = "0.9.0" diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index db8fee2c70..defa5e2322 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -1,6 +1,7 @@ //! This module takes care of lexing python source text. This means source //! code is translated into separate tokens. +extern crate unic_emoji_char; extern crate unicode_xid; pub use super::token::Tok; @@ -9,6 +10,7 @@ use num_traits::Num; use std::cmp::Ordering; use std::collections::HashMap; use std::str::FromStr; +use unic_emoji_char::is_emoji_presentation; use unicode_xid::UnicodeXID; #[derive(Clone, Copy, PartialEq, Debug)] @@ -545,7 +547,7 @@ where fn is_identifier_start(&self, c: char) -> bool { match c { - 'a'..='z' | 'A'..='Z' | '_' => true, + '_' => true, c => UnicodeXID::is_xid_start(c), } } @@ -553,7 +555,7 @@ where fn is_identifier_continuation(&self) -> bool { if let Some(c) = self.chr0 { match c { - 'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => true, + '_' | '0'..='9' => true, c => UnicodeXID::is_xid_continue(c), } } else { @@ -705,6 +707,18 @@ where // First check identifier: if self.is_identifier_start(c) { return Some(self.lex_identifier()); + } else if is_emoji_presentation(c) { + let tok_start = self.get_pos(); + self.next_char(); + let tok_end = self.get_pos(); + println!("Emoji: {}", c); + return Some(Ok(( + tok_start, + Tok::Name { + name: c.to_string(), + }, + tok_end, + ))); } else { match c { '0'..='9' => return Some(self.lex_number()), @@ -801,11 +815,7 @@ where } _ => { let tok_end = self.get_pos(); - return Some(Ok(( - tok_start, - Tok::DoubleSlash, - tok_end, - ))); + return Some(Ok((tok_start, Tok::DoubleSlash, tok_end))); } } } From 3562b8f59c2eccb034ac5c0f3de7638fcd586140 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 25 Mar 2019 11:21:01 +0000 Subject: [PATCH 062/884] Store class attributes inside PyClass struct. --- vm/src/obj/objtype.rs | 65 ++++++++++++++++++++++--------------------- vm/src/pyobject.rs | 12 ++++++-- 2 files changed, 42 insertions(+), 35 deletions(-) diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 89bd8f34a5..6399d68c9b 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -22,6 +22,7 @@ pub struct PyClass { pub name: String, pub mro: Vec, pub subclasses: RefCell>, + pub attributes: RefCell, } impl fmt::Display for PyClass { @@ -149,6 +150,25 @@ impl PyClassRef { } } + fn set_attr( + self, + attr_name: PyStringRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + if let Some(attr) = class_get_attr(&self.type_pyref(), &attr_name.value) { + if let Some(descriptor) = class_get_attr(&attr.type_pyref(), "__set__") { + vm.invoke(descriptor, vec![attr, self.into_object(), value])?; + return Ok(()); + } + } + + self.attributes + .borrow_mut() + .insert(attr_name.to_string(), value); + Ok(()) + } + fn subclasses(self, _vm: &VirtualMachine) -> PyList { let mut subclasses = self.subclasses.borrow_mut(); subclasses.retain(|x| x.upgrade().is_some()); @@ -181,6 +201,7 @@ pub fn init(ctx: &PyContext) { "__repr__" => ctx.new_rustfunc(PyClassRef::repr), "__prepare__" => ctx.new_rustfunc(PyClassRef::prepare), "__getattribute__" => ctx.new_rustfunc(PyClassRef::getattribute), + "__setattr__" => ctx.new_rustfunc(PyClassRef::set_attr), "__subclasses__" => ctx.new_rustfunc(PyClassRef::subclasses), "__getattribute__" => ctx.new_rustfunc(PyClassRef::getattribute), "__instancecheck__" => ctx.new_rustfunc(PyClassRef::instance_check), @@ -260,32 +281,13 @@ pub fn type_call(class: PyClassRef, args: Args, kwargs: KwArgs, vm: &VirtualMach Ok(obj) } -// Very private helper function for class_get_attr -fn class_get_attr_in_dict(class: &PyClassRef, attr_name: &str) -> Option { - if let Some(ref dict) = class.as_object().dict { - dict.borrow().get(attr_name).cloned() - } else { - panic!("Only classes should be in MRO!"); - } -} - -// Very private helper function for class_has_attr -fn class_has_attr_in_dict(class: &PyClassRef, attr_name: &str) -> bool { - if let Some(ref dict) = class.as_object().dict { - dict.borrow().contains_key(attr_name) - } else { - panic!("All classes are expected to have dicts!"); - } -} - // This is the internal get_attr implementation for fast lookup on a class. -pub fn class_get_attr(zelf: &PyClassRef, attr_name: &str) -> Option { - let mro = &zelf.mro; - if let Some(item) = class_get_attr_in_dict(zelf, attr_name) { +pub fn class_get_attr(class: &PyClassRef, attr_name: &str) -> Option { + if let Some(item) = class.attributes.borrow().get(attr_name).cloned() { return Some(item); } - for class in mro { - if let Some(item) = class_get_attr_in_dict(class, attr_name) { + for class in &class.mro { + if let Some(item) = class.attributes.borrow().get(attr_name).cloned() { return Some(item); } } @@ -293,12 +295,12 @@ pub fn class_get_attr(zelf: &PyClassRef, attr_name: &str) -> Option } // This is the internal has_attr implementation for fast lookup on a class. -pub fn class_has_attr(zelf: &PyClassRef, attr_name: &str) -> bool { - class_has_attr_in_dict(zelf, attr_name) - || zelf +pub fn class_has_attr(class: &PyClassRef, attr_name: &str) -> bool { + class.attributes.borrow().contains_key(attr_name) + || class .mro .iter() - .any(|d| class_has_attr_in_dict(d, attr_name)) + .any(|c| c.attributes.borrow().contains_key(attr_name)) } pub fn get_attributes(cls: PyClassRef) -> PyAttributes { @@ -309,10 +311,8 @@ pub fn get_attributes(cls: PyClassRef) -> PyAttributes { base_classes.reverse(); for bc in base_classes { - if let Some(ref dict) = &bc.as_object().dict { - for (name, value) in dict.borrow().iter() { - attributes.insert(name.to_string(), value.clone()); - } + for (name, value) in bc.attributes.borrow().iter() { + attributes.insert(name.to_string(), value.clone()); } } @@ -374,8 +374,9 @@ pub fn new( name: String::from(name), mro, subclasses: RefCell::new(vec![]), + attributes: RefCell::new(dict), }, - dict: Some(RefCell::new(dict)), + dict: None, typ, } .into_ref(); diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index e4488bdb49..81fec3406e 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -186,22 +186,24 @@ fn init_type_hierarchy() -> (PyClassRef, PyClassRef) { let (type_type, object_type) = unsafe { let object_type = PyObject { typ: mem::uninitialized(), // ! - dict: Some(RefCell::new(PyAttributes::new())), + dict: None, payload: PyClass { name: String::from("object"), mro: vec![], subclasses: RefCell::new(vec![]), + attributes: RefCell::new(PyAttributes::new()), }, } .into_ref(); let type_type = PyObject { typ: mem::uninitialized(), // ! - dict: Some(RefCell::new(PyAttributes::new())), + dict: None, payload: PyClass { name: String::from("type"), mro: vec![object_type.clone().downcast().unwrap()], subclasses: RefCell::new(vec![]), + attributes: RefCell::new(PyAttributes::new()), }, } .into_ref(); @@ -655,7 +657,11 @@ impl PyContext { value: V, ) { let obj = obj.into(); - if let Some(PyModule { ref dict, .. }) = obj.payload::() { + if let Some(PyClass { ref attributes, .. }) = obj.payload::() { + attributes + .borrow_mut() + .insert(attr_name.to_string(), value.into()); + } else if let Some(PyModule { ref dict, .. }) = obj.payload::() { dict.set_item(self, attr_name, value.into()) } else if let Some(ref dict) = obj.dict { dict.borrow_mut() From 17c3f9f024f79f972b20f019c4464b9b2a28593b Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 25 Mar 2019 16:34:07 +0000 Subject: [PATCH 063/884] builtin_locals can just return a reference to the locals dict. --- vm/src/builtins.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 9dfe7afd63..acd0719d80 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -11,7 +11,6 @@ use num_traits::{Signed, ToPrimitive}; use crate::compile; use crate::import::import_module; use crate::obj::objbool; -use crate::obj::objdict; use crate::obj::objint; use crate::obj::objiter; use crate::obj::objstr::{self, PyStringRef}; @@ -29,14 +28,7 @@ use crate::obj::objcode::PyCodeRef; use crate::stdlib::io::io_open; fn get_locals(vm: &VirtualMachine) -> PyObjectRef { - let d = vm.new_dict(); - // TODO: implement dict_iter_items? - let locals = vm.get_locals(); - let key_value_pairs = objdict::get_key_value_pairs(&locals); - for (key, value) in key_value_pairs { - objdict::set_item(&d, vm, &key, &value); - } - d + vm.get_locals() } fn dir_locals(vm: &VirtualMachine) -> PyObjectRef { From 602f2a83da41a4f9d857a8abd55fd581117ab016 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 25 Mar 2019 17:35:21 +0100 Subject: [PATCH 064/884] Update grcov --- tests/.travis-runner.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/.travis-runner.sh b/tests/.travis-runner.sh index 8a004d3b19..36b9f43aca 100755 --- a/tests/.travis-runner.sh +++ b/tests/.travis-runner.sh @@ -31,7 +31,7 @@ then zip -0 ccov.zip `find . \( -name "rustpython*.gc*" \) -print` # Install grcov - curl -L https://github.com/mozilla/grcov/releases/download/v0.4.1/grcov-linux-x86_64.tar.bz2 | tar jxf - + curl -L https://github.com/mozilla/grcov/releases/download/v0.4.2/grcov-linux-x86_64.tar.bz2 | tar jxf - ./grcov ccov.zip -s . -t lcov --llvm --branch --ignore-not-existing --ignore-dir "/*" -p "x" > lcov.info From bbb716247266e6c0eca7993e2aacb456c3b79208 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 25 Mar 2019 16:37:20 +0000 Subject: [PATCH 065/884] Various dictionary changes. * vm.ctx.new_dict returns a PyDictRef * Special case for module goes away. * Instances get a real dictionary. --- vm/src/builtins.rs | 4 +- vm/src/frame.rs | 14 +++-- vm/src/function.rs | 9 +++ vm/src/import.rs | 2 +- vm/src/obj/objdict.rs | 110 +++++++++++++++++++-------------- vm/src/obj/objmodule.rs | 26 ++++---- vm/src/obj/objobject.rs | 38 +++++------- vm/src/obj/objproperty.rs | 4 +- vm/src/obj/objsequence.rs | 2 + vm/src/obj/objtype.rs | 4 +- vm/src/pyobject.rs | 94 +++++++++++++--------------- vm/src/stdlib/json.rs | 2 +- vm/src/vm.rs | 8 +-- wasm/lib/src/browser_module.rs | 1 + wasm/lib/src/convert.rs | 4 +- 15 files changed, 164 insertions(+), 158 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index acd0719d80..4d9fb79896 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -815,9 +815,9 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu let prepare = vm.get_attribute(metaclass.clone().into_object(), "__prepare__")?; let namespace = vm.invoke(prepare, vec![name_arg.clone(), bases.clone()])?; - let cells = vm.new_dict(); + let cells = vm.ctx.new_dict(); - vm.invoke_with_locals(function, cells.clone(), namespace.clone())?; + vm.invoke_with_locals(function, cells.clone().into_object(), namespace.clone())?; let class = vm.call_method( metaclass.as_object(), "__call__", diff --git a/vm/src/frame.rs b/vm/src/frame.rs index b9e6cf9653..3584a57a81 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -119,7 +119,7 @@ impl Scope { } pub fn child_scope(&self, ctx: &PyContext) -> Scope { - self.child_scope_with_locals(ctx.new_dict()) + self.child_scope_with_locals(ctx.new_dict().into_object()) } } @@ -142,7 +142,7 @@ impl NameProtocol for Scope { return Some(value); } - vm.builtins.get_item(name) + vm.get_attribute(vm.builtins.clone(), name).ok() } fn load_cell(&self, _vm: &VirtualMachine, name: &str) -> Option { @@ -386,7 +386,7 @@ impl Frame { Ok(None) } bytecode::Instruction::BuildMap { size, unpack } => { - let map_obj = vm.ctx.new_dict(); + let map_obj = vm.ctx.new_dict().into_object(); for _x in 0..*size { let obj = self.pop_value(); if *unpack { @@ -572,7 +572,7 @@ impl Frame { let annotations = if flags.contains(bytecode::FunctionOpArg::HAS_ANNOTATIONS) { self.pop_value() } else { - vm.new_dict() + vm.ctx.new_dict().into_object() }; let defaults = if flags.contains(bytecode::FunctionOpArg::HAS_DEFAULTS) { @@ -839,8 +839,10 @@ impl Frame { let module = vm.import(module)?; // Grab all the names from the module and put them in the context - for (k, v) in module.get_key_value_pairs().iter() { - self.scope.store_name(&vm, &objstr::get_value(k), v.clone()); + if let Some(dict) = &module.dict { + for (k, v) in dict.get_key_value_pairs().iter() { + self.scope.store_name(&vm, &objstr::get_value(k), v.clone()); + } } Ok(None) } diff --git a/vm/src/function.rs b/vm/src/function.rs index 1e2a38f71e..ed959d85c9 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -247,6 +247,15 @@ where } } +impl IntoIterator for KwArgs { + type Item = (String, T); + type IntoIter = std::collections::hash_map::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + /// A list of positional argument values. /// /// A built-in function with a `Args` parameter is analagous to a Python diff --git a/vm/src/import.rs b/vm/src/import.rs index 936b896805..987555915f 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -40,7 +40,7 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s let attrs = vm.ctx.new_dict(); attrs.set_item(&vm.ctx, "__name__", vm.new_str(module.to_string())); - vm.run_code_obj(code_obj, Scope::new(None, attrs.clone()))?; + vm.run_code_obj(code_obj, Scope::new(None, attrs.clone().into_object()))?; Ok(vm.ctx.new_module(module, attrs)) } diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 0c8d5fe5ec..061c3477e7 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use std::fmt; use std::ops::{Deref, DerefMut}; -use crate::function::{OptionalArg, PyFuncArgs}; +use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ - PyAttributes, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + DictProtocol, PyAttributes, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -124,57 +124,48 @@ pub fn py_dict_to_attributes(dict: &PyObjectRef) -> PyAttributes { attrs } -pub fn attributes_to_py_dict(vm: &VirtualMachine, attributes: PyAttributes) -> PyResult { - let dict = vm.ctx.new_dict(); - for (key, value) in attributes { - let key = vm.ctx.new_str(key); - set_item(&dict, vm, &key, &value); - } - Ok(dict) -} - // Python dict methods: -fn dict_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(_ty, Some(vm.ctx.type_type()))], - optional = [(dict_obj, None)] - ); - let dict = vm.ctx.new_dict(); - if let Some(dict_obj) = dict_obj { - if objtype::isinstance(&dict_obj, &vm.ctx.dict_type()) { - for (needle, value) in get_key_value_pairs(&dict_obj) { - set_item(&dict, vm, &needle, &value); - } - } else { - let iter = objiter::get_iter(vm, dict_obj)?; - loop { - fn err(vm: &VirtualMachine) -> PyObjectRef { - vm.new_type_error("Iterator must have exactly two elements".to_string()) +impl PyDictRef { + fn new( + _class: PyClassRef, // TODO Support subclasses of int. + dict_obj: OptionalArg, + kwargs: KwArgs, + vm: &VirtualMachine, + ) -> PyResult { + let dict = vm.ctx.new_dict(); + if let OptionalArg::Present(dict_obj) = dict_obj { + if objtype::isinstance(&dict_obj, &vm.ctx.dict_type()) { + for (needle, value) in get_key_value_pairs(&dict_obj) { + set_item(dict.as_object(), vm, &needle, &value); } - let element = match objiter::get_next_object(vm, &iter)? { - Some(obj) => obj, - None => break, - }; - let elem_iter = objiter::get_iter(vm, &element)?; - let needle = objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?; - let value = objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?; - if objiter::get_next_object(vm, &elem_iter)?.is_some() { - return Err(err(vm)); + } else { + let iter = objiter::get_iter(vm, &dict_obj)?; + loop { + fn err(vm: &VirtualMachine) -> PyObjectRef { + vm.new_type_error("Iterator must have exactly two elements".to_string()) + } + let element = match objiter::get_next_object(vm, &iter)? { + Some(obj) => obj, + None => break, + }; + let elem_iter = objiter::get_iter(vm, &element)?; + let needle = + objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?; + let value = objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?; + if objiter::get_next_object(vm, &elem_iter)?.is_some() { + return Err(err(vm)); + } + set_item(dict.as_object(), vm, &needle, &value); } - set_item(&dict, vm, &needle, &value); } } + for (needle, value) in kwargs.into_iter() { + let py_needle = vm.new_str(needle); + set_item(&dict.as_object(), vm, &py_needle, &value); + } + Ok(dict) } - for (needle, value) in args.kwargs { - let py_needle = vm.new_str(needle); - set_item(&dict, vm, &py_needle, &value); - } - Ok(dict) -} -impl PyDictRef { fn bool(self, _vm: &VirtualMachine) -> bool { !self.entries.borrow().is_empty() } @@ -302,6 +293,31 @@ impl PyDictRef { } } +impl DictProtocol for PyDictRef { + fn contains_key(&self, k: &str) -> bool { + content_contains_key_str(&self.entries.borrow(), k) + } + + fn get_item(&self, k: &str) -> Option { + content_get_key_str(&self.entries.borrow(), k) + } + + fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { + get_key_value_pairs(self.as_object()) + } + + // Item set/get: + fn set_item(&self, ctx: &PyContext, key: &str, v: PyObjectRef) { + let key = ctx.new_str(key.to_string()); + set_item_in_content(&mut self.entries.borrow_mut(), &key, &v); + } + + fn del_item(&self, key: &str) { + let mut elements = get_mut_elements(self.as_object()); + elements.remove(key).unwrap(); + } +} + pub fn init(context: &PyContext) { extend_class!(context, &context.dict_type, { "__bool__" => context.new_rustfunc(PyDictRef::bool), @@ -310,7 +326,7 @@ pub fn init(context: &PyContext) { "__delitem__" => context.new_rustfunc(PyDictRef::delitem), "__getitem__" => context.new_rustfunc(PyDictRef::getitem), "__iter__" => context.new_rustfunc(PyDictRef::iter), - "__new__" => context.new_rustfunc(dict_new), + "__new__" => context.new_rustfunc(PyDictRef::new), "__repr__" => context.new_rustfunc(PyDictRef::repr), "__setitem__" => context.new_rustfunc(PyDictRef::setitem), "clear" => context.new_rustfunc(PyDictRef::clear), diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs index 58d8688db2..b641f04268 100644 --- a/vm/src/obj/objmodule.rs +++ b/vm/src/obj/objmodule.rs @@ -1,12 +1,10 @@ -use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{DictProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{DictProtocol, PyContext, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; #[derive(Debug)] pub struct PyModule { pub name: String, - pub dict: PyObjectRef, } pub type PyModuleRef = PyRef; @@ -18,23 +16,21 @@ impl PyValue for PyModule { impl PyModuleRef { fn dir(self: PyModuleRef, vm: &VirtualMachine) -> PyResult { - let keys = self - .dict - .get_key_value_pairs() - .iter() - .map(|(k, _v)| k.clone()) - .collect(); - Ok(vm.ctx.new_list(keys)) - } - - fn set_attr(self, attr: PyStringRef, value: PyObjectRef, vm: &VirtualMachine) { - self.dict.set_item(&vm.ctx, &attr.value, value) + if let Some(dict) = &self.into_object().dict { + let keys = dict + .get_key_value_pairs() + .iter() + .map(|(k, _v)| k.clone()) + .collect(); + Ok(vm.ctx.new_list(keys)) + } else { + panic!("Modules should definitely have a dict."); + } } } pub fn init(context: &PyContext) { extend_class!(&context, &context.module_type, { "__dir__" => context.new_rustfunc(PyModuleRef::dir), - "__setattr__" => context.new_rustfunc(PyModuleRef::set_attr) }); } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 46a90114dc..60473fad03 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -1,5 +1,5 @@ +use super::objdict::{self, PyDictRef}; use super::objlist::PyList; -use super::objmodule::PyModule; use super::objstr::{self, PyStringRef}; use super::objtype; use crate::function::PyFuncArgs; @@ -23,11 +23,12 @@ impl PyValue for PyInstance { pub fn new_instance(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { // more or less __new__ operator let cls = PyClassRef::try_from_object(vm, args.shift())?; - Ok(if cls.is(&vm.ctx.object) { - PyObject::new_without_dict(PyInstance, cls) + let dict = if cls.is(&vm.ctx.object) { + None } else { - PyObject::new(PyInstance, cls) - }) + Some(vm.ctx.new_dict()) + }; + Ok(PyObject::new(PyInstance, cls, dict)) } fn object_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -114,7 +115,7 @@ fn object_setattr( } if let Some(ref dict) = obj.clone().dict { - dict.borrow_mut().insert(attr_name.value.clone(), value); + dict.set_item(&vm.ctx, &attr_name.value, value); Ok(()) } else { let type_name = objtype::get_type_name(obj.type_ref()); @@ -135,7 +136,7 @@ fn object_delattr(obj: PyObjectRef, attr_name: PyStringRef, vm: &VirtualMachine) } if let Some(ref dict) = obj.dict { - dict.borrow_mut().remove(&attr_name.value); + dict.del_item(&attr_name.value); Ok(()) } else { let type_name = objtype::get_type_name(obj.type_ref()); @@ -227,13 +228,9 @@ fn object_class_setter( Err(vm.new_type_error(format!("can't change class of type '{}'", type_repr))) } -fn object_dict(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - if let Some(ref dict) = args.args[0].dict { - let new_dict = vm.new_dict(); - for (attr, value) in dict.borrow().iter() { - new_dict.set_item(&vm.ctx, &attr, value.clone()); - } - Ok(new_dict) +fn object_dict(object: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(ref dict) = object.dict { + Ok(dict.clone()) } else { Err(vm.new_type_error("TypeError: no dictionary.".to_string())) } @@ -273,15 +270,8 @@ fn object_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn object_getattr(obj: &PyObjectRef, attr_name: &str) -> Option { - // TODO: - // This is an all kinds of wrong work-around for the temporary difference in - // shape between modules and object. It will disappear once that is fixed. - if let Some(PyModule { ref dict, .. }) = obj.payload::() { - return dict.get_item(attr_name); - } - if let Some(ref dict) = obj.dict { - dict.borrow().get(attr_name).cloned() + dict.get_item(attr_name) } else { None } @@ -293,8 +283,8 @@ pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes { // Get instance attributes: if let Some(dict) = &obj.dict { - for (name, value) in dict.borrow().iter() { - attributes.insert(name.to_string(), value.clone()); + for (key, value) in objdict::get_key_value_pairs(dict.as_object()) { + attributes.insert(key.to_string(), value.clone()); } } diff --git a/vm/src/obj/objproperty.rs b/vm/src/obj/objproperty.rs index c5459a9fb2..e6b11904eb 100644 --- a/vm/src/obj/objproperty.rs +++ b/vm/src/obj/objproperty.rs @@ -200,7 +200,7 @@ impl<'a> PropertyBuilder<'a> { deleter: None, }; - PyObject::new(payload, self.ctx.property_type()) + PyObject::new(payload, self.ctx.property_type(), None) } else { let payload = PyReadOnlyProperty { getter: self.getter.expect( @@ -208,7 +208,7 @@ impl<'a> PropertyBuilder<'a> { ), }; - PyObject::new(payload, self.ctx.readonly_property_type()) + PyObject::new(payload, self.ctx.readonly_property_type(), None) } } } diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index b8d91e5909..80f4bf40f6 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -164,11 +164,13 @@ pub fn get_item( Ok(PyObject::new( PyList::from(elements.to_vec().get_slice_items(vm, &subscript)?), sequence.type_pyref(), + None, )) } else if sequence.payload::().is_some() { Ok(PyObject::new( PyTuple::from(elements.to_vec().get_slice_items(vm, &subscript)?), sequence.type_pyref(), + None, )) } else { panic!("sequence get_item called for non-sequence") diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 6399d68c9b..d87ca3843c 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -110,8 +110,8 @@ impl PyClassRef { format!("", self.name) } - fn prepare(_name: PyStringRef, _bases: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - vm.new_dict() + fn prepare(_name: PyStringRef, _bases: PyObjectRef, vm: &VirtualMachine) -> PyDictRef { + vm.ctx.new_dict() } fn getattribute(self, name_ref: PyStringRef, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 81fec3406e..78e82c51bd 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -24,7 +24,7 @@ use crate::obj::objclassmethod; use crate::obj::objcode; use crate::obj::objcode::PyCodeRef; use crate::obj::objcomplex::{self, PyComplex}; -use crate::obj::objdict::{self, PyDict}; +use crate::obj::objdict::{self, PyDict, PyDictRef}; use crate::obj::objellipsis; use crate::obj::objenumerate; use crate::obj::objfilter; @@ -271,7 +271,7 @@ impl PyContext { fn create_object(payload: T, cls: &PyClassRef) -> PyRef { PyRef { - obj: PyObject::new(payload, cls.clone()), + obj: PyObject::new(payload, cls.clone(), None), _payload: PhantomData, } } @@ -521,32 +521,32 @@ impl PyContext { self.object.clone() } - pub fn new_object(&self) -> PyObjectRef { - self.new_instance(self.object.clone(), None) - } - pub fn new_int>(&self, i: T) -> PyObjectRef { - PyObject::new(PyInt::new(i), self.int_type()) + PyObject::new(PyInt::new(i), self.int_type(), None) } pub fn new_float(&self, value: f64) -> PyObjectRef { - PyObject::new(PyFloat::from(value), self.float_type()) + PyObject::new(PyFloat::from(value), self.float_type(), None) } pub fn new_complex(&self, value: Complex64) -> PyObjectRef { - PyObject::new(PyComplex::from(value), self.complex_type()) + PyObject::new(PyComplex::from(value), self.complex_type(), None) } pub fn new_str(&self, s: String) -> PyObjectRef { - PyObject::new(objstr::PyString { value: s }, self.str_type()) + PyObject::new(objstr::PyString { value: s }, self.str_type(), None) } pub fn new_bytes(&self, data: Vec) -> PyObjectRef { - PyObject::new(objbytes::PyBytes::new(data), self.bytes_type()) + PyObject::new(objbytes::PyBytes::new(data), self.bytes_type(), None) } pub fn new_bytearray(&self, data: Vec) -> PyObjectRef { - PyObject::new(objbytearray::PyByteArray::new(data), self.bytearray_type()) + PyObject::new( + objbytearray::PyByteArray::new(data), + self.bytearray_type(), + None, + ) } pub fn new_bool(&self, b: bool) -> PyObjectRef { @@ -558,21 +558,23 @@ impl PyContext { } pub fn new_tuple(&self, elements: Vec) -> PyObjectRef { - PyObject::new(PyTuple::from(elements), self.tuple_type()) + PyObject::new(PyTuple::from(elements), self.tuple_type(), None) } pub fn new_list(&self, elements: Vec) -> PyObjectRef { - PyObject::new(PyList::from(elements), self.list_type()) + PyObject::new(PyList::from(elements), self.list_type(), None) } pub fn new_set(&self) -> PyObjectRef { // Initialized empty, as calling __hash__ is required for adding each object to the set // which requires a VM context - this is done in the objset code itself. - PyObject::new(PySet::default(), self.set_type()) + PyObject::new(PySet::default(), self.set_type(), None) } - pub fn new_dict(&self) -> PyObjectRef { - PyObject::new(PyDict::default(), self.dict_type()) + pub fn new_dict(&self) -> PyDictRef { + PyObject::new(PyDict::default(), self.dict_type(), None) + .downcast() + .unwrap() } pub fn new_class(&self, name: &str, base: PyClassRef) -> PyClassRef { @@ -580,16 +582,16 @@ impl PyContext { } pub fn new_scope(&self) -> Scope { - Scope::new(None, self.new_dict()) + Scope::new(None, self.new_dict().into_object()) } - pub fn new_module(&self, name: &str, dict: PyObjectRef) -> PyObjectRef { + pub fn new_module(&self, name: &str, dict: PyDictRef) -> PyObjectRef { PyObject::new( PyModule { name: name.to_string(), - dict, }, self.module_type.clone(), + Some(dict), ) } @@ -600,6 +602,7 @@ impl PyContext { PyObject::new( PyBuiltinFunction::new(f.into_func()), self.builtin_function_or_method_type(), + None, ) } @@ -611,7 +614,7 @@ impl PyContext { } pub fn new_code_object(&self, code: bytecode::CodeObject) -> PyObjectRef { - PyObject::new(objcode::PyCode::new(code), self.code_type()) + PyObject::new(objcode::PyCode::new(code), self.code_type(), None) } pub fn new_function( @@ -623,18 +626,22 @@ impl PyContext { PyObject::new( PyFunction::new(code_obj, scope, defaults), self.function_type(), + Some(self.new_dict()), ) } pub fn new_bound_method(&self, function: PyObjectRef, object: PyObjectRef) -> PyObjectRef { - PyObject::new(PyMethod::new(object, function), self.bound_method_type()) + PyObject::new( + PyMethod::new(object, function), + self.bound_method_type(), + None, + ) } - pub fn new_instance(&self, class: PyClassRef, dict: Option) -> PyObjectRef { - let dict = dict.unwrap_or_default(); + pub fn new_instance(&self, class: PyClassRef, dict: Option) -> PyObjectRef { PyObject { typ: class, - dict: Some(RefCell::new(dict)), + dict: dict, payload: objobject::PyInstance, } .into_ref() @@ -661,11 +668,8 @@ impl PyContext { attributes .borrow_mut() .insert(attr_name.to_string(), value.into()); - } else if let Some(PyModule { ref dict, .. }) = obj.payload::() { - dict.set_item(self, attr_name, value.into()) } else if let Some(ref dict) = obj.dict { - dict.borrow_mut() - .insert(attr_name.to_string(), value.into()); + dict.set_item(self, attr_name, value.into()); } else { unimplemented!("set_attr unimplemented for: {:?}", obj); }; @@ -707,7 +711,7 @@ where T: ?Sized + PyObjectPayload, { pub typ: PyClassRef, - pub dict: Option>, // __dict__ member + pub dict: Option, // __dict__ member pub payload: T, } @@ -917,8 +921,6 @@ impl DictProtocol for PyObjectRef { fn get_item(&self, k: &str) -> Option { if let Some(dict) = self.payload::() { objdict::content_get_key_str(&dict.entries.borrow(), k) - } else if let Some(PyModule { ref dict, .. }) = self.payload::() { - dict.get_item(k) } else { panic!("TODO {:?}", k) } @@ -927,8 +929,6 @@ impl DictProtocol for PyObjectRef { fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { if self.payload_is::() { objdict::get_key_value_pairs(self) - } else if let Some(PyModule { ref dict, .. }) = self.payload::() { - dict.get_key_value_pairs() } else { panic!("TODO") } @@ -939,8 +939,6 @@ impl DictProtocol for PyObjectRef { if let Some(dict) = self.payload::() { let key = ctx.new_str(key.to_string()); objdict::set_item_in_content(&mut dict.entries.borrow_mut(), &key, &v); - } else if let Some(PyModule { ref dict, .. }) = self.payload::() { - dict.set_item(ctx, key, v); } else { panic!("TODO {:?}", self); } @@ -1121,7 +1119,7 @@ where T: PyValue + Sized, { fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new(self, T::class(vm))) + Ok(PyObject::new(self, T::class(vm), None)) } } @@ -1143,19 +1141,10 @@ impl PyObject where T: Sized + PyObjectPayload, { - pub fn new(payload: T, typ: PyClassRef) -> PyObjectRef { - PyObject { - typ, - dict: Some(RefCell::new(PyAttributes::new())), - payload, - } - .into_ref() - } - - pub fn new_without_dict(payload: T, typ: PyClassRef) -> PyObjectRef { + pub fn new(payload: T, typ: PyClassRef, dict: Option) -> PyObjectRef { PyObject { typ, - dict: None, + dict: dict, payload, } .into_ref() @@ -1184,7 +1173,7 @@ pub trait PyValue: fmt::Debug + Sized + 'static { fn into_ref(self, vm: &VirtualMachine) -> PyRef { PyRef { - obj: PyObject::new(self, Self::class(vm)), + obj: PyObject::new(self, Self::class(vm), None), _payload: PhantomData, } } @@ -1192,8 +1181,13 @@ pub trait PyValue: fmt::Debug + Sized + 'static { fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyClassRef) -> PyResult> { let class = Self::class(vm); if objtype::issubclass(&cls, &class) { + let dict = if cls.is(&class) { + None + } else { + Some(vm.ctx.new_dict()) + }; Ok(PyRef { - obj: PyObject::new(self, cls), + obj: PyObject::new(self, cls, dict), _payload: PhantomData, }) } else { diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 2c5c95d34b..eac3619c26 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -177,7 +177,7 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { }; dict.set_item(&self.vm.ctx, &key, value); } - Ok(dict) + Ok(dict.into_object()) } fn visit_unit(self) -> Result diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 7713c561da..0ee9798ab0 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -141,10 +141,6 @@ impl VirtualMachine { self.ctx.new_bool(b) } - pub fn new_dict(&self) -> PyObjectRef { - self.ctx.new_dict() - } - pub fn new_empty_exception(&self, exc_type: PyClassRef) -> PyResult { info!("New exception created: no msg"); let args = PyFuncArgs::default(); @@ -454,11 +450,11 @@ impl VirtualMachine { // Do we support `**kwargs` ? let kwargs = match code_object.varkeywords { bytecode::Varargs::Named(ref kwargs_name) => { - let d = self.new_dict(); + let d = self.ctx.new_dict().into_object(); locals.set_item(&self.ctx, kwargs_name, d.clone()); Some(d) } - bytecode::Varargs::Unnamed => Some(self.new_dict()), + bytecode::Varargs::Unnamed => Some(self.ctx.new_dict().into_object()), bytecode::Varargs::None => None, }; diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index a309284cf3..0e13aeb3ab 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -363,6 +363,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { doc: window().document().expect("Document missing from window"), }, document_class.clone(), + None, ); let element = py_class!(ctx, "Element", ctx.object(), { diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index ecdb4b1102..588c7571cd 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -187,13 +187,13 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { u8_array.for_each(&mut |byte, _, _| vec.push(byte)); vm.ctx.new_bytes(vec) } else { - let dict = vm.new_dict(); + let dict = vm.ctx.new_dict(); for pair in object_entries(&Object::from(js_val)) { let (key, val) = pair.expect("iteration over object to not fail"); let py_val = js_to_py(vm, val); dict.set_item(&vm.ctx, &String::from(js_sys::JsString::from(key)), py_val); } - dict + dict.into_object() } } else if js_val.is_function() { let func = js_sys::Function::from(js_val); From 55e0fb1f19f84d377f9aa5368d606e40f2a49371 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 25 Mar 2019 16:39:25 +0000 Subject: [PATCH 066/884] AST class for nodes instead of just using object everywhere. --- vm/src/stdlib/ast.rs | 459 ++++++++++++++++++++++--------------------- 1 file changed, 238 insertions(+), 221 deletions(-) diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index a3010a5199..0011407aba 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -11,9 +11,20 @@ use rustpython_parser::{ast, parser}; use crate::function::PyFuncArgs; use crate::obj::objstr; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::obj::objtype::PyClassRef; +use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; +#[derive(Debug)] +struct AstNode; +// type AstNodeRef = PyRef; + +impl PyValue for AstNode { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("ast", "AST") + } +} + /* * Idea: maybe we can create a sort of struct with some helper functions? struct AstToPyAst { @@ -30,35 +41,35 @@ impl AstToPyAst { } */ -fn program_to_ast(ctx: &PyContext, program: &ast::Program) -> PyObjectRef { +fn program_to_ast(vm: &VirtualMachine, program: &ast::Program) -> PyObjectRef { let mut body = vec![]; for statement in &program.statements { - body.push(statement_to_ast(ctx, statement)); + body.push(statement_to_ast(&vm, statement)); } // TODO: create Module node: // let ast_node = ctx.new_instance(this.Module); - let ast_node = ctx.new_object(); - let py_body = ctx.new_list(body); - ctx.set_attr(&ast_node, "body", py_body); + let ast_node = create_node(vm, "program"); + let py_body = vm.ctx.new_list(body); + vm.ctx.set_attr(&ast_node, "body", py_body); ast_node } // Create a node class instance -fn create_node(ctx: &PyContext, _name: &str) -> PyObjectRef { +fn create_node(vm: &VirtualMachine, _name: &str) -> PyObjectRef { // TODO: instantiate a class of type given by name // TODO: lookup in the current module? - ctx.new_object() + PyObject::new(AstNode, AstNode::class(vm), Some(vm.ctx.new_dict())) } -fn statements_to_ast(ctx: &PyContext, statements: &[ast::LocatedStatement]) -> PyObjectRef { +fn statements_to_ast(vm: &VirtualMachine, statements: &[ast::LocatedStatement]) -> PyObjectRef { let mut py_statements = vec![]; for statement in statements { - py_statements.push(statement_to_ast(ctx, statement)); + py_statements.push(statement_to_ast(vm, statement)); } - ctx.new_list(py_statements) + vm.ctx.new_list(py_statements) } -fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObjectRef { +fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> PyObjectRef { let node = match &statement.node { ast::Statement::ClassDef { name, @@ -66,17 +77,18 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj decorator_list, .. } => { - let node = create_node(ctx, "ClassDef"); + let node = create_node(vm, "ClassDef"); // Set name: - ctx.set_attr(&node, "name", ctx.new_str(name.to_string())); + vm.ctx + .set_attr(&node, "name", vm.ctx.new_str(name.to_string())); // Set body: - let py_body = statements_to_ast(ctx, body); - ctx.set_attr(&node, "body", py_body); + let py_body = statements_to_ast(vm, body); + vm.ctx.set_attr(&node, "body", py_body); - let py_decorator_list = expressions_to_ast(ctx, decorator_list); - ctx.set_attr(&node, "decorator_list", py_decorator_list); + let py_decorator_list = expressions_to_ast(vm, decorator_list); + vm.ctx.set_attr(&node, "decorator_list", py_decorator_list); node } ast::Statement::FunctionDef { @@ -86,80 +98,83 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj decorator_list, returns, } => { - let node = create_node(ctx, "FunctionDef"); + let node = create_node(vm, "FunctionDef"); // Set name: - ctx.set_attr(&node, "name", ctx.new_str(name.to_string())); + vm.ctx + .set_attr(&node, "name", vm.ctx.new_str(name.to_string())); - ctx.set_attr(&node, "args", parameters_to_ast(ctx, args)); + vm.ctx.set_attr(&node, "args", parameters_to_ast(vm, args)); // Set body: - let py_body = statements_to_ast(ctx, body); - ctx.set_attr(&node, "body", py_body); + let py_body = statements_to_ast(vm, body); + vm.ctx.set_attr(&node, "body", py_body); - let py_decorator_list = expressions_to_ast(ctx, decorator_list); - ctx.set_attr(&node, "decorator_list", py_decorator_list); + let py_decorator_list = expressions_to_ast(vm, decorator_list); + vm.ctx.set_attr(&node, "decorator_list", py_decorator_list); let py_returns = if let Some(hint) = returns { - expression_to_ast(ctx, hint) + expression_to_ast(vm, hint) } else { - ctx.none() + vm.ctx.none() }; - ctx.set_attr(&node, "returns", py_returns); + vm.ctx.set_attr(&node, "returns", py_returns); node } - ast::Statement::Continue => create_node(ctx, "Continue"), - ast::Statement::Break => create_node(ctx, "Break"), - ast::Statement::Pass => create_node(ctx, "Pass"), + ast::Statement::Continue => create_node(vm, "Continue"), + ast::Statement::Break => create_node(vm, "Break"), + ast::Statement::Pass => create_node(vm, "Pass"), ast::Statement::Assert { test, msg } => { - let node = create_node(ctx, "Pass"); + let node = create_node(vm, "Pass"); - ctx.set_attr(&node, "test", expression_to_ast(ctx, test)); + vm.ctx.set_attr(&node, "test", expression_to_ast(vm, test)); let py_msg = match msg { - Some(msg) => expression_to_ast(ctx, msg), - None => ctx.none(), + Some(msg) => expression_to_ast(vm, msg), + None => vm.ctx.none(), }; - ctx.set_attr(&node, "msg", py_msg); + vm.ctx.set_attr(&node, "msg", py_msg); node } ast::Statement::Delete { targets } => { - let node = create_node(ctx, "Delete"); + let node = create_node(vm, "Delete"); - let py_targets = - ctx.new_tuple(targets.iter().map(|v| expression_to_ast(ctx, v)).collect()); - ctx.set_attr(&node, "targets", py_targets); + let py_targets = vm + .ctx + .new_tuple(targets.iter().map(|v| expression_to_ast(vm, v)).collect()); + vm.ctx.set_attr(&node, "targets", py_targets); node } ast::Statement::Return { value } => { - let node = create_node(ctx, "Return"); + let node = create_node(vm, "Return"); let py_value = if let Some(value) = value { - ctx.new_tuple(value.iter().map(|v| expression_to_ast(ctx, v)).collect()) + vm.ctx + .new_tuple(value.iter().map(|v| expression_to_ast(vm, v)).collect()) } else { - ctx.none() + vm.ctx.none() }; - ctx.set_attr(&node, "value", py_value); + vm.ctx.set_attr(&node, "value", py_value); node } ast::Statement::If { test, body, orelse } => { - let node = create_node(ctx, "If"); + let node = create_node(vm, "If"); - let py_test = expression_to_ast(ctx, test); - ctx.set_attr(&node, "test", py_test); + let py_test = expression_to_ast(vm, test); + vm.ctx.set_attr(&node, "test", py_test); - let py_body = statements_to_ast(ctx, body); - ctx.set_attr(&node, "body", py_body); + let py_body = statements_to_ast(vm, body); + vm.ctx.set_attr(&node, "body", py_body); let py_orelse = if let Some(orelse) = orelse { - statements_to_ast(ctx, orelse) + statements_to_ast(vm, orelse) } else { - ctx.none() + vm.ctx.none() }; - ctx.set_attr(&node, "orelse", py_orelse); + vm.ctx.set_attr(&node, "orelse", py_orelse); node } @@ -169,49 +184,49 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj body, orelse, } => { - let node = create_node(ctx, "For"); + let node = create_node(vm, "For"); - let py_target = expression_to_ast(ctx, target); - ctx.set_attr(&node, "target", py_target); + let py_target = expression_to_ast(vm, target); + vm.ctx.set_attr(&node, "target", py_target); - let py_iter = expressions_to_ast(ctx, iter); - ctx.set_attr(&node, "iter", py_iter); + let py_iter = expressions_to_ast(vm, iter); + vm.ctx.set_attr(&node, "iter", py_iter); - let py_body = statements_to_ast(ctx, body); - ctx.set_attr(&node, "body", py_body); + let py_body = statements_to_ast(vm, body); + vm.ctx.set_attr(&node, "body", py_body); let py_orelse = if let Some(orelse) = orelse { - statements_to_ast(ctx, orelse) + statements_to_ast(vm, orelse) } else { - ctx.none() + vm.ctx.none() }; - ctx.set_attr(&node, "orelse", py_orelse); + vm.ctx.set_attr(&node, "orelse", py_orelse); node } ast::Statement::While { test, body, orelse } => { - let node = create_node(ctx, "While"); + let node = create_node(vm, "While"); - let py_test = expression_to_ast(ctx, test); - ctx.set_attr(&node, "test", py_test); + let py_test = expression_to_ast(vm, test); + vm.ctx.set_attr(&node, "test", py_test); - let py_body = statements_to_ast(ctx, body); - ctx.set_attr(&node, "body", py_body); + let py_body = statements_to_ast(vm, body); + vm.ctx.set_attr(&node, "body", py_body); let py_orelse = if let Some(orelse) = orelse { - statements_to_ast(ctx, orelse) + statements_to_ast(vm, orelse) } else { - ctx.none() + vm.ctx.none() }; - ctx.set_attr(&node, "orelse", py_orelse); + vm.ctx.set_attr(&node, "orelse", py_orelse); node } ast::Statement::Expression { expression } => { - let node = create_node(ctx, "Expr"); + let node = create_node(vm, "Expr"); - let value = expression_to_ast(ctx, expression); - ctx.set_attr(&node, "value", value); + let value = expression_to_ast(vm, expression); + vm.ctx.set_attr(&node, "value", value); node } @@ -221,38 +236,38 @@ fn statement_to_ast(ctx: &PyContext, statement: &ast::LocatedStatement) -> PyObj }; // set lineno on node: - let lineno = ctx.new_int(statement.location.get_row()); - ctx.set_attr(&node, "lineno", lineno); + let lineno = vm.ctx.new_int(statement.location.get_row()); + vm.ctx.set_attr(&node, "lineno", lineno); node } -fn expressions_to_ast(ctx: &PyContext, expressions: &[ast::Expression]) -> PyObjectRef { +fn expressions_to_ast(vm: &VirtualMachine, expressions: &[ast::Expression]) -> PyObjectRef { let mut py_expression_nodes = vec![]; for expression in expressions { - py_expression_nodes.push(expression_to_ast(ctx, expression)); + py_expression_nodes.push(expression_to_ast(vm, expression)); } - ctx.new_list(py_expression_nodes) + vm.ctx.new_list(py_expression_nodes) } -fn expression_to_ast(ctx: &PyContext, expression: &ast::Expression) -> PyObjectRef { +fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObjectRef { let node = match &expression { ast::Expression::Call { function, args, .. } => { - let node = create_node(ctx, "Call"); + let node = create_node(vm, "Call"); - let py_func_ast = expression_to_ast(ctx, function); - ctx.set_attr(&node, "func", py_func_ast); + let py_func_ast = expression_to_ast(vm, function); + vm.ctx.set_attr(&node, "func", py_func_ast); - let py_args = expressions_to_ast(ctx, args); - ctx.set_attr(&node, "args", py_args); + let py_args = expressions_to_ast(vm, args); + vm.ctx.set_attr(&node, "args", py_args); node } ast::Expression::Binop { a, op, b } => { - let node = create_node(ctx, "BinOp"); + let node = create_node(vm, "BinOp"); - let py_a = expression_to_ast(ctx, a); - ctx.set_attr(&node, "left", py_a); + let py_a = expression_to_ast(vm, a); + vm.ctx.set_attr(&node, "left", py_a); // Operator: let str_op = match op { @@ -270,15 +285,15 @@ fn expression_to_ast(ctx: &PyContext, expression: &ast::Expression) -> PyObjectR ast::Operator::BitAnd => "BitAnd", ast::Operator::FloorDiv => "FloorDiv", }; - let py_op = ctx.new_str(str_op.to_string()); - ctx.set_attr(&node, "op", py_op); + let py_op = vm.ctx.new_str(str_op.to_string()); + vm.ctx.set_attr(&node, "op", py_op); - let py_b = expression_to_ast(ctx, b); - ctx.set_attr(&node, "right", py_b); + let py_b = expression_to_ast(vm, b); + vm.ctx.set_attr(&node, "right", py_b); node } ast::Expression::Unop { op, a } => { - let node = create_node(ctx, "UnaryOp"); + let node = create_node(vm, "UnaryOp"); let str_op = match op { ast::UnaryOperator::Not => "Not", @@ -286,37 +301,37 @@ fn expression_to_ast(ctx: &PyContext, expression: &ast::Expression) -> PyObjectR ast::UnaryOperator::Neg => "USub", ast::UnaryOperator::Pos => "UAdd", }; - let py_op = ctx.new_str(str_op.to_string()); - ctx.set_attr(&node, "op", py_op); + let py_op = vm.ctx.new_str(str_op.to_string()); + vm.ctx.set_attr(&node, "op", py_op); - let py_a = expression_to_ast(ctx, a); - ctx.set_attr(&node, "operand", py_a); + let py_a = expression_to_ast(vm, a); + vm.ctx.set_attr(&node, "operand", py_a); node } ast::Expression::BoolOp { a, op, b } => { - let node = create_node(ctx, "BoolOp"); + let node = create_node(vm, "BoolOp"); // Attach values: - let py_a = expression_to_ast(ctx, a); - let py_b = expression_to_ast(ctx, b); - let py_values = ctx.new_tuple(vec![py_a, py_b]); - ctx.set_attr(&node, "values", py_values); + let py_a = expression_to_ast(vm, a); + let py_b = expression_to_ast(vm, b); + let py_values = vm.ctx.new_tuple(vec![py_a, py_b]); + vm.ctx.set_attr(&node, "values", py_values); let str_op = match op { ast::BooleanOperator::And => "And", ast::BooleanOperator::Or => "Or", }; - let py_op = ctx.new_str(str_op.to_string()); - ctx.set_attr(&node, "op", py_op); + let py_op = vm.ctx.new_str(str_op.to_string()); + vm.ctx.set_attr(&node, "op", py_op); node } ast::Expression::Compare { a, op, b } => { - let node = create_node(ctx, "Compare"); + let node = create_node(vm, "Compare"); - let py_a = expression_to_ast(ctx, a); - ctx.set_attr(&node, "left", py_a); + let py_a = expression_to_ast(vm, a); + vm.ctx.set_attr(&node, "left", py_a); // Operator: let str_op = match op { @@ -331,282 +346,283 @@ fn expression_to_ast(ctx: &PyContext, expression: &ast::Expression) -> PyObjectR ast::Comparison::Is => "Is", ast::Comparison::IsNot => "IsNot", }; - let py_ops = ctx.new_list(vec![ctx.new_str(str_op.to_string())]); - ctx.set_attr(&node, "ops", py_ops); + let py_ops = vm.ctx.new_list(vec![vm.ctx.new_str(str_op.to_string())]); + vm.ctx.set_attr(&node, "ops", py_ops); - let py_b = ctx.new_list(vec![expression_to_ast(ctx, b)]); - ctx.set_attr(&node, "comparators", py_b); + let py_b = vm.ctx.new_list(vec![expression_to_ast(vm, b)]); + vm.ctx.set_attr(&node, "comparators", py_b); node } ast::Expression::Identifier { name } => { - let node = create_node(ctx, "Identifier"); + let node = create_node(vm, "Identifier"); // Id: - let py_name = ctx.new_str(name.clone()); - ctx.set_attr(&node, "id", py_name); + let py_name = vm.ctx.new_str(name.clone()); + vm.ctx.set_attr(&node, "id", py_name); node } ast::Expression::Lambda { args, body } => { - let node = create_node(ctx, "Lambda"); + let node = create_node(vm, "Lambda"); - ctx.set_attr(&node, "args", parameters_to_ast(ctx, args)); + vm.ctx.set_attr(&node, "args", parameters_to_ast(vm, args)); - let py_body = expression_to_ast(ctx, body); - ctx.set_attr(&node, "body", py_body); + let py_body = expression_to_ast(vm, body); + vm.ctx.set_attr(&node, "body", py_body); node } ast::Expression::IfExpression { test, body, orelse } => { - let node = create_node(ctx, "IfExp"); + let node = create_node(vm, "IfExp"); - let py_test = expression_to_ast(ctx, test); - ctx.set_attr(&node, "test", py_test); + let py_test = expression_to_ast(vm, test); + vm.ctx.set_attr(&node, "test", py_test); - let py_body = expression_to_ast(ctx, body); - ctx.set_attr(&node, "body", py_body); + let py_body = expression_to_ast(vm, body); + vm.ctx.set_attr(&node, "body", py_body); - let py_orelse = expression_to_ast(ctx, orelse); - ctx.set_attr(&node, "orelse", py_orelse); + let py_orelse = expression_to_ast(vm, orelse); + vm.ctx.set_attr(&node, "orelse", py_orelse); node } ast::Expression::Number { value } => { - let node = create_node(ctx, "Num"); + let node = create_node(vm, "Num"); let py_n = match value { - ast::Number::Integer { value } => ctx.new_int(value.clone()), - ast::Number::Float { value } => ctx.new_float(*value), + ast::Number::Integer { value } => vm.ctx.new_int(value.clone()), + ast::Number::Float { value } => vm.ctx.new_float(*value), ast::Number::Complex { real, imag } => { - ctx.new_complex(Complex64::new(*real, *imag)) + vm.ctx.new_complex(Complex64::new(*real, *imag)) } }; - ctx.set_attr(&node, "n", py_n); + vm.ctx.set_attr(&node, "n", py_n); node } ast::Expression::True => { - let node = create_node(ctx, "NameConstant"); + let node = create_node(vm, "NameConstant"); - ctx.set_attr(&node, "value", ctx.new_bool(true)); + vm.ctx.set_attr(&node, "value", vm.ctx.new_bool(true)); node } ast::Expression::False => { - let node = create_node(ctx, "NameConstant"); + let node = create_node(vm, "NameConstant"); - ctx.set_attr(&node, "value", ctx.new_bool(false)); + vm.ctx.set_attr(&node, "value", vm.ctx.new_bool(false)); node } ast::Expression::None => { - let node = create_node(ctx, "NameConstant"); + let node = create_node(vm, "NameConstant"); - ctx.set_attr(&node, "value", ctx.none()); + vm.ctx.set_attr(&node, "value", vm.ctx.none()); node } - ast::Expression::Ellipsis => create_node(ctx, "Ellipsis"), + ast::Expression::Ellipsis => create_node(vm, "Ellipsis"), ast::Expression::List { elements } => { - let node = create_node(ctx, "List"); + let node = create_node(vm, "List"); - let elts = elements.iter().map(|e| expression_to_ast(ctx, e)).collect(); - let py_elts = ctx.new_list(elts); - ctx.set_attr(&node, "elts", py_elts); + let elts = elements.iter().map(|e| expression_to_ast(vm, e)).collect(); + let py_elts = vm.ctx.new_list(elts); + vm.ctx.set_attr(&node, "elts", py_elts); node } ast::Expression::Tuple { elements } => { - let node = create_node(ctx, "Tuple"); + let node = create_node(vm, "Tuple"); - let elts = elements.iter().map(|e| expression_to_ast(ctx, e)).collect(); - let py_elts = ctx.new_list(elts); - ctx.set_attr(&node, "elts", py_elts); + let elts = elements.iter().map(|e| expression_to_ast(vm, e)).collect(); + let py_elts = vm.ctx.new_list(elts); + vm.ctx.set_attr(&node, "elts", py_elts); node } ast::Expression::Set { elements } => { - let node = create_node(ctx, "Set"); + let node = create_node(vm, "Set"); - let elts = elements.iter().map(|e| expression_to_ast(ctx, e)).collect(); - let py_elts = ctx.new_list(elts); - ctx.set_attr(&node, "elts", py_elts); + let elts = elements.iter().map(|e| expression_to_ast(vm, e)).collect(); + let py_elts = vm.ctx.new_list(elts); + vm.ctx.set_attr(&node, "elts", py_elts); node } ast::Expression::Dict { elements } => { - let node = create_node(ctx, "Dict"); + let node = create_node(vm, "Dict"); let mut keys = Vec::new(); let mut values = Vec::new(); for (k, v) in elements { - keys.push(expression_to_ast(ctx, k)); - values.push(expression_to_ast(ctx, v)); + keys.push(expression_to_ast(vm, k)); + values.push(expression_to_ast(vm, v)); } - let py_keys = ctx.new_list(keys); - ctx.set_attr(&node, "keys", py_keys); + let py_keys = vm.ctx.new_list(keys); + vm.ctx.set_attr(&node, "keys", py_keys); - let py_values = ctx.new_list(values); - ctx.set_attr(&node, "values", py_values); + let py_values = vm.ctx.new_list(values); + vm.ctx.set_attr(&node, "values", py_values); node } ast::Expression::Comprehension { kind, generators } => { let node = match kind.deref() { ast::ComprehensionKind::GeneratorExpression { .. } => { - create_node(ctx, "GeneratorExp") + create_node(vm, "GeneratorExp") } - ast::ComprehensionKind::List { .. } => create_node(ctx, "ListComp"), - ast::ComprehensionKind::Set { .. } => create_node(ctx, "SetComp"), - ast::ComprehensionKind::Dict { .. } => create_node(ctx, "DictComp"), + ast::ComprehensionKind::List { .. } => create_node(vm, "ListComp"), + ast::ComprehensionKind::Set { .. } => create_node(vm, "SetComp"), + ast::ComprehensionKind::Dict { .. } => create_node(vm, "DictComp"), }; let g = generators .iter() - .map(|g| comprehension_to_ast(ctx, g)) + .map(|g| comprehension_to_ast(vm, g)) .collect(); - let py_generators = ctx.new_list(g); - ctx.set_attr(&node, "generators", py_generators); + let py_generators = vm.ctx.new_list(g); + vm.ctx.set_attr(&node, "generators", py_generators); node } ast::Expression::Yield { value } => { - let node = create_node(ctx, "Yield"); + let node = create_node(vm, "Yield"); let py_value = match value { - Some(value) => expression_to_ast(ctx, value), - None => ctx.none(), + Some(value) => expression_to_ast(vm, value), + None => vm.ctx.none(), }; - ctx.set_attr(&node, "value", py_value); + vm.ctx.set_attr(&node, "value", py_value); node } ast::Expression::YieldFrom { value } => { - let node = create_node(ctx, "YieldFrom"); + let node = create_node(vm, "YieldFrom"); - let py_value = expression_to_ast(ctx, value); - ctx.set_attr(&node, "value", py_value); + let py_value = expression_to_ast(vm, value); + vm.ctx.set_attr(&node, "value", py_value); node } ast::Expression::Subscript { a, b } => { - let node = create_node(ctx, "Subscript"); + let node = create_node(vm, "Subscript"); - let py_value = expression_to_ast(ctx, a); - ctx.set_attr(&node, "value", py_value); + let py_value = expression_to_ast(vm, a); + vm.ctx.set_attr(&node, "value", py_value); - let py_slice = expression_to_ast(ctx, b); - ctx.set_attr(&node, "slice", py_slice); + let py_slice = expression_to_ast(vm, b); + vm.ctx.set_attr(&node, "slice", py_slice); node } ast::Expression::Attribute { value, name } => { - let node = create_node(ctx, "Attribute"); + let node = create_node(vm, "Attribute"); - let py_value = expression_to_ast(ctx, value); - ctx.set_attr(&node, "value", py_value); + let py_value = expression_to_ast(vm, value); + vm.ctx.set_attr(&node, "value", py_value); - let py_attr = ctx.new_str(name.to_string()); - ctx.set_attr(&node, "attr", py_attr); + let py_attr = vm.ctx.new_str(name.to_string()); + vm.ctx.set_attr(&node, "attr", py_attr); node } ast::Expression::Starred { value } => { - let node = create_node(ctx, "Starred"); + let node = create_node(vm, "Starred"); - let py_value = expression_to_ast(ctx, value); - ctx.set_attr(&node, "value", py_value); + let py_value = expression_to_ast(vm, value); + vm.ctx.set_attr(&node, "value", py_value); node } ast::Expression::Slice { elements } => { - let node = create_node(ctx, "Slice"); + let node = create_node(vm, "Slice"); - let py_value = expressions_to_ast(ctx, elements); - ctx.set_attr(&node, "bounds", py_value); + let py_value = expressions_to_ast(vm, elements); + vm.ctx.set_attr(&node, "bounds", py_value); node } - ast::Expression::String { value } => string_to_ast(ctx, value), + ast::Expression::String { value } => string_to_ast(vm, value), ast::Expression::Bytes { value } => { - let node = create_node(ctx, "Bytes"); - ctx.set_attr(&node, "s", ctx.new_bytes(value.clone())); + let node = create_node(vm, "Bytes"); + vm.ctx.set_attr(&node, "s", vm.ctx.new_bytes(value.clone())); node } }; // TODO: retrieve correct lineno: - let lineno = ctx.new_int(1); - ctx.set_attr(&node, "lineno", lineno); + let lineno = vm.ctx.new_int(1); + vm.ctx.set_attr(&node, "lineno", lineno); node } -fn parameters_to_ast(ctx: &PyContext, args: &ast::Parameters) -> PyObjectRef { - let node = create_node(ctx, "arguments"); +fn parameters_to_ast(vm: &VirtualMachine, args: &ast::Parameters) -> PyObjectRef { + let node = create_node(vm, "arguments"); - ctx.set_attr( + vm.ctx.set_attr( &node, "args", - ctx.new_list(args.args.iter().map(|a| parameter_to_ast(ctx, a)).collect()), + vm.ctx + .new_list(args.args.iter().map(|a| parameter_to_ast(vm, a)).collect()), ); node } -fn parameter_to_ast(ctx: &PyContext, parameter: &ast::Parameter) -> PyObjectRef { - let node = create_node(ctx, "arg"); +fn parameter_to_ast(vm: &VirtualMachine, parameter: &ast::Parameter) -> PyObjectRef { + let node = create_node(vm, "arg"); - let py_arg = ctx.new_str(parameter.arg.to_string()); - ctx.set_attr(&node, "arg", py_arg); + let py_arg = vm.ctx.new_str(parameter.arg.to_string()); + vm.ctx.set_attr(&node, "arg", py_arg); let py_annotation = if let Some(annotation) = ¶meter.annotation { - expression_to_ast(ctx, annotation) + expression_to_ast(vm, annotation) } else { - ctx.none() + vm.ctx.none() }; - ctx.set_attr(&node, "annotation", py_annotation); + vm.ctx.set_attr(&node, "annotation", py_annotation); node } -fn comprehension_to_ast(ctx: &PyContext, comprehension: &ast::Comprehension) -> PyObjectRef { - let node = create_node(ctx, "comprehension"); +fn comprehension_to_ast(vm: &VirtualMachine, comprehension: &ast::Comprehension) -> PyObjectRef { + let node = create_node(vm, "comprehension"); - let py_target = expression_to_ast(ctx, &comprehension.target); - ctx.set_attr(&node, "target", py_target); + let py_target = expression_to_ast(vm, &comprehension.target); + vm.ctx.set_attr(&node, "target", py_target); - let py_iter = expression_to_ast(ctx, &comprehension.iter); - ctx.set_attr(&node, "iter", py_iter); + let py_iter = expression_to_ast(vm, &comprehension.iter); + vm.ctx.set_attr(&node, "iter", py_iter); - let py_ifs = expressions_to_ast(ctx, &comprehension.ifs); - ctx.set_attr(&node, "ifs", py_ifs); + let py_ifs = expressions_to_ast(vm, &comprehension.ifs); + vm.ctx.set_attr(&node, "ifs", py_ifs); node } -fn string_to_ast(ctx: &PyContext, string: &ast::StringGroup) -> PyObjectRef { +fn string_to_ast(vm: &VirtualMachine, string: &ast::StringGroup) -> PyObjectRef { match string { ast::StringGroup::Constant { value } => { - let node = create_node(ctx, "Str"); - ctx.set_attr(&node, "s", ctx.new_str(value.clone())); + let node = create_node(vm, "Str"); + vm.ctx.set_attr(&node, "s", vm.ctx.new_str(value.clone())); node } ast::StringGroup::FormattedValue { value, .. } => { - let node = create_node(ctx, "FormattedValue"); - let py_value = expression_to_ast(ctx, value); - ctx.set_attr(&node, "value", py_value); + let node = create_node(vm, "FormattedValue"); + let py_value = expression_to_ast(vm, value); + vm.ctx.set_attr(&node, "value", py_value); node } ast::StringGroup::Joined { values } => { - let node = create_node(ctx, "JoinedStr"); - let py_values = ctx.new_list( + let node = create_node(vm, "JoinedStr"); + let py_values = vm.ctx.new_list( values .iter() - .map(|value| string_to_ast(ctx, value)) + .map(|value| string_to_ast(vm, value)) .collect(), ); - ctx.set_attr(&node, "values", py_values); + vm.ctx.set_attr(&node, "values", py_values); node } } @@ -619,7 +635,7 @@ fn ast_parse(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let internal_ast = parser::parse_program(&source_string) .map_err(|err| vm.new_value_error(format!("{}", err)))?; // source.clone(); - let ast_node = program_to_ast(&vm.ctx, &internal_ast); + let ast_node = program_to_ast(&vm, &internal_ast); Ok(ast_node) } @@ -628,6 +644,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "parse" => ctx.new_rustfunc(ast_parse), "Module" => py_class!(ctx, "_ast.Module", ctx.object(), {}), "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ctx.object(), {}), - "Call" => py_class!(ctx, "_ast.Call", ctx.object(), {}) + "Call" => py_class!(ctx, "_ast.Call", ctx.object(), {}), + "AST" => py_class!(ctx, "_ast.AST", ctx.object(), {}), }) } From e44b02b88f726408478377fc62f094661d59c8ce Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 25 Mar 2019 17:52:37 +0100 Subject: [PATCH 067/884] Avoid trace log in tests --- tests/test_snippets.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_snippets.py b/tests/test_snippets.py index 4be73b7bde..bc4c43a775 100644 --- a/tests/test_snippets.py +++ b/tests/test_snippets.py @@ -65,8 +65,7 @@ def run_via_cpython_bytecode(filename, test_type): # Step2: run cpython bytecode: env = os.environ.copy() - log_level = 'info' if test_type == _TestType.benchmark else 'debug' - env['RUST_LOG'] = '{},cargo=error,jobserver=error'.format(log_level) + env['RUST_LOG'] = 'info,cargo=error,jobserver=error' env['RUST_BACKTRACE'] = '1' with pushd(CPYTHON_RUNNER_DIR): subprocess.check_call(['cargo', 'run', bytecode_filename], env=env) @@ -74,8 +73,7 @@ def run_via_cpython_bytecode(filename, test_type): def run_via_rustpython(filename, test_type): env = os.environ.copy() - log_level = 'info' if test_type == _TestType.benchmark else 'trace' - env['RUST_LOG'] = '{},cargo=error,jobserver=error'.format(log_level) + env['RUST_LOG'] = 'info,cargo=error,jobserver=error' env['RUST_BACKTRACE'] = '1' if env.get('CODE_COVERAGE', 'false') == 'true': subprocess.check_call( From 4ab59a8e4c369104ab8518f651038877ada301f1 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 25 Mar 2019 17:53:13 +0100 Subject: [PATCH 068/884] Do not run benchmarks in the main test suite --- tests/test_snippets.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_snippets.py b/tests/test_snippets.py index bc4c43a775..7aa9c2456b 100644 --- a/tests/test_snippets.py +++ b/tests/test_snippets.py @@ -17,7 +17,6 @@ class _TestType(enum.Enum): functional = 1 - benchmark = 2 logger = logging.getLogger('tests') @@ -25,7 +24,6 @@ class _TestType(enum.Enum): TEST_ROOT = os.path.abspath(os.path.join(ROOT_DIR, 'tests')) TEST_DIRS = { _TestType.functional: os.path.join(TEST_ROOT, 'snippets'), - _TestType.benchmark: os.path.join(TEST_ROOT, 'benchmarks'), } CPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR, 'py_code_object')) RUSTPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR)) From 16e04cf298a67f1c2686fe846177789425a26df4 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 25 Mar 2019 17:54:04 +0100 Subject: [PATCH 069/884] Run rustpython tests without cargo middleman --- tests/test_snippets.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/test_snippets.py b/tests/test_snippets.py index 7aa9c2456b..7edbd7b7e5 100644 --- a/tests/test_snippets.py +++ b/tests/test_snippets.py @@ -73,12 +73,13 @@ def run_via_rustpython(filename, test_type): env = os.environ.copy() env['RUST_LOG'] = 'info,cargo=error,jobserver=error' env['RUST_BACKTRACE'] = '1' + + target = 'release' if env.get('CODE_COVERAGE', 'false') == 'true': - subprocess.check_call( - ['cargo', 'run', filename], env=env) - else: - subprocess.check_call( - ['cargo', 'run', '--release', filename], env=env) + target = 'debug' + binary = os.path.abspath(os.path.join(ROOT_DIR, 'target', target, 'rustpython')) + + subprocess.check_call([binary, filename], env=env) def create_test_function(cls, filename, method, test_type): @@ -120,4 +121,7 @@ def get_test_files(): # @populate('cpython_bytecode') @populate('rustpython') class SampleTestCase(unittest.TestCase): - pass + @classmethod + def setUpClass(cls): + subprocess.check_call(['cargo', 'build']) + subprocess.check_call(['cargo', 'build', '--release']) From 2131a8130cfcf344a81437d7c7413211976dc25e Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 25 Mar 2019 17:55:17 +0100 Subject: [PATCH 070/884] Clean up some tests --- tests/snippets/append.py | 2 -- tests/snippets/strings.py | 8 ++++++++ tests/snippets/xfail_3.1.2.17.py | 7 ------- 3 files changed, 8 insertions(+), 9 deletions(-) delete mode 100644 tests/snippets/append.py delete mode 100644 tests/snippets/xfail_3.1.2.17.py diff --git a/tests/snippets/append.py b/tests/snippets/append.py deleted file mode 100644 index a0490cb6d3..0000000000 --- a/tests/snippets/append.py +++ /dev/null @@ -1,2 +0,0 @@ -x = [] -x.append(1) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 4483b37f62..31511db11e 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -1,3 +1,5 @@ +from testutils import assert_raises + assert "a" == 'a' assert """a""" == "a" assert len(""" " "" " "" """) == 11 @@ -124,3 +126,9 @@ assert 'z' > 'b' assert 'z' >= 'b' assert 'a' >= 'a' + +def try_mutate_str(): + word = "word" + word[0] = 'x' + +assert_raises(TypeError, try_mutate_str) diff --git a/tests/snippets/xfail_3.1.2.17.py b/tests/snippets/xfail_3.1.2.17.py deleted file mode 100644 index fc956bef43..0000000000 --- a/tests/snippets/xfail_3.1.2.17.py +++ /dev/null @@ -1,7 +0,0 @@ -word = "Python" - -word[0] = "J" # Should raise a error, immutable -word[2:] = "Jy" # Should raise a error, immutable - - - From 6ae10ed938a12dfdcede666a147fb4bba99666b7 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Mon, 25 Mar 2019 20:23:13 +0100 Subject: [PATCH 071/884] Fix build and add extend_class macro usage for set and frozenset --- vm/src/obj/objset.rs | 180 +++++++++++++++---------------------------- 1 file changed, 62 insertions(+), 118 deletions(-) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 300dc9bca9..72ee8c8970 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -85,12 +85,12 @@ fn create_set( PySet { elements: RefCell::new(elements), }, - PySet::class(vm).into_object(), + PySet::class(vm), )) } else if objtype::issubclass(&cls, &vm.ctx.frozenset_type()) { Ok(PyObject::new( PyFrozenSet { elements: elements }, - PyFrozenSet::class(vm).into_object(), + PyFrozenSet::class(vm), )) } else { Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", cls))) @@ -593,65 +593,43 @@ pub fn init(context: &PyContext) { set(iterable) -> new set object\n\n\ Build an unordered collection of unique elements."; - context.set_attr(set_type, "__contains__", context.new_rustfunc(set_contains)); - context.set_attr(set_type, "__len__", context.new_rustfunc(set_len)); - context.set_attr(set_type, "__new__", context.new_rustfunc(set_new)); - context.set_attr(set_type, "__repr__", context.new_rustfunc(set_repr)); - context.set_attr(set_type, "__eq__", context.new_rustfunc(set_eq)); - context.set_attr(set_type, "__ge__", context.new_rustfunc(set_ge)); - context.set_attr(set_type, "__gt__", context.new_rustfunc(set_gt)); - context.set_attr(set_type, "__le__", context.new_rustfunc(set_le)); - context.set_attr(set_type, "__lt__", context.new_rustfunc(set_lt)); - context.set_attr(set_type, "issubset", context.new_rustfunc(set_le)); - context.set_attr(set_type, "issuperset", context.new_rustfunc(set_ge)); - context.set_attr(set_type, "union", context.new_rustfunc(set_union)); - context.set_attr(set_type, "__or__", context.new_rustfunc(set_union)); - context.set_attr( - set_type, - "intersection", - context.new_rustfunc(set_intersection), - ); - context.set_attr(set_type, "__and__", context.new_rustfunc(set_intersection)); - context.set_attr(set_type, "difference", context.new_rustfunc(set_difference)); - context.set_attr(set_type, "__sub__", context.new_rustfunc(set_difference)); - context.set_attr( - set_type, - "symmetric_difference", - context.new_rustfunc(set_symmetric_difference), - ); - context.set_attr( - set_type, - "__xor__", - context.new_rustfunc(set_symmetric_difference), - ); - context.set_attr(set_type, "__doc__", context.new_str(set_doc.to_string())); - context.set_attr(set_type, "add", context.new_rustfunc(set_add)); - context.set_attr(set_type, "remove", context.new_rustfunc(set_remove)); - context.set_attr(set_type, "discard", context.new_rustfunc(set_discard)); - context.set_attr(set_type, "clear", context.new_rustfunc(set_clear)); - context.set_attr(set_type, "copy", context.new_rustfunc(set_copy)); - context.set_attr(set_type, "pop", context.new_rustfunc(set_pop)); - context.set_attr(set_type, "update", context.new_rustfunc(set_update)); - context.set_attr(set_type, "__ior__", context.new_rustfunc(set_ior)); - context.set_attr( - set_type, - "intersection_update", - context.new_rustfunc(set_intersection_update), - ); - context.set_attr(set_type, "__iand__", context.new_rustfunc(set_iand)); - context.set_attr( - set_type, - "difference_update", - context.new_rustfunc(set_difference_update), - ); - context.set_attr(set_type, "__isub__", context.new_rustfunc(set_isub)); - context.set_attr( - set_type, - "symmetric_difference_update", - context.new_rustfunc(set_symmetric_difference_update), - ); - context.set_attr(set_type, "__ixor__", context.new_rustfunc(set_ixor)); - context.set_attr(set_type, "__iter__", context.new_rustfunc(set_iter)); + extend_class!(context, set_type, { + "__contains__" => context.new_rustfunc(set_contains), + "__len__" => context.new_rustfunc(set_len), + "__new__" => context.new_rustfunc(set_new), + "__repr__" => context.new_rustfunc(set_repr), + "__eq__" => context.new_rustfunc(set_eq), + "__ge__" => context.new_rustfunc(set_ge), + "__gt__" => context.new_rustfunc(set_gt), + "__le__" => context.new_rustfunc(set_le), + "__lt__" => context.new_rustfunc(set_lt), + "issubset" => context.new_rustfunc(set_le), + "issuperset" => context.new_rustfunc(set_ge), + "union" => context.new_rustfunc(set_union), + "__or__" => context.new_rustfunc(set_union), + "intersection" => context.new_rustfunc(set_intersection), + "__and__" => context.new_rustfunc(set_intersection), + "difference" => context.new_rustfunc(set_difference), + "__sub__" => context.new_rustfunc(set_difference), + "symmetric_difference" => context.new_rustfunc(set_symmetric_difference), + "__xor__" => context.new_rustfunc(set_symmetric_difference), + "__doc__" => context.new_str(set_doc.to_string()), + "add" => context.new_rustfunc(set_add), + "remove" => context.new_rustfunc(set_remove), + "discard" => context.new_rustfunc(set_discard), + "clear" => context.new_rustfunc(set_clear), + "copy" => context.new_rustfunc(set_copy), + "pop" => context.new_rustfunc(set_pop), + "update" => context.new_rustfunc(set_update), + "__ior__" => context.new_rustfunc(set_ior), + "intersection_update" => context.new_rustfunc(set_intersection_update), + "__iand__" => context.new_rustfunc(set_iand), + "difference_update" => context.new_rustfunc(set_difference_update), + "__isub__" => context.new_rustfunc(set_isub), + "symmetric_difference_update" => context.new_rustfunc(set_symmetric_difference_update), + "__ixor__" => context.new_rustfunc(set_ixor), + "__iter__" => context.new_rustfunc(set_iter) + }); let frozenset_type = &context.frozenset_type; @@ -659,61 +637,27 @@ pub fn init(context: &PyContext) { frozenset(iterable) -> frozenset object\n\n\ Build an immutable unordered collection of unique elements."; - context.set_attr(frozenset_type, "__new__", context.new_rustfunc(set_new)); - context.set_attr(frozenset_type, "__eq__", context.new_rustfunc(set_eq)); - context.set_attr(frozenset_type, "__ge__", context.new_rustfunc(set_ge)); - context.set_attr(frozenset_type, "__gt__", context.new_rustfunc(set_gt)); - context.set_attr(frozenset_type, "__le__", context.new_rustfunc(set_le)); - context.set_attr(frozenset_type, "__lt__", context.new_rustfunc(set_lt)); - context.set_attr(frozenset_type, "issubset", context.new_rustfunc(set_le)); - context.set_attr(frozenset_type, "issuperset", context.new_rustfunc(set_ge)); - context.set_attr(frozenset_type, "union", context.new_rustfunc(set_union)); - context.set_attr(frozenset_type, "__or__", context.new_rustfunc(set_union)); - context.set_attr( - frozenset_type, - "intersection", - context.new_rustfunc(set_intersection), - ); - context.set_attr( - frozenset_type, - "__and__", - context.new_rustfunc(set_intersection), - ); - context.set_attr( - frozenset_type, - "difference", - context.new_rustfunc(set_difference), - ); - context.set_attr( - frozenset_type, - "__sub__", - context.new_rustfunc(set_difference), - ); - context.set_attr( - frozenset_type, - "symmetric_difference", - context.new_rustfunc(set_symmetric_difference), - ); - context.set_attr( - frozenset_type, - "__xor__", - context.new_rustfunc(set_symmetric_difference), - ); - context.set_attr( - frozenset_type, - "__contains__", - context.new_rustfunc(set_contains), - ); - context.set_attr(frozenset_type, "__len__", context.new_rustfunc(set_len)); - context.set_attr( - frozenset_type, - "__doc__", - context.new_str(frozenset_doc.to_string()), - ); - context.set_attr( - frozenset_type, - "__repr__", - context.new_rustfunc(frozenset_repr), - ); - context.set_attr(frozenset_type, "copy", context.new_rustfunc(set_copy)); + extend_class!(context, frozenset_type, { + "__new__" => context.new_rustfunc(set_new), + "__eq__" => context.new_rustfunc(set_eq), + "__ge__" => context.new_rustfunc(set_ge), + "__gt__" => context.new_rustfunc(set_gt), + "__le__" => context.new_rustfunc(set_le), + "__lt__" => context.new_rustfunc(set_lt), + "issubset" => context.new_rustfunc(set_le), + "issuperset" => context.new_rustfunc(set_ge), + "union" => context.new_rustfunc(set_union), + "__or__" => context.new_rustfunc(set_union), + "intersection" => context.new_rustfunc(set_intersection), + "__and__" => context.new_rustfunc(set_intersection), + "difference" => context.new_rustfunc(set_difference), + "__sub__" => context.new_rustfunc(set_difference), + "symmetric_difference" => context.new_rustfunc(set_symmetric_difference), + "__xor__" => context.new_rustfunc(set_symmetric_difference), + "__contains__" => context.new_rustfunc(set_contains), + "__len__" => context.new_rustfunc(set_len), + "__doc__" => context.new_str(frozenset_doc.to_string()), + "__repr__" => context.new_rustfunc(frozenset_repr), + "copy" => context.new_rustfunc(set_copy) + }); } From 17b816fbf83069332441c98daee231a9772cf809 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 25 Mar 2019 20:37:47 +0000 Subject: [PATCH 072/884] Fix objset compilation error caused by merge race. --- vm/src/obj/objset.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 72ee8c8970..7071cc6e4c 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -86,11 +86,13 @@ fn create_set( elements: RefCell::new(elements), }, PySet::class(vm), + None, )) } else if objtype::issubclass(&cls, &vm.ctx.frozenset_type()) { Ok(PyObject::new( PyFrozenSet { elements: elements }, PyFrozenSet::class(vm), + None, )) } else { Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", cls))) From ac2a5e281c55cc9fce29491e1795152e498f2395 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 24 Mar 2019 13:36:13 -0400 Subject: [PATCH 073/884] Add comment explaining bytes -> Uint8Array conversion --- wasm/lib/src/convert.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 909955b6df..641eb3538f 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -126,6 +126,10 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { { let bytes = objbytes::get_value(&py_obj); unsafe { + // `Uint8Array::view` is an `unsafe fn` because it provides + // a direct view into the WASM linear memory; if you were to allocate + // something with Rust that view would probably become invalid. It's safe + // because we then copy the array using `Uint8Array::slice`. let view = Uint8Array::view(&bytes); view.slice(0, bytes.len() as u32).into() } From cc4f3fdb4097a6701158da7c30a8bf1f70e2f954 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Mon, 25 Mar 2019 19:18:01 -0700 Subject: [PATCH 074/884] Clean up TypeProtocol --- vm/src/builtins.rs | 14 +++++++------- vm/src/exceptions.rs | 2 +- vm/src/frame.rs | 6 +++--- vm/src/function.rs | 2 +- vm/src/macros.rs | 2 +- vm/src/obj/objfloat.rs | 2 +- vm/src/obj/objfunction.rs | 2 +- vm/src/obj/objint.rs | 10 +++++----- vm/src/obj/objiter.rs | 2 +- vm/src/obj/objnone.rs | 10 +++++----- vm/src/obj/objobject.rs | 24 ++++++++++++------------ vm/src/obj/objproperty.rs | 10 ++++++---- vm/src/obj/objsequence.rs | 4 ++-- vm/src/obj/objset.rs | 30 +++++++++++++++--------------- vm/src/obj/objstr.rs | 6 +++--- vm/src/obj/objsuper.rs | 2 +- vm/src/obj/objtype.rs | 28 +++++++++------------------- vm/src/pyobject.rs | 36 ++++++++++++++++++------------------ vm/src/stdlib/json.rs | 2 +- vm/src/vm.rs | 16 ++++++++-------- 20 files changed, 101 insertions(+), 109 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 4d9fb79896..fbd2d53568 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -88,7 +88,7 @@ fn builtin_bin(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_callable(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - let is_callable = objtype::class_has_attr(&obj.type_pyref(), "__call__"); + let is_callable = objtype::class_has_attr(&obj.class(), "__call__"); Ok(vm.new_bool(is_callable)) } @@ -240,7 +240,7 @@ fn make_scope( } else if vm.isinstance(arg, &dict_type)? { Some(arg) } else { - let arg_typ = arg.typ(); + let arg_typ = arg.class(); let actual_type = vm.to_pystr(&arg_typ)?; let expected_type_name = vm.to_pystr(&dict_type)?; return Err(vm.new_type_error(format!( @@ -368,7 +368,7 @@ fn builtin_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(value) => vm.invoke(value, PyFuncArgs::default()), Err(..) => Err(vm.new_type_error(format!( "object of type '{}' has no method {:?}", - objtype::get_type_name(&obj.typ()), + objtype::get_type_name(&obj.class()), len_method_name ))), } @@ -607,7 +607,7 @@ fn builtin_reversed(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: fallback to using __len__ and __getitem__, if object supports sequence protocol Err(..) => Err(vm.new_type_error(format!( "'{}' object is not reversible", - objtype::get_type_name(&obj.typ()), + objtype::get_type_name(&obj.class()), ))), } } @@ -802,9 +802,9 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu }; for base in bases.clone() { - if objtype::issubclass(&base.type_pyref(), &metaclass) { - metaclass = base.type_pyref(); - } else if !objtype::issubclass(&metaclass, &base.type_pyref()) { + if objtype::issubclass(&base.class(), &metaclass) { + metaclass = base.class(); + } else if !objtype::issubclass(&metaclass, &base.class()) { return Err(vm.new_type_error("metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases".to_string())); } } diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 3eb962edfe..7177527da4 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -68,7 +68,7 @@ fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))] ); - let type_name = objtype::get_type_name(&exc.typ()); + let type_name = objtype::get_type_name(&exc.class()); let msg = if let Ok(m) = vm.get_attribute(exc.clone(), "msg") { match vm.to_pystr(&m) { Ok(msg) => msg, diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 3584a57a81..53479f8fc8 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -949,7 +949,7 @@ impl Frame { // let args = PyFuncArgs::default(); // TODO: what happens when we got an error during handling exception? let args = if let Some(exc) = exc { - let exc_type = exc.typ(); + let exc_type = exc.class().into_object(); let exc_val = exc.clone(); let exc_tb = vm.ctx.none(); // TODO: retrieve traceback? vec![exc_type, exc_val, exc_tb] @@ -1093,7 +1093,7 @@ impl Frame { Ok(found) => Ok(found), Err(_) => Err(vm.new_type_error(format!( "{} has no __contains__ method", - objtype::get_type_name(&haystack.typ()) + objtype::get_type_name(&haystack.class()) ))), } } @@ -1103,7 +1103,7 @@ impl Frame { Ok(found) => Ok(vm.ctx.new_bool(!objbool::get_value(&found))), Err(_) => Err(vm.new_type_error(format!( "{} has no __contains__ method", - objtype::get_type_name(&haystack.typ()) + objtype::get_type_name(&haystack.class()) ))), } } diff --git a/vm/src/function.rs b/vm/src/function.rs index ed959d85c9..1c61ffc87c 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -99,7 +99,7 @@ impl PyFuncArgs { Ok(Some(kwarg)) } else { let expected_ty_name = vm.to_pystr(&ty)?; - let actual_ty_name = vm.to_pystr(&kwarg.typ())?; + let actual_ty_name = vm.to_pystr(&kwarg.class())?; Err(vm.new_type_error(format!( "argument of type {} is required for named parameter `{}` (got: {})", expected_ty_name, key, actual_ty_name diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 0c205ffc80..a631867976 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -20,7 +20,7 @@ macro_rules! type_check { let arg = &$args.args[$arg_count]; if !$crate::obj::objtype::isinstance(arg, &expected_type) { - let arg_typ = arg.typ(); + let arg_typ = arg.class(); let expected_type_name = $vm.to_pystr(&expected_type)?; let actual_type = $vm.to_pystr(&arg_typ)?; return Err($vm.new_type_error(format!( diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 958d368a89..5e68f59603 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -190,7 +190,7 @@ impl PyFloatRef { } } } else { - let type_name = objtype::get_type_name(&arg.typ()); + let type_name = objtype::get_type_name(&arg.class()); return Err(vm.new_type_error(format!("can't convert {} to float", type_name))); }; PyFloat { value }.into_ref_with_type(vm, cls) diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index 1488ee79c7..507d7697a9 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -76,7 +76,7 @@ fn bind_method(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(function, None), (obj, None), (cls, None)] ); - if obj.is(&vm.get_none()) && !cls.is(&obj.typ()) { + if obj.is(&vm.get_none()) && !cls.is(&obj.class()) { Ok(function.clone()) } else { Ok(vm.ctx.new_bound_method(function.clone(), obj.clone())) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index dd40f06a42..f31997eadb 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -213,8 +213,8 @@ impl PyIntRef { if !objtype::isinstance(&other, &vm.ctx.int_type()) { return Err(vm.new_type_error(format!( "unsupported operand type(s) for << '{}' and '{}'", - objtype::get_type_name(&self.as_object().typ()), - objtype::get_type_name(&other.typ()) + objtype::get_type_name(&self.as_object().class()), + objtype::get_type_name(&other.class()) ))); } @@ -236,8 +236,8 @@ impl PyIntRef { if !objtype::isinstance(&other, &vm.ctx.int_type()) { return Err(vm.new_type_error(format!( "unsupported operand type(s) for >> '{}' and '{}'", - objtype::get_type_name(&self.as_object().typ()), - objtype::get_type_name(&other.typ()) + objtype::get_type_name(&self.as_object().class()), + objtype::get_type_name(&other.class()) ))); } @@ -420,7 +420,7 @@ pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult PyResult { vm.call_method(iter_target, "__iter__", vec![]) - // let type_str = objstr::get_value(&vm.to_str(iter_target.typ()).unwrap()); + // let type_str = objstr::get_value(&vm.to_str(iter_target.class()).unwrap()); // let type_error = vm.new_type_error(format!("Cannot iterate over {}", type_str)); // return Err(type_error); } diff --git a/vm/src/obj/objnone.rs b/vm/src/obj/objnone.rs index 840b00eccc..aff20f465f 100644 --- a/vm/src/obj/objnone.rs +++ b/vm/src/obj/objnone.rs @@ -12,7 +12,7 @@ pub type PyNoneRef = PyRef; impl PyValue for PyNone { fn class(vm: &VirtualMachine) -> PyClassRef { - vm.ctx.none().type_pyref() + vm.ctx.none().class() } } @@ -44,7 +44,7 @@ impl PyNoneRef { fn get_attribute(self, name: PyStringRef, vm: &VirtualMachine) -> PyResult { trace!("None.__getattribute__({:?}, {:?})", self, name); - let cls = self.typ(); + let cls = self.class(); // Properties use a comparision with None to determine if they are either invoked by am // instance binding or a class binding. But if the object itself is None then this detection @@ -69,7 +69,7 @@ impl PyNoneRef { } if let Some(attr) = class_get_attr(&cls, &name.value) { - let attr_class = attr.type_pyref(); + let attr_class = attr.class(); if class_has_attr(&attr_class, "__set__") { if let Some(get_func) = class_get_attr(&attr_class, "__get__") { return call_descriptor( @@ -88,7 +88,7 @@ impl PyNoneRef { // Ok(obj_attr) // } else if let Some(attr) = class_get_attr(&cls, &name.value) { - let attr_class = attr.type_pyref(); + let attr_class = attr.class(); if let Some(get_func) = class_get_attr(&attr_class, "__get__") { call_descriptor(attr, get_func, self.into_object(), cls.into_object(), vm) } else { @@ -107,7 +107,7 @@ fn none_new(_: PyClassRef, vm: &VirtualMachine) -> PyNoneRef { } pub fn init(context: &PyContext) { - extend_class!(context, &context.none.typ(), { + extend_class!(context, &context.none.class(), { "__new__" => context.new_rustfunc(none_new), "__repr__" => context.new_rustfunc(PyNoneRef::repr), "__bool__" => context.new_rustfunc(PyNoneRef::bool), diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 60473fad03..14dbb2e26f 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -104,10 +104,10 @@ fn object_setattr( vm: &VirtualMachine, ) -> PyResult<()> { trace!("object.__setattr__({:?}, {}, {:?})", obj, attr_name, value); - let cls = obj.type_pyref(); + let cls = obj.class(); if let Some(attr) = objtype::class_get_attr(&cls, &attr_name.value) { - if let Some(descriptor) = objtype::class_get_attr(&attr.type_pyref(), "__set__") { + if let Some(descriptor) = objtype::class_get_attr(&attr.class(), "__set__") { return vm .invoke(descriptor, vec![attr, obj.clone(), value]) .map(|_| ()); @@ -118,7 +118,7 @@ fn object_setattr( dict.set_item(&vm.ctx, &attr_name.value, value); Ok(()) } else { - let type_name = objtype::get_type_name(obj.type_ref()); + let type_name = objtype::get_type_name(&obj.class()); Err(vm.new_attribute_error(format!( "'{}' object has no attribute '{}'", type_name, &attr_name.value @@ -127,10 +127,10 @@ fn object_setattr( } fn object_delattr(obj: PyObjectRef, attr_name: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { - let cls = obj.type_pyref(); + let cls = obj.class(); if let Some(attr) = objtype::class_get_attr(&cls, &attr_name.value) { - if let Some(descriptor) = objtype::class_get_attr(&attr.type_pyref(), "__delete__") { + if let Some(descriptor) = objtype::class_get_attr(&attr.class(), "__delete__") { return vm.invoke(descriptor, vec![attr, obj.clone()]).map(|_| ()); } } @@ -139,7 +139,7 @@ fn object_delattr(obj: PyObjectRef, attr_name: PyStringRef, vm: &VirtualMachine) dict.del_item(&attr_name.value); Ok(()) } else { - let type_name = objtype::get_type_name(obj.type_ref()); + let type_name = objtype::get_type_name(&obj.class()); Err(vm.new_attribute_error(format!( "'{}' object has no attribute '{}'", type_name, &attr_name.value @@ -154,7 +154,7 @@ fn object_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn object_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.object()))]); - let type_name = objtype::get_type_name(&obj.typ()); + let type_name = objtype::get_type_name(&obj.class()); let address = obj.get_id(); Ok(vm.new_str(format!("<{} object at 0x{:x}>", type_name, address))) } @@ -216,7 +216,7 @@ fn object_init(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { } fn object_class(obj: PyObjectRef, _vm: &VirtualMachine) -> PyObjectRef { - obj.typ() + obj.class().into_object() } fn object_class_setter( @@ -224,7 +224,7 @@ fn object_class_setter( _value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - let type_repr = vm.to_pystr(&instance.typ())?; + let type_repr = vm.to_pystr(&instance.class())?; Err(vm.new_type_error(format!("can't change class of type '{}'", type_repr))) } @@ -247,10 +247,10 @@ fn object_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { ); let name = objstr::get_value(&name_str); trace!("object.__getattribute__({:?}, {:?})", obj, name); - let cls = obj.type_pyref(); + let cls = obj.class(); if let Some(attr) = objtype::class_get_attr(&cls, &name) { - let attr_class = attr.type_pyref(); + let attr_class = attr.class(); if objtype::class_has_attr(&attr_class, "__set__") { if let Some(descriptor) = objtype::class_get_attr(&attr_class, "__get__") { return vm.invoke(descriptor, vec![attr, obj.clone(), cls.into_object()]); @@ -279,7 +279,7 @@ fn object_getattr(obj: &PyObjectRef, attr_name: &str) -> Option { pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes { // Get class attributes: - let mut attributes = objtype::get_attributes(obj.type_pyref()); + let mut attributes = objtype::get_attributes(obj.class()); // Get instance attributes: if let Some(dict) = &obj.dict { diff --git a/vm/src/obj/objproperty.rs b/vm/src/obj/objproperty.rs index e6b11904eb..9d72171106 100644 --- a/vm/src/obj/objproperty.rs +++ b/vm/src/obj/objproperty.rs @@ -6,7 +6,9 @@ use crate::function::IntoPyNativeFunc; use crate::function::OptionalArg; use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{IdProtocol, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{ + IdProtocol, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, +}; use crate::vm::VirtualMachine; /// Read-only property, doesn't have __set__ or __delete__ @@ -137,7 +139,7 @@ impl PyPropertyRef { setter: self.setter.clone(), deleter: self.deleter.clone(), } - .into_ref_with_type(vm, self.typ()) + .into_ref_with_type(vm, TypeProtocol::class(&self)) } fn setter(self, setter: Option, vm: &VirtualMachine) -> PyResult { @@ -146,7 +148,7 @@ impl PyPropertyRef { setter: setter.or_else(|| self.setter.clone()), deleter: self.deleter.clone(), } - .into_ref_with_type(vm, self.typ()) + .into_ref_with_type(vm, TypeProtocol::class(&self)) } fn deleter(self, deleter: Option, vm: &VirtualMachine) -> PyResult { @@ -155,7 +157,7 @@ impl PyPropertyRef { setter: self.setter.clone(), deleter: deleter.or_else(|| self.deleter.clone()), } - .into_ref_with_type(vm, self.typ()) + .into_ref_with_type(vm, TypeProtocol::class(&self)) } } diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index 80f4bf40f6..a07b2454de 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -163,13 +163,13 @@ pub fn get_item( if sequence.payload::().is_some() { Ok(PyObject::new( PyList::from(elements.to_vec().get_slice_items(vm, &subscript)?), - sequence.type_pyref(), + sequence.class(), None, )) } else if sequence.payload::().is_some() { Ok(PyObject::new( PyTuple::from(elements.to_vec().get_slice_items(vm, &subscript)?), - sequence.type_pyref(), + sequence.class(), None, )) } else { diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 7071cc6e4c..ad82c34de0 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -231,16 +231,16 @@ fn set_new(cls: PyClassRef, iterable: OptionalArg, vm: &VirtualMach fn set_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { trace!("set.len called with: {:?}", args); arg_check!(vm, args, required = [(s, None)]); - validate_set_or_frozenset(vm, s.type_pyref())?; + validate_set_or_frozenset(vm, s.class())?; let elements = get_elements(s); Ok(vm.context().new_int(elements.len())) } fn set_copy(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { trace!("set.copy called with: {:?}", obj); - validate_set_or_frozenset(vm, obj.type_pyref())?; + validate_set_or_frozenset(vm, obj.class())?; let elements = get_elements(&obj).clone(); - create_set(vm, elements, obj.type_pyref()) + create_set(vm, elements, obj.class()) } fn set_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -265,7 +265,7 @@ fn set_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn set_contains(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(set, None), (needle, None)]); - validate_set_or_frozenset(vm, set.type_pyref())?; + validate_set_or_frozenset(vm, set.class())?; for element in get_elements(set).iter() { match vm._eq(needle.clone(), element.1.clone()) { Ok(value) => { @@ -333,8 +333,8 @@ fn set_compare_inner( ) -> PyResult { arg_check!(vm, args, required = [(zelf, None), (other, None)]); - validate_set_or_frozenset(vm, zelf.type_pyref())?; - validate_set_or_frozenset(vm, other.type_pyref())?; + validate_set_or_frozenset(vm, zelf.class())?; + validate_set_or_frozenset(vm, other.class())?; let get_zelf = |swap: bool| -> &PyObjectRef { if swap { @@ -370,13 +370,13 @@ fn set_compare_inner( } fn set_union(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - validate_set_or_frozenset(vm, zelf.type_pyref())?; - validate_set_or_frozenset(vm, other.type_pyref())?; + validate_set_or_frozenset(vm, zelf.class())?; + validate_set_or_frozenset(vm, other.class())?; let mut elements = get_elements(&zelf).clone(); elements.extend(get_elements(&other).clone()); - create_set(vm, elements, zelf.type_pyref()) + create_set(vm, elements, zelf.class()) } fn set_intersection(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -392,8 +392,8 @@ fn set_symmetric_difference( other: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - validate_set_or_frozenset(vm, zelf.type_pyref())?; - validate_set_or_frozenset(vm, other.type_pyref())?; + validate_set_or_frozenset(vm, zelf.class())?; + validate_set_or_frozenset(vm, other.class())?; let mut elements = HashMap::new(); for element in get_elements(&zelf).iter() { @@ -410,7 +410,7 @@ fn set_symmetric_difference( } } - create_set(vm, elements, zelf.type_pyref()) + create_set(vm, elements, zelf.class()) } enum SetCombineOperation { @@ -424,8 +424,8 @@ fn set_combine_inner( vm: &VirtualMachine, op: SetCombineOperation, ) -> PyResult { - validate_set_or_frozenset(vm, zelf.type_pyref())?; - validate_set_or_frozenset(vm, other.type_pyref())?; + validate_set_or_frozenset(vm, zelf.class())?; + validate_set_or_frozenset(vm, other.class())?; let mut elements = HashMap::new(); for element in get_elements(&zelf).iter() { @@ -439,7 +439,7 @@ fn set_combine_inner( } } - create_set(vm, elements, zelf.type_pyref()) + create_set(vm, elements, zelf.class()) } fn set_pop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 67ee8e5e7e..aa62812a24 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -715,7 +715,7 @@ fn str_format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let zelf = &args.args[0]; if !objtype::isinstance(&zelf, &vm.ctx.str_type()) { - let zelf_typ = zelf.typ(); + let zelf_typ = zelf.class(); let actual_type = vm.to_pystr(&zelf_typ)?; return Err(vm.new_type_error(format!( "descriptor 'format' requires a 'str' object but received a '{}'", @@ -738,7 +738,7 @@ fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: & let returned_type = vm.ctx.new_str(format_spec.to_string()); let result = vm.call_method(&argument, "__format__", vec![returned_type])?; if !objtype::isinstance(&result, &vm.ctx.str_type()) { - let result_type = result.typ(); + let result_type = result.class(); let actual_type = vm.to_pystr(&result_type)?; return Err(vm.new_type_error(format!("__format__ must return a str, not {}", actual_type))); } @@ -809,7 +809,7 @@ fn str_new( OptionalArg::Present(ref input) => vm.to_str(input)?.into_object(), OptionalArg::Missing => vm.new_str("".to_string()), }; - if string.typ().is(&cls) { + if string.class().is(&cls) { TryFromObject::try_from_object(vm, string) } else { let payload = string.payload::().unwrap(); diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index f04fb66058..fad365e249 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -112,7 +112,7 @@ fn super_new( // Check type argument: if !objtype::isinstance(py_type.as_object(), &vm.get_type()) { - let type_name = objtype::get_type_name(py_type.as_object().type_ref()); + let type_name = objtype::get_type_name(&py_type.as_object().class()); return Err(vm.new_type_error(format!( "super() argument 1 must be type, not {}", type_name diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index d87ca3843c..857ec402fb 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -39,12 +39,6 @@ impl PyValue for PyClass { } } -impl TypeProtocol for PyClassRef { - fn type_ref(&self) -> &PyObjectRef { - &self.as_object().type_ref() - } -} - struct IterMro<'a> { cls: &'a PyClassRef, offset: Option, @@ -117,10 +111,10 @@ impl PyClassRef { fn getattribute(self, name_ref: PyStringRef, vm: &VirtualMachine) -> PyResult { let name = &name_ref.value; trace!("type.__getattribute__({:?}, {:?})", self, name); - let mcl = self.type_pyref(); + let mcl = self.class(); if let Some(attr) = class_get_attr(&mcl, &name) { - let attr_class = attr.type_pyref(); + let attr_class = attr.class(); if class_has_attr(&attr_class, "__set__") { if let Some(descriptor) = class_get_attr(&attr_class, "__get__") { return vm.invoke( @@ -132,7 +126,7 @@ impl PyClassRef { } if let Some(attr) = class_get_attr(&self, &name) { - let attr_class = attr.type_pyref(); + let attr_class = attr.class(); if let Some(descriptor) = class_get_attr(&attr_class, "__get__") { let none = vm.get_none(); return vm.invoke(descriptor, vec![attr, none, self.into_object()]); @@ -156,8 +150,8 @@ impl PyClassRef { value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult<()> { - if let Some(attr) = class_get_attr(&self.type_pyref(), &attr_name.value) { - if let Some(descriptor) = class_get_attr(&attr.type_pyref(), "__set__") { + if let Some(attr) = class_get_attr(&self.class(), &attr_name.value) { + if let Some(descriptor) = class_get_attr(&attr.class(), "__set__") { vm.invoke(descriptor, vec![attr, self.into_object(), value])?; return Ok(()); } @@ -218,7 +212,7 @@ fn _mro(cls: &PyClassRef) -> Vec { /// Determines if `obj` actually an instance of `cls`, this doesn't call __instancecheck__, so only /// use this if `cls` is known to have not overridden the base __instancecheck__ magic method. pub fn isinstance(obj: &PyObjectRef, cls: &PyClassRef) -> bool { - issubclass(&obj.type_pyref(), &cls) + issubclass(&obj.class(), &cls) } /// Determines if `subclass` is actually a subclass of `cls`, this doesn't call __subclasscheck__, @@ -229,18 +223,14 @@ pub fn issubclass(subclass: &PyClassRef, cls: &PyClassRef) -> bool { subclass.is(cls) || mro.iter().any(|c| c.is(cls.as_object())) } -pub fn get_type_name(typ: &PyObjectRef) -> String { - if let Some(PyClass { name, .. }) = &typ.payload::() { - name.clone() - } else { - panic!("Cannot get type_name of non-type type {:?}", typ); - } +pub fn get_type_name(typ: &PyClassRef) -> String { + typ.name.clone() } pub fn type_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { debug!("type.__new__ {:?}", args); if args.args.len() == 2 { - Ok(args.args[1].typ()) + Ok(args.args[1].class().into_object()) } else if args.args.len() == 4 { let (typ, name, bases, dict) = args.bind(vm)?; type_new_class(vm, typ, name, bases, dict).map(|x| x.into_object()) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 78e82c51bd..739012c5ec 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -88,7 +88,7 @@ impl fmt::Display for PyObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::TypeProtocol; if let Some(PyClass { ref name, .. }) = self.payload::() { - let type_name = objtype::get_type_name(&self.typ()); + let type_name = objtype::get_type_name(&self.class()); // We don't have access to a vm, so just assume that if its parent's name // is type, it's a type if type_name == "type" { @@ -101,7 +101,7 @@ impl fmt::Display for PyObject { if let Some(PyModule { ref name, .. }) = self.payload::() { return write!(f, "module '{}'", name); } - write!(f, "'{}' object", objtype::get_type_name(&self.typ())) + write!(f, "'{}' object", objtype::get_type_name(&self.class())) } } @@ -163,7 +163,7 @@ pub struct PyNotImplemented; impl PyValue for PyNotImplemented { fn class(vm: &VirtualMachine) -> PyClassRef { - vm.ctx.not_implemented().type_pyref() + vm.ctx.not_implemented().class() } } @@ -772,7 +772,7 @@ impl PyRef { pub fn typ(&self) -> PyClassRef { PyRef { - obj: self.obj.typ(), + obj: self.obj.class().into_object(), _payload: PhantomData, } } @@ -802,7 +802,7 @@ where } else { let class = T::class(vm); let expected_type = vm.to_pystr(&class)?; - let actual_type = vm.to_pystr(&obj.typ())?; + let actual_type = vm.to_pystr(&obj.class())?; Err(vm.new_type_error(format!( "Expected type {}, not {}", expected_type, actual_type, @@ -877,18 +877,12 @@ impl IdProtocol for PyRef { } pub trait TypeProtocol { - fn typ(&self) -> PyObjectRef { - self.type_ref().clone() - } - fn type_pyref(&self) -> PyClassRef { - self.typ().downcast().unwrap() - } - fn type_ref(&self) -> &PyObjectRef; + fn class(&self) -> PyClassRef; } impl TypeProtocol for PyObjectRef { - fn type_ref(&self) -> &PyObjectRef { - (**self).type_ref() + fn class(&self) -> PyClassRef { + (**self).class() } } @@ -896,8 +890,14 @@ impl TypeProtocol for PyObject where T: ?Sized + PyObjectPayload, { - fn type_ref(&self) -> &PyObjectRef { - self.typ.as_object() + fn class(&self) -> PyClassRef { + self.typ.clone() + } +} + +impl TypeProtocol for PyRef { + fn class(&self) -> PyClassRef { + self.obj.typ.clone() } } @@ -956,7 +956,7 @@ pub trait BufferProtocol { impl BufferProtocol for PyObjectRef { fn readonly(&self) -> bool { - match objtype::get_type_name(&self.typ()).as_ref() { + match objtype::get_type_name(&self.class()).as_ref() { "bytes" => false, "bytearray" | "memoryview" => true, _ => panic!("Bytes-Like type expected not {:?}", self), @@ -1249,7 +1249,7 @@ where "must be {} or {}, not {}", A::class(vm), B::class(vm), - obj.type_pyref() + obj.class() )) }) } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index eac3619c26..b881b7a724 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -74,7 +74,7 @@ impl<'s> serde::Serialize for PyObjectSerializer<'s> { } else { Err(serde::ser::Error::custom(format!( "Object of type '{:?}' is not serializable", - self.pyobject.typ() + self.pyobject.class() ))) } } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 0ee9798ab0..92c264bcd9 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -174,8 +174,8 @@ impl VirtualMachine { b: PyObjectRef, op: &str, ) -> PyObjectRef { - let a_type_name = objtype::get_type_name(&a.typ()); - let b_type_name = objtype::get_type_name(&b.typ()); + let a_type_name = objtype::get_type_name(&a.class()); + let b_type_name = objtype::get_type_name(&b.class()); self.new_type_error(format!( "Unsupported operand types for '{}': '{}' and '{}'", op, a_type_name, b_type_name @@ -270,7 +270,7 @@ impl VirtualMachine { pub fn isinstance(&self, obj: &PyObjectRef, cls: &PyClassRef) -> PyResult { // cpython first does an exact check on the type, although documentation doesn't state that // https://github.com/python/cpython/blob/a24107b04c1277e3c1105f98aff5bfa3a98b33a0/Objects/abstract.c#L2408 - if Rc::ptr_eq(&obj.typ(), cls.as_object()) { + if Rc::ptr_eq(&obj.class().into_object(), cls.as_object()) { Ok(true) } else { let ret = self.call_method(cls.as_object(), "__instancecheck__", vec![obj.clone()])?; @@ -286,10 +286,10 @@ impl VirtualMachine { } pub fn call_get_descriptor(&self, attr: PyObjectRef, obj: PyObjectRef) -> PyResult { - let attr_class = attr.type_pyref(); + let attr_class = attr.class(); if let Some(descriptor) = objtype::class_get_attr(&attr_class, "__get__") { - let cls = obj.typ(); - self.invoke(descriptor, vec![attr, obj.clone(), cls]) + let cls = obj.class(); + self.invoke(descriptor, vec![attr, obj.clone(), cls.into_object()]) } else { Ok(attr) } @@ -300,7 +300,7 @@ impl VirtualMachine { T: Into, { // This is only used in the vm for magic methods, which use a greatly simplified attribute lookup. - let cls = obj.type_pyref(); + let cls = obj.class(); match objtype::class_get_attr(&cls, method_name) { Some(func) => { trace!( @@ -583,7 +583,7 @@ impl VirtualMachine { // get_method should be used for internal access to magic methods (by-passing // the full getattribute look-up. pub fn get_method(&self, obj: PyObjectRef, method_name: &str) -> PyResult { - let cls = obj.type_pyref(); + let cls = obj.class(); match objtype::class_get_attr(&cls, method_name) { Some(method) => self.call_get_descriptor(method, obj.clone()), None => Err(self.new_type_error(format!("{} has no method {:?}", obj, method_name))), From 6474a4a6ef895c8b57e4482ec6d26b0474ffbd2c Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Mon, 25 Mar 2019 19:18:07 -0700 Subject: [PATCH 075/884] Remove objtype::get_type_name() --- vm/src/builtins.rs | 7 ++----- vm/src/exceptions.rs | 3 +-- vm/src/frame.rs | 14 ++++++-------- vm/src/obj/objfloat.rs | 3 +-- vm/src/obj/objint.rs | 15 +++------------ vm/src/obj/objobject.rs | 11 +++++------ vm/src/obj/objsuper.rs | 3 +-- vm/src/obj/objtype.rs | 4 ---- vm/src/pyobject.rs | 6 +++--- vm/src/vm.rs | 6 +++--- 10 files changed, 25 insertions(+), 47 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index fbd2d53568..27ce8aadda 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -368,7 +368,7 @@ fn builtin_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(value) => vm.invoke(value, PyFuncArgs::default()), Err(..) => Err(vm.new_type_error(format!( "object of type '{}' has no method {:?}", - objtype::get_type_name(&obj.class()), + obj.class(), len_method_name ))), } @@ -605,10 +605,7 @@ fn builtin_reversed(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { match vm.get_method(obj.clone(), "__reversed__") { Ok(value) => vm.invoke(value, PyFuncArgs::default()), // TODO: fallback to using __len__ and __getitem__, if object supports sequence protocol - Err(..) => Err(vm.new_type_error(format!( - "'{}' object is not reversible", - objtype::get_type_name(&obj.class()), - ))), + Err(..) => Err(vm.new_type_error(format!("'{}' object is not reversible", obj.class()))), } } // builtin_reversed diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 7177527da4..f850d76974 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -68,7 +68,6 @@ fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { args, required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))] ); - let type_name = objtype::get_type_name(&exc.class()); let msg = if let Ok(m) = vm.get_attribute(exc.clone(), "msg") { match vm.to_pystr(&m) { Ok(msg) => msg, @@ -77,7 +76,7 @@ fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } else { panic!("Error message must be set"); }; - let s = format!("{}: {}", type_name, msg); + let s = format!("{}: {}", exc.class(), msg); Ok(vm.new_str(s)) } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 53479f8fc8..6f8815cbec 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1091,20 +1091,18 @@ impl Frame { fn _in(&self, vm: &VirtualMachine, needle: PyObjectRef, haystack: PyObjectRef) -> PyResult { match self._membership(vm, needle, &haystack) { Ok(found) => Ok(found), - Err(_) => Err(vm.new_type_error(format!( - "{} has no __contains__ method", - objtype::get_type_name(&haystack.class()) - ))), + Err(_) => { + Err(vm.new_type_error(format!("{} has no __contains__ method", haystack.class()))) + } } } fn _not_in(&self, vm: &VirtualMachine, needle: PyObjectRef, haystack: PyObjectRef) -> PyResult { match self._membership(vm, needle, &haystack) { Ok(found) => Ok(vm.ctx.new_bool(!objbool::get_value(&found))), - Err(_) => Err(vm.new_type_error(format!( - "{} has no __contains__ method", - objtype::get_type_name(&haystack.class()) - ))), + Err(_) => { + Err(vm.new_type_error(format!("{} has no __contains__ method", haystack.class()))) + } } } diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 5e68f59603..e261a689eb 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -190,8 +190,7 @@ impl PyFloatRef { } } } else { - let type_name = objtype::get_type_name(&arg.class()); - return Err(vm.new_type_error(format!("can't convert {} to float", type_name))); + return Err(vm.new_type_error(format!("can't convert {} to float", arg.class()))); }; PyFloat { value }.into_ref_with_type(vm, cls) } diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index f31997eadb..e2203469ae 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -211,11 +211,7 @@ impl PyIntRef { fn lshift(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if !objtype::isinstance(&other, &vm.ctx.int_type()) { - return Err(vm.new_type_error(format!( - "unsupported operand type(s) for << '{}' and '{}'", - objtype::get_type_name(&self.as_object().class()), - objtype::get_type_name(&other.class()) - ))); + return Ok(vm.ctx.not_implemented()); } if let Some(n_bits) = get_value(&other).to_usize() { @@ -234,11 +230,7 @@ impl PyIntRef { fn rshift(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if !objtype::isinstance(&other, &vm.ctx.int_type()) { - return Err(vm.new_type_error(format!( - "unsupported operand type(s) for >> '{}' and '{}'", - objtype::get_type_name(&self.as_object().class()), - objtype::get_type_name(&other.class()) - ))); + return Ok(vm.ctx.not_implemented()); } if let Some(n_bits) = get_value(&other).to_usize() { @@ -420,10 +412,9 @@ pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult PyResult { fn object_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.object()))]); - let type_name = objtype::get_type_name(&obj.class()); let address = obj.get_id(); - Ok(vm.new_str(format!("<{} object at 0x{:x}>", type_name, address))) + Ok(vm.new_str(format!("<{} object at 0x{:x}>", obj.class(), address))) } pub fn object_dir(obj: PyObjectRef, vm: &VirtualMachine) -> PyList { diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index fad365e249..0061b41e9d 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -112,10 +112,9 @@ fn super_new( // Check type argument: if !objtype::isinstance(py_type.as_object(), &vm.get_type()) { - let type_name = objtype::get_type_name(&py_type.as_object().class()); return Err(vm.new_type_error(format!( "super() argument 1 must be type, not {}", - type_name + py_type.class() ))); } diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 857ec402fb..14958300bf 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -223,10 +223,6 @@ pub fn issubclass(subclass: &PyClassRef, cls: &PyClassRef) -> bool { subclass.is(cls) || mro.iter().any(|c| c.is(cls.as_object())) } -pub fn get_type_name(typ: &PyClassRef) -> String { - typ.name.clone() -} - pub fn type_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { debug!("type.__new__ {:?}", args); if args.args.len() == 2 { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 739012c5ec..f15c5e09b2 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -88,7 +88,7 @@ impl fmt::Display for PyObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::TypeProtocol; if let Some(PyClass { ref name, .. }) = self.payload::() { - let type_name = objtype::get_type_name(&self.class()); + let type_name = self.class().name.clone(); // We don't have access to a vm, so just assume that if its parent's name // is type, it's a type if type_name == "type" { @@ -101,7 +101,7 @@ impl fmt::Display for PyObject { if let Some(PyModule { ref name, .. }) = self.payload::() { return write!(f, "module '{}'", name); } - write!(f, "'{}' object", objtype::get_type_name(&self.class())) + write!(f, "'{}' object", self.class()) } } @@ -956,7 +956,7 @@ pub trait BufferProtocol { impl BufferProtocol for PyObjectRef { fn readonly(&self) -> bool { - match objtype::get_type_name(&self.class()).as_ref() { + match self.class().name.as_str() { "bytes" => false, "bytearray" | "memoryview" => true, _ => panic!("Bytes-Like type expected not {:?}", self), diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 92c264bcd9..df092bdc8b 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -174,11 +174,11 @@ impl VirtualMachine { b: PyObjectRef, op: &str, ) -> PyObjectRef { - let a_type_name = objtype::get_type_name(&a.class()); - let b_type_name = objtype::get_type_name(&b.class()); self.new_type_error(format!( "Unsupported operand types for '{}': '{}' and '{}'", - op, a_type_name, b_type_name + op, + a.class(), + b.class() )) } From 8bdc766bedf04063a5a0027f73db6d469202570a Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Mon, 25 Mar 2019 19:32:58 -0700 Subject: [PATCH 076/884] Use name field directly --- vm/src/builtins.rs | 6 ++++-- vm/src/exceptions.rs | 2 +- vm/src/frame.rs | 14 ++++++++------ vm/src/obj/objfloat.rs | 2 +- vm/src/obj/objint.rs | 2 +- vm/src/obj/objobject.rs | 6 +++--- vm/src/obj/objsuper.rs | 2 +- vm/src/pyobject.rs | 2 +- vm/src/vm.rs | 4 ++-- 9 files changed, 22 insertions(+), 18 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 27ce8aadda..8a4a6adc30 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -368,7 +368,7 @@ fn builtin_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(value) => vm.invoke(value, PyFuncArgs::default()), Err(..) => Err(vm.new_type_error(format!( "object of type '{}' has no method {:?}", - obj.class(), + obj.class().name, len_method_name ))), } @@ -605,7 +605,9 @@ fn builtin_reversed(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { match vm.get_method(obj.clone(), "__reversed__") { Ok(value) => vm.invoke(value, PyFuncArgs::default()), // TODO: fallback to using __len__ and __getitem__, if object supports sequence protocol - Err(..) => Err(vm.new_type_error(format!("'{}' object is not reversible", obj.class()))), + Err(..) => { + Err(vm.new_type_error(format!("'{}' object is not reversible", obj.class().name))) + } } } // builtin_reversed diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index f850d76974..fb757cd0f3 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -76,7 +76,7 @@ fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } else { panic!("Error message must be set"); }; - let s = format!("{}: {}", exc.class(), msg); + let s = format!("{}: {}", exc.class().name, msg); Ok(vm.new_str(s)) } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 6f8815cbec..6fd5cfea80 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1091,18 +1091,20 @@ impl Frame { fn _in(&self, vm: &VirtualMachine, needle: PyObjectRef, haystack: PyObjectRef) -> PyResult { match self._membership(vm, needle, &haystack) { Ok(found) => Ok(found), - Err(_) => { - Err(vm.new_type_error(format!("{} has no __contains__ method", haystack.class()))) - } + Err(_) => Err(vm.new_type_error(format!( + "{} has no __contains__ method", + haystack.class().name + ))), } } fn _not_in(&self, vm: &VirtualMachine, needle: PyObjectRef, haystack: PyObjectRef) -> PyResult { match self._membership(vm, needle, &haystack) { Ok(found) => Ok(vm.ctx.new_bool(!objbool::get_value(&found))), - Err(_) => { - Err(vm.new_type_error(format!("{} has no __contains__ method", haystack.class()))) - } + Err(_) => Err(vm.new_type_error(format!( + "{} has no __contains__ method", + haystack.class().name + ))), } } diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index e261a689eb..ca6bad9d22 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -190,7 +190,7 @@ impl PyFloatRef { } } } else { - return Err(vm.new_type_error(format!("can't convert {} to float", arg.class()))); + return Err(vm.new_type_error(format!("can't convert {} to float", arg.class().name))); }; PyFloat { value }.into_ref_with_type(vm, cls) } diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index e2203469ae..c7f0ff5af4 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -414,7 +414,7 @@ pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult PyResult { fn object_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, Some(vm.ctx.object()))]); let address = obj.get_id(); - Ok(vm.new_str(format!("<{} object at 0x{:x}>", obj.class(), address))) + Ok(vm.new_str(format!("<{} object at 0x{:x}>", obj.class().name, address))) } pub fn object_dir(obj: PyObjectRef, vm: &VirtualMachine) -> PyList { diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 0061b41e9d..24424d265f 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -114,7 +114,7 @@ fn super_new( if !objtype::isinstance(py_type.as_object(), &vm.get_type()) { return Err(vm.new_type_error(format!( "super() argument 1 must be type, not {}", - py_type.class() + py_type.class().name ))); } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index f15c5e09b2..afee7060e2 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -101,7 +101,7 @@ impl fmt::Display for PyObject { if let Some(PyModule { ref name, .. }) = self.payload::() { return write!(f, "module '{}'", name); } - write!(f, "'{}' object", self.class()) + write!(f, "'{}' object", self.class().name) } } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index df092bdc8b..41321c2667 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -177,8 +177,8 @@ impl VirtualMachine { self.new_type_error(format!( "Unsupported operand types for '{}': '{}' and '{}'", op, - a.class(), - b.class() + a.class().name, + b.class().name )) } From 625d2357bd493175a7379eaf876fc33711fdb1c1 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Mon, 25 Mar 2019 19:34:24 -0700 Subject: [PATCH 077/884] Fix wasm --- wasm/lib/src/wasm_builtins.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index d5bc830dfe..ada0c83f53 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -25,7 +25,7 @@ pub fn format_print_args(vm: &VirtualMachine, args: PyFuncArgs) -> Result Result Date: Tue, 26 Mar 2019 15:16:30 +0000 Subject: [PATCH 078/884] Change types inside Scope to PyDictRef. --- vm/src/builtins.rs | 28 +++++++++-------------- vm/src/frame.rs | 29 +++++++++++------------ vm/src/import.rs | 2 +- vm/src/obj/objframe.rs | 52 ++++++++++++++++++------------------------ vm/src/pyobject.rs | 10 +++++--- vm/src/vm.rs | 9 ++++---- 6 files changed, 59 insertions(+), 71 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 8a4a6adc30..1de24b86ab 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -11,6 +11,7 @@ use num_traits::{Signed, ToPrimitive}; use crate::compile; use crate::import::import_module; use crate::obj::objbool; +use crate::obj::objdict::PyDictRef; use crate::obj::objint; use crate::obj::objiter; use crate::obj::objstr::{self, PyStringRef}; @@ -27,14 +28,6 @@ use crate::obj::objcode::PyCodeRef; #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::io::io_open; -fn get_locals(vm: &VirtualMachine) -> PyObjectRef { - vm.get_locals() -} - -fn dir_locals(vm: &VirtualMachine) -> PyObjectRef { - get_locals(vm) -} - fn builtin_abs(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(x, None)]); match vm.get_method(x.clone(), "__abs__") { @@ -146,7 +139,7 @@ fn builtin_delattr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_dir(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if args.args.is_empty() { - Ok(dir_locals(vm)) + Ok(vm.get_locals().into_object()) } else { let obj = args.args.into_iter().next().unwrap(); let seq = vm.call_method(&obj, "__dir__", vec![])?; @@ -254,11 +247,11 @@ fn make_scope( let current_scope = vm.current_scope(); let globals = match globals { - Some(dict) => dict.clone(), + Some(dict) => dict.clone().downcast().unwrap(), None => current_scope.globals.clone(), }; let locals = match locals { - Some(dict) => Some(dict.clone()), + Some(dict) => dict.clone().downcast().ok(), None => current_scope.get_only_locals(), }; @@ -300,7 +293,7 @@ fn builtin_getattr( } } -fn builtin_globals(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { +fn builtin_globals(vm: &VirtualMachine) -> PyResult { Ok(vm.current_scope().globals.clone()) } @@ -374,9 +367,8 @@ fn builtin_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn builtin_locals(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args); - Ok(vm.get_locals()) +fn builtin_locals(vm: &VirtualMachine) -> PyDictRef { + vm.get_locals() } fn builtin_max(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -814,13 +806,15 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu let prepare = vm.get_attribute(metaclass.clone().into_object(), "__prepare__")?; let namespace = vm.invoke(prepare, vec![name_arg.clone(), bases.clone()])?; + let namespace: PyDictRef = TryFromObject::try_from_object(vm, namespace)?; + let cells = vm.ctx.new_dict(); - vm.invoke_with_locals(function, cells.clone().into_object(), namespace.clone())?; + vm.invoke_with_locals(function, cells.clone(), namespace.clone())?; let class = vm.call_method( metaclass.as_object(), "__call__", - vec![name_arg, bases, namespace], + vec![name_arg, bases, namespace.into_object()], )?; cells.set_item(&vm.ctx, "__class__", class.clone()); Ok(class) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 6fd5cfea80..88a619eb36 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -12,8 +12,7 @@ use crate::function::PyFuncArgs; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode::PyCodeRef; -use crate::obj::objdict; -use crate::obj::objdict::PyDict; +use crate::obj::objdict::{self, PyDictRef}; use crate::obj::objint::PyInt; use crate::obj::objiter; use crate::obj::objlist; @@ -80,8 +79,8 @@ impl<'a, T> Iterator for Iter<'a, T> { #[derive(Clone)] pub struct Scope { - locals: RcList, - pub globals: PyObjectRef, + locals: RcList, + pub globals: PyDictRef, } impl fmt::Debug for Scope { @@ -92,7 +91,7 @@ impl fmt::Debug for Scope { } impl Scope { - pub fn new(locals: Option, globals: PyObjectRef) -> Scope { + pub fn new(locals: Option, globals: PyDictRef) -> Scope { let locals = match locals { Some(dict) => RcList::new().insert(dict), None => RcList::new(), @@ -100,18 +99,18 @@ impl Scope { Scope { locals, globals } } - pub fn get_locals(&self) -> PyObjectRef { + pub fn get_locals(&self) -> PyDictRef { match self.locals.iter().next() { Some(dict) => dict.clone(), None => self.globals.clone(), } } - pub fn get_only_locals(&self) -> Option { + pub fn get_only_locals(&self) -> Option { self.locals.iter().next().cloned() } - pub fn child_scope_with_locals(&self, locals: PyObjectRef) -> Scope { + pub fn child_scope_with_locals(&self, locals: PyDictRef) -> Scope { Scope { locals: self.locals.clone().insert(locals), globals: self.globals.clone(), @@ -119,7 +118,7 @@ impl Scope { } pub fn child_scope(&self, ctx: &PyContext) -> Scope { - self.child_scope_with_locals(ctx.new_dict().into_object()) + self.child_scope_with_locals(ctx.new_dict()) } } @@ -1232,13 +1231,11 @@ impl fmt::Debug for Frame { .iter() .map(|elem| format!("\n > {:?}", elem)) .collect::(); - let local_str = match self.scope.get_locals().payload::() { - Some(dict) => objdict::get_key_value_pairs_from_content(&dict.entries.borrow()) - .iter() - .map(|elem| format!("\n {:?} = {:?}", elem.0, elem.1)) - .collect::(), - None => panic!("locals unexpectedly not wrapping a dict!",), - }; + let dict = self.scope.get_locals(); + let local_str = objdict::get_key_value_pairs_from_content(&dict.entries.borrow()) + .iter() + .map(|elem| format!("\n {:?} = {:?}", elem.0, elem.1)) + .collect::(); write!( f, "Frame Object {{ \n Stack:{}\n Blocks:{}\n Locals:{}\n}}", diff --git a/vm/src/import.rs b/vm/src/import.rs index 987555915f..936b896805 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -40,7 +40,7 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s let attrs = vm.ctx.new_dict(); attrs.set_item(&vm.ctx, "__name__", vm.new_str(module.to_string())); - vm.run_code_obj(code_obj, Scope::new(None, attrs.clone().into_object()))?; + vm.run_code_obj(code_obj, Scope::new(None, attrs.clone()))?; Ok(vm.ctx.new_module(module, attrs)) } diff --git a/vm/src/obj/objframe.rs b/vm/src/obj/objframe.rs index 8657dc2800..b4ed15f74a 100644 --- a/vm/src/obj/objframe.rs +++ b/vm/src/obj/objframe.rs @@ -2,43 +2,35 @@ */ -use crate::frame::Frame; -use crate::function::PyFuncArgs; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use super::objcode::PyCodeRef; +use super::objdict::PyDictRef; +use crate::frame::FrameRef; +use crate::pyobject::{PyContext, PyResult}; use crate::vm::VirtualMachine; pub fn init(context: &PyContext) { - let frame_type = &context.frame_type; - extend_class!(context, frame_type, { - "__new__" => context.new_rustfunc(frame_new), - "__repr__" => context.new_rustfunc(frame_repr), - "f_locals" => context.new_property(frame_flocals), - "f_code" => context.new_property(frame_fcode) + extend_class!(context, &context.frame_type, { + "__new__" => context.new_rustfunc(FrameRef::new), + "__repr__" => context.new_rustfunc(FrameRef::repr), + "f_locals" => context.new_property(FrameRef::flocals), + "f_code" => context.new_property(FrameRef::fcode), }); } -fn frame_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(_cls, None)]); - Err(vm.new_type_error("Cannot directly create frame object".to_string())) -} +impl FrameRef { + fn new(_class: FrameRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("Cannot directly create frame object".to_string())) + } -fn frame_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(_frame, Some(vm.ctx.frame_type()))]); - let repr = "".to_string(); - Ok(vm.new_str(repr)) -} + fn repr(self, _vm: &VirtualMachine) -> String { + "".to_string() + } -fn frame_flocals(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(frame, Some(vm.ctx.frame_type()))]); - let frame = get_value(frame); - Ok(frame.scope.get_locals().clone()) -} - -fn frame_fcode(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(frame, Some(vm.ctx.frame_type()))]); - Ok(vm.ctx.new_code_object(get_value(frame).code.clone())) -} + fn flocals(self, _vm: &VirtualMachine) -> PyDictRef { + self.scope.get_locals() + } -pub fn get_value(obj: &PyObjectRef) -> &Frame { - &obj.payload::().unwrap() + fn fcode(self, vm: &VirtualMachine) -> PyCodeRef { + vm.ctx.new_code_object(self.code.clone()) + } } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index afee7060e2..aa3348be59 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -582,7 +582,7 @@ impl PyContext { } pub fn new_scope(&self) -> Scope { - Scope::new(None, self.new_dict().into_object()) + Scope::new(None, self.new_dict()) } pub fn new_module(&self, name: &str, dict: PyDictRef) -> PyObjectRef { @@ -613,8 +613,10 @@ impl PyContext { PropertyBuilder::new(self).add_getter(f).create() } - pub fn new_code_object(&self, code: bytecode::CodeObject) -> PyObjectRef { + pub fn new_code_object(&self, code: bytecode::CodeObject) -> PyCodeRef { PyObject::new(objcode::PyCode::new(code), self.code_type(), None) + .downcast() + .unwrap() } pub fn new_function( @@ -683,7 +685,9 @@ impl PyContext { bytecode::Constant::String { ref value } => self.new_str(value.clone()), bytecode::Constant::Bytes { ref value } => self.new_bytes(value.clone()), bytecode::Constant::Boolean { ref value } => self.new_bool(value.clone()), - bytecode::Constant::Code { ref code } => self.new_code_object(*code.clone()), + bytecode::Constant::Code { ref code } => { + self.new_code_object(*code.clone()).into_object() + } bytecode::Constant::Tuple { ref elements } => { let elements = elements .iter() diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 41321c2667..321d659b8f 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -19,6 +19,7 @@ use crate::function::PyFuncArgs; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode::PyCodeRef; +use crate::obj::objdict::PyDictRef; use crate::obj::objfunction::{PyFunction, PyMethod}; use crate::obj::objgenerator::PyGeneratorRef; use crate::obj::objiter; @@ -231,7 +232,7 @@ impl VirtualMachine { self.ctx.object() } - pub fn get_locals(&self) -> PyObjectRef { + pub fn get_locals(&self) -> PyDictRef { self.current_scope().get_locals().clone() } @@ -371,8 +372,8 @@ impl VirtualMachine { pub fn invoke_with_locals( &self, function: PyObjectRef, - cells: PyObjectRef, - locals: PyObjectRef, + cells: PyDictRef, + locals: PyDictRef, ) -> PyResult { if let Some(PyFunction { code, @@ -395,7 +396,7 @@ impl VirtualMachine { fn fill_locals_from_args( &self, code_object: &bytecode::CodeObject, - locals: &PyObjectRef, + locals: &PyDictRef, args: PyFuncArgs, defaults: &PyObjectRef, ) -> PyResult<()> { From c5203a4934621292fb87d7e23d91b4068fd9ae51 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Tue, 26 Mar 2019 19:00:06 +0100 Subject: [PATCH 079/884] Remove .gcno files before a coverage run --- tests/.travis-runner.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/.travis-runner.sh b/tests/.travis-runner.sh index 36b9f43aca..f555265898 100755 --- a/tests/.travis-runner.sh +++ b/tests/.travis-runner.sh @@ -13,6 +13,7 @@ pip install pipenv if [ $CODE_COVERAGE = "true" ] then find . -name '*.gcda' -delete + find . -name '*.gcno' -delete export CARGO_INCREMENTAL=0 export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Cinline-threshold=0 -Clink-dead-code -Coverflow-checks=off -Zno-landing-pads" From 4cb7f026dee431e611863d7378815a98401c5b48 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Tue, 26 Mar 2019 15:38:19 +0000 Subject: [PATCH 080/884] Remove unnecessary cast to pyobject in kwarg construction. --- vm/src/vm.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 321d659b8f..4148184f42 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -451,11 +451,11 @@ impl VirtualMachine { // Do we support `**kwargs` ? let kwargs = match code_object.varkeywords { bytecode::Varargs::Named(ref kwargs_name) => { - let d = self.ctx.new_dict().into_object(); - locals.set_item(&self.ctx, kwargs_name, d.clone()); + let d = self.ctx.new_dict(); + locals.set_item(&self.ctx, kwargs_name, d.as_object().clone()); Some(d) } - bytecode::Varargs::Unnamed => Some(self.ctx.new_dict().into_object()), + bytecode::Varargs::Unnamed => Some(self.ctx.new_dict()), bytecode::Varargs::None => None, }; From c64417654700084d508458ecb4cb7d9fe4d1dbfe Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Tue, 26 Mar 2019 15:38:40 +0000 Subject: [PATCH 081/884] Modules have attributes, not items. --- vm/src/stdlib/json.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index b881b7a724..fbe7ea6b1e 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -199,8 +199,7 @@ pub fn de_pyobject(vm: &VirtualMachine, s: &str) -> PyResult { de.deserialize(&mut serde_json::Deserializer::from_str(s)) .map_err(|err| { let json_decode_error = vm - .sys_module - .get_item("modules") + .get_attribute(vm.sys_module.clone(), "modules") .unwrap() .get_item("json") .unwrap() From 3ca387b509dca33d09dba944d3573b8bd0cc5f21 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 28 Mar 2019 05:55:15 +1300 Subject: [PATCH 082/884] Enhance FromArgs derive proc macro to allow positional, and positional or keyword arguments. --- derive/src/lib.rs | 55 ++++++++++++++++++++++------ tests/snippets/ints.py | 18 +++++++++- vm/src/builtins.rs | 23 ++++++++---- vm/src/function.rs | 82 +++++++++++++++++++++++++++++++++++++++--- vm/src/obj/objint.rs | 52 ++++++++++++++++----------- 5 files changed, 188 insertions(+), 42 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index ad743e72fe..558bf3dda4 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -3,9 +3,9 @@ extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::{Data, DeriveInput, Fields}; +use syn::{Data, DeriveInput, Field, Fields}; -#[proc_macro_derive(FromArgs)] +#[proc_macro_derive(FromArgs, attributes(positional, keyword))] pub fn derive_from_args(input: TokenStream) -> TokenStream { let ast: DeriveInput = syn::parse(input).unwrap(); @@ -13,21 +13,54 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream { gen.to_string().parse().unwrap() } +enum ArgType { + Positional, + PositionalKeyword, + Keyword, +} + +fn generate_field(field: &Field) -> TokenStream2 { + let arg_type = if let Some(attr) = field.attrs.first() { + if attr.path.is_ident("positional") { + ArgType::Positional + } else if attr.path.is_ident("keyword") { + ArgType::Keyword + } else { + panic!("Unrecognised attribute") + } + } else { + ArgType::PositionalKeyword + }; + + let name = &field.ident; + match arg_type { + ArgType::Positional => { + quote! { + #name: args.take_positional(vm)?, + } + } + ArgType::PositionalKeyword => { + quote! { + #name: args.take_positional_keyword(vm, stringify!(#name))?, + } + } + ArgType::Keyword => { + quote! { + #name: args.take_keyword(vm, stringify!(#name))?, + } + } + } +} + fn impl_from_args(input: &DeriveInput) -> TokenStream2 { // FIXME: This references types using `crate` instead of `rustpython_vm` // so that it can be used in the latter. How can we support both? + // Can use extern crate self as rustpython_vm; once in stable. + // https://github.com/rust-lang/rust/issues/56409 let fields = match input.data { Data::Struct(ref data) => { match data.fields { - Fields::Named(ref fields) => fields.named.iter().map(|field| { - let name = &field.ident; - quote! { - #name: crate::pyobject::TryFromObject::try_from_object( - vm, - args.take_keyword(stringify!(#name)).unwrap_or_else(|| vm.ctx.none()) - )?, - } - }), + Fields::Named(ref fields) => fields.named.iter().map(generate_field), Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message } } diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index 2d36afcb6e..1fbbc2bef7 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -1,4 +1,4 @@ -from testutils import assert_raises +from testutils import assert_raises, assertRaises # int to int comparisons @@ -58,3 +58,19 @@ assert (2).__rmul__(1.0) == NotImplemented assert (2).__truediv__(1.0) == NotImplemented assert (2).__rtruediv__(1.0) == NotImplemented + + +assert int() == 0 +assert int("101", 2) == 5 +assert int("101", base=2) == 5 +assert int(1) == 1 + +with assertRaises(TypeError): + int(base=2) + +with assertRaises(TypeError): + int(1, base=2) + +with assertRaises(TypeError): + # check that first parameter is truly positional only + int(val_options=1) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 4d9fb79896..cf0c0a3f67 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -559,11 +559,22 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } +// Idea: Should we have a 'default' attribute, so we don't have to use OptionalArg's in this case +//#[derive(Debug, FromArgs)] +//pub struct PrintOptions { +// #[keyword] #[default(None)] sep: Option, +// #[keyword] #[default(None)] end: Option, +// #[keyword] #[default(flush)] flush: bool, +//} + #[derive(Debug, FromArgs)] pub struct PrintOptions { - sep: Option, - end: Option, - flush: bool, + #[keyword] + sep: OptionalArg>, + #[keyword] + end: OptionalArg>, + #[keyword] + flush: OptionalArg, } pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> { @@ -573,7 +584,7 @@ pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) for object in objects { if first { first = false; - } else if let Some(ref sep) = options.sep { + } else if let OptionalArg::Present(Some(ref sep)) = options.sep { write!(stdout_lock, "{}", sep.value).unwrap(); } else { write!(stdout_lock, " ").unwrap(); @@ -582,13 +593,13 @@ pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) write!(stdout_lock, "{}", s).unwrap(); } - if let Some(end) = options.end { + if let OptionalArg::Present(Some(end)) = options.end { write!(stdout_lock, "{}", end.value).unwrap(); } else { writeln!(stdout_lock).unwrap(); } - if options.flush { + if options.flush.into_option().unwrap_or(false) { stdout_lock.flush().unwrap(); } diff --git a/vm/src/function.rs b/vm/src/function.rs index ed959d85c9..49c2f02f5b 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -1,12 +1,11 @@ use std::collections::HashMap; use std::ops::RangeInclusive; -use crate::obj::objtype; +use crate::obj::objtype::{isinstance, PyClassRef}; use crate::pyobject::{IntoPyObject, PyObjectRef, PyResult, TryFromObject, TypeProtocol}; use crate::vm::VirtualMachine; use self::OptionalArg::*; -use crate::obj::objtype::PyClassRef; /// The `PyFuncArgs` struct is one of the most used structs then creating /// a rust function that can be called from python. It holds both positional @@ -95,7 +94,7 @@ impl PyFuncArgs { ) -> Result, PyObjectRef> { match self.get_optional_kwarg(key) { Some(kwarg) => { - if objtype::isinstance(&kwarg, &ty) { + if isinstance(&kwarg, &ty) { Ok(Some(kwarg)) } else { let expected_ty_name = vm.to_pystr(&ty)?; @@ -118,7 +117,7 @@ impl PyFuncArgs { } } - pub fn take_keyword(&mut self, name: &str) -> Option { + fn extract_keyword(&mut self, name: &str) -> Option { // TODO: change kwarg representation so this scan isn't necessary if let Some(index) = self .kwargs @@ -131,6 +130,49 @@ impl PyFuncArgs { } } + pub fn take_positional( + &mut self, + vm: &VirtualMachine, + ) -> Result { + if let Some(arg) = self.next_positional() { + H::from_arg(vm, arg).map_err(|err| ArgumentError::Exception(err)) + } else if let Some(default) = H::DEFAULT { + Ok(default) + } else { + Err(ArgumentError::TooFewArgs) + } + } + + pub fn take_positional_keyword( + &mut self, + vm: &VirtualMachine, + name: &str, + ) -> Result { + if let Some(arg) = self.next_positional() { + H::from_arg(vm, arg).map_err(|err| ArgumentError::Exception(err)) + } else if let Some(arg) = self.extract_keyword(name) { + H::from_arg(vm, arg).map_err(|err| ArgumentError::Exception(err)) + } else if let Some(default) = H::DEFAULT { + Ok(default) + } else { + Err(ArgumentError::TooFewArgs) + } + } + + pub fn take_keyword( + &mut self, + vm: &VirtualMachine, + name: &str, + ) -> Result { + if let Some(arg) = self.extract_keyword(name) { + H::from_arg(vm, arg).map_err(|err| ArgumentError::Exception(err)) + } else if let Some(default) = H::DEFAULT { + Ok(default) + } else { + Err(ArgumentError::RequiredKeywordArgument(name.to_string())) + } + } + pub fn remaining_keyword<'a>(&'a mut self) -> impl Iterator + 'a { self.kwargs.drain(..) } @@ -164,6 +206,9 @@ impl PyFuncArgs { Err(ArgumentError::InvalidKeywordArgument(name)) => { return Err(vm.new_type_error(format!("{} is an invalid keyword argument", name))); } + Err(ArgumentError::RequiredKeywordArgument(name)) => { + return Err(vm.new_type_error(format!("Required keyqord only argument {}", name))); + } Err(ArgumentError::Exception(ex)) => { return Err(ex); } @@ -192,6 +237,8 @@ pub enum ArgumentError { TooManyArgs, /// The function doesn't accept a keyword argument with the given name. InvalidKeywordArgument(String), + /// The function require a keyword argument with the given name, but one wasn't provided + RequiredKeywordArgument(String), /// An exception was raised while binding arguments to the function /// parameters. Exception(PyObjectRef), @@ -218,6 +265,32 @@ pub trait FromArgs: Sized { fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result; } +/// Handling behaviour when the argument is and isn't present, used to implement OptionalArg. +pub trait ArgHandler: Sized { + /// Default value that will be used if the argument isn't present, or None in which case the a + /// appropriate error is returned. + const DEFAULT: Option; + + /// Converts the arg argument when it is present + fn from_arg(vm: &VirtualMachine, object: PyObjectRef) -> PyResult; +} + +impl ArgHandler for OptionalArg { + const DEFAULT: Option = Some(Missing); + + fn from_arg(vm: &VirtualMachine, object: PyObjectRef) -> PyResult { + T::try_from_object(vm, object).map(|x| Present(x)) + } +} + +impl ArgHandler for T { + const DEFAULT: Option = None; + + fn from_arg(vm: &VirtualMachine, object: PyObjectRef) -> PyResult { + T::try_from_object(vm, object) + } +} + /// A map of keyword arguments to their values. /// /// A built-in function with a `KwArgs` parameter is analagous to a Python @@ -308,6 +381,7 @@ where /// An argument that may or may not be provided by the caller. /// /// This style of argument is not possible in pure Python. +#[derive(Debug)] pub enum OptionalArg { Present(T), Missing, diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index dd40f06a42..82394d6910 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -6,7 +6,7 @@ use num_integer::Integer; use num_traits::{Pow, Signed, ToPrimitive, Zero}; use crate::format::FormatSpec; -use crate::function::{OptionalArg, PyFuncArgs}; +use crate::function::OptionalArg; use crate::pyobject::{ IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; @@ -380,25 +380,37 @@ impl PyIntRef { } } -fn int_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(cls, None)], - optional = [(val_option, None)] - ); - - let base = match args.get_optional_kwarg("base") { - Some(argument) => get_value(&argument).to_u32().unwrap(), - None => 10, - }; - let val = match val_option { - Some(val) => to_int(vm, val, base)?, - None => Zero::zero(), - }; - Ok(PyInt::new(val) - .into_ref_with_type(vm, cls.clone().downcast().unwrap())? - .into_object()) +#[derive(FromArgs)] +struct IntOptions { + #[positional] + val_options: OptionalArg, + base: OptionalArg, +} + +impl IntOptions { + fn get_int_value(self, vm: &VirtualMachine) -> PyResult { + if let OptionalArg::Present(val) = self.val_options { + let base = if let OptionalArg::Present(base) = self.base { + if !objtype::isinstance(&val, &vm.ctx.str_type) { + return Err(vm.new_type_error( + "int() can't convert non-string with explicit base".to_string(), + )); + } + base + } else { + 10 + }; + to_int(vm, &val, base) + } else if let OptionalArg::Present(_) = self.base { + Err(vm.new_type_error("int() missing string argument".to_string())) + } else { + Ok(Zero::zero()) + } + } +} + +fn int_new(cls: PyClassRef, options: IntOptions, vm: &VirtualMachine) -> PyResult { + PyInt::new(options.get_int_value(vm)?).into_ref_with_type(vm, cls) } // Casting function: From 40dd75d5133626216bf24ea94c1075aa29edda7a Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Tue, 26 Mar 2019 20:22:33 +0100 Subject: [PATCH 083/884] Upgrade lalrpop --- Cargo.lock | 160 ++++++++++++++++++-------------------------- parser/Cargo.toml | 4 +- parser/build.rs | 5 +- parser/src/token.rs | 2 +- 4 files changed, 71 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6193772c40..79ba85f2cd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,11 +25,6 @@ dependencies = [ "scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "arrayref" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "arrayvec" version = "0.4.7" @@ -46,16 +41,6 @@ dependencies = [ "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "atty" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "atty" version = "0.2.10" @@ -89,15 +74,15 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.4.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "bit-vec" -version = "0.4.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -116,16 +101,26 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.3.3" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-padding 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "block-padding" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "byte-tools" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -186,10 +181,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "digest" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -204,14 +199,14 @@ dependencies = [ [[package]] name = "docopt" -version = "0.8.3" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -221,8 +216,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "ena" -version = "0.5.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "env_logger" @@ -299,7 +297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "generic-array" -version = "0.9.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -323,7 +321,7 @@ dependencies = [ [[package]] name = "itertools" -version = "0.7.8" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -353,45 +351,23 @@ dependencies = [ [[package]] name = "lalrpop" -version = "0.15.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ascii-canvas 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ena 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", - "lalrpop-snap 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", + "docopt 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ena 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lalrpop-util 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)", - "sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", - "string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "lalrpop-snap" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "ascii-canvas 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "atty 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "ena 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", - "lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", - "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -399,7 +375,7 @@ dependencies = [ [[package]] name = "lalrpop-util" -version = "0.15.2" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -529,6 +505,11 @@ name = "num-traits" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "opaque-debug" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "ordermap" version = "0.3.5" @@ -685,11 +666,6 @@ dependencies = [ "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "regex-syntax" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "regex-syntax" version = "0.5.6" @@ -754,8 +730,8 @@ dependencies = [ name = "rustpython_parser" version = "0.0.1" dependencies = [ - "lalrpop 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", - "lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lalrpop 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)", + "lalrpop-util 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -872,13 +848,13 @@ dependencies = [ [[package]] name = "sha2" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -943,11 +919,6 @@ name = "string_cache_shared" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "strsim" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "strsim" version = "0.7.0" @@ -1285,19 +1256,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" -"checksum arrayref 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0fd1479b7c29641adbd35ff3b5c293922d696a92f25c8c975da3e0acbc87258f" "checksum arrayvec 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a1e964f9e24d588183fcb43503abda40d288c8657dfc27311516ce2f05675aef" "checksum ascii-canvas 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b385d69402821a1c254533a011a312531cbcc0e3e24f19bbb4747a5a2daf37e2" -"checksum atty 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d0fd4c0631f06448cc45a6bbb3b710ebb7ff8ccb96a0800c994afe23a70d5df2" "checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1" "checksum backtrace 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "89a47830402e9981c5c41223151efcced65a0510c13097c769cede7efb34782a" "checksum backtrace-sys 0.1.24 (registry+https://github.com/rust-lang/crates.io-index)" = "c66d56ac8dabd07f6aacdaf633f4b8262f5b3601a810a0dcddffd5c22c69daa0" -"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c" -"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f" +"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" +"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" -"checksum block-buffer 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a076c298b9ecdb530ed9d967e74a6027d6a7478924520acddcddc24c1c8ab3ab" -"checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" +"checksum block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49665c62e0e700857531fa5d3763e91b539ff1abeebd56808d378b495870d60d" +"checksum block-padding 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d75255892aeb580d3c566f213a2b6fdc1c66667839f45719ee1d30ebf2aea591" +"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "90492c5858dd7d2e78691cfb89f90d273a2800fc11d98f60786e5d87e2f83781" "checksum caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "808dab3318747be122cb31d36de18d4d1c81277a76f8332a02b81a3d73463d7f" "checksum cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "f159dfd43363c4d08055a07703eb7a3406b0dac4d0584d96965a3262db3c9d16" @@ -1306,11 +1276,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" -"checksum digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00a49051fef47a72c9623101b19bd71924a45cca838826caae3eaa4d00772603" +"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" "checksum dirs 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "88972de891f6118092b643d85a0b28e0678e0f948d7f879aa32f2d5aafe97d2a" -"checksum docopt 0.8.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d8acd393692c503b168471874953a2531df0e9ab77d0b6bbc582395743300a4a" +"checksum docopt 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "db2906c2579b5b7207fc1e328796a9a8835dc44e22dbe8e460b1d636f9a7b225" "checksum either 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3be565ca5c557d7f59e7cfcf1844f9e3033650c929c6566f511e8005f205c1d0" -"checksum ena 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cabe5a5078ac8c506d3e4430763b1ba9b609b1286913e7d08e581d1c2de9b7e5" +"checksum ena 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f56c93cc076508c549d9bb747f79aa9b4eb098be7b8cad8830c3137ef52d1e00" "checksum env_logger 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0e6e40ebb0e66918a37b38c7acab4e10d299e0463fe2af5d29b9cc86710cfd2a" "checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e" "checksum failure 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7efb22686e4a466b1ec1a15c2898f91fa9cb340452496dca654032de20ff95b9" @@ -1320,16 +1290,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" "checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" -"checksum generic-array 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ef25c5683767570c2bbd7deba372926a55eaae9982d7726ee2a1050239d45b9d" +"checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" "checksum heck 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ea04fa3ead4e05e51a7c806fc07271fdbde4e246a6c6d1efd52e72230b771b82" "checksum humantime 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0484fda3e7007f2a4a0d9c3a703ca38c71c54c55602ce4660c419fd32e188c9e" -"checksum itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "f58856976b776fedd95533137617a02fb25719f40e7d9b01c7043cd65474f450" +"checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5adb58558dcd1d786b5f0bd15f3226ee23486e24b7b58304b60f64dc68e62606" "checksum js-sys 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58cfec35fd4a94f3cf357d5cb7da71c71cd52720c2f2a7320090a8db5f06f655" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum lalrpop 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ba451f7bd819b7afc99d4cf4bdcd5a4861e64955ba9680ac70df3a50625ad6cf" -"checksum lalrpop-snap 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60013fd6be14317d43f47658b1440956a9ca48a9ed0257e0e0a59aac13e43a1f" -"checksum lalrpop-util 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "60c6c48ba857cd700673ce88907cadcdd7e2cd7783ed02378537c5ffd4f6460c" +"checksum lalrpop 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4e2e80bee40b22bca46665b4ef1f3cd88ed0fb043c971407eac17a0712c02572" +"checksum lalrpop-util 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "33b27d8490dbe1f9704b0088d61e8d46edc10d5673a8829836c6ded26a9912c7" "checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" "checksum lexical 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4fac65df7e751b57bb3a334c346239cb4ce2601907d698726ceeb82a54ba4ef" "checksum lexical-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "025babf624c0c2b4bed1373efd684d5d0b2eecd61138d26ec3eec77bf0f2e33d" @@ -1346,6 +1315,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" +"checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" "checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" "checksum petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" "checksum phf_generator 0.7.22 (registry+https://github.com/rust-lang/crates.io-index)" = "05a079dd052e7b674d21cb31cbb6c05efd56a2cd2827db7692e2f1a507ebd998" @@ -1365,7 +1335,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum redox_users 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "214a97e49be64fd2c86f568dd0cb2c757d2cc53de95b273b6ad0a1c908482f26" "checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" "checksum regex 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3d8c9f33201f46669484bacc312b00e7541bed6aaf296dffe2bb4e0ac6b8ce2a" -"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" "checksum regex-syntax 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1ac0f60d675cc6cf13a20ec076568254472551051ad5dd050364d70671bf6b" "checksum rustc-demangle 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "bcfe5b13211b4d78e5c2cadfebd7769197d95c639c35a50057eb4c05de811395" @@ -1379,7 +1348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum serde 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "e9a2d9a9ac5120e0f768801ca2b58ad6eec929dc9d1d616c162f208869c2ce95" "checksum serde_derive 1.0.66 (registry+https://github.com/rust-lang/crates.io-index)" = "0a90213fa7e0f5eac3f7afe2d5ff6b088af515052cc7303bd68c7e3b91a3fb79" "checksum serde_json 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "44dd2cfde475037451fa99b7e5df77aa3cfd1536575fa8e7a538ab36dcde49ae" -"checksum sha2 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9eb6be24e4c23a84d7184280d2722f7f2731fcdd4a9d886efbfe4413e4847ea0" +"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" "checksum siphasher 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0df90a788073e8d0235a67e50441d47db7c8ad9debd91cbf43736a2a92d36537" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" "checksum stackvector 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c049c77bf85fbc036484c97b008276d539d9ebff9dfbde37b632ebcd5b8746b6" @@ -1388,7 +1357,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423" "checksum string_cache_codegen 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "35293b05cf1494e8ddd042a7df6756bf18d07f42d234f32e71dce8a7aabb0191" "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" -"checksum strsim 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b4d15c810519a91cf877e7e36e63fe068815c678181439f2f29e2562147c3694" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" "checksum syn 0.14.9 (registry+https://github.com/rust-lang/crates.io-index)" = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" "checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" diff --git a/parser/Cargo.toml b/parser/Cargo.toml index f2f8e5a6b4..e3c4e36bde 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -6,10 +6,10 @@ build = "build.rs" edition = "2018" [build-dependencies] -lalrpop="0.15.1" +lalrpop="0.16.3" [dependencies] -lalrpop-util="0.15.1" +lalrpop-util="0.16.3" log="0.4.1" regex="0.2.2" num-bigint = "0.2" diff --git a/parser/build.rs b/parser/build.rs index d35ace0c07..65147ed0ed 100644 --- a/parser/build.rs +++ b/parser/build.rs @@ -1,5 +1,8 @@ use lalrpop; fn main() { - lalrpop::process_root().unwrap(); + lalrpop::Configuration::new() + .generate_in_source_tree() + .process() + .unwrap(); } diff --git a/parser/src/token.rs b/parser/src/token.rs index ebe5050fef..d2494a1d95 100644 --- a/parser/src/token.rs +++ b/parser/src/token.rs @@ -3,7 +3,7 @@ use num_bigint::BigInt; /// Python source code can be tokenized in a sequence of these tokens. -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum Tok { Name { name: String }, Int { value: BigInt }, From 89f63e4c9a62409cee511d8763ad5e6985b79e42 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Wed, 27 Mar 2019 18:58:30 +0100 Subject: [PATCH 084/884] Convert objobject.rs to new args style --- vm/src/obj/objfunction.rs | 18 ++++---- vm/src/obj/objobject.rs | 95 ++++++++++----------------------------- 2 files changed, 31 insertions(+), 82 deletions(-) diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index 507d7697a9..4107946968 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -1,5 +1,4 @@ use crate::frame::Scope; -use crate::function::PyFuncArgs; use crate::obj::objcode::PyCodeRef; use crate::obj::objtype::PyClassRef; use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; @@ -69,16 +68,15 @@ pub fn init(context: &PyContext) { }); } -fn bind_method(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(function, None), (obj, None), (cls, None)] - ); - +fn bind_method( + function: PyObjectRef, + obj: PyObjectRef, + cls: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { if obj.is(&vm.get_none()) && !cls.is(&obj.class()) { - Ok(function.clone()) + Ok(function) } else { - Ok(vm.ctx.new_bound_method(function.clone(), obj.clone())) + Ok(vm.ctx.new_bound_method(function, obj)) } } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 74ba2dc3d4..c43e31a969 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -1,6 +1,6 @@ use super::objdict::{self, PyDictRef}; use super::objlist::PyList; -use super::objstr::{self, PyStringRef}; +use super::objstr::PyStringRef; use super::objtype; use crate::function::PyFuncArgs; use crate::obj::objproperty::PropertyBuilder; @@ -31,69 +31,31 @@ pub fn new_instance(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { Ok(PyObject::new(PyInstance, cls, dict)) } -fn object_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(_zelf, Some(vm.ctx.object())), (_other, None)] - ); - Ok(vm.ctx.not_implemented()) +fn object_eq(_zelf: PyObjectRef, _other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.not_implemented() } -fn object_ne(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(_zelf, Some(vm.ctx.object())), (_other, None)] - ); - - Ok(vm.ctx.not_implemented()) +fn object_ne(_zelf: PyObjectRef, _other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.not_implemented() } -fn object_lt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(_zelf, Some(vm.ctx.object())), (_other, None)] - ); - - Ok(vm.ctx.not_implemented()) +fn object_lt(_zelf: PyObjectRef, _other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.not_implemented() } -fn object_le(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(_zelf, Some(vm.ctx.object())), (_other, None)] - ); - - Ok(vm.ctx.not_implemented()) +fn object_le(_zelf: PyObjectRef, _other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.not_implemented() } -fn object_gt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(_zelf, Some(vm.ctx.object())), (_other, None)] - ); - - Ok(vm.ctx.not_implemented()) +fn object_gt(_zelf: PyObjectRef, _other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.not_implemented() } -fn object_ge(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(_zelf, Some(vm.ctx.object())), (_other, None)] - ); - - Ok(vm.ctx.not_implemented()) +fn object_ge(_zelf: PyObjectRef, _other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx.not_implemented() } -fn object_hash(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(_zelf, Some(vm.ctx.object()))]); - - // For now default to non hashable +fn object_hash(_zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error("unhashable type".to_string())) } @@ -147,15 +109,12 @@ fn object_delattr(obj: PyObjectRef, attr_name: PyStringRef, vm: &VirtualMachine) } } -fn object_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.object()))]); - vm.call_method(zelf, "__repr__", vec![]) +fn object_str(zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { + vm.call_method(&zelf, "__repr__", vec![]) } -fn object_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.object()))]); - let address = obj.get_id(); - Ok(vm.new_str(format!("<{} object at 0x{:x}>", obj.class().name, address))) +fn object_repr(zelf: PyObjectRef, _vm: &VirtualMachine) -> String { + format!("<{} object at 0x{:x}>", zelf.class().name, zelf.get_id()) } pub fn object_dir(obj: PyObjectRef, vm: &VirtualMachine) -> PyList { @@ -235,16 +194,8 @@ fn object_dict(object: PyObjectRef, vm: &VirtualMachine) -> PyResult } } -fn object_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (obj, Some(vm.ctx.object())), - (name_str, Some(vm.ctx.str_type())) - ] - ); - let name = objstr::get_value(&name_str); +fn object_getattribute(obj: PyObjectRef, name_str: PyStringRef, vm: &VirtualMachine) -> PyResult { + let name = &name_str.value; trace!("object.__getattribute__({:?}, {:?})", obj, name); let cls = obj.class(); @@ -252,7 +203,7 @@ fn object_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let attr_class = attr.class(); if objtype::class_has_attr(&attr_class, "__set__") { if let Some(descriptor) = objtype::class_get_attr(&attr_class, "__get__") { - return vm.invoke(descriptor, vec![attr, obj.clone(), cls.into_object()]); + return vm.invoke(descriptor, vec![attr, obj, cls.into_object()]); } } } @@ -260,9 +211,9 @@ fn object_getattribute(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if let Some(obj_attr) = object_getattr(&obj, &name) { Ok(obj_attr) } else if let Some(attr) = objtype::class_get_attr(&cls, &name) { - vm.call_get_descriptor(attr, obj.clone()) + vm.call_get_descriptor(attr, obj) } else if let Some(getter) = objtype::class_get_attr(&cls, "__getattr__") { - vm.invoke(getter, vec![obj.clone(), name_str.clone()]) + vm.invoke(getter, vec![obj, name_str.into_object()]) } else { Err(vm.new_attribute_error(format!("{} has no attribute '{}'", obj, name))) } From 57fa041d08a99007bf947524332fa7cd02f59cea Mon Sep 17 00:00:00 2001 From: Joey Date: Wed, 27 Mar 2019 18:05:33 -0700 Subject: [PATCH 085/884] bytearray: convert to new args style --- vm/src/obj/objbytearray.rs | 228 ++++++++++++++++--------------------- 1 file changed, 101 insertions(+), 127 deletions(-) diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 087c99dbf8..c609fdb2fe 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -6,12 +6,12 @@ use std::ops::{Deref, DerefMut}; use num_traits::ToPrimitive; -use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::function::OptionalArg; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint; -use super::objtype::{self, PyClassRef}; +use super::objtype::PyClassRef; #[derive(Debug)] pub struct PyByteArray { @@ -63,22 +63,22 @@ pub fn init(context: &PyContext) { extend_class!(context, bytearray_type, { "__doc__" => context.new_str(bytearray_doc.to_string()), - "__eq__" => context.new_rustfunc(bytearray_eq), - "__len__" => context.new_rustfunc(bytesarray_len), "__new__" => context.new_rustfunc(bytearray_new), - "__repr__" => context.new_rustfunc(bytearray_repr), - "clear" => context.new_rustfunc(bytearray_clear), - "isalnum" => context.new_rustfunc(bytearray_isalnum), - "isalpha" => context.new_rustfunc(bytearray_isalpha), - "isascii" => context.new_rustfunc(bytearray_isascii), - "isdigit" => context.new_rustfunc(bytearray_isdigit), - "islower" => context.new_rustfunc(bytearray_islower), - "isspace" => context.new_rustfunc(bytearray_isspace), - "istitle" =>context.new_rustfunc(bytearray_istitle), - "isupper" => context.new_rustfunc(bytearray_isupper), - "lower" => context.new_rustfunc(bytearray_lower), - "pop" => context.new_rustfunc(bytearray_pop), - "upper" => context.new_rustfunc(bytearray_upper) + "__eq__" => context.new_rustfunc(PyByteArrayRef::eq), + "__len__" => context.new_rustfunc(PyByteArrayRef::len), + "__repr__" => context.new_rustfunc(PyByteArrayRef::repr), + "clear" => context.new_rustfunc(PyByteArrayRef::clear), + "isalnum" => context.new_rustfunc(PyByteArrayRef::isalnum), + "isalpha" => context.new_rustfunc(PyByteArrayRef::isalpha), + "isascii" => context.new_rustfunc(PyByteArrayRef::isascii), + "isdigit" => context.new_rustfunc(PyByteArrayRef::isdigit), + "islower" => context.new_rustfunc(PyByteArrayRef::islower), + "isspace" => context.new_rustfunc(PyByteArrayRef::isspace), + "istitle" =>context.new_rustfunc(PyByteArrayRef::istitle), + "isupper" => context.new_rustfunc(PyByteArrayRef::isupper), + "lower" => context.new_rustfunc(PyByteArrayRef::lower), + "pop" => context.new_rustfunc(PyByteArrayRef::pop), + "upper" => context.new_rustfunc(PyByteArrayRef::upper) }); } @@ -107,89 +107,69 @@ fn bytearray_new( PyByteArray::new(value).into_ref_with_type(vm, cls.clone()) } -fn bytesarray_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(a, Some(vm.ctx.bytearray_type()))]); - - let byte_vec = get_value(a).to_vec(); - Ok(vm.ctx.new_int(byte_vec.len())) -} - -fn bytearray_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytearray_type())), (b, None)] - ); +impl PyByteArrayRef { + fn len(self, _vm: &VirtualMachine) -> usize { + self.value.borrow().len() + } - let result = if objtype::isinstance(b, &vm.ctx.bytearray_type()) { - get_value(a).to_vec() == get_value(b).to_vec() - } else { - false - }; - Ok(vm.ctx.new_bool(result)) -} + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if let Ok(other) = other.downcast::() { + vm.ctx + .new_bool(self.value.borrow().as_slice() == other.value.borrow().as_slice()) + } else { + vm.ctx.not_implemented() + } + } -fn bytearray_isalnum(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - let bytes = get_value(zelf); - Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_alphanumeric()))) -} + fn isalnum(self, _vm: &VirtualMachine) -> bool { + let bytes = self.value.borrow(); + !bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_alphanumeric()) + } -fn bytearray_isalpha(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - let bytes = get_value(zelf); - Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_alphabetic()))) -} + fn isalpha(self, _vm: &VirtualMachine) -> bool { + let bytes = self.value.borrow(); + !bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_alphabetic()) + } -fn bytearray_isascii(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - let bytes = get_value(zelf); - Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_ascii()))) -} + fn isascii(self, _vm: &VirtualMachine) -> bool { + let bytes = self.value.borrow(); + !bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_ascii()) + } -fn bytearray_isdigit(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - let bytes = get_value(zelf); - Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_digit(10)))) -} + fn isdigit(self, _vm: &VirtualMachine) -> bool { + let bytes = self.value.borrow(); + !bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_digit(10)) + } -fn bytearray_islower(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - let bytes = get_value(zelf); - Ok(vm.new_bool( + fn islower(self, _vm: &VirtualMachine) -> bool { + let bytes = self.value.borrow(); !bytes.is_empty() && bytes .iter() .filter(|x| !char::from(**x).is_whitespace()) - .all(|x| char::from(*x).is_lowercase()), - )) -} + .all(|x| char::from(*x).is_lowercase()) + } -fn bytearray_isspace(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - let bytes = get_value(zelf); - Ok(vm.new_bool(!bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_whitespace()))) -} + fn isspace(self, _vm: &VirtualMachine) -> bool { + let bytes = self.value.borrow(); + !bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_whitespace()) + } -fn bytearray_isupper(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - let bytes = get_value(zelf); - Ok(vm.new_bool( + fn isupper(self, _vm: &VirtualMachine) -> bool { + let bytes = self.value.borrow(); !bytes.is_empty() && bytes .iter() .filter(|x| !char::from(**x).is_whitespace()) - .all(|x| char::from(*x).is_uppercase()), - )) -} + .all(|x| char::from(*x).is_uppercase()) + } -fn bytearray_istitle(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - let bytes = get_value(zelf); + fn istitle(self, _vm: &VirtualMachine) -> bool { + let bytes = self.value.borrow(); + if bytes.is_empty() { + return false; + } - if bytes.is_empty() { - Ok(vm.new_bool(false)) - } else { let mut iter = bytes.iter().peekable(); let mut prev_cased = false; @@ -198,21 +178,52 @@ fn bytearray_istitle(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let next = if let Some(k) = iter.peek() { char::from(**k) } else if current.is_uppercase() { - return Ok(vm.new_bool(!prev_cased)); + return !prev_cased; } else { - return Ok(vm.new_bool(prev_cased)); + return prev_cased; }; if (is_cased(current) && next.is_uppercase() && !prev_cased) || (!is_cased(current) && next.is_lowercase()) { - return Ok(vm.new_bool(false)); + return false; } prev_cased = is_cased(current); } - Ok(vm.new_bool(true)) + true + } + + fn repr(self, _vm: &VirtualMachine) -> String { + let bytes = self.value.borrow(); + let data = String::from_utf8(bytes.to_vec()).unwrap_or_else(|_| to_hex(&bytes.to_vec())); + format!("bytearray(b'{}')", data) + } + + fn clear(self, _vm: &VirtualMachine) { + self.value.borrow_mut().clear(); + } + + fn pop(self, vm: &VirtualMachine) -> PyResult { + let mut bytes = self.value.borrow_mut(); + bytes + .pop() + .ok_or_else(|| vm.new_index_error("pop from empty bytearray".to_string())) + } + + fn lower(self, _vm: &VirtualMachine) -> PyByteArray { + let bytes = self.value.borrow().clone().to_ascii_lowercase(); + PyByteArray { + value: RefCell::new(bytes), + } + } + + fn upper(self, _vm: &VirtualMachine) -> PyByteArray { + let bytes = self.value.borrow().clone().to_ascii_uppercase(); + PyByteArray { + value: RefCell::new(bytes), + } } } @@ -222,7 +233,7 @@ fn is_cased(c: char) -> bool { } /* -fn bytearray_getitem(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { +fn getitem(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, @@ -239,56 +250,19 @@ fn set_value(obj: &PyObjectRef, value: Vec) { */ /// Return a lowercase hex representation of a bytearray -fn bytearray_to_hex(bytearray: &[u8]) -> String { +fn to_hex(bytearray: &[u8]) -> String { bytearray.iter().fold(String::new(), |mut s, b| { let _ = write!(s, "\\x{:02x}", b); s }) } -fn bytearray_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytearray_type()))]); - let value = get_value(obj); - let data = - String::from_utf8(value.to_vec()).unwrap_or_else(|_| bytearray_to_hex(&value.to_vec())); - Ok(vm.new_str(format!("bytearray(b'{}')", data))) -} - -fn bytearray_clear(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytearray_type()))]); - get_mut_value(zelf).clear(); - Ok(vm.get_none()) -} - -fn bytearray_pop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytearray_type()))]); - let mut value = get_mut_value(obj); - - if let Some(i) = value.pop() { - Ok(vm.ctx.new_int(i)) - } else { - Err(vm.new_index_error("pop from empty bytearray".to_string())) - } -} - -fn bytearray_lower(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytearray_type()))]); - let value = get_value(obj).to_vec().to_ascii_lowercase(); - Ok(vm.ctx.new_bytearray(value)) -} - -fn bytearray_upper(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytearray_type()))]); - let value = get_value(obj).to_vec().to_ascii_uppercase(); - Ok(vm.ctx.new_bytearray(value)) -} - #[cfg(test)] mod tests { use super::*; #[test] fn bytearray_to_hex_formatting() { - assert_eq!(&bytearray_to_hex(&[11u8, 222u8]), "\\x0b\\xde"); + assert_eq!(&to_hex(&[11u8, 222u8]), "\\x0b\\xde"); } } From 62842e6d194dca4ade3383c99be0403a8453542a Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Wed, 27 Mar 2019 19:00:34 -0700 Subject: [PATCH 086/884] convert some builtins --- tests/snippets/builtin_format.py | 5 ++ vm/src/builtins.rs | 125 +++++++++++++------------------ vm/src/function.rs | 2 +- vm/src/vm.rs | 5 +- 4 files changed, 62 insertions(+), 75 deletions(-) diff --git a/tests/snippets/builtin_format.py b/tests/snippets/builtin_format.py index 6c06cbd98c..55a6a3da12 100644 --- a/tests/snippets/builtin_format.py +++ b/tests/snippets/builtin_format.py @@ -7,3 +7,8 @@ assert format({}) == "{}" assert_raises(TypeError, lambda: format({}, 'b'), 'format_spec not empty for dict') + +class BadFormat: + def __format__(self, spec): + return 42 +assert_raises(TypeError, lambda: format(BadFormat())) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 1de24b86ab..9d5980759c 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -6,21 +6,22 @@ use std::char; use std::io::{self, Write}; use std::path::PathBuf; -use num_traits::{Signed, ToPrimitive}; +use num_traits::Signed; use crate::compile; use crate::import::import_module; use crate::obj::objbool; use crate::obj::objdict::PyDictRef; -use crate::obj::objint; +use crate::obj::objint::{self, PyIntRef}; use crate::obj::objiter; -use crate::obj::objstr::{self, PyStringRef}; +use crate::obj::objstr::{self, PyString, PyStringRef}; use crate::obj::objtype::{self, PyClassRef}; use crate::frame::Scope; use crate::function::{Args, OptionalArg, PyFuncArgs}; use crate::pyobject::{ - DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TryFromObject, TypeProtocol, + DictProtocol, IdProtocol, PyContext, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, + TypeProtocol, }; use crate::vm::VirtualMachine; @@ -28,74 +29,53 @@ use crate::obj::objcode::PyCodeRef; #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::io::io_open; -fn builtin_abs(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(x, None)]); +fn builtin_abs(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { match vm.get_method(x.clone(), "__abs__") { Ok(attrib) => vm.invoke(attrib, PyFuncArgs::new(vec![], vec![])), Err(..) => Err(vm.new_type_error("bad operand for abs".to_string())), } } -fn builtin_all(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(iterable, None)]); - let items = vm.extract_elements(iterable)?; - for item in items { - let result = objbool::boolval(vm, item)?; - if !result { - return Ok(vm.new_bool(false)); +fn builtin_all(iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + for item in iterable.iter(vm)? { + if !item? { + return Ok(false); } } - Ok(vm.new_bool(true)) + Ok(true) } -fn builtin_any(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(iterable, None)]); - let iterator = objiter::get_iter(vm, iterable)?; - - while let Some(item) = objiter::get_next_object(vm, &iterator)? { - let result = objbool::boolval(vm, item)?; - if result { - return Ok(vm.new_bool(true)); +fn builtin_any(iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + for item in iterable.iter(vm)? { + if item? { + return Ok(true); } } - - Ok(vm.new_bool(false)) + Ok(false) } // builtin_ascii -fn builtin_bin(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(number, Some(vm.ctx.int_type()))]); - - let n = objint::get_value(number); - let s = if n.is_negative() { - format!("-0b{:b}", n.abs()) +fn builtin_bin(x: PyIntRef, _vm: &VirtualMachine) -> String { + let x = x.as_bigint(); + if x.is_negative() { + format!("-0b{:b}", x.abs()) } else { - format!("0b{:b}", n) - }; - - Ok(vm.new_str(s)) + format!("0b{:b}", x) + } } // builtin_breakpoint -fn builtin_callable(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, None)]); - let is_callable = objtype::class_has_attr(&obj.class(), "__call__"); - Ok(vm.new_bool(is_callable)) +fn builtin_callable(obj: PyObjectRef, _vm: &VirtualMachine) -> bool { + objtype::class_has_attr(&obj.class(), "__call__") } -fn builtin_chr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.int_type()))]); - - let code_point = objint::get_value(i).to_u32().unwrap(); - - let txt = match char::from_u32(code_point) { +fn builtin_chr(i: u32, _vm: &VirtualMachine) -> String { + match char::from_u32(i) { Some(value) => value.to_string(), None => '_'.to_string(), - }; - - Ok(vm.new_str(txt)) + } } fn builtin_compile( @@ -128,13 +108,8 @@ fn builtin_compile( }) } -fn builtin_delattr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(obj, None), (attr, Some(vm.ctx.str_type()))] - ); - vm.del_attr(obj, attr.clone()) +fn builtin_delattr(obj: PyObjectRef, attr: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + vm.del_attr(&obj, attr.into_object()) } fn builtin_dir(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -258,17 +233,26 @@ fn make_scope( Ok(Scope::new(locals, globals)) } -fn builtin_format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(obj, None)], - optional = [(format_spec, Some(vm.ctx.str_type()))] - ); - let format_spec = format_spec - .cloned() - .unwrap_or_else(|| vm.new_str("".to_string())); - vm.call_method(obj, "__format__", vec![format_spec]) +fn builtin_format( + value: PyObjectRef, + format_spec: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let format_spec = format_spec.into_option().unwrap_or_else(|| { + PyString { + value: "".to_string(), + } + .into_ref(vm) + }); + + vm.call_method(&value, "__format__", vec![format_spec.into_object()])? + .downcast() + .map_err(|obj| { + vm.new_type_error(format!( + "__format__ must return a str, not {}", + obj.class().name + )) + }) } fn catch_attr_exception(ex: PyObjectRef, default: T, vm: &VirtualMachine) -> PyResult { @@ -644,14 +628,11 @@ fn builtin_sorted(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { Ok(lst) } -fn builtin_sum(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(iterable, None)]); - let items = vm.extract_elements(iterable)?; - +fn builtin_sum(iterable: PyIterable, start: OptionalArg, vm: &VirtualMachine) -> PyResult { // Start with zero and add at will: - let mut sum = vm.ctx.new_int(0); - for item in items { - sum = vm._add(sum, item)?; + let mut sum = start.into_option().unwrap_or_else(|| vm.ctx.new_int(0)); + for item in iterable.iter(vm)? { + sum = vm._add(sum, item?)?; } Ok(sum) } diff --git a/vm/src/function.rs b/vm/src/function.rs index 1c61ffc87c..a567e79038 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -308,7 +308,7 @@ where /// An argument that may or may not be provided by the caller. /// /// This style of argument is not possible in pure Python. -pub enum OptionalArg { +pub enum OptionalArg { Present(T), Missing, } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 4148184f42..36f4450e60 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -577,8 +577,9 @@ impl VirtualMachine { self.call_method(&obj, "__setattr__", vec![attr_name, attr_value]) } - pub fn del_attr(&self, obj: &PyObjectRef, attr_name: PyObjectRef) -> PyResult { - self.call_method(&obj, "__delattr__", vec![attr_name]) + pub fn del_attr(&self, obj: &PyObjectRef, attr_name: PyObjectRef) -> PyResult<()> { + self.call_method(&obj, "__delattr__", vec![attr_name])?; + Ok(()) } // get_method should be used for internal access to magic methods (by-passing From b0d7960cc59ed1a9a1a8f794818349024eac3f4f Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Wed, 27 Mar 2019 19:14:37 -0700 Subject: [PATCH 087/884] bytes: move methods to impl block --- vm/src/obj/objbytes.rs | 208 +++++++++++++++++++++-------------------- 1 file changed, 105 insertions(+), 103 deletions(-) diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 8d64cc5076..5b725be501 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -57,16 +57,16 @@ pub fn init(context: &PyContext) { - an integer"; extend_class!(context, bytes_type, { - "__eq__" => context.new_rustfunc(bytes_eq), - "__lt__" => context.new_rustfunc(bytes_lt), - "__le__" => context.new_rustfunc(bytes_le), - "__gt__" => context.new_rustfunc(bytes_gt), - "__ge__" => context.new_rustfunc(bytes_ge), - "__hash__" => context.new_rustfunc(bytes_hash), "__new__" => context.new_rustfunc(bytes_new), - "__repr__" => context.new_rustfunc(bytes_repr), - "__len__" => context.new_rustfunc(bytes_len), - "__iter__" => context.new_rustfunc(bytes_iter), + "__eq__" => context.new_rustfunc(PyBytesRef::eq), + "__lt__" => context.new_rustfunc(PyBytesRef::lt), + "__le__" => context.new_rustfunc(PyBytesRef::le), + "__gt__" => context.new_rustfunc(PyBytesRef::gt), + "__ge__" => context.new_rustfunc(PyBytesRef::ge), + "__hash__" => context.new_rustfunc(PyBytesRef::hash), + "__repr__" => context.new_rustfunc(PyBytesRef::repr), + "__len__" => context.new_rustfunc(PyBytesRef::len), + "__iter__" => context.new_rustfunc(PyBytesRef::iter), "__doc__" => context.new_str(bytes_doc.to_string()) }); } @@ -93,111 +93,113 @@ fn bytes_new( PyBytes::new(value).into_ref_with_type(vm, cls) } -fn bytes_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytes_type())), (b, None)] - ); - - let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { - get_value(a).to_vec() == get_value(b).to_vec() - } else { - false - }; - Ok(vm.ctx.new_bool(result)) -} - -fn bytes_ge(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytes_type())), (b, None)] - ); - - let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { - get_value(a).to_vec() >= get_value(b).to_vec() - } else { - return Err(vm.new_type_error(format!("Cannot compare {} and {} using '>'", a, b))); - }; - Ok(vm.ctx.new_bool(result)) -} +impl PyBytesRef { + fn eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(a, Some(vm.ctx.bytes_type())), (b, None)] + ); + + let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { + get_value(a).to_vec() == get_value(b).to_vec() + } else { + false + }; + Ok(vm.ctx.new_bool(result)) + } -fn bytes_gt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytes_type())), (b, None)] - ); + fn ge(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(a, Some(vm.ctx.bytes_type())), (b, None)] + ); + + let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { + get_value(a).to_vec() >= get_value(b).to_vec() + } else { + return Err(vm.new_type_error(format!("Cannot compare {} and {} using '>'", a, b))); + }; + Ok(vm.ctx.new_bool(result)) + } - let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { - get_value(a).to_vec() > get_value(b).to_vec() - } else { - return Err(vm.new_type_error(format!("Cannot compare {} and {} using '>='", a, b))); - }; - Ok(vm.ctx.new_bool(result)) -} + fn gt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(a, Some(vm.ctx.bytes_type())), (b, None)] + ); + + let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { + get_value(a).to_vec() > get_value(b).to_vec() + } else { + return Err(vm.new_type_error(format!("Cannot compare {} and {} using '>='", a, b))); + }; + Ok(vm.ctx.new_bool(result)) + } -fn bytes_le(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytes_type())), (b, None)] - ); + fn le(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(a, Some(vm.ctx.bytes_type())), (b, None)] + ); + + let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { + get_value(a).to_vec() <= get_value(b).to_vec() + } else { + return Err(vm.new_type_error(format!("Cannot compare {} and {} using '<'", a, b))); + }; + Ok(vm.ctx.new_bool(result)) + } - let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { - get_value(a).to_vec() <= get_value(b).to_vec() - } else { - return Err(vm.new_type_error(format!("Cannot compare {} and {} using '<'", a, b))); - }; - Ok(vm.ctx.new_bool(result)) -} + fn lt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(a, Some(vm.ctx.bytes_type())), (b, None)] + ); + + let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { + get_value(a).to_vec() < get_value(b).to_vec() + } else { + return Err(vm.new_type_error(format!("Cannot compare {} and {} using '<='", a, b))); + }; + Ok(vm.ctx.new_bool(result)) + } -fn bytes_lt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytes_type())), (b, None)] - ); + fn len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(a, Some(vm.ctx.bytes_type()))]); - let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { - get_value(a).to_vec() < get_value(b).to_vec() - } else { - return Err(vm.new_type_error(format!("Cannot compare {} and {} using '<='", a, b))); - }; - Ok(vm.ctx.new_bool(result)) -} + let byte_vec = get_value(a).to_vec(); + Ok(vm.ctx.new_int(byte_vec.len())) + } -fn bytes_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(a, Some(vm.ctx.bytes_type()))]); + fn hash(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytes_type()))]); + let data = get_value(zelf); + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + data.hash(&mut hasher); + let hash = hasher.finish(); + Ok(vm.ctx.new_int(hash)) + } - let byte_vec = get_value(a).to_vec(); - Ok(vm.ctx.new_int(byte_vec.len())) -} + fn repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytes_type()))]); + let value = get_value(obj); + let data = String::from_utf8(value.to_vec()).unwrap(); + Ok(vm.new_str(format!("b'{}'", data))) + } -fn bytes_hash(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytes_type()))]); - let data = get_value(zelf); - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - data.hash(&mut hasher); - let hash = hasher.finish(); - Ok(vm.ctx.new_int(hash)) + fn iter(obj: PyBytesRef, _vm: &VirtualMachine) -> PyIteratorValue { + PyIteratorValue { + position: Cell::new(0), + iterated_obj: obj.into_object(), + } + } } pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { &obj.payload::().unwrap().value } - -fn bytes_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytes_type()))]); - let value = get_value(obj); - let data = String::from_utf8(value.to_vec()).unwrap(); - Ok(vm.new_str(format!("b'{}'", data))) -} - -fn bytes_iter(obj: PyBytesRef, _vm: &VirtualMachine) -> PyIteratorValue { - PyIteratorValue { - position: Cell::new(0), - iterated_obj: obj.into_object(), - } -} From e4272126cf5589ecd24a6a89a77daba364968c49 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Wed, 27 Mar 2019 19:15:38 -0700 Subject: [PATCH 088/884] bytes: return NotImplemented where appropriate --- vm/src/obj/objbytes.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 5b725be501..bed57157e9 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -104,7 +104,7 @@ impl PyBytesRef { let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { get_value(a).to_vec() == get_value(b).to_vec() } else { - false + return Ok(vm.ctx.not_implemented()); }; Ok(vm.ctx.new_bool(result)) } @@ -119,7 +119,7 @@ impl PyBytesRef { let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { get_value(a).to_vec() >= get_value(b).to_vec() } else { - return Err(vm.new_type_error(format!("Cannot compare {} and {} using '>'", a, b))); + return Ok(vm.ctx.not_implemented()); }; Ok(vm.ctx.new_bool(result)) } @@ -134,7 +134,7 @@ impl PyBytesRef { let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { get_value(a).to_vec() > get_value(b).to_vec() } else { - return Err(vm.new_type_error(format!("Cannot compare {} and {} using '>='", a, b))); + return Ok(vm.ctx.not_implemented()); }; Ok(vm.ctx.new_bool(result)) } @@ -149,7 +149,7 @@ impl PyBytesRef { let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { get_value(a).to_vec() <= get_value(b).to_vec() } else { - return Err(vm.new_type_error(format!("Cannot compare {} and {} using '<'", a, b))); + return Ok(vm.ctx.not_implemented()); }; Ok(vm.ctx.new_bool(result)) } @@ -164,7 +164,7 @@ impl PyBytesRef { let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { get_value(a).to_vec() < get_value(b).to_vec() } else { - return Err(vm.new_type_error(format!("Cannot compare {} and {} using '<='", a, b))); + return Ok(vm.ctx.not_implemented()); }; Ok(vm.ctx.new_bool(result)) } From e0aca86473738ea8cf592fce5b7e4769293b6227 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Wed, 27 Mar 2019 19:17:27 -0700 Subject: [PATCH 089/884] bytes: convert methods to new args style --- vm/src/obj/objbytes.rs | 121 +++++++++++++---------------------------- 1 file changed, 39 insertions(+), 82 deletions(-) diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index bed57157e9..9390e70704 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,17 +1,16 @@ use std::cell::Cell; +use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use std::ops::Deref; use num_traits::ToPrimitive; -use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{ - PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, -}; +use crate::function::OptionalArg; +use crate::pyobject::{PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint; -use super::objtype::{self, PyClassRef}; +use super::objtype::PyClassRef; #[derive(Debug)] pub struct PyBytes { @@ -94,102 +93,60 @@ fn bytes_new( } impl PyBytesRef { - fn eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytes_type())), (b, None)] - ); - - let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { - get_value(a).to_vec() == get_value(b).to_vec() + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if let Ok(other) = other.downcast::() { + vm.ctx.new_bool(self.value == other.value) } else { - return Ok(vm.ctx.not_implemented()); - }; - Ok(vm.ctx.new_bool(result)) + vm.ctx.not_implemented() + } } - fn ge(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytes_type())), (b, None)] - ); - - let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { - get_value(a).to_vec() >= get_value(b).to_vec() + fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if let Ok(other) = other.downcast::() { + vm.ctx.new_bool(self.value >= other.value) } else { - return Ok(vm.ctx.not_implemented()); - }; - Ok(vm.ctx.new_bool(result)) + vm.ctx.not_implemented() + } } - fn gt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytes_type())), (b, None)] - ); - - let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { - get_value(a).to_vec() > get_value(b).to_vec() + fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if let Ok(other) = other.downcast::() { + vm.ctx.new_bool(self.value > other.value) } else { - return Ok(vm.ctx.not_implemented()); - }; - Ok(vm.ctx.new_bool(result)) + vm.ctx.not_implemented() + } } - fn le(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytes_type())), (b, None)] - ); - - let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { - get_value(a).to_vec() <= get_value(b).to_vec() + fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if let Ok(other) = other.downcast::() { + vm.ctx.new_bool(self.value <= other.value) } else { - return Ok(vm.ctx.not_implemented()); - }; - Ok(vm.ctx.new_bool(result)) + vm.ctx.not_implemented() + } } - fn lt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(a, Some(vm.ctx.bytes_type())), (b, None)] - ); - - let result = if objtype::isinstance(b, &vm.ctx.bytes_type()) { - get_value(a).to_vec() < get_value(b).to_vec() + fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if let Ok(other) = other.downcast::() { + vm.ctx.new_bool(self.value < other.value) } else { - return Ok(vm.ctx.not_implemented()); - }; - Ok(vm.ctx.new_bool(result)) + vm.ctx.not_implemented() + } } - fn len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(a, Some(vm.ctx.bytes_type()))]); - - let byte_vec = get_value(a).to_vec(); - Ok(vm.ctx.new_int(byte_vec.len())) + fn len(self, _vm: &VirtualMachine) -> usize { + self.value.len() } - fn hash(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.bytes_type()))]); - let data = get_value(zelf); - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - data.hash(&mut hasher); - let hash = hasher.finish(); - Ok(vm.ctx.new_int(hash)) + fn hash(self, _vm: &VirtualMachine) -> u64 { + let mut hasher = DefaultHasher::new(); + self.value.hash(&mut hasher); + hasher.finish() } - fn repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.bytes_type()))]); - let value = get_value(obj); - let data = String::from_utf8(value.to_vec()).unwrap(); - Ok(vm.new_str(format!("b'{}'", data))) + fn repr(self, _vm: &VirtualMachine) -> String { + // TODO: don't just unwrap + let data = String::from_utf8(self.value.clone()).unwrap(); + format!("b'{}'", data) } fn iter(obj: PyBytesRef, _vm: &VirtualMachine) -> PyIteratorValue { From 657d025592dade327e485b744c41a80d1f673059 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 28 Mar 2019 21:34:11 +1300 Subject: [PATCH 090/884] Change syntax of attributes in FromArgs proc macro --- derive/src/lib.rs | 161 ++++++++++++++++++++++++++++++++++++++----- vm/src/builtins.rs | 26 +++---- vm/src/function.rs | 83 +++------------------- vm/src/obj/objint.rs | 3 +- 4 files changed, 164 insertions(+), 109 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 558bf3dda4..f1b9ead085 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -3,9 +3,9 @@ extern crate proc_macro; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::{Data, DeriveInput, Field, Fields}; +use syn::{Attribute, Data, DeriveInput, Expr, Field, Fields, Ident, Lit, Meta, NestedMeta}; -#[proc_macro_derive(FromArgs, attributes(positional, keyword))] +#[proc_macro_derive(FromArgs, attributes(pyarg))] pub fn derive_from_args(input: TokenStream) -> TokenStream { let ast: DeriveInput = syn::parse(input).unwrap(); @@ -13,40 +13,167 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream { gen.to_string().parse().unwrap() } -enum ArgType { +enum ArgKind { Positional, PositionalKeyword, Keyword, } -fn generate_field(field: &Field) -> TokenStream2 { - let arg_type = if let Some(attr) = field.attrs.first() { - if attr.path.is_ident("positional") { - ArgType::Positional - } else if attr.path.is_ident("keyword") { - ArgType::Keyword +impl ArgKind { + fn from_ident(ident: &Ident) -> ArgKind { + if ident == "positional" { + ArgKind::Positional + } else if ident == "positional_keyword" { + ArgKind::PositionalKeyword + } else if ident == "keyword" { + ArgKind::Keyword } else { panic!("Unrecognised attribute") } + } +} + +struct ArgAttribute { + kind: ArgKind, + default: Option, + optional: bool, +} + +impl ArgAttribute { + fn from_attribute(attr: &Attribute) -> Option { + if !attr.path.is_ident("pyarg") { + return None; + } + + match attr.parse_meta().unwrap() { + Meta::List(list) => { + let mut iter = list.nested.iter(); + let first_arg = iter.next().expect("at least one argument in pyarg list"); + let kind = match first_arg { + NestedMeta::Meta(Meta::Word(ident)) => ArgKind::from_ident(ident), + _ => panic!("Bad syntax for first pyarg attribute argument"), + }; + + let mut attribute = ArgAttribute { + kind, + default: None, + optional: false, + }; + + while let Some(arg) = iter.next() { + attribute.parse_argument(arg); + } + + assert!( + attribute.default.is_none() || !attribute.optional, + "Can't set both a default value and optional" + ); + + Some(attribute) + } + _ => panic!("Bad syntax for pyarg attribute"), + } + } + + fn parse_argument(&mut self, arg: &NestedMeta) { + match arg { + NestedMeta::Meta(Meta::Word(ident)) => { + if ident == "default" { + assert!(self.default.is_none(), "Default already set"); + let expr = syn::parse_str::("Default::default()").unwrap(); + self.default = Some(expr); + } else if ident == "optional" { + self.optional = true; + } else { + panic!("Unrecognised pyarg attribute '{}'", ident); + } + } + NestedMeta::Meta(Meta::NameValue(name_value)) => { + if name_value.ident == "default" { + assert!(self.default.is_none(), "Default already set"); + + match name_value.lit { + Lit::Str(ref val) => { + let expr = val + .parse::() + .expect("a valid expression for default argument"); + self.default = Some(expr); + } + _ => panic!("Expected string value for default argument"), + } + } else if name_value.ident == "optional" { + match name_value.lit { + Lit::Bool(ref val) => { + self.optional = val.value; + } + _ => panic!("Expected boolean value for optional argument"), + } + } else { + panic!("Unrecognised pyarg attribute '{}'", name_value.ident); + } + } + _ => panic!("Bad syntax for first pyarg attribute argument"), + }; + } +} + +fn generate_field(field: &Field) -> TokenStream2 { + let mut pyarg_attrs = field + .attrs + .iter() + .filter_map(ArgAttribute::from_attribute) + .collect::>(); + let attr = if pyarg_attrs.is_empty() { + ArgAttribute { + kind: ArgKind::PositionalKeyword, + default: None, + optional: false, + } + } else if pyarg_attrs.len() == 1 { + pyarg_attrs.remove(0) } else { - ArgType::PositionalKeyword + panic!( + "Multiple pyarg attributes on field '{}'", + field.ident.as_ref().unwrap() + ); }; let name = &field.ident; - match arg_type { - ArgType::Positional => { + let middle = quote! { + .map(|x| crate::pyobject::TryFromObject::try_from_object(vm, x)).transpose()? + }; + let ending = if let Some(default) = attr.default { + quote! { + .unwrap_or_else(|| #default) + } + } else { + let err = match attr.kind { + ArgKind::Positional | ArgKind::PositionalKeyword => { + quote!(crate::function::ArgumentError::TooFewArgs) + } + ArgKind::Keyword => quote!(crate::function::ArgumentError::RequiredKeywordArgument( + stringify!(#name) + )), + }; + quote! { + .ok_or_else(|| #err)? + } + }; + + match attr.kind { + ArgKind::Positional => { quote! { - #name: args.take_positional(vm)?, + #name: args.take_positional()#middle#ending, } } - ArgType::PositionalKeyword => { + ArgKind::PositionalKeyword => { quote! { - #name: args.take_positional_keyword(vm, stringify!(#name))?, + #name: args.take_positional_keyword(stringify!(#name))#middle#ending, } } - ArgType::Keyword => { + ArgKind::Keyword => { quote! { - #name: args.take_keyword(vm, stringify!(#name))?, + #name: args.take_keyword(stringify!(#name))#middle#ending, } } } diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index cf0c0a3f67..5a238208a9 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -559,22 +559,14 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } -// Idea: Should we have a 'default' attribute, so we don't have to use OptionalArg's in this case -//#[derive(Debug, FromArgs)] -//pub struct PrintOptions { -// #[keyword] #[default(None)] sep: Option, -// #[keyword] #[default(None)] end: Option, -// #[keyword] #[default(flush)] flush: bool, -//} - #[derive(Debug, FromArgs)] pub struct PrintOptions { - #[keyword] - sep: OptionalArg>, - #[keyword] - end: OptionalArg>, - #[keyword] - flush: OptionalArg, + #[pyarg(keyword, default = "None")] + sep: Option, + #[pyarg(keyword, default = "None")] + end: Option, + #[pyarg(keyword, default = "false")] + flush: bool, } pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> { @@ -584,7 +576,7 @@ pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) for object in objects { if first { first = false; - } else if let OptionalArg::Present(Some(ref sep)) = options.sep { + } else if let Some(ref sep) = options.sep { write!(stdout_lock, "{}", sep.value).unwrap(); } else { write!(stdout_lock, " ").unwrap(); @@ -593,13 +585,13 @@ pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) write!(stdout_lock, "{}", s).unwrap(); } - if let OptionalArg::Present(Some(end)) = options.end { + if let Some(end) = options.end { write!(stdout_lock, "{}", end.value).unwrap(); } else { writeln!(stdout_lock).unwrap(); } - if options.flush.into_option().unwrap_or(false) { + if options.flush { stdout_lock.flush().unwrap(); } diff --git a/vm/src/function.rs b/vm/src/function.rs index 49c2f02f5b..e5267cf457 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -109,7 +109,7 @@ impl PyFuncArgs { } } - pub fn next_positional(&mut self) -> Option { + pub fn take_positional(&mut self) -> Option { if self.args.is_empty() { None } else { @@ -117,7 +117,11 @@ impl PyFuncArgs { } } - fn extract_keyword(&mut self, name: &str) -> Option { + pub fn take_positional_keyword(&mut self, name: &str) -> Option { + self.take_positional().or_else(|| self.take_keyword(name)) + } + + pub fn take_keyword(&mut self, name: &str) -> Option { // TODO: change kwarg representation so this scan isn't necessary if let Some(index) = self .kwargs @@ -130,49 +134,6 @@ impl PyFuncArgs { } } - pub fn take_positional( - &mut self, - vm: &VirtualMachine, - ) -> Result { - if let Some(arg) = self.next_positional() { - H::from_arg(vm, arg).map_err(|err| ArgumentError::Exception(err)) - } else if let Some(default) = H::DEFAULT { - Ok(default) - } else { - Err(ArgumentError::TooFewArgs) - } - } - - pub fn take_positional_keyword( - &mut self, - vm: &VirtualMachine, - name: &str, - ) -> Result { - if let Some(arg) = self.next_positional() { - H::from_arg(vm, arg).map_err(|err| ArgumentError::Exception(err)) - } else if let Some(arg) = self.extract_keyword(name) { - H::from_arg(vm, arg).map_err(|err| ArgumentError::Exception(err)) - } else if let Some(default) = H::DEFAULT { - Ok(default) - } else { - Err(ArgumentError::TooFewArgs) - } - } - - pub fn take_keyword( - &mut self, - vm: &VirtualMachine, - name: &str, - ) -> Result { - if let Some(arg) = self.extract_keyword(name) { - H::from_arg(vm, arg).map_err(|err| ArgumentError::Exception(err)) - } else if let Some(default) = H::DEFAULT { - Ok(default) - } else { - Err(ArgumentError::RequiredKeywordArgument(name.to_string())) - } - } - pub fn remaining_keyword<'a>(&'a mut self) -> impl Iterator + 'a { self.kwargs.drain(..) } @@ -265,32 +226,6 @@ pub trait FromArgs: Sized { fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result; } -/// Handling behaviour when the argument is and isn't present, used to implement OptionalArg. -pub trait ArgHandler: Sized { - /// Default value that will be used if the argument isn't present, or None in which case the a - /// appropriate error is returned. - const DEFAULT: Option; - - /// Converts the arg argument when it is present - fn from_arg(vm: &VirtualMachine, object: PyObjectRef) -> PyResult; -} - -impl ArgHandler for OptionalArg { - const DEFAULT: Option = Some(Missing); - - fn from_arg(vm: &VirtualMachine, object: PyObjectRef) -> PyResult { - T::try_from_object(vm, object).map(|x| Present(x)) - } -} - -impl ArgHandler for T { - const DEFAULT: Option = None; - - fn from_arg(vm: &VirtualMachine, object: PyObjectRef) -> PyResult { - T::try_from_object(vm, object) - } -} - /// A map of keyword arguments to their values. /// /// A built-in function with a `KwArgs` parameter is analagous to a Python @@ -345,7 +280,7 @@ where { fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { let mut varargs = Vec::new(); - while let Some(value) = args.next_positional() { + while let Some(value) = args.take_positional() { varargs.push(T::try_from_object(vm, value)?); } Ok(Args(varargs)) @@ -370,7 +305,7 @@ where } fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { - if let Some(value) = args.next_positional() { + if let Some(value) = args.take_positional() { Ok(T::try_from_object(vm, value)?) } else { Err(ArgumentError::TooFewArgs) @@ -414,7 +349,7 @@ where } fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { - if let Some(value) = args.next_positional() { + if let Some(value) = args.take_positional() { Ok(Present(T::try_from_object(vm, value)?)) } else { Ok(Missing) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 82394d6910..2470ea5f9d 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -382,8 +382,9 @@ impl PyIntRef { #[derive(FromArgs)] struct IntOptions { - #[positional] + #[pyarg(positional, optional = true)] val_options: OptionalArg, + #[pyarg(positional_keyword, optional = true)] base: OptionalArg, } From c8f4474e7f0e31388384b40f54c508c53bb41079 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 28 Mar 2019 21:46:26 +1300 Subject: [PATCH 091/884] Implement handling of optional attribute in proc macro --- derive/src/lib.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index f1b9ead085..ad81829455 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -146,14 +146,19 @@ fn generate_field(field: &Field) -> TokenStream2 { quote! { .unwrap_or_else(|| #default) } + } else if attr.optional { + quote! { + .map(crate::function::OptionalArg::Present) + .unwrap_or(crate::function::OptionalArg::Missing) + } } else { let err = match attr.kind { - ArgKind::Positional | ArgKind::PositionalKeyword => { - quote!(crate::function::ArgumentError::TooFewArgs) - } - ArgKind::Keyword => quote!(crate::function::ArgumentError::RequiredKeywordArgument( - stringify!(#name) - )), + ArgKind::Positional | ArgKind::PositionalKeyword => quote! { + crate::function::ArgumentError::TooFewArgs + }, + ArgKind::Keyword => quote! { + crate::function::ArgumentError::RequiredKeywordArgument(tringify!(#name)) + }, }; quote! { .ok_or_else(|| #err)? From 6fd6cc647b1a804d95a41b3d006acf2d49f28e29 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 28 Mar 2019 22:00:55 +1300 Subject: [PATCH 092/884] Renamed ArgKind, and member to be consistent with python inspect module --- derive/src/lib.rs | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index ad81829455..b4048ec838 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -13,20 +13,22 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream { gen.to_string().parse().unwrap() } -enum ArgKind { - Positional, - PositionalKeyword, - Keyword, +/// The kind of the python parameter, this corresponds to the value of Parameter.kind +/// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind) +enum ParameterKind { + PositionalOnly, + PositionalOrKeyword, + KeywordOnly, } -impl ArgKind { - fn from_ident(ident: &Ident) -> ArgKind { +impl ParameterKind { + fn from_ident(ident: &Ident) -> ParameterKind { if ident == "positional" { - ArgKind::Positional + ParameterKind::PositionalOnly } else if ident == "positional_keyword" { - ArgKind::PositionalKeyword + ParameterKind::PositionalOrKeyword } else if ident == "keyword" { - ArgKind::Keyword + ParameterKind::KeywordOnly } else { panic!("Unrecognised attribute") } @@ -34,7 +36,7 @@ impl ArgKind { } struct ArgAttribute { - kind: ArgKind, + kind: ParameterKind, default: Option, optional: bool, } @@ -50,7 +52,7 @@ impl ArgAttribute { let mut iter = list.nested.iter(); let first_arg = iter.next().expect("at least one argument in pyarg list"); let kind = match first_arg { - NestedMeta::Meta(Meta::Word(ident)) => ArgKind::from_ident(ident), + NestedMeta::Meta(Meta::Word(ident)) => ParameterKind::from_ident(ident), _ => panic!("Bad syntax for first pyarg attribute argument"), }; @@ -125,7 +127,7 @@ fn generate_field(field: &Field) -> TokenStream2 { .collect::>(); let attr = if pyarg_attrs.is_empty() { ArgAttribute { - kind: ArgKind::PositionalKeyword, + kind: ParameterKind::PositionalOrKeyword, default: None, optional: false, } @@ -153,10 +155,10 @@ fn generate_field(field: &Field) -> TokenStream2 { } } else { let err = match attr.kind { - ArgKind::Positional | ArgKind::PositionalKeyword => quote! { + ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword => quote! { crate::function::ArgumentError::TooFewArgs }, - ArgKind::Keyword => quote! { + ParameterKind::KeywordOnly => quote! { crate::function::ArgumentError::RequiredKeywordArgument(tringify!(#name)) }, }; @@ -166,17 +168,17 @@ fn generate_field(field: &Field) -> TokenStream2 { }; match attr.kind { - ArgKind::Positional => { + ParameterKind::PositionalOnly => { quote! { #name: args.take_positional()#middle#ending, } } - ArgKind::PositionalKeyword => { + ParameterKind::PositionalOrKeyword => { quote! { #name: args.take_positional_keyword(stringify!(#name))#middle#ending, } } - ArgKind::Keyword => { + ParameterKind::KeywordOnly => { quote! { #name: args.take_keyword(stringify!(#name))#middle#ending, } From 73cd680d8d761bd6f0616f09bfab1f5e87be9d56 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Thu, 28 Mar 2019 17:50:41 +0100 Subject: [PATCH 093/884] Add support for trailing comma in function defs. Parser code simplications. --- parser/src/lexer.rs | 17 +++++++-- parser/src/python.lalrpop | 72 ++++++++++++-------------------------- tests/snippets/function.py | 5 +++ 3 files changed, 42 insertions(+), 52 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index defa5e2322..edf21c973c 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -466,8 +466,16 @@ where loop { match self.next_char() { Some('\\') => { - if is_raw { + if self.chr0 == Some(quote_char) { + string_content.push(quote_char); + self.next_char(); + } else if is_raw { string_content.push('\\'); + if let Some(c) = self.next_char() { + string_content.push(c) + } else { + return Err(LexicalError::StringError); + } } else { match self.next_char() { Some('\\') => { @@ -711,7 +719,6 @@ where let tok_start = self.get_pos(); self.next_char(); let tok_end = self.get_pos(); - println!("Emoji: {}", c); return Some(Ok(( tok_start, Tok::Name { @@ -1438,7 +1445,7 @@ mod tests { #[test] fn test_string() { - let source = String::from(r#""double" 'single' 'can\'t' "\\\"" '\t\r\n' '\g'"#); + let source = String::from(r#""double" 'single' 'can\'t' "\\\"" '\t\r\n' '\g' r'raw\''"#); let tokens = lex_source(&source); assert_eq!( tokens, @@ -1467,6 +1474,10 @@ mod tests { value: String::from("\\g"), is_fstring: false, }, + Tok::String { + value: String::from("raw\'"), + is_fstring: false, + }, ] ); } diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 9e5b309b1e..7f010278d2 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -337,10 +337,7 @@ CompoundStatement: ast::LocatedStatement = { IfStatement: ast::LocatedStatement = { "if" ":" => { // Determine last else: - let mut last = match s3 { - Some(s) => Some(s.2), - None => None, - }; + let mut last = s3.map(|s| s.2); // handle elif: for i in s2.into_iter().rev() { @@ -360,10 +357,7 @@ IfStatement: ast::LocatedStatement = { WhileStatement: ast::LocatedStatement = { "while" ":" => { - let or_else = match s2 { - Some(s) => Some(s.2), - None => None, - }; + let or_else = s2.map(|s| s.2); ast::LocatedStatement { location: loc, node: ast::Statement::While { test: e, body: s, orelse: or_else }, @@ -373,10 +367,7 @@ WhileStatement: ast::LocatedStatement = { ForStatement: ast::LocatedStatement = { "for" "in" ":" => { - let or_else = match s2 { - Some(s) => Some(s.2), - None => None, - }; + let or_else = s2.map(|s| s.2); ast::LocatedStatement { location: loc, node: ast::Statement::For { @@ -389,14 +380,8 @@ ForStatement: ast::LocatedStatement = { TryStatement: ast::LocatedStatement = { "try" ":" => { - let or_else = match else_suite { - Some(s) => Some(s.2), - None => None, - }; - let finalbody = match finally { - Some(s) => Some(s.2), - None => None, - }; + let or_else = else_suite.map(|s| s.2); + let finalbody = finally.map(|s| s.2); ast::LocatedStatement { location: loc, node: ast::Statement::Try { @@ -437,10 +422,7 @@ WithStatement: ast::LocatedStatement = { WithItem: ast::WithItem = { => { - let optional_vars = match n { - Some(val) => Some(val.1), - None => None, - }; + let optional_vars = n.map(|val| val.1); ast::WithItem { context_expr: t, optional_vars } }, }; @@ -461,27 +443,19 @@ FuncDef: ast::LocatedStatement = { }; Parameters: ast::Parameters = { - "(" )?> ")" => { - match a { - Some(a) => a, - None => Default::default(), - } + "(" )?> ")" => { + a.unwrap_or_else(Default::default) }, }; -// parameters are (String, None), kwargs are (String, Some(Test)) where Test is -// the default // Note that this is a macro which is used once for function defs, and // once for lambda defs. -TypedArgsList: ast::Parameters = { - > )?> => { +ParameterList: ast::Parameters = { + > )?> ","? => { let (names, default_elements) = param1; // Now gather rest of parameters: - let (vararg, kwonlyargs, kw_defaults, kwarg) = match args2 { - Some((_, x)) => x, - None => (None, vec![], vec![], None), - }; + let (vararg, kwonlyargs, kw_defaults, kwarg) = args2.map_or((None, vec![], vec![], None), |x| x.1); ast::Parameters { args: names, @@ -492,7 +466,7 @@ TypedArgsList: ast::Parameters = { kw_defaults: kw_defaults, } }, - > )> => { + > )> ","? => { let (names, default_elements) = param1; // Now gather rest of parameters: @@ -510,7 +484,7 @@ TypedArgsList: ast::Parameters = { kw_defaults: kw_defaults, } }, - > => { + > ","? => { let (vararg, kwonlyargs, kw_defaults, kwarg) = params; ast::Parameters { args: vec![], @@ -521,7 +495,7 @@ TypedArgsList: ast::Parameters = { kw_defaults: kw_defaults, } }, - > => { + > ","? => { ast::Parameters { args: vec![], kwonlyargs: vec![], @@ -535,8 +509,8 @@ TypedArgsList: ast::Parameters = { // Use inline here to make sure the "," is not creating an ambiguity. #[inline] -TypedParameters: (Vec, Vec) = { - > )*> => { +ParameterDefs: (Vec, Vec) = { + > )*> => { // Combine first parameters: let mut args = vec![param1]; args.extend(param2.into_iter().map(|x| x.1)); @@ -564,7 +538,7 @@ TypedParameters: (Vec, Vec) = { } }; -TypedParameterDef: (ast::Parameter, Option) = { +ParameterDef: (ast::Parameter, Option) = { => (i, None), "=" => (i, Some(e)), }; @@ -580,8 +554,11 @@ TypedParameter: ast::Parameter = { }, }; +// Use inline here to make sure the "," is not creating an ambiguity. +// TODO: figure out another grammar that makes this inline no longer required. +#[inline] ParameterListStarArgs: (Option>, Vec, Vec>, Option>) = { - "*" )*> )?> => { + "*" )*> )?> => { // Extract keyword arguments: let mut kwonlyargs = vec![]; let mut kw_defaults = vec![]; @@ -590,10 +567,7 @@ ParameterListStarArgs: (Option>, Vec Some(name), - None => None, - }; + let kwarg = kwarg.map(|n| n.1); (Some(va), kwonlyargs, kw_defaults, kwarg) } @@ -684,7 +658,7 @@ Test: ast::Expression = { }; LambdaDef: ast::Expression = { - "lambda" ?> ":" => + "lambda" ?> ":" => ast::Expression::Lambda { args: p.unwrap_or(Default::default()), body:Box::new(b) diff --git a/tests/snippets/function.py b/tests/snippets/function.py index a69178bb78..b1ea968609 100644 --- a/tests/snippets/function.py +++ b/tests/snippets/function.py @@ -2,3 +2,8 @@ def foo(): return 42 assert foo() == 42 + +def my_func(a,): + return a+2 + +assert my_func(2) == 4 From 792c6a103e38a35cd21a36ba063d86d26a0f347c Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Wed, 27 Mar 2019 18:56:42 +0100 Subject: [PATCH 094/884] Convert re to new style args --- vm/src/stdlib/re.rs | 184 ++++++++++++++------------------------------ 1 file changed, 56 insertions(+), 128 deletions(-) diff --git a/vm/src/stdlib/re.rs b/vm/src/stdlib/re.rs index 4dab1b0c3f..fee100bfeb 100644 --- a/vm/src/stdlib/re.rs +++ b/vm/src/stdlib/re.rs @@ -6,11 +6,9 @@ */ use regex::{Match, Regex}; -use crate::function::PyFuncArgs; -use crate::obj::objstr; use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; impl PyValue for Regex { @@ -19,73 +17,39 @@ impl PyValue for Regex { } } -/// Create the python `re` module with all its members. -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - let match_type = py_class!(ctx, "Match", ctx.object(), { - "start" => ctx.new_rustfunc(match_start), - "end" => ctx.new_rustfunc(match_end) - }); - - let pattern_type = py_class!(ctx, "Pattern", ctx.object(), { - "match" => ctx.new_rustfunc(pattern_match), - "search" => ctx.new_rustfunc(pattern_search) - }); +/// Inner data for a match object. +#[derive(Debug)] +struct PyMatch { + start: usize, + end: usize, +} - py_module!(ctx, "re", { - "compile" => ctx.new_rustfunc(re_compile), - "Match" => match_type, - "match" => ctx.new_rustfunc(re_match), - "Pattern" => pattern_type, - "search" => ctx.new_rustfunc(re_search) - }) +impl PyValue for PyMatch { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("re", "Match") + } } -/// Implement re.match -/// See also: -/// https://docs.python.org/3/library/re.html#re.match -fn re_match(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (pattern, Some(vm.ctx.str_type())), - (string, Some(vm.ctx.str_type())) - ] - ); - let pattern_str = objstr::get_value(&pattern); - let regex = make_regex(vm, &pattern_str)?; - let search_text = objstr::get_value(string); - - do_match(vm, ®ex, search_text) +type PyRegexRef = PyRef; +type PyMatchRef = PyRef; + +fn re_match(pattern: PyStringRef, string: PyStringRef, vm: &VirtualMachine) -> PyResult { + let regex = make_regex(vm, &pattern.value)?; + do_match(vm, ®ex, &string.value) } -/// Implement re.search -/// See also: -/// https://docs.python.org/3/library/re.html#re.search -fn re_search(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (pattern, Some(vm.ctx.str_type())), - (string, Some(vm.ctx.str_type())) - ] - ); - - let pattern_str = objstr::get_value(&pattern); - let regex = make_regex(vm, &pattern_str)?; - let search_text = objstr::get_value(string); - - do_search(vm, ®ex, search_text) +fn re_search(pattern: PyStringRef, string: PyStringRef, vm: &VirtualMachine) -> PyResult { + let regex = make_regex(vm, &pattern.value)?; + do_search(vm, ®ex, &string.value) } -fn do_match(vm: &VirtualMachine, regex: &Regex, search_text: String) -> PyResult { +fn do_match(vm: &VirtualMachine, regex: &Regex, search_text: &str) -> PyResult { // TODO: implement match! do_search(vm, regex, search_text) } -fn do_search(vm: &VirtualMachine, regex: &Regex, search_text: String) -> PyResult { - match regex.find(&search_text) { +fn do_search(vm: &VirtualMachine, regex: &Regex, search_text: &str) -> PyResult { + match regex.find(search_text) { None => Ok(vm.get_none()), Some(result) => create_match(vm, &result), } @@ -98,19 +62,6 @@ fn make_regex(vm: &VirtualMachine, pattern: &str) -> PyResult { } } -/// Inner data for a match object. -#[derive(Debug)] -struct PyMatch { - start: usize, - end: usize, -} - -impl PyValue for PyMatch { - fn class(vm: &VirtualMachine) -> PyClassRef { - vm.class("re", "Match") - } -} - /// Take a found regular expression and convert it to proper match object. fn create_match(vm: &VirtualMachine, match_value: &Match) -> PyResult { // let mo = vm.invoke(match_class, PyFuncArgs::default())?; @@ -124,68 +75,45 @@ fn create_match(vm: &VirtualMachine, match_value: &Match) -> PyResult { .into_object()) } -/// Compile a regular expression into a Pattern object. -/// See also: -/// https://docs.python.org/3/library/re.html#re.compile -fn re_compile(pattern: PyStringRef, vm: &VirtualMachine) -> PyResult> { - let regex = make_regex(vm, &pattern.value)?; - - Ok(regex.into_ref(vm)) +fn re_compile(pattern: PyStringRef, vm: &VirtualMachine) -> PyResult { + make_regex(vm, &pattern.value) } -fn pattern_match(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, None), (text, Some(vm.ctx.str_type()))] - ); - - let regex = get_regex(zelf); - let search_text = objstr::get_value(text); - do_match(vm, ®ex, search_text) -} - -fn pattern_search(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, None), (text, Some(vm.ctx.str_type()))] - ); - - let regex = get_regex(zelf); - let search_text = objstr::get_value(text); - do_search(vm, ®ex, search_text) +impl PyRegexRef { + fn match_(self, text: PyStringRef, vm: &VirtualMachine) -> PyResult { + do_match(vm, &self, &text.value) + } + fn search(self, text: PyStringRef, vm: &VirtualMachine) -> PyResult { + do_search(vm, &self, &text.value) + } } -/// Returns start of match -/// see: https://docs.python.org/3/library/re.html#re.Match.start -fn match_start(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, None)]); - // TODO: implement groups - let m = get_match(zelf); - Ok(vm.new_int(m.start)) +impl PyMatchRef { + fn start(self, _vm: &VirtualMachine) -> usize { + self.start + } + fn end(self, _vm: &VirtualMachine) -> usize { + self.end + } } -fn match_end(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, None)]); - // TODO: implement groups - let m = get_match(zelf); - Ok(vm.new_int(m.end)) -} +/// Create the python `re` module with all its members. +pub fn make_module(ctx: &PyContext) -> PyObjectRef { + let match_type = py_class!(ctx, "Match", ctx.object(), { + "start" => ctx.new_rustfunc(PyMatchRef::start), + "end" => ctx.new_rustfunc(PyMatchRef::end) + }); -/// Retrieve inner rust regex from python object: -fn get_regex(obj: &PyObjectRef) -> &Regex { - // TODO: Regex shouldn't be stored in payload directly, create newtype wrapper - if let Some(regex) = obj.payload::() { - return regex; - } - panic!("Inner error getting regex {:?}", obj); -} + let pattern_type = py_class!(ctx, "Pattern", ctx.object(), { + "match" => ctx.new_rustfunc(PyRegexRef::match_), + "search" => ctx.new_rustfunc(PyRegexRef::search) + }); -/// Retrieve inner rust match from python object: -fn get_match(obj: &PyObjectRef) -> &PyMatch { - if let Some(value) = obj.payload::() { - return value; - } - panic!("Inner error getting match {:?}", obj); + py_module!(ctx, "re", { + "compile" => ctx.new_rustfunc(re_compile), + "Match" => match_type, + "match" => ctx.new_rustfunc(re_match), + "Pattern" => pattern_type, + "search" => ctx.new_rustfunc(re_search) + }) } From d5e2b3a8b6fd5624be89cb6d126f33e794f23020 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Thu, 28 Mar 2019 22:42:21 +0100 Subject: [PATCH 095/884] Convert iterators next() to new args style Except objiter, as it'll need to be reworked anyway --- vm/src/obj/objenumerate.rs | 24 +++++++----------------- vm/src/obj/objfilter.rs | 19 ++++++------------- vm/src/obj/objmap.rs | 23 ++++++++--------------- vm/src/obj/objzip.rs | 19 ++++++++----------- 4 files changed, 29 insertions(+), 56 deletions(-) diff --git a/vm/src/obj/objenumerate.rs b/vm/src/obj/objenumerate.rs index 68d1e49f6b..85781bb7a0 100644 --- a/vm/src/obj/objenumerate.rs +++ b/vm/src/obj/objenumerate.rs @@ -4,8 +4,8 @@ use std::ops::AddAssign; use num_bigint::BigInt; use num_traits::Zero; -use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::function::OptionalArg; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint::PyIntRef; @@ -44,18 +44,10 @@ fn enumerate_new( .into_ref_with_type(vm, cls) } -fn enumerate_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(enumerate, Some(vm.ctx.enumerate_type()))] - ); - - if let Some(PyEnumerate { - ref counter, - ref iterator, - }) = enumerate.payload() - { +impl PyEnumerateRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + let iterator = &self.iterator; + let counter = &self.counter; let next_obj = objiter::call_next(vm, iterator)?; let result = vm .ctx @@ -64,8 +56,6 @@ fn enumerate_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { AddAssign::add_assign(&mut counter.borrow_mut() as &mut BigInt, 1); Ok(result) - } else { - panic!("enumerate doesn't have correct payload"); } } @@ -74,6 +64,6 @@ pub fn init(context: &PyContext) { objiter::iter_type_init(context, enumerate_type); extend_class!(context, enumerate_type, { "__new__" => context.new_rustfunc(enumerate_new), - "__next__" => context.new_rustfunc(enumerate_next) + "__next__" => context.new_rustfunc(PyEnumerateRef::next) }); } diff --git a/vm/src/obj/objfilter.rs b/vm/src/obj/objfilter.rs index e547d2c43e..07c512f6dd 100644 --- a/vm/src/obj/objfilter.rs +++ b/vm/src/obj/objfilter.rs @@ -1,5 +1,4 @@ -use crate::function::PyFuncArgs; -use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; // Required for arg_check! to use isinstance use super::objbool; @@ -35,14 +34,10 @@ fn filter_new( .into_ref_with_type(vm, cls) } -fn filter_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(filter, Some(vm.ctx.filter_type()))]); - - if let Some(PyFilter { - ref predicate, - ref iterator, - }) = filter.payload() - { +impl PyFilterRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + let predicate = &self.predicate; + let iterator = &self.iterator; loop { let next_obj = objiter::call_next(vm, iterator)?; let predicate_value = if predicate.is(&vm.get_none()) { @@ -56,8 +51,6 @@ fn filter_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { return Ok(next_obj); } } - } else { - panic!("filter doesn't have correct payload"); } } @@ -74,6 +67,6 @@ pub fn init(context: &PyContext) { extend_class!(context, filter_type, { "__new__" => context.new_rustfunc(filter_new), "__doc__" => context.new_str(filter_doc.to_string()), - "__next__" => context.new_rustfunc(filter_next) + "__next__" => context.new_rustfunc(PyFilterRef::next) }); } diff --git a/vm/src/obj/objmap.rs b/vm/src/obj/objmap.rs index 49cc539e58..fbf0fa035a 100644 --- a/vm/src/obj/objmap.rs +++ b/vm/src/obj/objmap.rs @@ -1,5 +1,5 @@ -use crate::function::{Args, PyFuncArgs}; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::function::Args; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objiter; @@ -35,23 +35,16 @@ fn map_new( .into_ref_with_type(vm, cls.clone()) } -fn map_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(map, Some(vm.ctx.map_type()))]); - - if let Some(PyMap { - ref mapper, - ref iterators, - }) = map.payload() - { - let next_objs = iterators +impl PyMapRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + let next_objs = self + .iterators .iter() .map(|iterator| objiter::call_next(vm, iterator)) .collect::, _>>()?; // the mapper itself can raise StopIteration which does stop the map iteration - vm.invoke(mapper.clone(), next_objs) - } else { - panic!("map doesn't have correct payload"); + vm.invoke(self.mapper.clone(), next_objs) } } @@ -65,7 +58,7 @@ pub fn init(context: &PyContext) { objiter::iter_type_init(context, map_type); extend_class!(context, map_type, { "__new__" => context.new_rustfunc(map_new), - "__next__" => context.new_rustfunc(map_next), + "__next__" => context.new_rustfunc(PyMapRef::next), "__doc__" => context.new_str(map_doc.to_string()) }); } diff --git a/vm/src/obj/objzip.rs b/vm/src/obj/objzip.rs index e628cb218a..a12152eeec 100644 --- a/vm/src/obj/objzip.rs +++ b/vm/src/obj/objzip.rs @@ -1,5 +1,5 @@ -use crate::function::{Args, PyFuncArgs}; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::function::Args; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objiter; @@ -26,22 +26,19 @@ fn zip_new(cls: PyClassRef, iterables: Args, vm: &VirtualMachine) -> PyResult PyResult { - arg_check!(vm, args, required = [(zip, Some(vm.ctx.zip_type()))]); - - if let Some(PyZip { ref iterators }) = zip.payload() { - if iterators.is_empty() { +impl PyZipRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + if self.iterators.is_empty() { Err(objiter::new_stop_iteration(vm)) } else { - let next_objs = iterators + let next_objs = self + .iterators .iter() .map(|iterator| objiter::call_next(vm, iterator)) .collect::, _>>()?; Ok(vm.ctx.new_tuple(next_objs)) } - } else { - panic!("zip doesn't have correct payload"); } } @@ -50,6 +47,6 @@ pub fn init(context: &PyContext) { objiter::iter_type_init(context, zip_type); extend_class!(context, zip_type, { "__new__" => context.new_rustfunc(zip_new), - "__next__" => context.new_rustfunc(zip_next) + "__next__" => context.new_rustfunc(PyZipRef::next) }); } From 8f1ec3dd23a359e29f4bd6934b8a0b4d19aaaa6a Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Thu, 28 Mar 2019 23:06:38 +0100 Subject: [PATCH 096/884] Convert slice::{start, stop, stop} to new args style --- vm/src/obj/objslice.rs | 39 +++++++++++++-------------------------- 1 file changed, 13 insertions(+), 26 deletions(-) diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index 3d955b3f2c..c82a3ca6ef 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -66,38 +66,25 @@ fn slice_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { .map(|x| x.into_object()) } -fn get_property_value(vm: &VirtualMachine, value: &Option) -> PyResult { +fn get_property_value(vm: &VirtualMachine, value: &Option) -> PyObjectRef { if let Some(value) = value { - Ok(vm.ctx.new_int(value.clone())) + vm.ctx.new_int(value.clone()) } else { - Ok(vm.get_none()) + vm.get_none() } } -fn slice_start(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); - if let Some(PySlice { start, .. }) = &slice.payload() { - get_property_value(vm, start) - } else { - panic!("Slice has incorrect payload."); +impl PySliceRef { + fn start(self, vm: &VirtualMachine) -> PyObjectRef { + get_property_value(vm, &self.start) } -} -fn slice_stop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); - if let Some(PySlice { stop, .. }) = &slice.payload() { - get_property_value(vm, stop) - } else { - panic!("Slice has incorrect payload."); + fn stop(self, vm: &VirtualMachine) -> PyObjectRef { + get_property_value(vm, &self.stop) } -} -fn slice_step(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(slice, Some(vm.ctx.slice_type()))]); - if let Some(PySlice { step, .. }) = &slice.payload() { - get_property_value(vm, step) - } else { - panic!("Slice has incorrect payload."); + fn step(self, vm: &VirtualMachine) -> PyObjectRef { + get_property_value(vm, &self.step) } } @@ -106,8 +93,8 @@ pub fn init(context: &PyContext) { extend_class!(context, slice_type, { "__new__" => context.new_rustfunc(slice_new), - "start" => context.new_property(slice_start), - "stop" => context.new_property(slice_stop), - "step" => context.new_property(slice_step) + "start" => context.new_property(PySliceRef::start), + "stop" => context.new_property(PySliceRef::stop), + "step" => context.new_property(PySliceRef::step) }); } From ff85838556989d9dccc79f3d8baa1ae44202a65e Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 29 Mar 2019 00:16:34 -0500 Subject: [PATCH 097/884] Add #[py_class] attribute proc macro --- derive/Cargo.toml | 2 +- derive/src/lib.rs | 198 ++++++++++++++- vm/src/builtins.rs | 1 + vm/src/lib.rs | 2 +- vm/src/obj/objstr.rs | 572 +++++++++++++++++++++++-------------------- vm/src/pyobject.rs | 20 ++ 6 files changed, 517 insertions(+), 278 deletions(-) diff --git a/derive/Cargo.toml b/derive/Cargo.toml index c15d47ad58..91fdc150e9 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -8,6 +8,6 @@ edition = "2018" proc-macro = true [dependencies] -syn = "0.15.29" +syn = { version = "0.15.29", features = ["full"] } quote = "0.6.11" proc-macro2 = "0.4.27" diff --git a/derive/src/lib.rs b/derive/src/lib.rs index ad743e72fe..64853170fc 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,21 +1,57 @@ extern crate proc_macro; use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; -use syn::{Data, DeriveInput, Fields}; +use syn::{ + parse_macro_input, AttributeArgs, Data, DeriveInput, Fields, Ident, ImplItem, Item, Lit, Meta, + NestedMeta, +}; -#[proc_macro_derive(FromArgs)] +fn rustpython_path(inside_vm: bool) -> syn::Path { + let path = if inside_vm { + quote!(crate) + } else { + quote!(::rustpython_vm) + }; + syn::parse2(path).unwrap() +} + +/// Does the item have the #[__inside_vm] attribute on it, signifying that the derive target is +/// being derived from inside the `rustpython_vm` crate. +fn rustpython_path_derive(input: &DeriveInput) -> syn::Path { + rustpython_path( + input + .attrs + .iter() + .any(|attr| attr.path.is_ident("__inside_vm")), + ) +} + +fn rustpython_path_attr(attr: &AttributeArgs) -> syn::Path { + rustpython_path(attr.iter().any(|meta| { + if let syn::NestedMeta::Meta(meta) = meta { + if let syn::Meta::Word(ident) = meta { + ident == "__inside_vm" + } else { + false + } + } else { + false + } + })) +} + +#[proc_macro_derive(FromArgs, attributes(__inside_vm))] pub fn derive_from_args(input: TokenStream) -> TokenStream { let ast: DeriveInput = syn::parse(input).unwrap(); - let gen = impl_from_args(&ast); + let gen = impl_from_args(ast); gen.to_string().parse().unwrap() } -fn impl_from_args(input: &DeriveInput) -> TokenStream2 { - // FIXME: This references types using `crate` instead of `rustpython_vm` - // so that it can be used in the latter. How can we support both? +fn impl_from_args(input: DeriveInput) -> TokenStream2 { + let rp_path = rustpython_path_derive(&input); let fields = match input.data { Data::Struct(ref data) => { match data.fields { @@ -36,7 +72,7 @@ fn impl_from_args(input: &DeriveInput) -> TokenStream2 { let name = &input.ident; quote! { - impl crate::function::FromArgs for #name { + impl #rp_path::function::FromArgs for #name { fn from_args( vm: &crate::vm::VirtualMachine, args: &mut crate::function::PyFuncArgs @@ -46,3 +82,149 @@ fn impl_from_args(input: &DeriveInput) -> TokenStream2 { } } } + +#[proc_macro_attribute] +pub fn py_class(attr: TokenStream, item: TokenStream) -> TokenStream { + let attr = parse_macro_input!(attr as AttributeArgs); + let item = parse_macro_input!(item as Item); + impl_py_class(attr, item).into() +} + +enum MethodKind { + Method, + Property, +} + +impl MethodKind { + fn to_ctx_constructor_fn(&self) -> Ident { + let f = match self { + MethodKind::Method => "new_rustfunc", + MethodKind::Property => "new_property", + }; + Ident::new(f, Span::call_site()) + } +} + +struct Method { + fn_name: Ident, + py_name: String, + kind: MethodKind, +} + +fn item_impl_to_methods<'a>(imp: &'a syn::ItemImpl) -> impl Iterator + 'a { + imp.items.iter().filter_map(|item| { + if let ImplItem::Method(meth) = item { + let mut py_name = None; + let mut kind = MethodKind::Method; + let metas_iter = meth + .attrs + .iter() + .filter_map(|attr| { + if attr.path.is_ident("py_class") { + let meta = attr.parse_meta().expect("Invalid attribute"); + if let Meta::List(list) = meta { + Some(list) + } else { + panic!( + "#[py_class] attribute on a method should be a list, like \ + #[py_class(...)]" + ) + } + } else { + None + } + }) + .flat_map(|attr| attr.nested); + for meta in metas_iter { + if let NestedMeta::Meta(meta) = meta { + match meta { + Meta::NameValue(name_value) => { + if name_value.ident == "name" { + if let Lit::Str(s) = &name_value.lit { + py_name = Some(s.value()); + } else { + panic!("#[py_class(name = ...)] must be a string"); + } + } + } + Meta::Word(ident) => match ident.to_string().as_str() { + "property" => kind = MethodKind::Property, + _ => {} + }, + _ => {} + } + } + } + let py_name = py_name.unwrap_or_else(|| meth.sig.ident.to_string()); + Some(Method { + fn_name: meth.sig.ident.clone(), + py_name, + kind, + }) + } else { + None + } + }) +} + +fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { + let imp = if let Item::Impl(imp) = item { + imp + } else { + return quote!(#item); + }; + let rp_path = rustpython_path_attr(&attr); + let mut class_name = None; + let mut doc = None; + for attr in attr { + if let NestedMeta::Meta(meta) = attr { + if let Meta::NameValue(name_value) = meta { + if name_value.ident == "name" { + if let Lit::Str(s) = name_value.lit { + class_name = Some(s.value()); + } else { + panic!("#[py_class(name = ...)] must be a string"); + } + } else if name_value.ident == "doc" { + if let Lit::Str(s) = name_value.lit { + doc = Some(s.value()); + } else { + panic!("#[py_class(name = ...)] must be a string"); + } + } + } + } + } + let class_name = class_name.expect("#[py_class] must have a name"); + let doc = match doc { + Some(doc) => quote!(Some(#doc)), + None => quote!(None), + }; + let ty = &imp.self_ty; + let methods = item_impl_to_methods(&imp).map( + |Method { + py_name, + fn_name, + kind, + }| { + let constructor_fn = kind.to_ctx_constructor_fn(); + quote! { + ctx.set_attr(class, #py_name, ctx.#constructor_fn(#ty::#fn_name)); + } + }, + ); + + quote! { + #imp + impl #rp_path::pyobject::IntoPyClass for #ty { + const NAME: &'static str = #class_name; + const DOC: Option<&'static str> = #doc; + fn _extend_class( + ctx: &#rp_path::pyobject::PyContext, + class: &#rp_path::obj::objtype::PyClassRef, + ) { + #(#methods)* + } + } + } +} diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 9d5980759c..6b5dda16b0 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -536,6 +536,7 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } #[derive(Debug, FromArgs)] +#[__inside_vm] pub struct PrintOptions { sep: Option, end: Option, diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 3892fe2a45..29124ad118 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -30,7 +30,7 @@ extern crate statrs; extern crate rustpython_parser; #[macro_use] -extern crate rustpython_derive; +pub extern crate rustpython_derive; //extern crate eval; use eval::eval::*; // use py_code_object::{Function, NativeType, PyCodeObject}; diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index aa62812a24..82ac806e63 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -10,8 +10,8 @@ use unicode_segmentation::UnicodeSegmentation; use crate::format::{FormatParseError, FormatPart, FormatString}; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - IdProtocol, IntoPyObject, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, - TryFromObject, TryIntoRef, TypeProtocol, + IdProtocol, IntoPyClass, IntoPyObject, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, + PyValue, TryFromObject, TryIntoRef, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -48,80 +48,138 @@ impl TryIntoRef for &str { } } -impl PyStringRef { - fn add(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { +fn get_fill_char<'a>(rep: &'a OptionalArg, vm: &VirtualMachine) -> PyResult<&'a str> { + let rep_str = match rep { + OptionalArg::Present(ref st) => &st.value, + OptionalArg::Missing => " ", + }; + if rep_str.len() == 1 { + Ok(rep_str) + } else { + Err(vm.new_type_error("The fill character must be exactly one character long".to_string())) + } +} + +#[py_class( + __inside_vm, + name = "str", + doc = "str(object='') -> str\n\ + str(bytes_or_buffer[, encoding[, errors]]) -> str\n\ + \n\ + Create a new string object from the given object. If encoding or\n\ + errors is specified, then the object must expose a data buffer\n\ + that will be decoded using the given encoding and error handler.\n\ + Otherwise, returns the result of object.__str__() (if defined)\n\ + or repr(object).\n\ + encoding defaults to sys.getdefaultencoding().\n\ + errors defaults to 'strict'." +)] +impl PyString { + // TODO: should with following format + // class str(object='') + // class str(object=b'', encoding='utf-8', errors='strict') + #[py_class(name = "__new__")] + fn new( + cls: PyClassRef, + object: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let string = match object { + OptionalArg::Present(ref input) => vm.to_str(input)?.into_object(), + OptionalArg::Missing => vm.new_str("".to_string()), + }; + if string.class().is(&cls) { + TryFromObject::try_from_object(vm, string) + } else { + let payload = string.payload::().unwrap(); + payload.clone().into_ref_with_type(vm, cls) + } + } + #[py_class(name = "__add__")] + fn add(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - Ok(format!("{}{}", self.value, get_value(&rhs))) + Ok(format!("{}{}", zelf.value, get_value(&rhs))) } else { - Err(vm.new_type_error(format!("Cannot add {} and {}", self, rhs))) + Err(vm.new_type_error(format!("Cannot add {} and {}", zelf, rhs))) } } - fn bool(self, _vm: &VirtualMachine) -> bool { - !self.value.is_empty() + #[py_class(name = "__bool__")] + fn bool(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + !zelf.value.is_empty() } - fn eq(self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { + #[py_class(name = "__eq__")] + fn eq(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - self.value == get_value(&rhs) + zelf.value == get_value(&rhs) } else { false } } - fn contains(self, needle: PyStringRef, _vm: &VirtualMachine) -> bool { - self.value.contains(&needle.value) + #[py_class(name = "__contains__")] + fn contains(zelf: PyStringRef, needle: PyStringRef, _vm: &VirtualMachine) -> bool { + zelf.value.contains(&needle.value) } - fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - subscript(vm, &self.value, needle) + #[py_class(name = "__getitem__")] + fn getitem(zelf: PyStringRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + subscript(vm, &zelf.value, needle) } - fn gt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[py_class(name = "__gt__")] + fn gt(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - Ok(self.value > get_value(&rhs)) + Ok(zelf.value > get_value(&rhs)) } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) + Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs))) } } - fn ge(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[py_class(name = "__ge__")] + fn ge(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - Ok(self.value >= get_value(&rhs)) + Ok(zelf.value >= get_value(&rhs)) } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) + Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs))) } } - fn lt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[py_class(name = "__lt__")] + fn lt(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - Ok(self.value < get_value(&rhs)) + Ok(zelf.value < get_value(&rhs)) } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) + Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs))) } } - fn le(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[py_class(name = "__le__")] + fn le(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - Ok(self.value <= get_value(&rhs)) + Ok(zelf.value <= get_value(&rhs)) } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) + Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs))) } } - fn hash(self, _vm: &VirtualMachine) -> usize { + #[py_class(name = "__hash__")] + fn hash(zelf: PyStringRef, _vm: &VirtualMachine) -> usize { let mut hasher = std::collections::hash_map::DefaultHasher::new(); - self.value.hash(&mut hasher); + zelf.value.hash(&mut hasher); hasher.finish() as usize } - fn len(self, _vm: &VirtualMachine) -> usize { - self.value.chars().count() + #[py_class(name = "__len__")] + fn len(zelf: PyStringRef, _vm: &VirtualMachine) -> usize { + zelf.value.chars().count() } - fn mul(self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[py_class(name = "__mul__")] + fn mul(zelf: PyStringRef, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&val, &vm.ctx.int_type()) { - let value = &self.value; + let value = &zelf.value; let multiplier = objint::get_value(&val).to_i32().unwrap(); let mut result = String::new(); for _x in 0..multiplier { @@ -129,16 +187,18 @@ impl PyStringRef { } Ok(result) } else { - Err(vm.new_type_error(format!("Cannot multiply {} and {}", self, val))) + Err(vm.new_type_error(format!("Cannot multiply {} and {}", zelf, val))) } } - fn str(self, _vm: &VirtualMachine) -> PyStringRef { - self + #[py_class(name = "__str__")] + fn str(zelf: PyStringRef, _vm: &VirtualMachine) -> PyStringRef { + zelf } - fn repr(self, _vm: &VirtualMachine) -> String { - let value = &self.value; + #[py_class(name = "__repr__")] + fn repr(zelf: PyStringRef, _vm: &VirtualMachine) -> String { + let value = &zelf.value; let quote_char = if count_char(value, '\'') > count_char(value, '"') { '"' } else { @@ -167,31 +227,31 @@ impl PyStringRef { formatted } - fn lower(self, _vm: &VirtualMachine) -> String { - self.value.to_lowercase() + fn lower(zelf: PyStringRef, _vm: &VirtualMachine) -> String { + zelf.value.to_lowercase() } // casefold is much more aggressive than lower - fn casefold(self, _vm: &VirtualMachine) -> String { - caseless::default_case_fold_str(&self.value) + fn casefold(zelf: PyStringRef, _vm: &VirtualMachine) -> String { + caseless::default_case_fold_str(&zelf.value) } - fn upper(self, _vm: &VirtualMachine) -> String { - self.value.to_uppercase() + fn upper(zelf: PyStringRef, _vm: &VirtualMachine) -> String { + zelf.value.to_uppercase() } - fn capitalize(self, _vm: &VirtualMachine) -> String { - let (first_part, lower_str) = self.value.split_at(1); + fn capitalize(zelf: PyStringRef, _vm: &VirtualMachine) -> String { + let (first_part, lower_str) = zelf.value.split_at(1); format!("{}{}", first_part.to_uppercase(), lower_str) } fn split( - self, - pattern: OptionalArg, + zelf: PyStringRef, + pattern: OptionalArg, num: OptionalArg, vm: &VirtualMachine, ) -> PyObjectRef { - let value = &self.value; + let value = &zelf.value; let pattern = match pattern { OptionalArg::Present(ref s) => &s.value, OptionalArg::Missing => " ", @@ -207,12 +267,12 @@ impl PyStringRef { } fn rsplit( - self, - pattern: OptionalArg, + zelf: PyStringRef, + pattern: OptionalArg, num: OptionalArg, vm: &VirtualMachine, ) -> PyObjectRef { - let value = &self.value; + let value = &zelf.value; let pattern = match pattern { OptionalArg::Present(ref s) => &s.value, OptionalArg::Missing => " ", @@ -227,85 +287,113 @@ impl PyStringRef { vm.ctx.new_list(elements) } - fn strip(self, _vm: &VirtualMachine) -> String { - self.value.trim().to_string() + fn strip(zelf: PyStringRef, _vm: &VirtualMachine) -> String { + zelf.value.trim().to_string() } - fn lstrip(self, _vm: &VirtualMachine) -> String { - self.value.trim_start().to_string() + fn lstrip(zelf: PyStringRef, _vm: &VirtualMachine) -> String { + zelf.value.trim_start().to_string() } - fn rstrip(self, _vm: &VirtualMachine) -> String { - self.value.trim_end().to_string() + fn rstrip(zelf: PyStringRef, _vm: &VirtualMachine) -> String { + zelf.value.trim_end().to_string() } fn endswith( - self, + zelf: PyStringRef, suffix: PyStringRef, start: OptionalArg, end: OptionalArg, _vm: &VirtualMachine, ) -> bool { - if let Some((start, end)) = adjust_indices(start, end, self.value.len()) { - self.value[start..end].ends_with(&suffix.value) + if let Some((start, end)) = adjust_indices(start, end, zelf.value.len()) { + zelf.value[start..end].ends_with(&suffix.value) } else { false } } fn startswith( - self, + zelf: PyStringRef, prefix: PyStringRef, start: OptionalArg, end: OptionalArg, _vm: &VirtualMachine, ) -> bool { - if let Some((start, end)) = adjust_indices(start, end, self.value.len()) { - self.value[start..end].starts_with(&prefix.value) + if let Some((start, end)) = adjust_indices(start, end, zelf.value.len()) { + zelf.value[start..end].starts_with(&prefix.value) } else { false } } - fn isalnum(self, _vm: &VirtualMachine) -> bool { - !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) + fn isalnum(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + !zelf.value.is_empty() && zelf.value.chars().all(char::is_alphanumeric) } - fn isnumeric(self, _vm: &VirtualMachine) -> bool { - !self.value.is_empty() && self.value.chars().all(char::is_numeric) + fn isnumeric(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + !zelf.value.is_empty() && zelf.value.chars().all(char::is_numeric) } - fn isdigit(self, _vm: &VirtualMachine) -> bool { + fn isdigit(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { // python's isdigit also checks if exponents are digits, these are the unicodes for exponents let valid_unicodes: [u16; 10] = [ 0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079, ]; - if self.value.is_empty() { + if zelf.value.is_empty() { false } else { - self.value + zelf.value .chars() .filter(|c| !c.is_digit(10)) .all(|c| valid_unicodes.contains(&(c as u16))) } } - fn isdecimal(self, _vm: &VirtualMachine) -> bool { - if self.value.is_empty() { + fn isdecimal(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + if zelf.value.is_empty() { false } else { - self.value.chars().all(|c| c.is_ascii_digit()) + zelf.value.chars().all(|c| c.is_ascii_digit()) } } - fn title(self, _vm: &VirtualMachine) -> String { - make_title(&self.value) + fn format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + if args.args.is_empty() { + return Err(vm.new_type_error( + "descriptor 'format' of 'str' object needs an argument".to_string(), + )); + } + + let zelf = &args.args[0]; + if !objtype::isinstance(&zelf, &vm.ctx.str_type()) { + let zelf_typ = zelf.class(); + let actual_type = vm.to_pystr(&zelf_typ)?; + return Err(vm.new_type_error(format!( + "descriptor 'format' requires a 'str' object but received a '{}'", + actual_type + ))); + } + let format_string_text = get_value(zelf); + match FormatString::from_str(format_string_text.as_str()) { + Ok(format_string) => perform_format(vm, &format_string, &args), + Err(err) => match err { + FormatParseError::UnmatchedBracket => { + Err(vm.new_value_error("expected '}' before end of string".to_string())) + } + _ => Err(vm.new_value_error("Unexpected error parsing format string".to_string())), + }, + } } - fn swapcase(self, _vm: &VirtualMachine) -> String { - let mut swapped_str = String::with_capacity(self.value.len()); - for c in self.value.chars() { + fn title(zelf: PyStringRef, _vm: &VirtualMachine) -> String { + make_title(&zelf.value) + } + + fn swapcase(zelf: PyStringRef, _vm: &VirtualMachine) -> String { + let mut swapped_str = String::with_capacity(zelf.value.len()); + for c in zelf.value.chars() { // to_uppercase returns an iterator, to_ascii_uppercase returns the char if c.is_lowercase() { swapped_str.push(c.to_ascii_uppercase()); @@ -318,54 +406,54 @@ impl PyStringRef { swapped_str } - fn isalpha(self, _vm: &VirtualMachine) -> bool { - !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) + fn isalpha(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + !zelf.value.is_empty() && zelf.value.chars().all(char::is_alphanumeric) } fn replace( - self, - old: Self, - new: Self, + zelf: PyStringRef, + old: PyStringRef, + new: PyStringRef, num: OptionalArg, _vm: &VirtualMachine, ) -> String { match num.into_option() { - Some(num) => self.value.replacen(&old.value, &new.value, num), - None => self.value.replace(&old.value, &new.value), + Some(num) => zelf.value.replacen(&old.value, &new.value, num), + None => zelf.value.replace(&old.value, &new.value), } } // cpython's isspace ignores whitespace, including \t and \n, etc, unless the whole string is empty // which is why isspace is using is_ascii_whitespace. Same for isupper & islower - fn isspace(self, _vm: &VirtualMachine) -> bool { - !self.value.is_empty() && self.value.chars().all(|c| c.is_ascii_whitespace()) + fn isspace(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + !zelf.value.is_empty() && zelf.value.chars().all(|c| c.is_ascii_whitespace()) } - fn isupper(self, _vm: &VirtualMachine) -> bool { - !self.value.is_empty() - && self + fn isupper(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + !zelf.value.is_empty() + && zelf .value .chars() .filter(|x| !x.is_ascii_whitespace()) .all(char::is_uppercase) } - fn islower(self, _vm: &VirtualMachine) -> bool { - !self.value.is_empty() - && self + fn islower(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + !zelf.value.is_empty() + && zelf .value .chars() .filter(|x| !x.is_ascii_whitespace()) .all(char::is_lowercase) } - fn isascii(self, _vm: &VirtualMachine) -> bool { - !self.value.is_empty() && self.value.chars().all(|c| c.is_ascii()) + fn isascii(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + !zelf.value.is_empty() && zelf.value.chars().all(|c| c.is_ascii()) } // doesn't implement keep new line delimiter just yet - fn splitlines(self, vm: &VirtualMachine) -> PyObjectRef { - let elements = self + fn splitlines(zelf: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { + let elements = zelf .value .split('\n') .map(|e| vm.ctx.new_str(e.to_string())) @@ -373,13 +461,17 @@ impl PyStringRef { vm.ctx.new_list(elements) } - fn join(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + fn join( + zelf: PyStringRef, + iterable: PyIterable, + vm: &VirtualMachine, + ) -> PyResult { let mut joined = String::new(); for (idx, elem) in iterable.iter(vm)?.enumerate() { let elem = elem?; if idx != 0 { - joined.push_str(&self.value); + joined.push_str(&zelf.value); } joined.push_str(&elem.value) } @@ -388,13 +480,13 @@ impl PyStringRef { } fn find( - self, - sub: Self, + zelf: PyStringRef, + sub: PyStringRef, start: OptionalArg, end: OptionalArg, _vm: &VirtualMachine, ) -> isize { - let value = &self.value; + let value = &zelf.value; if let Some((start, end)) = adjust_indices(start, end, value.len()) { match value[start..end].find(&sub.value) { Some(num) => (start + num) as isize, @@ -406,13 +498,13 @@ impl PyStringRef { } fn rfind( - self, - sub: Self, + zelf: PyStringRef, + sub: PyStringRef, start: OptionalArg, end: OptionalArg, _vm: &VirtualMachine, ) -> isize { - let value = &self.value; + let value = &zelf.value; if let Some((start, end)) = adjust_indices(start, end, value.len()) { match value[start..end].rfind(&sub.value) { Some(num) => (start + num) as isize, @@ -424,13 +516,13 @@ impl PyStringRef { } fn index( - self, - sub: Self, + zelf: PyStringRef, + sub: PyStringRef, start: OptionalArg, end: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - let value = &self.value; + let value = &zelf.value; if let Some((start, end)) = adjust_indices(start, end, value.len()) { match value[start..end].find(&sub.value) { Some(num) => Ok(start + num), @@ -442,13 +534,13 @@ impl PyStringRef { } fn rindex( - self, - sub: Self, + zelf: PyStringRef, + sub: PyStringRef, start: OptionalArg, end: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - let value = &self.value; + let value = &zelf.value; if let Some((start, end)) = adjust_indices(start, end, value.len()) { match value[start..end].rfind(&sub.value) { Some(num) => Ok(start + num), @@ -459,8 +551,8 @@ impl PyStringRef { } } - fn partition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { - let value = &self.value; + fn partition(zelf: PyStringRef, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { + let value = &zelf.value; let sub = &sub.value; let mut new_tup = Vec::new(); if value.contains(sub) { @@ -477,8 +569,8 @@ impl PyStringRef { vm.ctx.new_tuple(new_tup) } - fn rpartition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { - let value = &self.value; + fn rpartition(zelf: PyStringRef, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { + let value = &zelf.value; let sub = &sub.value; let mut new_tup = Vec::new(); if value.contains(sub) { @@ -496,31 +588,31 @@ impl PyStringRef { vm.ctx.new_tuple(new_tup) } - fn istitle(self, _vm: &VirtualMachine) -> bool { - if self.value.is_empty() { + fn istitle(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + if zelf.value.is_empty() { false } else { - self.value.split(' ').all(|word| word == make_title(word)) + zelf.value.split(' ').all(|word| word == make_title(word)) } } fn count( - self, - sub: Self, + zelf: PyStringRef, + sub: PyStringRef, start: OptionalArg, end: OptionalArg, _vm: &VirtualMachine, ) -> usize { - let value = &self.value; + let value = &zelf.value; if let Some((start, end)) = adjust_indices(start, end, value.len()) { - self.value[start..end].matches(&sub.value).count() + zelf.value[start..end].matches(&sub.value).count() } else { 0 } } - fn zfill(self, len: usize, _vm: &VirtualMachine) -> String { - let value = &self.value; + fn zfill(zelf: PyStringRef, len: usize, _vm: &VirtualMachine) -> String { + let value = &zelf.value; if len <= value.len() { value.to_string() } else { @@ -528,35 +620,36 @@ impl PyStringRef { } } - fn get_fill_char<'a>(rep: &'a OptionalArg, vm: &VirtualMachine) -> PyResult<&'a str> { - let rep_str = match rep { - OptionalArg::Present(ref st) => &st.value, - OptionalArg::Missing => " ", - }; - if rep_str.len() == 1 { - Ok(rep_str) - } else { - Err(vm.new_type_error( - "The fill character must be exactly one character long".to_string(), - )) - } - } - - fn ljust(self, len: usize, rep: OptionalArg, vm: &VirtualMachine) -> PyResult { - let value = &self.value; - let rep_char = PyStringRef::get_fill_char(&rep, vm)?; + fn ljust( + zelf: PyStringRef, + len: usize, + rep: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let value = &zelf.value; + let rep_char = get_fill_char(&rep, vm)?; Ok(format!("{}{}", value, rep_char.repeat(len))) } - fn rjust(self, len: usize, rep: OptionalArg, vm: &VirtualMachine) -> PyResult { - let value = &self.value; - let rep_char = PyStringRef::get_fill_char(&rep, vm)?; + fn rjust( + zelf: PyStringRef, + len: usize, + rep: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let value = &zelf.value; + let rep_char = get_fill_char(&rep, vm)?; Ok(format!("{}{}", rep_char.repeat(len), value)) } - fn center(self, len: usize, rep: OptionalArg, vm: &VirtualMachine) -> PyResult { - let value = &self.value; - let rep_char = PyStringRef::get_fill_char(&rep, vm)?; + fn center( + zelf: PyStringRef, + len: usize, + rep: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let value = &zelf.value; + let rep_char = get_fill_char(&rep, vm)?; let left_buff: usize = (len - value.len()) / 2; let right_buff = len - value.len() - left_buff; Ok(format!( @@ -567,12 +660,12 @@ impl PyStringRef { )) } - fn expandtabs(self, tab_stop: OptionalArg, _vm: &VirtualMachine) -> String { + fn expandtabs(zelf: PyStringRef, tab_stop: OptionalArg, _vm: &VirtualMachine) -> String { let tab_stop = tab_stop.into_option().unwrap_or(8 as usize); let mut expanded_str = String::new(); let mut tab_size = tab_stop; let mut col_count = 0 as usize; - for ch in self.value.chars() { + for ch in zelf.value.chars() { // 0x0009 is tab if ch == 0x0009 as char { let num_spaces = tab_size - col_count; @@ -590,8 +683,8 @@ impl PyStringRef { expanded_str } - fn isidentifier(self, _vm: &VirtualMachine) -> bool { - let value = &self.value; + fn isidentifier(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + let value = &zelf.value; // a string is not an identifier if it has whitespace or starts with a number if !value.chars().any(|c| c.is_ascii_whitespace()) && !value.chars().nth(0).unwrap().is_digit(10) @@ -623,75 +716,66 @@ impl IntoPyObject for String { #[rustfmt::skip] // to avoid line splitting pub fn init(context: &PyContext) { let str_type = &context.str_type; - let str_doc = "str(object='') -> str\n\ - str(bytes_or_buffer[, encoding[, errors]]) -> str\n\ - \n\ - Create a new string object from the given object. If encoding or\n\ - errors is specified, then the object must expose a data buffer\n\ - that will be decoded using the given encoding and error handler.\n\ - Otherwise, returns the result of object.__str__() (if defined)\n\ - or repr(object).\n\ - encoding defaults to sys.getdefaultencoding().\n\ - errors defaults to 'strict'."; - - extend_class!(context, str_type, { - "__add__" => context.new_rustfunc(PyStringRef::add), - "__bool__" => context.new_rustfunc(PyStringRef::bool), - "__contains__" => context.new_rustfunc(PyStringRef::contains), - "__doc__" => context.new_str(str_doc.to_string()), - "__eq__" => context.new_rustfunc(PyStringRef::eq), - "__ge__" => context.new_rustfunc(PyStringRef::ge), - "__getitem__" => context.new_rustfunc(PyStringRef::getitem), - "__gt__" => context.new_rustfunc(PyStringRef::gt), - "__hash__" => context.new_rustfunc(PyStringRef::hash), - "__lt__" => context.new_rustfunc(PyStringRef::lt), - "__le__" => context.new_rustfunc(PyStringRef::le), - "__len__" => context.new_rustfunc(PyStringRef::len), - "__mul__" => context.new_rustfunc(PyStringRef::mul), - "__new__" => context.new_rustfunc(str_new), - "__repr__" => context.new_rustfunc(PyStringRef::repr), - "__str__" => context.new_rustfunc(PyStringRef::str), - "capitalize" => context.new_rustfunc(PyStringRef::capitalize), - "casefold" => context.new_rustfunc(PyStringRef::casefold), - "center" => context.new_rustfunc(PyStringRef::center), - "count" => context.new_rustfunc(PyStringRef::count), - "endswith" => context.new_rustfunc(PyStringRef::endswith), - "expandtabs" => context.new_rustfunc(PyStringRef::expandtabs), - "find" => context.new_rustfunc(PyStringRef::find), - "format" => context.new_rustfunc(str_format), - "index" => context.new_rustfunc(PyStringRef::index), - "isalnum" => context.new_rustfunc(PyStringRef::isalnum), - "isalpha" => context.new_rustfunc(PyStringRef::isalpha), - "isascii" => context.new_rustfunc(PyStringRef::isascii), - "isdecimal" => context.new_rustfunc(PyStringRef::isdecimal), - "isdigit" => context.new_rustfunc(PyStringRef::isdigit), - "isidentifier" => context.new_rustfunc(PyStringRef::isidentifier), - "islower" => context.new_rustfunc(PyStringRef::islower), - "isnumeric" => context.new_rustfunc(PyStringRef::isnumeric), - "isspace" => context.new_rustfunc(PyStringRef::isspace), - "isupper" => context.new_rustfunc(PyStringRef::isupper), - "istitle" => context.new_rustfunc(PyStringRef::istitle), - "join" => context.new_rustfunc(PyStringRef::join), - "lower" => context.new_rustfunc(PyStringRef::lower), - "ljust" => context.new_rustfunc(PyStringRef::ljust), - "lstrip" => context.new_rustfunc(PyStringRef::lstrip), - "partition" => context.new_rustfunc(PyStringRef::partition), - "replace" => context.new_rustfunc(PyStringRef::replace), - "rfind" => context.new_rustfunc(PyStringRef::rfind), - "rindex" => context.new_rustfunc(PyStringRef::rindex), - "rjust" => context.new_rustfunc(PyStringRef::rjust), - "rpartition" => context.new_rustfunc(PyStringRef::rpartition), - "rsplit" => context.new_rustfunc(PyStringRef::rsplit), - "rstrip" => context.new_rustfunc(PyStringRef::rstrip), - "split" => context.new_rustfunc(PyStringRef::split), - "splitlines" => context.new_rustfunc(PyStringRef::splitlines), - "startswith" => context.new_rustfunc(PyStringRef::startswith), - "strip" => context.new_rustfunc(PyStringRef::strip), - "swapcase" => context.new_rustfunc(PyStringRef::swapcase), - "title" => context.new_rustfunc(PyStringRef::title), - "upper" => context.new_rustfunc(PyStringRef::upper), - "zfill" => context.new_rustfunc(PyStringRef::zfill), - }); + + PyString::extend_class(context, str_type); + // extend_class!(context, str_type, { + // "__add__" => context.new_rustfunc(PyStringRef::add), + // "__bool__" => context.new_rustfunc(PyStringRef::bool), + // "__contains__" => context.new_rustfunc(PyStringRef::contains), + // "__doc__" => context.new_str(str_doc.to_string()), + // "__eq__" => context.new_rustfunc(PyStringRef::eq), + // "__ge__" => context.new_rustfunc(PyStringRef::ge), + // "__getitem__" => context.new_rustfunc(PyStringRef::getitem), + // "__gt__" => context.new_rustfunc(PyStringRef::gt), + // "__hash__" => context.new_rustfunc(PyStringRef::hash), + // "__lt__" => context.new_rustfunc(PyStringRef::lt), + // "__le__" => context.new_rustfunc(PyStringRef::le), + // "__len__" => context.new_rustfunc(PyStringRef::len), + // "__mul__" => context.new_rustfunc(PyStringRef::mul), + // "__new__" => context.new_rustfunc(str_new), + // "__repr__" => context.new_rustfunc(PyStringRef::repr), + // "__str__" => context.new_rustfunc(PyStringRef::str), + // "capitalize" => context.new_rustfunc(PyStringRef::capitalize), + // "casefold" => context.new_rustfunc(PyStringRef::casefold), + // "center" => context.new_rustfunc(PyStringRef::center), + // "count" => context.new_rustfunc(PyStringRef::count), + // "endswith" => context.new_rustfunc(PyStringRef::endswith), + // "expandtabs" => context.new_rustfunc(PyStringRef::expandtabs), + // "find" => context.new_rustfunc(PyStringRef::find), + // "format" => context.new_rustfunc(str_format), + // "index" => context.new_rustfunc(PyStringRef::index), + // "isalnum" => context.new_rustfunc(PyStringRef::isalnum), + // "isalpha" => context.new_rustfunc(PyStringRef::isalpha), + // "isascii" => context.new_rustfunc(PyStringRef::isascii), + // "isdecimal" => context.new_rustfunc(PyStringRef::isdecimal), + // "isdigit" => context.new_rustfunc(PyStringRef::isdigit), + // "isidentifier" => context.new_rustfunc(PyStringRef::isidentifier), + // "islower" => context.new_rustfunc(PyStringRef::islower), + // "isnumeric" => context.new_rustfunc(PyStringRef::isnumeric), + // "isspace" => context.new_rustfunc(PyStringRef::isspace), + // "isupper" => context.new_rustfunc(PyStringRef::isupper), + // "istitle" => context.new_rustfunc(PyStringRef::istitle), + // "join" => context.new_rustfunc(PyStringRef::join), + // "lower" => context.new_rustfunc(PyStringRef::lower), + // "ljust" => context.new_rustfunc(PyStringRef::ljust), + // "lstrip" => context.new_rustfunc(PyStringRef::lstrip), + // "partition" => context.new_rustfunc(PyStringRef::partition), + // "replace" => context.new_rustfunc(PyStringRef::replace), + // "rfind" => context.new_rustfunc(PyStringRef::rfind), + // "rindex" => context.new_rustfunc(PyStringRef::rindex), + // "rjust" => context.new_rustfunc(PyStringRef::rjust), + // "rpartition" => context.new_rustfunc(PyStringRef::rpartition), + // "rsplit" => context.new_rustfunc(PyStringRef::rsplit), + // "rstrip" => context.new_rustfunc(PyStringRef::rstrip), + // "split" => context.new_rustfunc(PyStringRef::split), + // "splitlines" => context.new_rustfunc(PyStringRef::splitlines), + // "startswith" => context.new_rustfunc(PyStringRef::startswith), + // "strip" => context.new_rustfunc(PyStringRef::strip), + // "swapcase" => context.new_rustfunc(PyStringRef::swapcase), + // "title" => context.new_rustfunc(PyStringRef::title), + // "upper" => context.new_rustfunc(PyStringRef::upper), + // "zfill" => context.new_rustfunc(PyStringRef::zfill), + // }); } pub fn get_value(obj: &PyObjectRef) -> String { @@ -706,34 +790,6 @@ fn count_char(s: &str, c: char) -> usize { s.chars().filter(|x| *x == c).count() } -fn str_format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - if args.args.is_empty() { - return Err( - vm.new_type_error("descriptor 'format' of 'str' object needs an argument".to_string()) - ); - } - - let zelf = &args.args[0]; - if !objtype::isinstance(&zelf, &vm.ctx.str_type()) { - let zelf_typ = zelf.class(); - let actual_type = vm.to_pystr(&zelf_typ)?; - return Err(vm.new_type_error(format!( - "descriptor 'format' requires a 'str' object but received a '{}'", - actual_type - ))); - } - let format_string_text = get_value(zelf); - match FormatString::from_str(format_string_text.as_str()) { - Ok(format_string) => perform_format(vm, &format_string, &args), - Err(err) => match err { - FormatParseError::UnmatchedBracket => { - Err(vm.new_value_error("expected '}' before end of string".to_string())) - } - _ => Err(vm.new_value_error("Unexpected error parsing format string".to_string())), - }, - } -} - fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: &str) -> PyResult { let returned_type = vm.ctx.new_str(format_spec.to_string()); let result = vm.call_method(&argument, "__format__", vec![returned_type])?; @@ -797,26 +853,6 @@ fn perform_format( Ok(vm.ctx.new_str(final_string)) } -// TODO: should with following format -// class str(object='') -// class str(object=b'', encoding='utf-8', errors='strict') -fn str_new( - cls: PyClassRef, - object: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { - let string = match object { - OptionalArg::Present(ref input) => vm.to_str(input)?.into_object(), - OptionalArg::Missing => vm.new_str("".to_string()), - }; - if string.class().is(&cls) { - TryFromObject::try_from_object(vm, string) - } else { - let payload = string.payload::().unwrap(); - payload.clone().into_ref_with_type(vm, cls) - } -} - impl PySliceableSequence for String { fn do_slice(&self, range: Range) -> Self { to_graphemes(self) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index aa3348be59..a0e737d4ca 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1259,6 +1259,26 @@ where } } +pub trait IntoPyClass { + const NAME: &'static str; + const DOC: Option<&'static str> = None; + + fn _extend_class(ctx: &PyContext, class: &PyClassRef); + + fn extend_class(ctx: &PyContext, class: &PyClassRef) { + Self::_extend_class(ctx, class); + if let Some(doc) = Self::DOC { + ctx.set_attr(class, "__doc__", ctx.new_str(doc.into())); + } + } + + fn make_class(ctx: &PyContext) -> PyClassRef { + let py_class = ctx.new_class(Self::NAME, ctx.object()); + Self::extend_class(ctx, &py_class); + py_class + } +} + #[cfg(test)] mod tests { use super::*; From ce9a909b9d34d2f6c1983facd1bcf211ca709b44 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 29 Mar 2019 19:03:13 +1300 Subject: [PATCH 098/884] Change kind identifier to fully use python terminology --- derive/src/lib.rs | 6 +++--- vm/src/builtins.rs | 6 +++--- vm/src/obj/objint.rs | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index b4048ec838..8f15f0e876 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -23,11 +23,11 @@ enum ParameterKind { impl ParameterKind { fn from_ident(ident: &Ident) -> ParameterKind { - if ident == "positional" { + if ident == "positional_only" { ParameterKind::PositionalOnly - } else if ident == "positional_keyword" { + } else if ident == "positional_or_keyword" { ParameterKind::PositionalOrKeyword - } else if ident == "keyword" { + } else if ident == "keyword_only" { ParameterKind::KeywordOnly } else { panic!("Unrecognised attribute") diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 3a695c5178..ed656c0fbf 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -537,11 +537,11 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { #[derive(Debug, FromArgs)] pub struct PrintOptions { - #[pyarg(keyword, default = "None")] + #[pyarg(keyword_only, default = "None")] sep: Option, - #[pyarg(keyword, default = "None")] + #[pyarg(keyword_only, default = "None")] end: Option, - #[pyarg(keyword, default = "false")] + #[pyarg(keyword_only, default = "false")] flush: bool, } diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 0fad529076..08175dc07f 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -374,9 +374,9 @@ impl PyIntRef { #[derive(FromArgs)] struct IntOptions { - #[pyarg(positional, optional = true)] + #[pyarg(positional_only, optional = true)] val_options: OptionalArg, - #[pyarg(positional_keyword, optional = true)] + #[pyarg(positional_or_keyword, optional = true)] base: OptionalArg, } From ab6031871cff7602e6160817465fbf561df8b597 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 29 Mar 2019 14:51:42 -0500 Subject: [PATCH 099/884] impl PyStringRef using py_class --- derive/src/lib.rs | 2 +- vm/src/obj/objstr.rs | 252 +++++++++++++++++++++---------------------- 2 files changed, 125 insertions(+), 129 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index be16403ef9..f3464b5f1a 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -374,7 +374,7 @@ fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { }| { let constructor_fn = kind.to_ctx_constructor_fn(); quote! { - ctx.set_attr(class, #py_name, ctx.#constructor_fn(#ty::#fn_name)); + ctx.set_attr(class, #py_name, ctx.#constructor_fn(Self::#fn_name)); } }, ); diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 82ac806e63..66590fdb2d 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -74,7 +74,7 @@ fn get_fill_char<'a>(rep: &'a OptionalArg, vm: &VirtualMachine) -> encoding defaults to sys.getdefaultencoding().\n\ errors defaults to 'strict'." )] -impl PyString { +impl PyStringRef { // TODO: should with following format // class str(object='') // class str(object=b'', encoding='utf-8', errors='strict') @@ -96,90 +96,90 @@ impl PyString { } } #[py_class(name = "__add__")] - fn add(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn add(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - Ok(format!("{}{}", zelf.value, get_value(&rhs))) + Ok(format!("{}{}", self.value, get_value(&rhs))) } else { - Err(vm.new_type_error(format!("Cannot add {} and {}", zelf, rhs))) + Err(vm.new_type_error(format!("Cannot add {} and {}", self, rhs))) } } #[py_class(name = "__bool__")] - fn bool(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - !zelf.value.is_empty() + fn bool(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() } #[py_class(name = "__eq__")] - fn eq(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { + fn eq(self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - zelf.value == get_value(&rhs) + self.value == get_value(&rhs) } else { false } } #[py_class(name = "__contains__")] - fn contains(zelf: PyStringRef, needle: PyStringRef, _vm: &VirtualMachine) -> bool { - zelf.value.contains(&needle.value) + fn contains(self, needle: PyStringRef, _vm: &VirtualMachine) -> bool { + self.value.contains(&needle.value) } #[py_class(name = "__getitem__")] - fn getitem(zelf: PyStringRef, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - subscript(vm, &zelf.value, needle) + fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + subscript(vm, &self.value, needle) } #[py_class(name = "__gt__")] - fn gt(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn gt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - Ok(zelf.value > get_value(&rhs)) + Ok(self.value > get_value(&rhs)) } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs))) + Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) } } #[py_class(name = "__ge__")] - fn ge(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn ge(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - Ok(zelf.value >= get_value(&rhs)) + Ok(self.value >= get_value(&rhs)) } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs))) + Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) } } #[py_class(name = "__lt__")] - fn lt(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn lt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - Ok(zelf.value < get_value(&rhs)) + Ok(self.value < get_value(&rhs)) } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs))) + Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) } } #[py_class(name = "__le__")] - fn le(zelf: PyStringRef, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn le(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { - Ok(zelf.value <= get_value(&rhs)) + Ok(self.value <= get_value(&rhs)) } else { - Err(vm.new_type_error(format!("Cannot compare {} and {}", zelf, rhs))) + Err(vm.new_type_error(format!("Cannot compare {} and {}", self, rhs))) } } #[py_class(name = "__hash__")] - fn hash(zelf: PyStringRef, _vm: &VirtualMachine) -> usize { + fn hash(self, _vm: &VirtualMachine) -> usize { let mut hasher = std::collections::hash_map::DefaultHasher::new(); - zelf.value.hash(&mut hasher); + self.value.hash(&mut hasher); hasher.finish() as usize } #[py_class(name = "__len__")] - fn len(zelf: PyStringRef, _vm: &VirtualMachine) -> usize { - zelf.value.chars().count() + fn len(self, _vm: &VirtualMachine) -> usize { + self.value.chars().count() } #[py_class(name = "__mul__")] - fn mul(zelf: PyStringRef, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn mul(self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&val, &vm.ctx.int_type()) { - let value = &zelf.value; + let value = &self.value; let multiplier = objint::get_value(&val).to_i32().unwrap(); let mut result = String::new(); for _x in 0..multiplier { @@ -187,18 +187,18 @@ impl PyString { } Ok(result) } else { - Err(vm.new_type_error(format!("Cannot multiply {} and {}", zelf, val))) + Err(vm.new_type_error(format!("Cannot multiply {} and {}", self, val))) } } #[py_class(name = "__str__")] - fn str(zelf: PyStringRef, _vm: &VirtualMachine) -> PyStringRef { - zelf + fn str(self, _vm: &VirtualMachine) -> PyStringRef { + self } #[py_class(name = "__repr__")] - fn repr(zelf: PyStringRef, _vm: &VirtualMachine) -> String { - let value = &zelf.value; + fn repr(self, _vm: &VirtualMachine) -> String { + let value = &self.value; let quote_char = if count_char(value, '\'') > count_char(value, '"') { '"' } else { @@ -227,31 +227,31 @@ impl PyString { formatted } - fn lower(zelf: PyStringRef, _vm: &VirtualMachine) -> String { - zelf.value.to_lowercase() + fn lower(self, _vm: &VirtualMachine) -> String { + self.value.to_lowercase() } // casefold is much more aggressive than lower - fn casefold(zelf: PyStringRef, _vm: &VirtualMachine) -> String { - caseless::default_case_fold_str(&zelf.value) + fn casefold(self, _vm: &VirtualMachine) -> String { + caseless::default_case_fold_str(&self.value) } - fn upper(zelf: PyStringRef, _vm: &VirtualMachine) -> String { - zelf.value.to_uppercase() + fn upper(self, _vm: &VirtualMachine) -> String { + self.value.to_uppercase() } - fn capitalize(zelf: PyStringRef, _vm: &VirtualMachine) -> String { - let (first_part, lower_str) = zelf.value.split_at(1); + fn capitalize(self, _vm: &VirtualMachine) -> String { + let (first_part, lower_str) = self.value.split_at(1); format!("{}{}", first_part.to_uppercase(), lower_str) } fn split( - zelf: PyStringRef, + self, pattern: OptionalArg, num: OptionalArg, vm: &VirtualMachine, ) -> PyObjectRef { - let value = &zelf.value; + let value = &self.value; let pattern = match pattern { OptionalArg::Present(ref s) => &s.value, OptionalArg::Missing => " ", @@ -267,12 +267,12 @@ impl PyString { } fn rsplit( - zelf: PyStringRef, + self, pattern: OptionalArg, num: OptionalArg, vm: &VirtualMachine, ) -> PyObjectRef { - let value = &zelf.value; + let value = &self.value; let pattern = match pattern { OptionalArg::Present(ref s) => &s.value, OptionalArg::Missing => " ", @@ -287,75 +287,75 @@ impl PyString { vm.ctx.new_list(elements) } - fn strip(zelf: PyStringRef, _vm: &VirtualMachine) -> String { - zelf.value.trim().to_string() + fn strip(self, _vm: &VirtualMachine) -> String { + self.value.trim().to_string() } - fn lstrip(zelf: PyStringRef, _vm: &VirtualMachine) -> String { - zelf.value.trim_start().to_string() + fn lstrip(self, _vm: &VirtualMachine) -> String { + self.value.trim_start().to_string() } - fn rstrip(zelf: PyStringRef, _vm: &VirtualMachine) -> String { - zelf.value.trim_end().to_string() + fn rstrip(self, _vm: &VirtualMachine) -> String { + self.value.trim_end().to_string() } fn endswith( - zelf: PyStringRef, + self, suffix: PyStringRef, start: OptionalArg, end: OptionalArg, _vm: &VirtualMachine, ) -> bool { - if let Some((start, end)) = adjust_indices(start, end, zelf.value.len()) { - zelf.value[start..end].ends_with(&suffix.value) + if let Some((start, end)) = adjust_indices(start, end, self.value.len()) { + self.value[start..end].ends_with(&suffix.value) } else { false } } fn startswith( - zelf: PyStringRef, + self, prefix: PyStringRef, start: OptionalArg, end: OptionalArg, _vm: &VirtualMachine, ) -> bool { - if let Some((start, end)) = adjust_indices(start, end, zelf.value.len()) { - zelf.value[start..end].starts_with(&prefix.value) + if let Some((start, end)) = adjust_indices(start, end, self.value.len()) { + self.value[start..end].starts_with(&prefix.value) } else { false } } - fn isalnum(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - !zelf.value.is_empty() && zelf.value.chars().all(char::is_alphanumeric) + fn isalnum(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) } - fn isnumeric(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - !zelf.value.is_empty() && zelf.value.chars().all(char::is_numeric) + fn isnumeric(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() && self.value.chars().all(char::is_numeric) } - fn isdigit(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { + fn isdigit(self, _vm: &VirtualMachine) -> bool { // python's isdigit also checks if exponents are digits, these are the unicodes for exponents let valid_unicodes: [u16; 10] = [ 0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079, ]; - if zelf.value.is_empty() { + if self.value.is_empty() { false } else { - zelf.value + self.value .chars() .filter(|c| !c.is_digit(10)) .all(|c| valid_unicodes.contains(&(c as u16))) } } - fn isdecimal(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - if zelf.value.is_empty() { + fn isdecimal(self, _vm: &VirtualMachine) -> bool { + if self.value.is_empty() { false } else { - zelf.value.chars().all(|c| c.is_ascii_digit()) + self.value.chars().all(|c| c.is_ascii_digit()) } } @@ -387,13 +387,13 @@ impl PyString { } } - fn title(zelf: PyStringRef, _vm: &VirtualMachine) -> String { - make_title(&zelf.value) + fn title(self, _vm: &VirtualMachine) -> String { + make_title(&self.value) } - fn swapcase(zelf: PyStringRef, _vm: &VirtualMachine) -> String { - let mut swapped_str = String::with_capacity(zelf.value.len()); - for c in zelf.value.chars() { + fn swapcase(self, _vm: &VirtualMachine) -> String { + let mut swapped_str = String::with_capacity(self.value.len()); + for c in self.value.chars() { // to_uppercase returns an iterator, to_ascii_uppercase returns the char if c.is_lowercase() { swapped_str.push(c.to_ascii_uppercase()); @@ -406,54 +406,54 @@ impl PyString { swapped_str } - fn isalpha(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - !zelf.value.is_empty() && zelf.value.chars().all(char::is_alphanumeric) + fn isalpha(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) } fn replace( - zelf: PyStringRef, + self, old: PyStringRef, new: PyStringRef, num: OptionalArg, _vm: &VirtualMachine, ) -> String { match num.into_option() { - Some(num) => zelf.value.replacen(&old.value, &new.value, num), - None => zelf.value.replace(&old.value, &new.value), + Some(num) => self.value.replacen(&old.value, &new.value, num), + None => self.value.replace(&old.value, &new.value), } } // cpython's isspace ignores whitespace, including \t and \n, etc, unless the whole string is empty // which is why isspace is using is_ascii_whitespace. Same for isupper & islower - fn isspace(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - !zelf.value.is_empty() && zelf.value.chars().all(|c| c.is_ascii_whitespace()) + fn isspace(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() && self.value.chars().all(|c| c.is_ascii_whitespace()) } - fn isupper(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - !zelf.value.is_empty() - && zelf + fn isupper(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() + && self .value .chars() .filter(|x| !x.is_ascii_whitespace()) .all(char::is_uppercase) } - fn islower(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - !zelf.value.is_empty() - && zelf + fn islower(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() + && self .value .chars() .filter(|x| !x.is_ascii_whitespace()) .all(char::is_lowercase) } - fn isascii(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - !zelf.value.is_empty() && zelf.value.chars().all(|c| c.is_ascii()) + fn isascii(self, _vm: &VirtualMachine) -> bool { + !self.value.is_empty() && self.value.chars().all(|c| c.is_ascii()) } // doesn't implement keep new line delimiter just yet - fn splitlines(zelf: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { - let elements = zelf + fn splitlines(self, vm: &VirtualMachine) -> PyObjectRef { + let elements = self .value .split('\n') .map(|e| vm.ctx.new_str(e.to_string())) @@ -461,17 +461,13 @@ impl PyString { vm.ctx.new_list(elements) } - fn join( - zelf: PyStringRef, - iterable: PyIterable, - vm: &VirtualMachine, - ) -> PyResult { + fn join(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { let mut joined = String::new(); for (idx, elem) in iterable.iter(vm)?.enumerate() { let elem = elem?; if idx != 0 { - joined.push_str(&zelf.value); + joined.push_str(&self.value); } joined.push_str(&elem.value) } @@ -480,13 +476,13 @@ impl PyString { } fn find( - zelf: PyStringRef, + self, sub: PyStringRef, start: OptionalArg, end: OptionalArg, _vm: &VirtualMachine, ) -> isize { - let value = &zelf.value; + let value = &self.value; if let Some((start, end)) = adjust_indices(start, end, value.len()) { match value[start..end].find(&sub.value) { Some(num) => (start + num) as isize, @@ -498,13 +494,13 @@ impl PyString { } fn rfind( - zelf: PyStringRef, + self, sub: PyStringRef, start: OptionalArg, end: OptionalArg, _vm: &VirtualMachine, ) -> isize { - let value = &zelf.value; + let value = &self.value; if let Some((start, end)) = adjust_indices(start, end, value.len()) { match value[start..end].rfind(&sub.value) { Some(num) => (start + num) as isize, @@ -516,13 +512,13 @@ impl PyString { } fn index( - zelf: PyStringRef, + self, sub: PyStringRef, start: OptionalArg, end: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - let value = &zelf.value; + let value = &self.value; if let Some((start, end)) = adjust_indices(start, end, value.len()) { match value[start..end].find(&sub.value) { Some(num) => Ok(start + num), @@ -534,13 +530,13 @@ impl PyString { } fn rindex( - zelf: PyStringRef, + self, sub: PyStringRef, start: OptionalArg, end: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - let value = &zelf.value; + let value = &self.value; if let Some((start, end)) = adjust_indices(start, end, value.len()) { match value[start..end].rfind(&sub.value) { Some(num) => Ok(start + num), @@ -551,8 +547,8 @@ impl PyString { } } - fn partition(zelf: PyStringRef, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { - let value = &zelf.value; + fn partition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { + let value = &self.value; let sub = &sub.value; let mut new_tup = Vec::new(); if value.contains(sub) { @@ -569,8 +565,8 @@ impl PyString { vm.ctx.new_tuple(new_tup) } - fn rpartition(zelf: PyStringRef, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { - let value = &zelf.value; + fn rpartition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { + let value = &self.value; let sub = &sub.value; let mut new_tup = Vec::new(); if value.contains(sub) { @@ -588,31 +584,31 @@ impl PyString { vm.ctx.new_tuple(new_tup) } - fn istitle(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - if zelf.value.is_empty() { + fn istitle(self, _vm: &VirtualMachine) -> bool { + if self.value.is_empty() { false } else { - zelf.value.split(' ').all(|word| word == make_title(word)) + self.value.split(' ').all(|word| word == make_title(word)) } } fn count( - zelf: PyStringRef, + self, sub: PyStringRef, start: OptionalArg, end: OptionalArg, _vm: &VirtualMachine, ) -> usize { - let value = &zelf.value; + let value = &self.value; if let Some((start, end)) = adjust_indices(start, end, value.len()) { - zelf.value[start..end].matches(&sub.value).count() + self.value[start..end].matches(&sub.value).count() } else { 0 } } - fn zfill(zelf: PyStringRef, len: usize, _vm: &VirtualMachine) -> String { - let value = &zelf.value; + fn zfill(self, len: usize, _vm: &VirtualMachine) -> String { + let value = &self.value; if len <= value.len() { value.to_string() } else { @@ -621,34 +617,34 @@ impl PyString { } fn ljust( - zelf: PyStringRef, + self, len: usize, rep: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - let value = &zelf.value; + let value = &self.value; let rep_char = get_fill_char(&rep, vm)?; Ok(format!("{}{}", value, rep_char.repeat(len))) } fn rjust( - zelf: PyStringRef, + self, len: usize, rep: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - let value = &zelf.value; + let value = &self.value; let rep_char = get_fill_char(&rep, vm)?; Ok(format!("{}{}", rep_char.repeat(len), value)) } fn center( - zelf: PyStringRef, + self, len: usize, rep: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - let value = &zelf.value; + let value = &self.value; let rep_char = get_fill_char(&rep, vm)?; let left_buff: usize = (len - value.len()) / 2; let right_buff = len - value.len() - left_buff; @@ -660,12 +656,12 @@ impl PyString { )) } - fn expandtabs(zelf: PyStringRef, tab_stop: OptionalArg, _vm: &VirtualMachine) -> String { + fn expandtabs(self, tab_stop: OptionalArg, _vm: &VirtualMachine) -> String { let tab_stop = tab_stop.into_option().unwrap_or(8 as usize); let mut expanded_str = String::new(); let mut tab_size = tab_stop; let mut col_count = 0 as usize; - for ch in zelf.value.chars() { + for ch in self.value.chars() { // 0x0009 is tab if ch == 0x0009 as char { let num_spaces = tab_size - col_count; @@ -683,8 +679,8 @@ impl PyString { expanded_str } - fn isidentifier(zelf: PyStringRef, _vm: &VirtualMachine) -> bool { - let value = &zelf.value; + fn isidentifier(self, _vm: &VirtualMachine) -> bool { + let value = &self.value; // a string is not an identifier if it has whitespace or starts with a number if !value.chars().any(|c| c.is_ascii_whitespace()) && !value.chars().nth(0).unwrap().is_digit(10) @@ -717,7 +713,7 @@ impl IntoPyObject for String { pub fn init(context: &PyContext) { let str_type = &context.str_type; - PyString::extend_class(context, str_type); + PyStringRef::extend_class(context, str_type); // extend_class!(context, str_type, { // "__add__" => context.new_rustfunc(PyStringRef::add), // "__bool__" => context.new_rustfunc(PyStringRef::bool), From 8376ec2d982bbe90ace709fe7585298f72743871 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 29 Mar 2019 15:17:58 -0500 Subject: [PATCH 100/884] Use doc comments for python __doc__ --- derive/src/lib.rs | 31 +++++++++++++++++++++++-------- vm/src/obj/objstr.rs | 25 +++++++++++-------------- 2 files changed, 34 insertions(+), 22 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index f3464b5f1a..52cfc75678 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -340,7 +340,6 @@ fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { }; let rp_path = rustpython_path_attr(&attr); let mut class_name = None; - let mut doc = None; for attr in attr { if let NestedMeta::Meta(meta) = attr { if let Meta::NameValue(name_value) = meta { @@ -350,19 +349,35 @@ fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { } else { panic!("#[py_class(name = ...)] must be a string"); } - } else if name_value.ident == "doc" { - if let Lit::Str(s) = name_value.lit { - doc = Some(s.value()); - } else { - panic!("#[py_class(name = ...)] must be a string"); - } } } } } let class_name = class_name.expect("#[py_class] must have a name"); + let mut doc: Option> = None; + for attr in imp.attrs.iter() { + if attr.path.is_ident("doc") { + let meta = attr.parse_meta().expect("expected doc attr to be a meta"); + if let Meta::NameValue(name_value) = meta { + if let Lit::Str(s) = name_value.lit { + let val = s.value().trim().to_string(); + match doc { + Some(ref mut doc) => doc.push(val), + None => doc = Some(vec![val]), + } + } else { + panic!("expected #[doc = ...] to be a string") + } + } else { + panic!("expected #[doc] to be a NameValue, e.g. #[doc = \"...\""); + } + } + } let doc = match doc { - Some(doc) => quote!(Some(#doc)), + Some(doc) => { + let doc = doc.join("\n"); + quote!(Some(#doc)) + } None => quote!(None), }; let ty = &imp.self_ty; diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 66590fdb2d..5e4035c5a9 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -60,20 +60,17 @@ fn get_fill_char<'a>(rep: &'a OptionalArg, vm: &VirtualMachine) -> } } -#[py_class( - __inside_vm, - name = "str", - doc = "str(object='') -> str\n\ - str(bytes_or_buffer[, encoding[, errors]]) -> str\n\ - \n\ - Create a new string object from the given object. If encoding or\n\ - errors is specified, then the object must expose a data buffer\n\ - that will be decoded using the given encoding and error handler.\n\ - Otherwise, returns the result of object.__str__() (if defined)\n\ - or repr(object).\n\ - encoding defaults to sys.getdefaultencoding().\n\ - errors defaults to 'strict'." -)] +#[py_class(__inside_vm, name = "str")] +/// str(object='') -> str +/// str(bytes_or_buffer[, encoding[, errors]]) -> str +/// +/// Create a new string object from the given object. If encoding or +/// errors is specified, then the object must expose a data buffer +/// that will be decoded using the given encoding and error handler. +/// Otherwise, returns the result of object.__str__() (if defined) +/// or repr(object). +/// encoding defaults to sys.getdefaultencoding(). +/// errors defaults to 'strict'." impl PyStringRef { // TODO: should with following format // class str(object='') From d9efe4ea3752b8c0f460786d487cacd74f62e772 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 29 Mar 2019 16:16:31 -0500 Subject: [PATCH 101/884] Require a #[pymethod] for a method to be recognized --- derive/src/lib.rs | 62 ++++++++++++++++---------- vm/src/obj/objstr.rs | 104 ++++++++++++++++++++++++++++++------------- 2 files changed, 112 insertions(+), 54 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 52cfc75678..374463f063 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -249,7 +249,7 @@ fn impl_from_args(input: DeriveInput) -> TokenStream2 { } #[proc_macro_attribute] -pub fn py_class(attr: TokenStream, item: TokenStream) -> TokenStream { +pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream { let attr = parse_macro_input!(attr as AttributeArgs); let item = parse_macro_input!(item as Item); impl_py_class(attr, item).into() @@ -276,31 +276,37 @@ struct Method { kind: MethodKind, } -fn item_impl_to_methods<'a>(imp: &'a syn::ItemImpl) -> impl Iterator + 'a { - imp.items.iter().filter_map(|item| { +/// Parse an impl block into an iterator of methods +fn item_impl_to_methods<'a>(imp: &'a mut syn::ItemImpl) -> impl Iterator + 'a { + imp.items.iter_mut().filter_map(|item| { if let ImplItem::Method(meth) = item { let mut py_name = None; let mut kind = MethodKind::Method; - let metas_iter = meth + let mut pymethod_to_remove = Vec::new(); + let metas = meth .attrs .iter() - .filter_map(|attr| { - if attr.path.is_ident("py_class") { + .enumerate() + .filter_map(|(i, attr)| { + if attr.path.is_ident("pymethod") { let meta = attr.parse_meta().expect("Invalid attribute"); - if let Meta::List(list) = meta { - Some(list) - } else { - panic!( - "#[py_class] attribute on a method should be a list, like \ - #[py_class(...)]" - ) + // remove #[pymethod] because there's no actual proc macro + // implementation for it + pymethod_to_remove.push(i); + match meta { + Meta::List(list) => Some(list), + Meta::Word(_) => None, + Meta::NameValue(_) => panic!( + "#[pymethod = ...] attribute on a method should be a list, like \ + #[pymethod(...)]" + ), } } else { None } }) .flat_map(|attr| attr.nested); - for meta in metas_iter { + for meta in metas { if let NestedMeta::Meta(meta) = meta { match meta { Meta::NameValue(name_value) => { @@ -308,18 +314,27 @@ fn item_impl_to_methods<'a>(imp: &'a syn::ItemImpl) -> impl Iterator match ident.to_string().as_str() { - "property" => kind = MethodKind::Property, - _ => {} - }, + Meta::Word(ident) => { + if ident == "property" { + kind = MethodKind::Property + } + } _ => {} } } } + // if there are no #[pymethods]s, then it's not a method, so exclude it from + // the final result + if pymethod_to_remove.is_empty() { + return None; + } + for i in pymethod_to_remove { + meth.attrs.remove(i); + } let py_name = py_name.unwrap_or_else(|| meth.sig.ident.to_string()); Some(Method { fn_name: meth.sig.ident.clone(), @@ -333,7 +348,7 @@ fn item_impl_to_methods<'a>(imp: &'a syn::ItemImpl) -> impl Iterator TokenStream2 { - let imp = if let Item::Impl(imp) = item { + let mut imp = if let Item::Impl(imp) = item { imp } else { return quote!(#item); @@ -347,13 +362,13 @@ fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { if let Lit::Str(s) = name_value.lit { class_name = Some(s.value()); } else { - panic!("#[py_class(name = ...)] must be a string"); + panic!("#[pyclass(name = ...)] must be a string"); } } } } } - let class_name = class_name.expect("#[py_class] must have a name"); + let class_name = class_name.expect("#[pyclass] must have a name"); let mut doc: Option> = None; for attr in imp.attrs.iter() { if attr.path.is_ident("doc") { @@ -380,8 +395,9 @@ fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { } None => quote!(None), }; + let methods: Vec<_> = item_impl_to_methods(&mut imp).collect(); let ty = &imp.self_ty; - let methods = item_impl_to_methods(&imp).map( + let methods = methods.iter().map( |Method { py_name, fn_name, diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 5e4035c5a9..70c4206656 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -48,19 +48,7 @@ impl TryIntoRef for &str { } } -fn get_fill_char<'a>(rep: &'a OptionalArg, vm: &VirtualMachine) -> PyResult<&'a str> { - let rep_str = match rep { - OptionalArg::Present(ref st) => &st.value, - OptionalArg::Missing => " ", - }; - if rep_str.len() == 1 { - Ok(rep_str) - } else { - Err(vm.new_type_error("The fill character must be exactly one character long".to_string())) - } -} - -#[py_class(__inside_vm, name = "str")] +#[pyclass(__inside_vm, name = "str")] /// str(object='') -> str /// str(bytes_or_buffer[, encoding[, errors]]) -> str /// @@ -75,7 +63,7 @@ impl PyStringRef { // TODO: should with following format // class str(object='') // class str(object=b'', encoding='utf-8', errors='strict') - #[py_class(name = "__new__")] + #[pymethod(name = "__new__")] fn new( cls: PyClassRef, object: OptionalArg, @@ -92,7 +80,7 @@ impl PyStringRef { payload.clone().into_ref_with_type(vm, cls) } } - #[py_class(name = "__add__")] + #[pymethod(name = "__add__")] fn add(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { Ok(format!("{}{}", self.value, get_value(&rhs))) @@ -101,12 +89,12 @@ impl PyStringRef { } } - #[py_class(name = "__bool__")] + #[pymethod(name = "__bool__")] fn bool(self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() } - #[py_class(name = "__eq__")] + #[pymethod(name = "__eq__")] fn eq(self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { self.value == get_value(&rhs) @@ -115,17 +103,17 @@ impl PyStringRef { } } - #[py_class(name = "__contains__")] + #[pymethod(name = "__contains__")] fn contains(self, needle: PyStringRef, _vm: &VirtualMachine) -> bool { self.value.contains(&needle.value) } - #[py_class(name = "__getitem__")] + #[pymethod(name = "__getitem__")] fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { subscript(vm, &self.value, needle) } - #[py_class(name = "__gt__")] + #[pymethod(name = "__gt__")] fn gt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { Ok(self.value > get_value(&rhs)) @@ -134,7 +122,7 @@ impl PyStringRef { } } - #[py_class(name = "__ge__")] + #[pymethod(name = "__ge__")] fn ge(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { Ok(self.value >= get_value(&rhs)) @@ -143,7 +131,7 @@ impl PyStringRef { } } - #[py_class(name = "__lt__")] + #[pymethod(name = "__lt__")] fn lt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { Ok(self.value < get_value(&rhs)) @@ -152,7 +140,7 @@ impl PyStringRef { } } - #[py_class(name = "__le__")] + #[pymethod(name = "__le__")] fn le(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { Ok(self.value <= get_value(&rhs)) @@ -161,19 +149,19 @@ impl PyStringRef { } } - #[py_class(name = "__hash__")] + #[pymethod(name = "__hash__")] fn hash(self, _vm: &VirtualMachine) -> usize { let mut hasher = std::collections::hash_map::DefaultHasher::new(); self.value.hash(&mut hasher); hasher.finish() as usize } - #[py_class(name = "__len__")] + #[pymethod(name = "__len__")] fn len(self, _vm: &VirtualMachine) -> usize { self.value.chars().count() } - #[py_class(name = "__mul__")] + #[pymethod(name = "__mul__")] fn mul(self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&val, &vm.ctx.int_type()) { let value = &self.value; @@ -188,12 +176,12 @@ impl PyStringRef { } } - #[py_class(name = "__str__")] + #[pymethod(name = "__str__")] fn str(self, _vm: &VirtualMachine) -> PyStringRef { self } - #[py_class(name = "__repr__")] + #[pymethod(name = "__repr__")] fn repr(self, _vm: &VirtualMachine) -> String { let value = &self.value; let quote_char = if count_char(value, '\'') > count_char(value, '"') { @@ -224,24 +212,29 @@ impl PyStringRef { formatted } + #[pymethod] fn lower(self, _vm: &VirtualMachine) -> String { self.value.to_lowercase() } // casefold is much more aggressive than lower + #[pymethod] fn casefold(self, _vm: &VirtualMachine) -> String { caseless::default_case_fold_str(&self.value) } + #[pymethod] fn upper(self, _vm: &VirtualMachine) -> String { self.value.to_uppercase() } + #[pymethod] fn capitalize(self, _vm: &VirtualMachine) -> String { let (first_part, lower_str) = self.value.split_at(1); format!("{}{}", first_part.to_uppercase(), lower_str) } + #[pymethod] fn split( self, pattern: OptionalArg, @@ -263,6 +256,7 @@ impl PyStringRef { vm.ctx.new_list(elements) } + #[pymethod] fn rsplit( self, pattern: OptionalArg, @@ -284,18 +278,22 @@ impl PyStringRef { vm.ctx.new_list(elements) } + #[pymethod] fn strip(self, _vm: &VirtualMachine) -> String { self.value.trim().to_string() } + #[pymethod] fn lstrip(self, _vm: &VirtualMachine) -> String { self.value.trim_start().to_string() } + #[pymethod] fn rstrip(self, _vm: &VirtualMachine) -> String { self.value.trim_end().to_string() } + #[pymethod] fn endswith( self, suffix: PyStringRef, @@ -310,6 +308,7 @@ impl PyStringRef { } } + #[pymethod] fn startswith( self, prefix: PyStringRef, @@ -324,14 +323,17 @@ impl PyStringRef { } } + #[pymethod] fn isalnum(self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) } + #[pymethod] fn isnumeric(self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self.value.chars().all(char::is_numeric) } + #[pymethod] fn isdigit(self, _vm: &VirtualMachine) -> bool { // python's isdigit also checks if exponents are digits, these are the unicodes for exponents let valid_unicodes: [u16; 10] = [ @@ -348,6 +350,7 @@ impl PyStringRef { } } + #[pymethod] fn isdecimal(self, _vm: &VirtualMachine) -> bool { if self.value.is_empty() { false @@ -356,6 +359,7 @@ impl PyStringRef { } } + #[pymethod] fn format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if args.args.is_empty() { return Err(vm.new_type_error( @@ -384,10 +388,12 @@ impl PyStringRef { } } + #[pymethod] fn title(self, _vm: &VirtualMachine) -> String { make_title(&self.value) } + #[pymethod] fn swapcase(self, _vm: &VirtualMachine) -> String { let mut swapped_str = String::with_capacity(self.value.len()); for c in self.value.chars() { @@ -403,10 +409,12 @@ impl PyStringRef { swapped_str } + #[pymethod] fn isalpha(self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) } + #[pymethod] fn replace( self, old: PyStringRef, @@ -422,10 +430,12 @@ impl PyStringRef { // cpython's isspace ignores whitespace, including \t and \n, etc, unless the whole string is empty // which is why isspace is using is_ascii_whitespace. Same for isupper & islower + #[pymethod] fn isspace(self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self.value.chars().all(|c| c.is_ascii_whitespace()) } + #[pymethod] fn isupper(self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self @@ -435,6 +445,7 @@ impl PyStringRef { .all(char::is_uppercase) } + #[pymethod] fn islower(self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self @@ -444,11 +455,13 @@ impl PyStringRef { .all(char::is_lowercase) } + #[pymethod] fn isascii(self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self.value.chars().all(|c| c.is_ascii()) } // doesn't implement keep new line delimiter just yet + #[pymethod] fn splitlines(self, vm: &VirtualMachine) -> PyObjectRef { let elements = self .value @@ -458,6 +471,7 @@ impl PyStringRef { vm.ctx.new_list(elements) } + #[pymethod] fn join(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { let mut joined = String::new(); @@ -472,6 +486,7 @@ impl PyStringRef { Ok(joined) } + #[pymethod] fn find( self, sub: PyStringRef, @@ -490,6 +505,7 @@ impl PyStringRef { } } + #[pymethod] fn rfind( self, sub: PyStringRef, @@ -508,6 +524,7 @@ impl PyStringRef { } } + #[pymethod] fn index( self, sub: PyStringRef, @@ -526,6 +543,7 @@ impl PyStringRef { } } + #[pymethod] fn rindex( self, sub: PyStringRef, @@ -544,6 +562,7 @@ impl PyStringRef { } } + #[pymethod] fn partition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { let value = &self.value; let sub = &sub.value; @@ -562,6 +581,7 @@ impl PyStringRef { vm.ctx.new_tuple(new_tup) } + #[pymethod] fn rpartition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { let value = &self.value; let sub = &sub.value; @@ -581,6 +601,7 @@ impl PyStringRef { vm.ctx.new_tuple(new_tup) } + #[pymethod] fn istitle(self, _vm: &VirtualMachine) -> bool { if self.value.is_empty() { false @@ -589,6 +610,7 @@ impl PyStringRef { } } + #[pymethod] fn count( self, sub: PyStringRef, @@ -604,6 +626,7 @@ impl PyStringRef { } } + #[pymethod] fn zfill(self, len: usize, _vm: &VirtualMachine) -> String { let value = &self.value; if len <= value.len() { @@ -613,6 +636,21 @@ impl PyStringRef { } } + fn get_fill_char<'a>(rep: &'a OptionalArg, vm: &VirtualMachine) -> PyResult<&'a str> { + let rep_str = match rep { + OptionalArg::Present(ref st) => &st.value, + OptionalArg::Missing => " ", + }; + if rep_str.len() == 1 { + Ok(rep_str) + } else { + Err(vm.new_type_error( + "The fill character must be exactly one character long".to_string(), + )) + } + } + + #[pymethod] fn ljust( self, len: usize, @@ -620,10 +658,11 @@ impl PyStringRef { vm: &VirtualMachine, ) -> PyResult { let value = &self.value; - let rep_char = get_fill_char(&rep, vm)?; + let rep_char = Self::get_fill_char(&rep, vm)?; Ok(format!("{}{}", value, rep_char.repeat(len))) } + #[pymethod] fn rjust( self, len: usize, @@ -631,10 +670,11 @@ impl PyStringRef { vm: &VirtualMachine, ) -> PyResult { let value = &self.value; - let rep_char = get_fill_char(&rep, vm)?; + let rep_char = Self::get_fill_char(&rep, vm)?; Ok(format!("{}{}", rep_char.repeat(len), value)) } + #[pymethod] fn center( self, len: usize, @@ -642,7 +682,7 @@ impl PyStringRef { vm: &VirtualMachine, ) -> PyResult { let value = &self.value; - let rep_char = get_fill_char(&rep, vm)?; + let rep_char = Self::get_fill_char(&rep, vm)?; let left_buff: usize = (len - value.len()) / 2; let right_buff = len - value.len() - left_buff; Ok(format!( @@ -653,6 +693,7 @@ impl PyStringRef { )) } + #[pymethod] fn expandtabs(self, tab_stop: OptionalArg, _vm: &VirtualMachine) -> String { let tab_stop = tab_stop.into_option().unwrap_or(8 as usize); let mut expanded_str = String::new(); @@ -676,6 +717,7 @@ impl PyStringRef { expanded_str } + #[pymethod] fn isidentifier(self, _vm: &VirtualMachine) -> bool { let value = &self.value; // a string is not an identifier if it has whitespace or starts with a number From c2d04f97d80a0eb2916ac7c4ce630b43a528f26b Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sat, 30 Mar 2019 13:33:50 -0700 Subject: [PATCH 102/884] bytes: add tests for NotImplemented --- tests/snippets/bytes.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/snippets/bytes.py diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py new file mode 100644 index 0000000000..19ebef68ae --- /dev/null +++ b/tests/snippets/bytes.py @@ -0,0 +1,6 @@ +assert b'foobar'.__eq__(2) == NotImplemented +assert b'foobar'.__ne__(2) == NotImplemented +assert b'foobar'.__gt__(2) == NotImplemented +assert b'foobar'.__ge__(2) == NotImplemented +assert b'foobar'.__lt__(2) == NotImplemented +assert b'foobar'.__le__(2) == NotImplemented From 7d18a1ce604a8a36d57e3b3caf34c7816ee545b3 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sat, 30 Mar 2019 21:47:20 +0100 Subject: [PATCH 103/884] Fix #746: invalid slice with start or stop =-1 when step<0 --- tests/snippets/builtin_slice.py | 75 +- tests/snippets/slice_res.py | 2252 +++++++++++++++++++++++++++++++ vm/src/obj/objsequence.rs | 21 +- 3 files changed, 2334 insertions(+), 14 deletions(-) create mode 100644 tests/snippets/slice_res.py diff --git a/tests/snippets/builtin_slice.py b/tests/snippets/builtin_slice.py index 1d8a93b479..619356211e 100644 --- a/tests/snippets/builtin_slice.py +++ b/tests/snippets/builtin_slice.py @@ -2,24 +2,24 @@ a = [] assert a[:] == [] -assert a[:2**100] == [] -assert a[-2**100:] == [] -assert a[::2**100] == [] +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[: 2 ** 100] == [1, 2] +assert b[-2 ** 100 :] == [1, 2] +assert b[2 ** 100 :] == [] +assert b[:: 2 ** 100] == [1] assert b[-10:1] == [1] assert b[0:0] == [] assert b[1:0] == [] -assert_raises(ValueError, lambda: b[::0], 'zero step slice') +assert_raises(ValueError, lambda: b[::0], "zero step slice") assert b[::-1] == [2, 1] assert b[1::-1] == [2, 1] @@ -33,7 +33,7 @@ assert c[9:6:-3] == [9] assert c[9::-3] == [9, 6, 3, 0] assert c[9::-4] == [9, 5, 1] -assert c[8::-2**100] == [8] +assert c[8 :: -2 ** 100] == [8] assert c[7:7:-2] == [] assert c[7:8:-2] == [] @@ -43,6 +43,7 @@ assert d[3::-1] == "4321" assert d[4::-3] == "52" +assert [1, 2, 3, 5, 6][-1:-5:-1] == [6, 5, 3, 2] # #746 slice_a = slice(5) assert slice_a.start is None @@ -71,3 +72,59 @@ def __setitem__(self, key, value): ss = SubScript() _ = ss[:] ss[:1] = 1 + + +def test_all_slices(): + """ + test all possible slices except big number + """ + MODE = None # set to "build" to rebuild slice_res.py + ll = [0, 1, 2, 3] + start = list(range(-7, 7)) + end = list(range(-7, 7)) + step = list(range(-5, 5)) + step.pop(step.index(0)) + + for i in [start, end, step]: + i.append(None) + + def build(): + # loop used to build slices_res.py with cpython + with open("slice_res.py", "wt") as f: + for s in start: + for e in end: + for t in step: + f.write(str(ll[s:e:t]) + "\n") + + def run(): + # test utility + from slice_res import SLICES_RES + + count = 0 + failures = [] + for s in start: + for e in end: + for t in step: + lhs = ll[s:e:t] + try: + assert lhs == SLICES_RES[count] + except AssertionError: + failures.append( + "start: {} ,stop: {}, step {}. Expected: {}, found: {}".format( + s, e, t, lhs, SLICES_RES[count] + ) + ) + count += 1 + + if failures: + for f in failures: + print(f) + print(len(failures), "slices failed") + + if MODE == "build": + build() + else: + run() + + +test_all_slices() diff --git a/tests/snippets/slice_res.py b/tests/snippets/slice_res.py new file mode 100644 index 0000000000..38eb87729c --- /dev/null +++ b/tests/snippets/slice_res.py @@ -0,0 +1,2252 @@ +SLICES_RES = [ + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [0], + [0], + [0], + [0], + [0], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [1], + [1], + [1], + [1], + [1, 0], + [], + [], + [], + [], + [], + [1], + [1], + [1], + [1], + [1, 0], + [], + [], + [], + [], + [], + [1], + [1], + [1], + [1], + [1, 0], + [], + [], + [], + [], + [], + [1], + [1], + [1], + [1], + [1], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [1], + [1], + [1], + [1], + [1], + [], + [], + [], + [], + [], + [1, 2], + [1], + [1], + [1], + [1, 2], + [1], + [1], + [1], + [1], + [1], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [1], + [1], + [1], + [1], + [1], + [], + [], + [], + [], + [], + [1, 2], + [1], + [1], + [1], + [1, 2], + [], + [], + [], + [], + [], + [1, 2, 3], + [1, 3], + [1], + [1], + [1, 2, 3], + [], + [], + [], + [], + [], + [1, 2, 3], + [1, 3], + [1], + [1], + [1, 2, 3], + [], + [], + [], + [], + [], + [1, 2, 3], + [1, 3], + [1], + [1], + [1, 2, 3], + [1], + [1], + [1], + [1], + [1, 0], + [1, 2, 3], + [1, 3], + [1], + [1], + [1, 2, 3], + [2], + [2], + [2], + [2, 0], + [2, 1, 0], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2, 0], + [2, 1, 0], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2, 0], + [2, 1, 0], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2], + [2, 1], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2], + [2], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2], + [2], + [2], + [2], + [2], + [2], + [2, 1], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2], + [2], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2], + [2], + [], + [], + [], + [], + [], + [2, 3], + [2], + [2], + [2], + [2, 3], + [], + [], + [], + [], + [], + [2, 3], + [2], + [2], + [2], + [2, 3], + [], + [], + [], + [], + [], + [2, 3], + [2], + [2], + [2], + [2, 3], + [2], + [2], + [2], + [2, 0], + [2, 1, 0], + [2, 3], + [2], + [2], + [2], + [2, 3], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [3], + [3], + [3], + [3], + [3], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [0], + [0], + [0], + [0], + [0], + [], + [], + [], + [], + [], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [0], + [0], + [0], + [0], + [0], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [1], + [1], + [1], + [1], + [1, 0], + [], + [], + [], + [], + [], + [1], + [1], + [1], + [1], + [1, 0], + [], + [], + [], + [], + [], + [1], + [1], + [1], + [1], + [1, 0], + [], + [], + [], + [], + [], + [1], + [1], + [1], + [1], + [1], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [1], + [1], + [1], + [1], + [1], + [], + [], + [], + [], + [], + [1, 2], + [1], + [1], + [1], + [1, 2], + [1], + [1], + [1], + [1], + [1], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [1], + [1], + [1], + [1], + [1], + [], + [], + [], + [], + [], + [1, 2], + [1], + [1], + [1], + [1, 2], + [], + [], + [], + [], + [], + [1, 2, 3], + [1, 3], + [1], + [1], + [1, 2, 3], + [], + [], + [], + [], + [], + [1, 2, 3], + [1, 3], + [1], + [1], + [1, 2, 3], + [], + [], + [], + [], + [], + [1, 2, 3], + [1, 3], + [1], + [1], + [1, 2, 3], + [1], + [1], + [1], + [1], + [1, 0], + [1, 2, 3], + [1, 3], + [1], + [1], + [1, 2, 3], + [2], + [2], + [2], + [2, 0], + [2, 1, 0], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2, 0], + [2, 1, 0], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2, 0], + [2, 1, 0], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2], + [2, 1], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2], + [2], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2], + [2], + [2], + [2], + [2], + [2], + [2, 1], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2], + [2], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [2], + [2], + [2], + [2], + [2], + [], + [], + [], + [], + [], + [2, 3], + [2], + [2], + [2], + [2, 3], + [], + [], + [], + [], + [], + [2, 3], + [2], + [2], + [2], + [2, 3], + [], + [], + [], + [], + [], + [2, 3], + [2], + [2], + [2], + [2, 3], + [2], + [2], + [2], + [2, 0], + [2, 1, 0], + [2, 3], + [2], + [2], + [2], + [2, 3], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [3], + [3], + [3], + [3], + [3], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [0], + [0], + [0], + [0], + [0], + [3], + [3], + [3], + [3], + [3], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [3], + [3], + [3], + [3, 1], + [3, 2, 1], + [], + [], + [], + [], + [], + [3], + [3], + [3], + [3], + [3, 2], + [0], + [0], + [0], + [0], + [0], + [3], + [3], + [3], + [3], + [3], + [0, 1], + [0], + [0], + [0], + [0, 1], + [], + [], + [], + [], + [], + [0, 1, 2], + [0, 2], + [0], + [0], + [0, 1, 2], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [], + [], + [], + [], + [], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], + [3], + [3], + [3, 0], + [3, 1], + [3, 2, 1, 0], + [0, 1, 2, 3], + [0, 2], + [0, 3], + [0], + [0, 1, 2, 3], +] diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index a07b2454de..55c56d47c2 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -2,11 +2,10 @@ use std::cell::RefCell; use std::marker::Sized; 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::vm::VirtualMachine; +use num_bigint::{BigInt, ToBigInt}; +use num_traits::{One, Signed, ToPrimitive, Zero}; use super::objbool; use super::objint::PyInt; @@ -86,8 +85,20 @@ pub trait PySliceableSequence { } 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 start = start.as_ref().map(|x| { + if *x == (-1).to_bigint().unwrap() { + self.len() + BigInt::one() //.to_bigint().unwrap() + } else { + x + 1 + } + }); + let stop = stop.as_ref().map(|x| { + if *x == (-1).to_bigint().unwrap() { + self.len().to_bigint().unwrap() + } else { + x + 1 + } + }); let range = self.get_slice_range(&stop, &start); if range.start < range.end { match (-step).to_i32() { From 5625f7e15e49772cb906ba23335356d341a470a4 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 31 Mar 2019 09:29:21 +1300 Subject: [PATCH 104/884] Allow arbitrary in slice, and convert slice.__new__ to new style --- tests/snippets/builtin_slice.py | 6 ++ vm/src/frame.rs | 27 ++----- vm/src/obj/objrange.rs | 6 +- vm/src/obj/objsequence.rs | 11 +-- vm/src/obj/objslice.rs | 127 +++++++++++++++++++------------- 5 files changed, 99 insertions(+), 78 deletions(-) diff --git a/tests/snippets/builtin_slice.py b/tests/snippets/builtin_slice.py index 1d8a93b479..03f800fba3 100644 --- a/tests/snippets/builtin_slice.py +++ b/tests/snippets/builtin_slice.py @@ -59,6 +59,12 @@ assert slice_c.stop == 5 assert slice_c.step == 2 +a = object() +slice_d = slice(a, "v", 1.0) +assert slice_d.start is a +assert slice_d.stop == "v" +assert slice_d.step == 1.0 + class SubScript(object): def __getitem__(self, item): diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 88a619eb36..3a5f4c5abe 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -2,8 +2,6 @@ use std::cell::RefCell; use std::fmt; use std::rc::Rc; -use num_bigint::BigInt; - use rustpython_parser::ast; use crate::builtins; @@ -13,7 +11,6 @@ use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode::PyCodeRef; use crate::obj::objdict::{self, PyDictRef}; -use crate::obj::objint::PyInt; use crate::obj::objiter; use crate::obj::objlist; use crate::obj::objslice::PySlice; @@ -404,24 +401,14 @@ impl Frame { } bytecode::Instruction::BuildSlice { size } => { assert!(*size == 2 || *size == 3); - let elements = self.pop_multiple(*size); - let mut out: Vec> = elements - .into_iter() - .map(|x| { - if x.is(&vm.ctx.none()) { - None - } else if let Some(i) = x.payload::() { - Some(i.as_bigint().clone()) - } else { - panic!("Expect Int or None as BUILD_SLICE arguments") - } - }) - .collect(); - - let start = out[0].take(); - let stop = out[1].take(); - let step = if out.len() == 3 { out[2].take() } else { None }; + let step = if *size == 3 { + Some(self.pop_value()) + } else { + None + }; + let stop = Some(self.pop_value()); + let start = Some(self.pop_value()); let obj = PySlice { start, stop, step }.into_ref(vm); self.push_value(obj.into_object()); diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 04c9c273b1..c1a0b94012 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -264,7 +264,7 @@ impl PyRangeRef { } } Either::B(slice) => { - let new_start = if let Some(int) = slice.start.as_ref() { + let new_start = if let Some(int) = slice.start_index(vm)? { if let Some(i) = self.get(int) { PyInt::new(i).into_ref(vm) } else { @@ -274,7 +274,7 @@ impl PyRangeRef { self.start.clone() }; - let new_end = if let Some(int) = slice.stop.as_ref() { + let new_end = if let Some(int) = slice.stop_index(vm)? { if let Some(i) = self.get(int) { PyInt::new(i).into_ref(vm) } else { @@ -284,7 +284,7 @@ impl PyRangeRef { self.stop.clone() }; - let new_step = if let Some(int) = slice.step.as_ref() { + let new_step = if let Some(int) = slice.step_index(vm)? { PyInt::new(int * self.step.as_bigint()).into_ref(vm) } else { self.step.clone() diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index a07b2454de..a293314bee 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -65,14 +65,15 @@ pub trait PySliceableSequence { where Self: Sized, { - // TODO: we could potentially avoid this copy and use slice - match slice.payload() { - Some(PySlice { start, stop, step }) => { - let step = step.clone().unwrap_or_else(BigInt::one); + match slice.clone().downcast::() { + Ok(slice) => { + let start = slice.start_index(vm)?; + let stop = slice.stop_index(vm)?; + let step = slice.step_index(vm)?.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); + let range = self.get_slice_range(&start, &stop); if range.start < range.end { #[allow(clippy::range_plus_one)] match step.to_i32() { diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index c82a3ca6ef..3eee8e4ec4 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -1,18 +1,19 @@ -use num_bigint::BigInt; - use crate::function::PyFuncArgs; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; -use super::objint; -use crate::obj::objtype::PyClassRef; +use crate::obj::objint::PyInt; +use crate::obj::objtype::{class_has_attr, PyClassRef}; +use num_bigint::BigInt; -#[derive(Debug)] +#[derive(Debug, FromArgs)] pub struct PySlice { - // TODO: should be private - pub start: Option, - pub stop: Option, - pub step: Option, + #[pyarg(positional_only)] + pub start: Option, + #[pyarg(positional_only)] + pub stop: Option, + #[pyarg(positional_only, default)] + pub step: Option, } impl PyValue for PySlice { @@ -24,51 +25,29 @@ impl PyValue for PySlice { pub type PySliceRef = PyRef; fn slice_new(vm: &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)) + let (cls, slice): (PyClassRef, PySlice) = match args.args.len() { + 0 | 1 => { + return Err(vm.new_type_error("slice() must have at least one arguments.".to_owned())); } - _ => { - 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)) + 2 => { + let (cls, stop) = args.bind(vm)?; + ( + cls, + PySlice { + start: None, + stop: Some(stop), + step: None, + }, + ) } - }?; - PySlice { - start: start.map(|x| objint::get_value(x).clone()), - stop: stop.map(|x| objint::get_value(x).clone()), - step: step.map(|x| objint::get_value(x).clone()), - } - .into_ref_with_type(vm, cls.clone().downcast().unwrap()) - .map(|x| x.into_object()) + _ => args.bind(vm)?, + }; + slice.into_ref_with_type(vm, cls).map(|x| x.into_object()) } -fn get_property_value(vm: &VirtualMachine, value: &Option) -> PyObjectRef { +fn get_property_value(vm: &VirtualMachine, value: &Option) -> PyObjectRef { if let Some(value) = value { - vm.ctx.new_int(value.clone()) + value.clone() } else { vm.get_none() } @@ -86,6 +65,54 @@ impl PySliceRef { fn step(self, vm: &VirtualMachine) -> PyObjectRef { get_property_value(vm, &self.step) } + + pub fn start_index(&self, vm: &VirtualMachine) -> PyResult> { + if let Some(obj) = &self.start { + to_index_value(vm, obj) + } else { + Ok(None) + } + } + + pub fn stop_index(&self, vm: &VirtualMachine) -> PyResult> { + if let Some(obj) = &self.stop { + to_index_value(vm, obj) + } else { + Ok(None) + } + } + + pub fn step_index(&self, vm: &VirtualMachine) -> PyResult> { + if let Some(obj) = &self.step { + to_index_value(vm, obj) + } else { + Ok(None) + } + } +} + +fn to_index_value(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult> { + if obj.is(&vm.ctx.none) { + return Ok(None); + } + + if let Some(val) = obj.payload::() { + Ok(Some(val.as_bigint().clone())) + } else { + let cls = obj.class(); + if class_has_attr(&cls, "__index__") { + let index_result = vm.call_method(obj, "__index__", vec![])?; + if let Some(val) = index_result.payload::() { + Ok(Some(val.as_bigint().clone())) + } else { + Err(vm.new_type_error("__index__ method returned non integer".to_string())) + } + } else { + Err(vm.new_type_error( + "slice indices must be integers or None or have an __index__ method".to_string(), + )) + } + } } pub fn init(context: &PyContext) { From a49895ef63a8a7d7f83ddbfae6e44b6abca9c450 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 31 Mar 2019 09:55:18 +1300 Subject: [PATCH 105/884] Cleanup slice_new and added more slice tests --- tests/snippets/builtin_slice.py | 13 +++++++++++++ vm/src/function.rs | 7 +++++++ vm/src/obj/objslice.rs | 25 +++++++++++-------------- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/tests/snippets/builtin_slice.py b/tests/snippets/builtin_slice.py index 03f800fba3..c5dbd5d908 100644 --- a/tests/snippets/builtin_slice.py +++ b/tests/snippets/builtin_slice.py @@ -11,6 +11,7 @@ b = [1, 2] assert b[:] == [1, 2] +assert b[slice(None)] == [1, 2] assert b[:2**100] == [1, 2] assert b[-2**100:] == [1, 2] assert b[2**100:] == [] @@ -77,3 +78,15 @@ def __setitem__(self, key, value): ss = SubScript() _ = ss[:] ss[:1] = 1 + + +class CustomIndex: + def __init__(self, x): + self.x = x + + def __index__(self): + return self.x + + +assert c[CustomIndex(1):CustomIndex(3)] == [1, 2] +assert d[CustomIndex(1):CustomIndex(3)] == "23" diff --git a/vm/src/function.rs b/vm/src/function.rs index aae4eefe89..9215345499 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -1,4 +1,5 @@ use std::collections::HashMap; +use std::mem; use std::ops::RangeInclusive; use crate::obj::objtype::{isinstance, PyClassRef}; @@ -46,6 +47,12 @@ impl From<(&Args, &KwArgs)> for PyFuncArgs { } } +impl FromArgs for PyFuncArgs { + fn from_args(_vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { + Ok(mem::replace(args, Default::default())) + } +} + impl PyFuncArgs { pub fn new(mut args: Vec, kwarg_names: Vec) -> PyFuncArgs { let mut kwargs = vec![]; diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index 3eee8e4ec4..352ba5ce54 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -24,25 +24,22 @@ impl PyValue for PySlice { pub type PySliceRef = PyRef; -fn slice_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let (cls, slice): (PyClassRef, PySlice) = match args.args.len() { - 0 | 1 => { +fn slice_new(cls: PyClassRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { + let slice: PySlice = match args.args.len() { + 0 => { return Err(vm.new_type_error("slice() must have at least one arguments.".to_owned())); } - 2 => { - let (cls, stop) = args.bind(vm)?; - ( - cls, - PySlice { - start: None, - stop: Some(stop), - step: None, - }, - ) + 1 => { + let stop = args.bind(vm)?; + PySlice { + start: None, + stop: Some(stop), + step: None, + } } _ => args.bind(vm)?, }; - slice.into_ref_with_type(vm, cls).map(|x| x.into_object()) + slice.into_ref_with_type(vm, cls) } fn get_property_value(vm: &VirtualMachine, value: &Option) -> PyObjectRef { From e0f7fbb191490a7ee27b34bb411d2f62f5f96fe1 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 31 Mar 2019 10:53:45 +1300 Subject: [PATCH 106/884] Don't derive FromArgs on payload type. --- vm/src/obj/objslice.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index 352ba5ce54..f6f02c8db2 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -1,4 +1,4 @@ -use crate::function::PyFuncArgs; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; @@ -6,13 +6,10 @@ use crate::obj::objint::PyInt; use crate::obj::objtype::{class_has_attr, PyClassRef}; use num_bigint::BigInt; -#[derive(Debug, FromArgs)] +#[derive(Debug)] pub struct PySlice { - #[pyarg(positional_only)] pub start: Option, - #[pyarg(positional_only)] pub stop: Option, - #[pyarg(positional_only, default)] pub step: Option, } @@ -37,7 +34,15 @@ fn slice_new(cls: PyClassRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult step: None, } } - _ => args.bind(vm)?, + _ => { + let (start, stop, step): (PyObjectRef, PyObjectRef, OptionalArg) = + args.bind(vm)?; + PySlice { + start: Some(start), + stop: Some(stop), + step: step.into_option(), + } + } }; slice.into_ref_with_type(vm, cls) } From f11f04da709d564114d8fcaec07abb130c7de214 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 30 Mar 2019 18:43:31 -0500 Subject: [PATCH 107/884] Split rustpython_derive into multiple files --- derive/src/from_args.rs | 202 +++++++++++++++++++++ derive/src/lib.rs | 382 +--------------------------------------- derive/src/pyclass.rs | 179 +++++++++++++++++++ vm/src/lib.rs | 4 +- 4 files changed, 390 insertions(+), 377 deletions(-) create mode 100644 derive/src/from_args.rs create mode 100644 derive/src/pyclass.rs diff --git a/derive/src/from_args.rs b/derive/src/from_args.rs new file mode 100644 index 0000000000..7845b28800 --- /dev/null +++ b/derive/src/from_args.rs @@ -0,0 +1,202 @@ +use super::rustpython_path_derive; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{Attribute, Data, DeriveInput, Expr, Field, Fields, Ident, Lit, Meta, NestedMeta}; + +/// The kind of the python parameter, this corresponds to the value of Parameter.kind +/// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind) +enum ParameterKind { + PositionalOnly, + PositionalOrKeyword, + KeywordOnly, +} + +impl ParameterKind { + fn from_ident(ident: &Ident) -> ParameterKind { + if ident == "positional_only" { + ParameterKind::PositionalOnly + } else if ident == "positional_or_keyword" { + ParameterKind::PositionalOrKeyword + } else if ident == "keyword_only" { + ParameterKind::KeywordOnly + } else { + panic!("Unrecognised attribute") + } + } +} + +struct ArgAttribute { + kind: ParameterKind, + default: Option, + optional: bool, +} + +impl ArgAttribute { + fn from_attribute(attr: &Attribute) -> Option { + if !attr.path.is_ident("pyarg") { + return None; + } + + match attr.parse_meta().unwrap() { + Meta::List(list) => { + let mut iter = list.nested.iter(); + let first_arg = iter.next().expect("at least one argument in pyarg list"); + let kind = match first_arg { + NestedMeta::Meta(Meta::Word(ident)) => ParameterKind::from_ident(ident), + _ => panic!("Bad syntax for first pyarg attribute argument"), + }; + + let mut attribute = ArgAttribute { + kind, + default: None, + optional: false, + }; + + while let Some(arg) = iter.next() { + attribute.parse_argument(arg); + } + + assert!( + attribute.default.is_none() || !attribute.optional, + "Can't set both a default value and optional" + ); + + Some(attribute) + } + _ => panic!("Bad syntax for pyarg attribute"), + } + } + + fn parse_argument(&mut self, arg: &NestedMeta) { + match arg { + NestedMeta::Meta(Meta::Word(ident)) => { + if ident == "default" { + assert!(self.default.is_none(), "Default already set"); + let expr = syn::parse_str::("Default::default()").unwrap(); + self.default = Some(expr); + } else if ident == "optional" { + self.optional = true; + } else { + panic!("Unrecognised pyarg attribute '{}'", ident); + } + } + NestedMeta::Meta(Meta::NameValue(name_value)) => { + if name_value.ident == "default" { + assert!(self.default.is_none(), "Default already set"); + + match name_value.lit { + Lit::Str(ref val) => { + let expr = val + .parse::() + .expect("a valid expression for default argument"); + self.default = Some(expr); + } + _ => panic!("Expected string value for default argument"), + } + } else if name_value.ident == "optional" { + match name_value.lit { + Lit::Bool(ref val) => { + self.optional = val.value; + } + _ => panic!("Expected boolean value for optional argument"), + } + } else { + panic!("Unrecognised pyarg attribute '{}'", name_value.ident); + } + } + _ => panic!("Bad syntax for first pyarg attribute argument"), + }; + } +} + +fn generate_field(field: &Field) -> TokenStream2 { + let mut pyarg_attrs = field + .attrs + .iter() + .filter_map(ArgAttribute::from_attribute) + .collect::>(); + let attr = if pyarg_attrs.is_empty() { + ArgAttribute { + kind: ParameterKind::PositionalOrKeyword, + default: None, + optional: false, + } + } else if pyarg_attrs.len() == 1 { + pyarg_attrs.remove(0) + } else { + panic!( + "Multiple pyarg attributes on field '{}'", + field.ident.as_ref().unwrap() + ); + }; + + let name = &field.ident; + let middle = quote! { + .map(|x| crate::pyobject::TryFromObject::try_from_object(vm, x)).transpose()? + }; + let ending = if let Some(default) = attr.default { + quote! { + .unwrap_or_else(|| #default) + } + } else if attr.optional { + quote! { + .map(crate::function::OptionalArg::Present) + .unwrap_or(crate::function::OptionalArg::Missing) + } + } else { + let err = match attr.kind { + ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword => quote! { + crate::function::ArgumentError::TooFewArgs + }, + ParameterKind::KeywordOnly => quote! { + crate::function::ArgumentError::RequiredKeywordArgument(tringify!(#name)) + }, + }; + quote! { + .ok_or_else(|| #err)? + } + }; + + match attr.kind { + ParameterKind::PositionalOnly => { + quote! { + #name: args.take_positional()#middle#ending, + } + } + ParameterKind::PositionalOrKeyword => { + quote! { + #name: args.take_positional_keyword(stringify!(#name))#middle#ending, + } + } + ParameterKind::KeywordOnly => { + quote! { + #name: args.take_keyword(stringify!(#name))#middle#ending, + } + } + } +} + +pub fn impl_from_args(input: DeriveInput) -> TokenStream2 { + let rp_path = rustpython_path_derive(&input); + let fields = match input.data { + Data::Struct(ref data) => { + match data.fields { + Fields::Named(ref fields) => fields.named.iter().map(generate_field), + Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message + } + } + Data::Enum(_) | Data::Union(_) => unimplemented!(), // TODO: better error message + }; + + let name = &input.ident; + quote! { + impl #rp_path::function::FromArgs for #name { + fn from_args( + vm: &crate::vm::VirtualMachine, + args: &mut crate::function::PyFuncArgs + ) -> Result { + Ok(#name { #(#fields)* }) + } + } + } +} diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 374463f063..5a26030e40 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,12 +1,11 @@ extern crate proc_macro; use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; -use syn::{ - parse_macro_input, Attribute, AttributeArgs, Data, DeriveInput, Expr, Field, Fields, Ident, - ImplItem, Item, Lit, Meta, NestedMeta, -}; +use syn::{parse_macro_input, AttributeArgs, DeriveInput, Item}; + +mod from_args; +mod pyclass; fn rustpython_path(inside_vm: bool) -> syn::Path { let path = if inside_vm { @@ -46,381 +45,12 @@ fn rustpython_path_attr(attr: &AttributeArgs) -> syn::Path { pub fn derive_from_args(input: TokenStream) -> TokenStream { let ast: DeriveInput = syn::parse(input).unwrap(); - let gen = impl_from_args(ast); - gen.to_string().parse().unwrap() -} - -/// The kind of the python parameter, this corresponds to the value of Parameter.kind -/// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind) -enum ParameterKind { - PositionalOnly, - PositionalOrKeyword, - KeywordOnly, -} - -impl ParameterKind { - fn from_ident(ident: &Ident) -> ParameterKind { - if ident == "positional_only" { - ParameterKind::PositionalOnly - } else if ident == "positional_or_keyword" { - ParameterKind::PositionalOrKeyword - } else if ident == "keyword_only" { - ParameterKind::KeywordOnly - } else { - panic!("Unrecognised attribute") - } - } -} - -struct ArgAttribute { - kind: ParameterKind, - default: Option, - optional: bool, -} - -impl ArgAttribute { - fn from_attribute(attr: &Attribute) -> Option { - if !attr.path.is_ident("pyarg") { - return None; - } - - match attr.parse_meta().unwrap() { - Meta::List(list) => { - let mut iter = list.nested.iter(); - let first_arg = iter.next().expect("at least one argument in pyarg list"); - let kind = match first_arg { - NestedMeta::Meta(Meta::Word(ident)) => ParameterKind::from_ident(ident), - _ => panic!("Bad syntax for first pyarg attribute argument"), - }; - - let mut attribute = ArgAttribute { - kind, - default: None, - optional: false, - }; - - while let Some(arg) = iter.next() { - attribute.parse_argument(arg); - } - - assert!( - attribute.default.is_none() || !attribute.optional, - "Can't set both a default value and optional" - ); - - Some(attribute) - } - _ => panic!("Bad syntax for pyarg attribute"), - } - } - - fn parse_argument(&mut self, arg: &NestedMeta) { - match arg { - NestedMeta::Meta(Meta::Word(ident)) => { - if ident == "default" { - assert!(self.default.is_none(), "Default already set"); - let expr = syn::parse_str::("Default::default()").unwrap(); - self.default = Some(expr); - } else if ident == "optional" { - self.optional = true; - } else { - panic!("Unrecognised pyarg attribute '{}'", ident); - } - } - NestedMeta::Meta(Meta::NameValue(name_value)) => { - if name_value.ident == "default" { - assert!(self.default.is_none(), "Default already set"); - - match name_value.lit { - Lit::Str(ref val) => { - let expr = val - .parse::() - .expect("a valid expression for default argument"); - self.default = Some(expr); - } - _ => panic!("Expected string value for default argument"), - } - } else if name_value.ident == "optional" { - match name_value.lit { - Lit::Bool(ref val) => { - self.optional = val.value; - } - _ => panic!("Expected boolean value for optional argument"), - } - } else { - panic!("Unrecognised pyarg attribute '{}'", name_value.ident); - } - } - _ => panic!("Bad syntax for first pyarg attribute argument"), - }; - } -} - -fn generate_field(field: &Field) -> TokenStream2 { - let mut pyarg_attrs = field - .attrs - .iter() - .filter_map(ArgAttribute::from_attribute) - .collect::>(); - let attr = if pyarg_attrs.is_empty() { - ArgAttribute { - kind: ParameterKind::PositionalOrKeyword, - default: None, - optional: false, - } - } else if pyarg_attrs.len() == 1 { - pyarg_attrs.remove(0) - } else { - panic!( - "Multiple pyarg attributes on field '{}'", - field.ident.as_ref().unwrap() - ); - }; - - let name = &field.ident; - let middle = quote! { - .map(|x| crate::pyobject::TryFromObject::try_from_object(vm, x)).transpose()? - }; - let ending = if let Some(default) = attr.default { - quote! { - .unwrap_or_else(|| #default) - } - } else if attr.optional { - quote! { - .map(crate::function::OptionalArg::Present) - .unwrap_or(crate::function::OptionalArg::Missing) - } - } else { - let err = match attr.kind { - ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword => quote! { - crate::function::ArgumentError::TooFewArgs - }, - ParameterKind::KeywordOnly => quote! { - crate::function::ArgumentError::RequiredKeywordArgument(tringify!(#name)) - }, - }; - quote! { - .ok_or_else(|| #err)? - } - }; - - match attr.kind { - ParameterKind::PositionalOnly => { - quote! { - #name: args.take_positional()#middle#ending, - } - } - ParameterKind::PositionalOrKeyword => { - quote! { - #name: args.take_positional_keyword(stringify!(#name))#middle#ending, - } - } - ParameterKind::KeywordOnly => { - quote! { - #name: args.take_keyword(stringify!(#name))#middle#ending, - } - } - } -} - -fn impl_from_args(input: DeriveInput) -> TokenStream2 { - let rp_path = rustpython_path_derive(&input); - let fields = match input.data { - Data::Struct(ref data) => { - match data.fields { - Fields::Named(ref fields) => fields.named.iter().map(generate_field), - Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message - } - } - Data::Enum(_) | Data::Union(_) => unimplemented!(), // TODO: better error message - }; - - let name = &input.ident; - quote! { - impl #rp_path::function::FromArgs for #name { - fn from_args( - vm: &crate::vm::VirtualMachine, - args: &mut crate::function::PyFuncArgs - ) -> Result { - Ok(#name { #(#fields)* }) - } - } - } + from_args::impl_from_args(ast).into() } #[proc_macro_attribute] pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream { let attr = parse_macro_input!(attr as AttributeArgs); let item = parse_macro_input!(item as Item); - impl_py_class(attr, item).into() -} - -enum MethodKind { - Method, - Property, -} - -impl MethodKind { - fn to_ctx_constructor_fn(&self) -> Ident { - let f = match self { - MethodKind::Method => "new_rustfunc", - MethodKind::Property => "new_property", - }; - Ident::new(f, Span::call_site()) - } -} - -struct Method { - fn_name: Ident, - py_name: String, - kind: MethodKind, -} - -/// Parse an impl block into an iterator of methods -fn item_impl_to_methods<'a>(imp: &'a mut syn::ItemImpl) -> impl Iterator + 'a { - imp.items.iter_mut().filter_map(|item| { - if let ImplItem::Method(meth) = item { - let mut py_name = None; - let mut kind = MethodKind::Method; - let mut pymethod_to_remove = Vec::new(); - let metas = meth - .attrs - .iter() - .enumerate() - .filter_map(|(i, attr)| { - if attr.path.is_ident("pymethod") { - let meta = attr.parse_meta().expect("Invalid attribute"); - // remove #[pymethod] because there's no actual proc macro - // implementation for it - pymethod_to_remove.push(i); - match meta { - Meta::List(list) => Some(list), - Meta::Word(_) => None, - Meta::NameValue(_) => panic!( - "#[pymethod = ...] attribute on a method should be a list, like \ - #[pymethod(...)]" - ), - } - } else { - None - } - }) - .flat_map(|attr| attr.nested); - for meta in metas { - if let NestedMeta::Meta(meta) = meta { - match meta { - Meta::NameValue(name_value) => { - if name_value.ident == "name" { - if let Lit::Str(s) = &name_value.lit { - py_name = Some(s.value()); - } else { - panic!("#[pymethod(name = ...)] must be a string"); - } - } - } - Meta::Word(ident) => { - if ident == "property" { - kind = MethodKind::Property - } - } - _ => {} - } - } - } - // if there are no #[pymethods]s, then it's not a method, so exclude it from - // the final result - if pymethod_to_remove.is_empty() { - return None; - } - for i in pymethod_to_remove { - meth.attrs.remove(i); - } - let py_name = py_name.unwrap_or_else(|| meth.sig.ident.to_string()); - Some(Method { - fn_name: meth.sig.ident.clone(), - py_name, - kind, - }) - } else { - None - } - }) -} - -fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { - let mut imp = if let Item::Impl(imp) = item { - imp - } else { - return quote!(#item); - }; - let rp_path = rustpython_path_attr(&attr); - let mut class_name = None; - for attr in attr { - if let NestedMeta::Meta(meta) = attr { - if let Meta::NameValue(name_value) = meta { - if name_value.ident == "name" { - if let Lit::Str(s) = name_value.lit { - class_name = Some(s.value()); - } else { - panic!("#[pyclass(name = ...)] must be a string"); - } - } - } - } - } - let class_name = class_name.expect("#[pyclass] must have a name"); - let mut doc: Option> = None; - for attr in imp.attrs.iter() { - if attr.path.is_ident("doc") { - let meta = attr.parse_meta().expect("expected doc attr to be a meta"); - if let Meta::NameValue(name_value) = meta { - if let Lit::Str(s) = name_value.lit { - let val = s.value().trim().to_string(); - match doc { - Some(ref mut doc) => doc.push(val), - None => doc = Some(vec![val]), - } - } else { - panic!("expected #[doc = ...] to be a string") - } - } else { - panic!("expected #[doc] to be a NameValue, e.g. #[doc = \"...\""); - } - } - } - let doc = match doc { - Some(doc) => { - let doc = doc.join("\n"); - quote!(Some(#doc)) - } - None => quote!(None), - }; - let methods: Vec<_> = item_impl_to_methods(&mut imp).collect(); - let ty = &imp.self_ty; - let methods = methods.iter().map( - |Method { - py_name, - fn_name, - kind, - }| { - let constructor_fn = kind.to_ctx_constructor_fn(); - quote! { - ctx.set_attr(class, #py_name, ctx.#constructor_fn(Self::#fn_name)); - } - }, - ); - - quote! { - #imp - impl #rp_path::pyobject::IntoPyClass for #ty { - const NAME: &'static str = #class_name; - const DOC: Option<&'static str> = #doc; - fn _extend_class( - ctx: &#rp_path::pyobject::PyContext, - class: &#rp_path::obj::objtype::PyClassRef, - ) { - #(#methods)* - } - } - } + pyclass::impl_py_class(attr, item).into() } diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs new file mode 100644 index 0000000000..4497424f2e --- /dev/null +++ b/derive/src/pyclass.rs @@ -0,0 +1,179 @@ +use super::rustpython_path_attr; +use proc_macro2::{Span, TokenStream as TokenStream2}; +use quote::quote; +use syn::{Attribute, AttributeArgs, Ident, ImplItem, Item, Lit, Meta, MethodSig, NestedMeta}; + +enum MethodKind { + Method, + Property, +} + +impl MethodKind { + fn to_ctx_constructor_fn(&self) -> Ident { + let f = match self { + MethodKind::Method => "new_rustfunc", + MethodKind::Property => "new_property", + }; + Ident::new(f, Span::call_site()) + } +} + +struct Method { + fn_name: Ident, + py_name: String, + kind: MethodKind, +} + +impl Method { + fn from_syn(attrs: &mut Vec, sig: &MethodSig) -> Option { + let mut py_name = None; + let mut kind = MethodKind::Method; + let mut pymethod_to_remove = Vec::new(); + let metas = attrs + .iter() + .enumerate() + .filter_map(|(i, attr)| { + if attr.path.is_ident("pymethod") { + let meta = attr.parse_meta().expect("Invalid attribute"); + // remove #[pymethod] because there's no actual proc macro + // implementation for it + pymethod_to_remove.push(i); + match meta { + Meta::List(list) => Some(list), + Meta::Word(_) => None, + Meta::NameValue(_) => panic!( + "#[pymethod = ...] attribute on a method should be a list, like \ + #[pymethod(...)]" + ), + } + } else { + None + } + }) + .flat_map(|attr| attr.nested); + for meta in metas { + if let NestedMeta::Meta(meta) = meta { + match meta { + Meta::NameValue(name_value) => { + if name_value.ident == "name" { + if let Lit::Str(s) = &name_value.lit { + py_name = Some(s.value()); + } else { + panic!("#[pymethod(name = ...)] must be a string"); + } + } + } + Meta::Word(ident) => { + if ident == "property" { + kind = MethodKind::Property + } + } + _ => {} + } + } + } + // if there are no #[pymethods]s, then it's not a method, so exclude it from + // the final result + if pymethod_to_remove.is_empty() { + return None; + } + for i in pymethod_to_remove { + attrs.remove(i); + } + let py_name = py_name.unwrap_or_else(|| sig.ident.to_string()); + Some(Method { + fn_name: sig.ident.clone(), + py_name, + kind, + }) + } +} + +/// Parse an impl block into an iterator of methods +fn item_impl_to_methods<'a>(imp: &'a mut syn::ItemImpl) -> impl Iterator + 'a { + imp.items.iter_mut().filter_map(|item| { + if let ImplItem::Method(meth) = item { + Method::from_syn(&mut meth.attrs, &meth.sig) + } else { + None + } + }) +} + +pub fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { + let mut imp = if let Item::Impl(imp) = item { + imp + } else { + return quote!(#item); + }; + let rp_path = rustpython_path_attr(&attr); + let mut class_name = None; + for attr in attr { + if let NestedMeta::Meta(meta) = attr { + if let Meta::NameValue(name_value) = meta { + if name_value.ident == "name" { + if let Lit::Str(s) = name_value.lit { + class_name = Some(s.value()); + } else { + panic!("#[pyclass(name = ...)] must be a string"); + } + } + } + } + } + let class_name = class_name.expect("#[pyclass] must have a name"); + let mut doc: Option> = None; + for attr in imp.attrs.iter() { + if attr.path.is_ident("doc") { + let meta = attr.parse_meta().expect("expected doc attr to be a meta"); + if let Meta::NameValue(name_value) = meta { + if let Lit::Str(s) = name_value.lit { + let val = s.value().trim().to_string(); + match doc { + Some(ref mut doc) => doc.push(val), + None => doc = Some(vec![val]), + } + } else { + panic!("expected #[doc = ...] to be a string") + } + } else { + panic!("expected #[doc] to be a NameValue, e.g. #[doc = \"...\""); + } + } + } + let doc = match doc { + Some(doc) => { + let doc = doc.join("\n"); + quote!(Some(#doc)) + } + None => quote!(None), + }; + let methods: Vec<_> = item_impl_to_methods(&mut imp).collect(); + let ty = &imp.self_ty; + let methods = methods.iter().map( + |Method { + py_name, + fn_name, + kind, + }| { + let constructor_fn = kind.to_ctx_constructor_fn(); + quote! { + ctx.set_attr(class, #py_name, ctx.#constructor_fn(Self::#fn_name)); + } + }, + ); + + quote! { + #imp + impl #rp_path::pyobject::IntoPyClass for #ty { + const NAME: &'static str = #class_name; + const DOC: Option<&'static str> = #doc; + fn _extend_class( + ctx: &#rp_path::pyobject::PyContext, + class: &#rp_path::obj::objtype::PyClassRef, + ) { + #(#methods)* + } + } + } +} diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 29124ad118..4a300dceff 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -30,7 +30,9 @@ extern crate statrs; extern crate rustpython_parser; #[macro_use] -pub extern crate rustpython_derive; +extern crate rustpython_derive; + +pub use rustpython_derive::*; //extern crate eval; use eval::eval::*; // use py_code_object::{Function, NativeType, PyCodeObject}; From 15cffc4d2b023ec31aade0c72d8821f72be74265 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 30 Mar 2019 22:30:14 -0500 Subject: [PATCH 108/884] Clean up a bit --- derive/src/pyclass.rs | 23 ++++++++------- vm/src/obj/objstr.rs | 65 ++----------------------------------------- 2 files changed, 13 insertions(+), 75 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 4497424f2e..ad03b58c9c 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -89,17 +89,6 @@ impl Method { } } -/// Parse an impl block into an iterator of methods -fn item_impl_to_methods<'a>(imp: &'a mut syn::ItemImpl) -> impl Iterator + 'a { - imp.items.iter_mut().filter_map(|item| { - if let ImplItem::Method(meth) = item { - Method::from_syn(&mut meth.attrs, &meth.sig) - } else { - None - } - }) -} - pub fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { let mut imp = if let Item::Impl(imp) = item { imp @@ -148,7 +137,17 @@ pub fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { } None => quote!(None), }; - let methods: Vec<_> = item_impl_to_methods(&mut imp).collect(); + let methods = imp + .items + .iter_mut() + .filter_map(|item| { + if let ImplItem::Method(meth) = item { + Method::from_syn(&mut meth.attrs, &meth.sig) + } else { + None + } + }) + .collect::>(); let ty = &imp.self_ty; let methods = methods.iter().map( |Method { diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 70c4206656..9520b8af9c 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -748,69 +748,8 @@ impl IntoPyObject for String { } } -#[rustfmt::skip] // to avoid line splitting -pub fn init(context: &PyContext) { - let str_type = &context.str_type; - - PyStringRef::extend_class(context, str_type); - // extend_class!(context, str_type, { - // "__add__" => context.new_rustfunc(PyStringRef::add), - // "__bool__" => context.new_rustfunc(PyStringRef::bool), - // "__contains__" => context.new_rustfunc(PyStringRef::contains), - // "__doc__" => context.new_str(str_doc.to_string()), - // "__eq__" => context.new_rustfunc(PyStringRef::eq), - // "__ge__" => context.new_rustfunc(PyStringRef::ge), - // "__getitem__" => context.new_rustfunc(PyStringRef::getitem), - // "__gt__" => context.new_rustfunc(PyStringRef::gt), - // "__hash__" => context.new_rustfunc(PyStringRef::hash), - // "__lt__" => context.new_rustfunc(PyStringRef::lt), - // "__le__" => context.new_rustfunc(PyStringRef::le), - // "__len__" => context.new_rustfunc(PyStringRef::len), - // "__mul__" => context.new_rustfunc(PyStringRef::mul), - // "__new__" => context.new_rustfunc(str_new), - // "__repr__" => context.new_rustfunc(PyStringRef::repr), - // "__str__" => context.new_rustfunc(PyStringRef::str), - // "capitalize" => context.new_rustfunc(PyStringRef::capitalize), - // "casefold" => context.new_rustfunc(PyStringRef::casefold), - // "center" => context.new_rustfunc(PyStringRef::center), - // "count" => context.new_rustfunc(PyStringRef::count), - // "endswith" => context.new_rustfunc(PyStringRef::endswith), - // "expandtabs" => context.new_rustfunc(PyStringRef::expandtabs), - // "find" => context.new_rustfunc(PyStringRef::find), - // "format" => context.new_rustfunc(str_format), - // "index" => context.new_rustfunc(PyStringRef::index), - // "isalnum" => context.new_rustfunc(PyStringRef::isalnum), - // "isalpha" => context.new_rustfunc(PyStringRef::isalpha), - // "isascii" => context.new_rustfunc(PyStringRef::isascii), - // "isdecimal" => context.new_rustfunc(PyStringRef::isdecimal), - // "isdigit" => context.new_rustfunc(PyStringRef::isdigit), - // "isidentifier" => context.new_rustfunc(PyStringRef::isidentifier), - // "islower" => context.new_rustfunc(PyStringRef::islower), - // "isnumeric" => context.new_rustfunc(PyStringRef::isnumeric), - // "isspace" => context.new_rustfunc(PyStringRef::isspace), - // "isupper" => context.new_rustfunc(PyStringRef::isupper), - // "istitle" => context.new_rustfunc(PyStringRef::istitle), - // "join" => context.new_rustfunc(PyStringRef::join), - // "lower" => context.new_rustfunc(PyStringRef::lower), - // "ljust" => context.new_rustfunc(PyStringRef::ljust), - // "lstrip" => context.new_rustfunc(PyStringRef::lstrip), - // "partition" => context.new_rustfunc(PyStringRef::partition), - // "replace" => context.new_rustfunc(PyStringRef::replace), - // "rfind" => context.new_rustfunc(PyStringRef::rfind), - // "rindex" => context.new_rustfunc(PyStringRef::rindex), - // "rjust" => context.new_rustfunc(PyStringRef::rjust), - // "rpartition" => context.new_rustfunc(PyStringRef::rpartition), - // "rsplit" => context.new_rustfunc(PyStringRef::rsplit), - // "rstrip" => context.new_rustfunc(PyStringRef::rstrip), - // "split" => context.new_rustfunc(PyStringRef::split), - // "splitlines" => context.new_rustfunc(PyStringRef::splitlines), - // "startswith" => context.new_rustfunc(PyStringRef::startswith), - // "strip" => context.new_rustfunc(PyStringRef::strip), - // "swapcase" => context.new_rustfunc(PyStringRef::swapcase), - // "title" => context.new_rustfunc(PyStringRef::title), - // "upper" => context.new_rustfunc(PyStringRef::upper), - // "zfill" => context.new_rustfunc(PyStringRef::zfill), - // }); +pub fn init(ctx: &PyContext) { + PyStringRef::extend_class(ctx, &ctx.str_type); } pub fn get_value(obj: &PyObjectRef) -> String { From 1b57f0783466d5e364b4307f6943fa8679426a30 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 30 Mar 2019 22:56:37 -0500 Subject: [PATCH 109/884] Process lalrpop in cargo's target directory --- parser/.gitignore | 4 ---- parser/build.rs | 5 +---- parser/src/lib.rs | 4 ++-- parser/src/python.lalrpop | 1 - 4 files changed, 3 insertions(+), 11 deletions(-) delete mode 100644 parser/.gitignore diff --git a/parser/.gitignore b/parser/.gitignore deleted file mode 100644 index 401e8cc22b..0000000000 --- a/parser/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -src/python.rs -target/ -Cargo.lock - diff --git a/parser/build.rs b/parser/build.rs index 65147ed0ed..19f1027217 100644 --- a/parser/build.rs +++ b/parser/build.rs @@ -1,8 +1,5 @@ use lalrpop; fn main() { - lalrpop::Configuration::new() - .generate_in_source_tree() - .process() - .unwrap(); + lalrpop::process_root().unwrap() } diff --git a/parser/src/lib.rs b/parser/src/lib.rs index b10c3d5514..4f0105a9ac 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -1,11 +1,11 @@ #[macro_use] extern crate log; +use lalrpop_util::lalrpop_mod; pub mod ast; pub mod error; mod fstring; pub mod lexer; pub mod parser; -#[cfg_attr(rustfmt, rustfmt_skip)] -mod python; +lalrpop_mod!(python); pub mod token; diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 9e5b309b1e..437d001fec 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -2,7 +2,6 @@ // See also: https://github.com/antlr/grammars-v4/blob/master/python3/Python3.g4 // See also: file:///usr/share/doc/python/html/reference/compound_stmts.html#function-definitions // See also: https://greentreesnakes.readthedocs.io/en/latest/nodes.html#keyword -#![allow(unknown_lints,clippy)] use std::iter::FromIterator; From 60ad5489f15d8911afead86b4c84907fbe20eeec Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 30 Mar 2019 22:58:46 -0500 Subject: [PATCH 110/884] Ignore clippy lints in the lalrpop generated file --- parser/src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 4f0105a9ac..5cc8ca3c44 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -7,5 +7,8 @@ pub mod error; mod fstring; pub mod lexer; pub mod parser; -lalrpop_mod!(python); +lalrpop_mod!( + #[allow(clippy::all)] + python +); pub mod token; From f994f8660e487de93f1ad1080512e4af24e92fee Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 28 Mar 2019 23:10:24 +0200 Subject: [PATCH 111/884] Function support __doc__ --- tests/snippets/function.py | 3 +++ vm/src/compile.rs | 32 +++++++++++++++++++++++++++++++- vm/src/frame.rs | 3 +++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/tests/snippets/function.py b/tests/snippets/function.py index b1ea968609..f327d88cb6 100644 --- a/tests/snippets/function.py +++ b/tests/snippets/function.py @@ -1,7 +1,10 @@ def foo(): + """test""" return 42 assert foo() == 42 +assert foo.__doc__ == "test" + def my_func(a,): return a+2 diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 00bafe7278..037d9e95c3 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -597,7 +597,20 @@ impl Compiler { self.in_loop = false; self.in_function_def = true; let mut flags = self.enter_function(name, args)?; - self.compile_statements(body)?; + + let doc = get_doc(body); + let (new_body, doc_str) = match doc { + Some(val) => { + if let Some((_, body_rest)) = body.split_first() { + (body_rest, val) + } else { + (body, "".to_string()) + } + } + None => (body, "".to_string()), + }; + + self.compile_statements(new_body)?; // Emit None at end: self.emit(Instruction::LoadConst { @@ -644,6 +657,10 @@ impl Compiler { }); } + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { value: doc_str }, + }); + self.emit(Instruction::LoadConst { value: bytecode::Constant::Code { code: Box::new(code), @@ -1511,6 +1528,19 @@ impl Compiler { } } +fn get_doc(body: &[ast::LocatedStatement]) -> Option { + if let Some(val) = body.get(0) { + if let ast::Statement::Expression { ref expression } = val.node { + if let ast::Expression::String { ref value } = expression { + if let ast::StringGroup::Constant { ref value } = value { + return Some(value.to_string()); + } + } + } + } + None +} + #[cfg(test)] mod tests { use super::Compiler; diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 88a619eb36..5a22aabe9a 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -568,6 +568,8 @@ impl Frame { .downcast() .expect("Second to top value on the stack must be a code object"); + let doc = self.pop_value(); + let annotations = if flags.contains(bytecode::FunctionOpArg::HAS_ANNOTATIONS) { self.pop_value() } else { @@ -586,6 +588,7 @@ impl Frame { let obj = vm.ctx.new_function(code_obj, scope, defaults); vm.ctx.set_attr(&obj, "__annotations__", annotations); + vm.ctx.set_attr(&obj, "__doc__", doc); self.push_value(obj); Ok(None) From c76b31866d2fae1d6b8112be47b6ccbaca1f5cf5 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 28 Mar 2019 23:15:39 +0200 Subject: [PATCH 112/884] Add more tests for function doc --- tests/snippets/function.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/tests/snippets/function.py b/tests/snippets/function.py index f327d88cb6..7d3f25709a 100644 --- a/tests/snippets/function.py +++ b/tests/snippets/function.py @@ -5,8 +5,35 @@ def foo(): assert foo() == 42 assert foo.__doc__ == "test" - def my_func(a,): return a+2 assert my_func(2) == 4 + + +def f1(): + + """test1""" + pass + +assert f1.__doc__ == "test1" + +def f2(): + '''test2''' + pass + +assert f2.__doc__ == "test2" + +def f3(): + """ + test3 + """ + pass + +assert f3.__doc__ == "\n test3\n " + +def f4(): + "test4" + pass + +assert f4.__doc__ == "test4" From ab3cde77b4b2b7d71ffe7e5d05b94e3c45c677e3 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 28 Mar 2019 23:45:45 +0200 Subject: [PATCH 113/884] Support class __doc__ --- tests/snippets/class.py | 3 +-- vm/src/builtins.rs | 2 ++ vm/src/compile.rs | 47 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/tests/snippets/class.py b/tests/snippets/class.py index 06e125f217..bd94e0516d 100644 --- a/tests/snippets/class.py +++ b/tests/snippets/class.py @@ -37,8 +37,7 @@ def kungfu(x): assert x == 3 -# TODO: -# assert Bar.__doc__ == " W00t " +assert Bar.__doc__ == " W00t " bar = Bar(42) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index ed656c0fbf..705ec69057 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -769,6 +769,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { let function = args.shift(); let name_arg = args.shift(); + let doc = args.shift(); let bases = args.args.clone(); let mut metaclass = if let Some(metaclass) = args.get_optional_kwarg("metaclass") { PyClassRef::try_from_object(vm, metaclass)? @@ -801,5 +802,6 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu vec![name_arg, bases, namespace.into_object()], )?; cells.set_item(&vm.ctx, "__class__", class.clone()); + vm.ctx.set_attr(&class, "__doc__", doc); Ok(class) } diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 037d9e95c3..58a582ae75 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -706,13 +706,34 @@ impl Compiler { line_number, name.to_string(), )); - self.compile_statements(body)?; + + let doc = get_doc(body); + let (new_body, doc_str) = match doc { + Some(val) => { + if let Some((_, body_rest)) = body.split_first() { + (body_rest, val) + } else { + (body, "".to_string()) + } + } + None => (body, "".to_string()), + }; + + self.compile_statements(new_body)?; self.emit(Instruction::LoadConst { value: bytecode::Constant::None, }); self.emit(Instruction::ReturnValue); let code = self.pop_code_object(); + + // function doc + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: "".to_string(), + }, + }); + self.emit(Instruction::LoadConst { value: bytecode::Constant::Code { code: Box::new(code), @@ -735,6 +756,11 @@ impl Compiler { }, }); + // class doc + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { value: doc_str }, + }); + for base in bases { self.compile_expression(base)?; } @@ -759,11 +785,11 @@ impl Compiler { }, }); self.emit(Instruction::CallFunction { - typ: CallType::Keyword(2 + keywords.len() + bases.len()), + typ: CallType::Keyword(3 + keywords.len() + bases.len()), }); } else { self.emit(Instruction::CallFunction { - typ: CallType::Positional(2 + bases.len()), + typ: CallType::Positional(3 + bases.len()), }); } @@ -1142,6 +1168,14 @@ impl Compiler { self.compile_expression(body)?; self.emit(Instruction::ReturnValue); let code = self.pop_code_object(); + + // function doc + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: "".to_string(), + }, + }); + self.emit(Instruction::LoadConst { value: bytecode::Constant::Code { code: Box::new(code), @@ -1428,6 +1462,13 @@ impl Compiler { // Fetch code for listcomp function: let code = self.pop_code_object(); + // function doc + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: "".to_string(), + }, + }); + // List comprehension code: self.emit(Instruction::LoadConst { value: bytecode::Constant::Code { From 6d53fe692408912c3558e827c296cb98a9f98e68 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 28 Mar 2019 23:49:40 +0200 Subject: [PATCH 114/884] Add more tests for class doc --- tests/snippets/class.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/snippets/class.py b/tests/snippets/class.py index bd94e0516d..9dbce566dc 100644 --- a/tests/snippets/class.py +++ b/tests/snippets/class.py @@ -116,3 +116,31 @@ def f(self): assert type(a) is super assert a.conjugate() == 1 + +class T1: + "test1" + +assert T1.__doc__ == "test1" + +class T2: + '''test2''' + +assert T2.__doc__ == "test2" + +class T3: + """ + test3 + """ + +assert T3.__doc__ == "\n test3\n " + +class T4: + + """test4""" + + def t1(self): + """t1""" + pass + +assert T4.__doc__ == "test4" +assert T4.t1.__doc__ == "t1" From 4e99350e5c7e8850d0557b2fac7c19773f7b1dab Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 31 Mar 2019 18:49:04 +0300 Subject: [PATCH 115/884] Use StoreAttr for __doc__ --- vm/src/builtins.rs | 2 - vm/src/compile.rs | 94 ++++++++++++++++++---------------------------- vm/src/frame.rs | 3 -- 3 files changed, 36 insertions(+), 63 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 705ec69057..ed656c0fbf 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -769,7 +769,6 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { let function = args.shift(); let name_arg = args.shift(); - let doc = args.shift(); let bases = args.args.clone(); let mut metaclass = if let Some(metaclass) = args.get_optional_kwarg("metaclass") { PyClassRef::try_from_object(vm, metaclass)? @@ -802,6 +801,5 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu vec![name_arg, bases, namespace.into_object()], )?; cells.set_item(&vm.ctx, "__class__", class.clone()); - vm.ctx.set_attr(&class, "__doc__", doc); Ok(class) } diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 58a582ae75..f79980af9f 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -598,17 +598,7 @@ impl Compiler { self.in_function_def = true; let mut flags = self.enter_function(name, args)?; - let doc = get_doc(body); - let (new_body, doc_str) = match doc { - Some(val) => { - if let Some((_, body_rest)) = body.split_first() { - (body_rest, val) - } else { - (body, "".to_string()) - } - } - None => (body, "".to_string()), - }; + let (new_body, doc_str) = get_doc(body); self.compile_statements(new_body)?; @@ -657,10 +647,6 @@ impl Compiler { }); } - self.emit(Instruction::LoadConst { - value: bytecode::Constant::String { value: doc_str }, - }); - self.emit(Instruction::LoadConst { value: bytecode::Constant::Code { code: Box::new(code), @@ -679,6 +665,20 @@ impl Compiler { self.emit(Instruction::StoreName { name: name.to_string(), }); + + if let Some(doc_string) = doc_str { + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: doc_string.to_string(), + }, + }); + self.emit(Instruction::LoadName { + name: name.to_string(), + }); + self.emit(Instruction::StoreAttr { + name: "__doc__".to_string(), + }); + } self.in_loop = was_in_loop; self.in_function_def = was_in_function_def; Ok(()) @@ -707,17 +707,7 @@ impl Compiler { name.to_string(), )); - let doc = get_doc(body); - let (new_body, doc_str) = match doc { - Some(val) => { - if let Some((_, body_rest)) = body.split_first() { - (body_rest, val) - } else { - (body, "".to_string()) - } - } - None => (body, "".to_string()), - }; + let (new_body, doc_str) = get_doc(body); self.compile_statements(new_body)?; self.emit(Instruction::LoadConst { @@ -727,13 +717,6 @@ impl Compiler { let code = self.pop_code_object(); - // function doc - self.emit(Instruction::LoadConst { - value: bytecode::Constant::String { - value: "".to_string(), - }, - }); - self.emit(Instruction::LoadConst { value: bytecode::Constant::Code { code: Box::new(code), @@ -756,11 +739,6 @@ impl Compiler { }, }); - // class doc - self.emit(Instruction::LoadConst { - value: bytecode::Constant::String { value: doc_str }, - }); - for base in bases { self.compile_expression(base)?; } @@ -785,11 +763,11 @@ impl Compiler { }, }); self.emit(Instruction::CallFunction { - typ: CallType::Keyword(3 + keywords.len() + bases.len()), + typ: CallType::Keyword(2 + keywords.len() + bases.len()), }); } else { self.emit(Instruction::CallFunction { - typ: CallType::Positional(3 + bases.len()), + typ: CallType::Positional(2 + bases.len()), }); } @@ -798,6 +776,19 @@ impl Compiler { self.emit(Instruction::StoreName { name: name.to_string(), }); + if let Some(doc_string) = doc_str { + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: doc_string.to_string(), + }, + }); + self.emit(Instruction::LoadName { + name: name.to_string(), + }); + self.emit(Instruction::StoreAttr { + name: "__doc__".to_string(), + }); + } self.in_loop = was_in_loop; Ok(()) } @@ -1168,14 +1159,6 @@ impl Compiler { self.compile_expression(body)?; self.emit(Instruction::ReturnValue); let code = self.pop_code_object(); - - // function doc - self.emit(Instruction::LoadConst { - value: bytecode::Constant::String { - value: "".to_string(), - }, - }); - self.emit(Instruction::LoadConst { value: bytecode::Constant::Code { code: Box::new(code), @@ -1462,13 +1445,6 @@ impl Compiler { // Fetch code for listcomp function: let code = self.pop_code_object(); - // function doc - self.emit(Instruction::LoadConst { - value: bytecode::Constant::String { - value: "".to_string(), - }, - }); - // List comprehension code: self.emit(Instruction::LoadConst { value: bytecode::Constant::Code { @@ -1569,17 +1545,19 @@ impl Compiler { } } -fn get_doc(body: &[ast::LocatedStatement]) -> Option { +fn get_doc(body: &[ast::LocatedStatement]) -> (&[ast::LocatedStatement], Option) { if let Some(val) = body.get(0) { if let ast::Statement::Expression { ref expression } = val.node { if let ast::Expression::String { ref value } = expression { if let ast::StringGroup::Constant { ref value } = value { - return Some(value.to_string()); + if let Some((_, body_rest)) = body.split_first() { + return (body_rest, Some(value.to_string())); + } } } } } - None + (body, None) } #[cfg(test)] diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 5a22aabe9a..88a619eb36 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -568,8 +568,6 @@ impl Frame { .downcast() .expect("Second to top value on the stack must be a code object"); - let doc = self.pop_value(); - let annotations = if flags.contains(bytecode::FunctionOpArg::HAS_ANNOTATIONS) { self.pop_value() } else { @@ -588,7 +586,6 @@ impl Frame { let obj = vm.ctx.new_function(code_obj, scope, defaults); vm.ctx.set_attr(&obj, "__annotations__", annotations); - vm.ctx.set_attr(&obj, "__doc__", doc); self.push_value(obj); Ok(None) From 53b46a7b3245741a27ec05b30a23f1a9f26538ee Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 31 Mar 2019 17:23:33 -0500 Subject: [PATCH 116/884] Split some of #[pyclass] off into #[pyimpl] --- derive/src/lib.rs | 9 ++++- derive/src/pyclass.rs | 94 ++++++++++++++++++++++++++----------------- vm/src/obj/objstr.rs | 25 ++++++------ vm/src/pyobject.rs | 16 ++++++-- 4 files changed, 91 insertions(+), 53 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 5a26030e40..adde078f03 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -52,5 +52,12 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream { pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream { let attr = parse_macro_input!(attr as AttributeArgs); let item = parse_macro_input!(item as Item); - pyclass::impl_py_class(attr, item).into() + pyclass::impl_pyclass(attr, item).into() +} + +#[proc_macro_attribute] +pub fn pyimpl(attr: TokenStream, item: TokenStream) -> TokenStream { + let attr = parse_macro_input!(attr as AttributeArgs); + let item = parse_macro_input!(item as Item); + pyclass::impl_pyimpl(attr, item).into() } diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index ad03b58c9c..56e216f357 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -89,13 +89,62 @@ impl Method { } } -pub fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { +pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { let mut imp = if let Item::Impl(imp) = item { imp } else { return quote!(#item); }; + + let rp_path = rustpython_path_attr(&attr); + + let methods = imp + .items + .iter_mut() + .filter_map(|item| { + if let ImplItem::Method(meth) = item { + Method::from_syn(&mut meth.attrs, &meth.sig) + } else { + None + } + }) + .collect::>(); + let ty = &imp.self_ty; + let methods = methods.iter().map( + |Method { + py_name, + fn_name, + kind, + }| { + let constructor_fn = kind.to_ctx_constructor_fn(); + quote! { + ctx.set_attr(class, #py_name, ctx.#constructor_fn(Self::#fn_name)); + } + }, + ); + + quote! { + #imp + impl #rp_path::pyobject::PyClassImpl for #ty { + fn impl_extend_class( + ctx: &#rp_path::pyobject::PyContext, + class: &#rp_path::obj::objtype::PyClassRef, + ) { + #(#methods)* + } + } + } +} + +pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { + let struc = if let Item::Struct(struc) = item { + struc + } else { + panic!("#[pyclass] can only be on a struct declaration"); + }; + let rp_path = rustpython_path_attr(&attr); + let mut class_name = None; for attr in attr { if let NestedMeta::Meta(meta) = attr { @@ -110,9 +159,10 @@ pub fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { } } } - let class_name = class_name.expect("#[pyclass] must have a name"); + let class_name = class_name.unwrap_or_else(|| struc.ident.to_string()); + let mut doc: Option> = None; - for attr in imp.attrs.iter() { + for attr in struc.attrs.iter() { if attr.path.is_ident("doc") { let meta = attr.parse_meta().expect("expected doc attr to be a meta"); if let Meta::NameValue(name_value) = meta { @@ -125,8 +175,6 @@ pub fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { } else { panic!("expected #[doc = ...] to be a string") } - } else { - panic!("expected #[doc] to be a NameValue, e.g. #[doc = \"...\""); } } } @@ -137,42 +185,14 @@ pub fn impl_py_class(attr: AttributeArgs, item: Item) -> TokenStream2 { } None => quote!(None), }; - let methods = imp - .items - .iter_mut() - .filter_map(|item| { - if let ImplItem::Method(meth) = item { - Method::from_syn(&mut meth.attrs, &meth.sig) - } else { - None - } - }) - .collect::>(); - let ty = &imp.self_ty; - let methods = methods.iter().map( - |Method { - py_name, - fn_name, - kind, - }| { - let constructor_fn = kind.to_ctx_constructor_fn(); - quote! { - ctx.set_attr(class, #py_name, ctx.#constructor_fn(Self::#fn_name)); - } - }, - ); + + let ty = &struc.ident; quote! { - #imp - impl #rp_path::pyobject::IntoPyClass for #ty { + #struc + impl #rp_path::pyobject::PyClassDef for #ty { const NAME: &'static str = #class_name; const DOC: Option<&'static str> = #doc; - fn _extend_class( - ctx: &#rp_path::pyobject::PyContext, - class: &#rp_path::obj::objtype::PyClassRef, - ) { - #(#methods)* - } } } } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 9520b8af9c..922306a9e3 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -10,7 +10,7 @@ use unicode_segmentation::UnicodeSegmentation; use crate::format::{FormatParseError, FormatPart, FormatString}; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - IdProtocol, IntoPyClass, IntoPyObject, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, + IdProtocol, IntoPyObject, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TryIntoRef, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -20,6 +20,17 @@ use super::objsequence::PySliceableSequence; use super::objslice::PySlice; use super::objtype::{self, PyClassRef}; +/// str(object='') -> str +/// str(bytes_or_buffer[, encoding[, errors]]) -> str +/// +/// Create a new string object from the given object. If encoding or +/// errors is specified, then the object must expose a data buffer +/// that will be decoded using the given encoding and error handler. +/// Otherwise, returns the result of object.__str__() (if defined) +/// or repr(object). +/// encoding defaults to sys.getdefaultencoding(). +/// errors defaults to 'strict'." +#[pyclass(name = "str", __inside_vm)] #[derive(Clone, Debug)] pub struct PyString { // TODO: shouldn't be public @@ -48,17 +59,7 @@ impl TryIntoRef for &str { } } -#[pyclass(__inside_vm, name = "str")] -/// str(object='') -> str -/// str(bytes_or_buffer[, encoding[, errors]]) -> str -/// -/// Create a new string object from the given object. If encoding or -/// errors is specified, then the object must expose a data buffer -/// that will be decoded using the given encoding and error handler. -/// Otherwise, returns the result of object.__str__() (if defined) -/// or repr(object). -/// encoding defaults to sys.getdefaultencoding(). -/// errors defaults to 'strict'." +#[pyimpl(__inside_vm)] impl PyStringRef { // TODO: should with following format // class str(object='') diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index a0e737d4ca..801522ea07 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1259,14 +1259,24 @@ where } } -pub trait IntoPyClass { +pub trait PyClassDef { const NAME: &'static str; const DOC: Option<&'static str> = None; +} + +impl PyClassDef for PyRef +where + T: PyClassDef, +{ + const NAME: &'static str = T::NAME; + const DOC: Option<&'static str> = T::DOC; +} - fn _extend_class(ctx: &PyContext, class: &PyClassRef); +pub trait PyClassImpl: PyClassDef { + fn impl_extend_class(ctx: &PyContext, class: &PyClassRef); fn extend_class(ctx: &PyContext, class: &PyClassRef) { - Self::_extend_class(ctx, class); + Self::impl_extend_class(ctx, class); if let Some(doc) = Self::DOC { ctx.set_attr(class, "__doc__", ctx.new_str(doc.into())); } From 42768b20e57a1a699340c78d21a5268910dc0b93 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 31 Mar 2019 17:53:53 -0500 Subject: [PATCH 117/884] Small demo improvements --- wasm/demo/snippets/mandelbrot.py | 9 +++++---- wasm/demo/src/style.css | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/wasm/demo/snippets/mandelbrot.py b/wasm/demo/snippets/mandelbrot.py index 2d664fdedf..b4010c7539 100644 --- a/wasm/demo/snippets/mandelbrot.py +++ b/wasm/demo/snippets/mandelbrot.py @@ -2,8 +2,7 @@ h = 50.0 def mandel(): - """Print a mandelbrot fractal to the console, yielding after each character - is printed""" + """Print a mandelbrot fractal to the console, yielding after each character is printed""" y = 0.0 while y < h: x = 0.0 @@ -32,12 +31,14 @@ def mandel(): y += 1 yield +# run the mandelbrot + try: from browser import request_animation_frame except: request_animation_frame = None gen = mandel() def gen_cb(_time=None): - gen.__next__() + for _ in range(4): gen.__next__() request_animation_frame(gen_cb) if request_animation_frame: gen_cb() -else: list(gen) +else: any(gen) diff --git a/wasm/demo/src/style.css b/wasm/demo/src/style.css index a8a5de5004..8fc18ecce6 100644 --- a/wasm/demo/src/style.css +++ b/wasm/demo/src/style.css @@ -1,6 +1,6 @@ textarea { font-family: monospace; - resize: none; + resize: vertical; } #code, From 51b766089f4f9f245499c280ff2a33627c024bb9 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 31 Mar 2019 19:53:52 -0500 Subject: [PATCH 118/884] Allow an enum with #[pyclass] --- derive/src/pyclass.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 56e216f357..7e196a2a17 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -137,10 +137,10 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { } pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { - let struc = if let Item::Struct(struc) = item { - struc - } else { - panic!("#[pyclass] can only be on a struct declaration"); + let (ident, attrs) = match item.clone() { + Item::Struct(struc) => (struc.ident, struc.attrs), + Item::Enum(enu) => (enu.ident, enu.attrs), + _ => panic!("#[pyclass] can only be on a struct or enum declaration"), }; let rp_path = rustpython_path_attr(&attr); @@ -159,10 +159,10 @@ pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { } } } - let class_name = class_name.unwrap_or_else(|| struc.ident.to_string()); + let class_name = class_name.unwrap_or_else(|| ident.to_string()); let mut doc: Option> = None; - for attr in struc.attrs.iter() { + for attr in attrs.iter() { if attr.path.is_ident("doc") { let meta = attr.parse_meta().expect("expected doc attr to be a meta"); if let Meta::NameValue(name_value) = meta { @@ -186,11 +186,9 @@ pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { None => quote!(None), }; - let ty = &struc.ident; - quote! { - #struc - impl #rp_path::pyobject::PyClassDef for #ty { + #item + impl #rp_path::pyobject::PyClassDef for #ident { const NAME: &'static str = #class_name; const DOC: Option<&'static str> = #doc; } From b3ea8155a5558b4c7fec3c1775f4fcaf2449442c Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 31 Mar 2019 22:40:57 -0500 Subject: [PATCH 119/884] Don't clone --- derive/src/pyclass.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 7e196a2a17..ad4ebf66ea 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -137,9 +137,9 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { } pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { - let (ident, attrs) = match item.clone() { - Item::Struct(struc) => (struc.ident, struc.attrs), - Item::Enum(enu) => (enu.ident, enu.attrs), + let (item, ident, attrs) = match item { + Item::Struct(struc) => (quote!(#struc), struc.ident, struc.attrs), + Item::Enum(enu) => (quote!(#enu), enu.ident, enu.attrs), _ => panic!("#[pyclass] can only be on a struct or enum declaration"), }; From 016ecf204dcb8ff9c956038572f03fbb9bd5d186 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 1 Apr 2019 19:42:30 +0200 Subject: [PATCH 120/884] Move iterator __contains__ to 'in' implementation --- vm/src/frame.rs | 30 ++++-------------------------- vm/src/obj/objiter.rs | 33 +++++---------------------------- vm/src/vm.rs | 25 +++++++++++++++++++++++++ 3 files changed, 34 insertions(+), 54 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 88a619eb36..b3cf2149bd 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1075,36 +1075,14 @@ impl Frame { a.get_id() } - // https://docs.python.org/3/reference/expressions.html#membership-test-operations - fn _membership( - &self, - vm: &VirtualMachine, - needle: PyObjectRef, - haystack: &PyObjectRef, - ) -> PyResult { - vm.call_method(&haystack, "__contains__", vec![needle]) - // TODO: implement __iter__ and __getitem__ cases when __contains__ is - // not implemented. - } - fn _in(&self, vm: &VirtualMachine, needle: PyObjectRef, haystack: PyObjectRef) -> PyResult { - match self._membership(vm, needle, &haystack) { - Ok(found) => Ok(found), - Err(_) => Err(vm.new_type_error(format!( - "{} has no __contains__ method", - haystack.class().name - ))), - } + let found = vm._membership(haystack.clone(), needle)?; + Ok(vm.ctx.new_bool(objbool::boolval(vm, found)?)) } fn _not_in(&self, vm: &VirtualMachine, needle: PyObjectRef, haystack: PyObjectRef) -> PyResult { - match self._membership(vm, needle, &haystack) { - Ok(found) => Ok(vm.ctx.new_bool(!objbool::get_value(&found))), - Err(_) => Err(vm.new_type_error(format!( - "{} has no __contains__ method", - haystack.class().name - ))), - } + let found = vm._membership(haystack.clone(), needle)?; + Ok(vm.ctx.new_bool(!objbool::boolval(vm, found)?)) } fn _is(&self, a: PyObjectRef, b: PyObjectRef) -> bool { diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 5c88a6337d..68c8b055ff 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -6,7 +6,6 @@ use crate::function::PyFuncArgs; use crate::pyobject::{PyContext, PyIteratorValue, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; -use super::objbool; use super::objbytearray::PyByteArray; use super::objbytes::PyBytes; use super::objrange::PyRange; @@ -24,6 +23,10 @@ pub fn get_iter(vm: &VirtualMachine, iter_target: &PyObjectRef) -> PyResult { // let type_str = objstr::get_value(&vm.to_str(iter_target.class()).unwrap()); // let type_error = vm.new_type_error(format!("Cannot iterate over {}", type_str)); // return Err(type_error); + + // TODO: special case when iter_target only has __getitem__ + // see: https://docs.python.org/3/library/functions.html#iter + // also https://docs.python.org/3.8/reference/datamodel.html#special-method-names } pub fn call_next(vm: &VirtualMachine, iter_obj: &PyObjectRef) -> PyResult { @@ -70,33 +73,8 @@ pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef { vm.new_exception(stop_iteration_type, "End of iterator".to_string()) } -fn contains(vm: &VirtualMachine, args: PyFuncArgs, iter_type: PyClassRef) -> PyResult { - arg_check!( - vm, - args, - required = [(iter, Some(iter_type)), (needle, None)] - ); - loop { - if let Some(element) = get_next_object(vm, iter)? { - let equal = vm._eq(needle.clone(), element.clone())?; - if objbool::get_value(&equal) { - return Ok(vm.new_bool(true)); - } else { - continue; - } - } else { - return Ok(vm.new_bool(false)); - } - } -} - -/// Common setup for iter types, adds __iter__ and __contains__ methods +/// Common setup for iter types, adds __iter__ method pub fn iter_type_init(context: &PyContext, iter_type: &PyClassRef) { - let contains_func = { - let cloned_iter_type = iter_type.clone(); - move |vm: &VirtualMachine, args: PyFuncArgs| contains(vm, args, cloned_iter_type.clone()) - }; - let iter_func = { let cloned_iter_type = iter_type.clone(); move |vm: &VirtualMachine, args: PyFuncArgs| { @@ -111,7 +89,6 @@ pub fn iter_type_init(context: &PyContext, iter_type: &PyClassRef) { }; extend_class!(context, iter_type, { - "__contains__" => context.new_rustfunc(contains_func), "__iter__" => context.new_rustfunc(iter_func) }); } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 36f4450e60..2fad29124b 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -867,6 +867,31 @@ impl VirtualMachine { Err(vm.new_unsupported_operand_error(a, b, ">=")) }) } + + // https://docs.python.org/3/reference/expressions.html#membership-test-operations + fn _membership_iter_search(&self, haystack: PyObjectRef, needle: PyObjectRef) -> PyResult { + let iter = objiter::get_iter(self, &haystack)?; + loop { + if let Some(element) = objiter::get_next_object(self, &iter)? { + let equal = self._eq(needle.clone(), element.clone())?; + if objbool::get_value(&equal) { + return Ok(self.new_bool(true)); + } else { + continue; + } + } else { + return Ok(self.new_bool(false)); + } + } + } + + pub fn _membership(&self, haystack: PyObjectRef, needle: PyObjectRef) -> PyResult { + if let Ok(method) = self.get_method(haystack.clone(), "__contains__") { + self.invoke(method, vec![needle]) + } else { + self._membership_iter_search(haystack, needle) + } + } } impl Default for VirtualMachine { From cba8aa9be52bfc6c9e0f4253de28cd476398bde0 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 1 Apr 2019 19:42:30 +0200 Subject: [PATCH 121/884] Drop iter_type_init, explicitly define __iter__ for iterators --- vm/src/obj/objenumerate.rs | 8 ++++-- vm/src/obj/objfilter.rs | 9 ++++-- vm/src/obj/objiter.rs | 56 +++++++++----------------------------- vm/src/obj/objmap.rs | 6 +++- vm/src/obj/objzip.rs | 8 ++++-- 5 files changed, 36 insertions(+), 51 deletions(-) diff --git a/vm/src/obj/objenumerate.rs b/vm/src/obj/objenumerate.rs index 85781bb7a0..c94aeabbdb 100644 --- a/vm/src/obj/objenumerate.rs +++ b/vm/src/obj/objenumerate.rs @@ -57,13 +57,17 @@ impl PyEnumerateRef { Ok(result) } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } } pub fn init(context: &PyContext) { let enumerate_type = &context.enumerate_type; - objiter::iter_type_init(context, enumerate_type); extend_class!(context, enumerate_type, { "__new__" => context.new_rustfunc(enumerate_new), - "__next__" => context.new_rustfunc(PyEnumerateRef::next) + "__next__" => context.new_rustfunc(PyEnumerateRef::next), + "__iter__" => context.new_rustfunc(PyEnumerateRef::iter), }); } diff --git a/vm/src/obj/objfilter.rs b/vm/src/obj/objfilter.rs index 07c512f6dd..858a0531a0 100644 --- a/vm/src/obj/objfilter.rs +++ b/vm/src/obj/objfilter.rs @@ -52,13 +52,15 @@ impl PyFilterRef { } } } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } } pub fn init(context: &PyContext) { let filter_type = &context.filter_type; - objiter::iter_type_init(context, filter_type); - let filter_doc = "filter(function or None, iterable) --> filter object\n\n\ Return an iterator yielding those items of iterable for which function(item)\n\ @@ -67,6 +69,7 @@ pub fn init(context: &PyContext) { extend_class!(context, filter_type, { "__new__" => context.new_rustfunc(filter_new), "__doc__" => context.new_str(filter_doc.to_string()), - "__next__" => context.new_rustfunc(PyFilterRef::next) + "__next__" => context.new_rustfunc(PyFilterRef::next), + "__iter__" => context.new_rustfunc(PyFilterRef::iter), }); } diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 68c8b055ff..67513a255d 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -2,8 +2,7 @@ * Various types to support iteration. */ -use crate::function::PyFuncArgs; -use crate::pyobject::{PyContext, PyIteratorValue, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult}; use crate::vm::VirtualMachine; use super::objbytearray::PyByteArray; @@ -11,7 +10,6 @@ use super::objbytes::PyBytes; use super::objrange::PyRange; use super::objsequence; use super::objtype; -use crate::obj::objtype::PyClassRef; /* * This helper function is called at multiple places. First, it is called @@ -73,41 +71,12 @@ pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef { vm.new_exception(stop_iteration_type, "End of iterator".to_string()) } -/// Common setup for iter types, adds __iter__ method -pub fn iter_type_init(context: &PyContext, iter_type: &PyClassRef) { - let iter_func = { - let cloned_iter_type = iter_type.clone(); - move |vm: &VirtualMachine, args: PyFuncArgs| { - arg_check!( - vm, - args, - required = [(iter, Some(cloned_iter_type.clone()))] - ); - // Return self: - Ok(iter.clone()) - } - }; - - extend_class!(context, iter_type, { - "__iter__" => context.new_rustfunc(iter_func) - }); -} - -// Sequence iterator: -fn iter_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(iter_target, None)]); +type PyIteratorValueRef = PyRef; - get_iter(vm, iter_target) -} - -fn iter_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(iter, Some(vm.ctx.iter_type()))]); - - if let Some(PyIteratorValue { - ref position, - iterated_obj: ref iterated_obj_ref, - }) = iter.payload() - { +impl PyIteratorValueRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + let position = &self.position; + let iterated_obj_ref = &self.iterated_obj; if let Some(range) = iterated_obj_ref.payload::() { if let Some(int) = range.get(position.get()) { position.set(position.get() + 1); @@ -141,8 +110,10 @@ fn iter_next(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Err(new_stop_iteration(vm)) } } - } else { - panic!("NOT IMPL"); + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self } } @@ -155,10 +126,9 @@ pub fn init(context: &PyContext) { supply its own iterator, or be a sequence.\n\ In the second form, the callable is called until it returns the sentinel."; - iter_type_init(context, iter_type); extend_class!(context, iter_type, { - "__new__" => context.new_rustfunc(iter_new), - "__next__" => context.new_rustfunc(iter_next), - "__doc__" => context.new_str(iter_doc.to_string()) + "__next__" => context.new_rustfunc(PyIteratorValueRef::next), + "__iter__" => context.new_rustfunc(PyIteratorValueRef::iter), + "__doc__" => context.new_str(iter_doc.to_string()), }); } diff --git a/vm/src/obj/objmap.rs b/vm/src/obj/objmap.rs index fbf0fa035a..061f4179f4 100644 --- a/vm/src/obj/objmap.rs +++ b/vm/src/obj/objmap.rs @@ -46,6 +46,10 @@ impl PyMapRef { // the mapper itself can raise StopIteration which does stop the map iteration vm.invoke(self.mapper.clone(), next_objs) } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } } pub fn init(context: &PyContext) { @@ -55,10 +59,10 @@ pub fn init(context: &PyContext) { Make an iterator that computes the function using arguments from\n\ each of the iterables. Stops when the shortest iterable is exhausted."; - objiter::iter_type_init(context, map_type); extend_class!(context, map_type, { "__new__" => context.new_rustfunc(map_new), "__next__" => context.new_rustfunc(PyMapRef::next), + "__iter__" => context.new_rustfunc(PyMapRef::iter), "__doc__" => context.new_str(map_doc.to_string()) }); } diff --git a/vm/src/obj/objzip.rs b/vm/src/obj/objzip.rs index a12152eeec..81640a724c 100644 --- a/vm/src/obj/objzip.rs +++ b/vm/src/obj/objzip.rs @@ -40,13 +40,17 @@ impl PyZipRef { Ok(vm.ctx.new_tuple(next_objs)) } } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } } pub fn init(context: &PyContext) { let zip_type = &context.zip_type; - objiter::iter_type_init(context, zip_type); extend_class!(context, zip_type, { "__new__" => context.new_rustfunc(zip_new), - "__next__" => context.new_rustfunc(PyZipRef::next) + "__next__" => context.new_rustfunc(PyZipRef::next), + "__iter__" => context.new_rustfunc(PyZipRef::iter), }); } From bbfca26b27cb03f20931f76d415ef95ff4706dd0 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 1 Apr 2019 19:42:30 +0200 Subject: [PATCH 122/884] Move PyIteratorValue to objiter.rs --- vm/src/obj/objbytes.rs | 3 ++- vm/src/obj/objdict.rs | 3 ++- vm/src/obj/objiter.rs | 20 +++++++++++++++++++- vm/src/obj/objlist.rs | 5 ++--- vm/src/obj/objrange.rs | 3 ++- vm/src/obj/objset.rs | 5 ++--- vm/src/obj/objtuple.rs | 5 ++--- vm/src/pyobject.rs | 16 +--------------- 8 files changed, 32 insertions(+), 28 deletions(-) diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 9390e70704..2f5bdc0d92 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -6,10 +6,11 @@ use std::ops::Deref; use num_traits::ToPrimitive; use crate::function::OptionalArg; -use crate::pyobject::{PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint; +use super::objiter::PyIteratorValue; use super::objtype::PyClassRef; #[derive(Debug)] diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 061c3477e7..5a06fbf996 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -5,11 +5,12 @@ use std::ops::{Deref, DerefMut}; use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ - DictProtocol, PyAttributes, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, + DictProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, PyValue, }; use crate::vm::{ReprGuard, VirtualMachine}; use super::objiter; +use super::objiter::PyIteratorValue; use super::objstr::{self, PyStringRef}; use super::objtype; use crate::obj::objtype::PyClassRef; diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 67513a255d..c1ffa77737 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -2,7 +2,9 @@ * Various types to support iteration. */ -use crate::pyobject::{PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult}; +use std::cell::Cell; + +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objbytearray::PyByteArray; @@ -10,6 +12,7 @@ use super::objbytes::PyBytes; use super::objrange::PyRange; use super::objsequence; use super::objtype; +use super::objtype::PyClassRef; /* * This helper function is called at multiple places. First, it is called @@ -71,6 +74,21 @@ pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef { vm.new_exception(stop_iteration_type, "End of iterator".to_string()) } +// TODO: This is a workaround and shouldn't exist. +// Each iterable type should have its own distinct iterator type. +// (however, this boilerplate can be reused for "generic iterator" for types with only __getiter__) +#[derive(Debug)] +pub struct PyIteratorValue { + pub position: Cell, + pub iterated_obj: PyObjectRef, +} + +impl PyValue for PyIteratorValue { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.iter_type() + } +} + type PyIteratorValueRef = PyRef; impl PyIteratorValueRef { diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index dd5eb055bd..444b5ad9d3 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -4,13 +4,12 @@ use std::fmt; use num_traits::ToPrimitive; use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{ - IdProtocol, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, -}; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objint; +use super::objiter::PyIteratorValue; use super::objsequence::{ get_elements, get_elements_cell, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, PySliceableSequence, diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 04c9c273b1..5af576bfc3 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -6,10 +6,11 @@ use num_integer::Integer; use num_traits::{One, Signed, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{Either, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{Either, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint::{PyInt, PyIntRef}; +use super::objiter::PyIteratorValue; use super::objslice::PySliceRef; use super::objtype::PyClassRef; diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index ad82c34de0..c048686d7e 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -8,14 +8,13 @@ use std::fmt; use std::hash::{Hash, Hasher}; use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{ - PyContext, PyIteratorValue, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, -}; +use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objint; use super::objiter; +use super::objiter::PyIteratorValue; use super::objtype; use super::objtype::PyClassRef; diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index a199e34b18..e7dc5d0ea0 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -3,13 +3,12 @@ use std::fmt; use std::hash::{Hash, Hasher}; use crate::function::OptionalArg; -use crate::pyobject::{ - IdProtocol, PyContext, PyIteratorValue, PyObjectRef, PyRef, PyResult, PyValue, -}; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objint; +use super::objiter::PyIteratorValue; use super::objsequence::{ get_elements, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, }; diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index aa3348be59..3574d71d9d 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1,5 +1,5 @@ use std::any::Any; -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::collections::HashMap; use std::fmt; use std::marker::PhantomData; @@ -1127,20 +1127,6 @@ where } } -// TODO: This is a workaround and shouldn't exist. -// Each iterable type should have its own distinct iterator type. -#[derive(Debug)] -pub struct PyIteratorValue { - pub position: Cell, - pub iterated_obj: PyObjectRef, -} - -impl PyValue for PyIteratorValue { - fn class(vm: &VirtualMachine) -> PyClassRef { - vm.ctx.iter_type() - } -} - impl PyObject where T: Sized + PyObjectPayload, From c918e9d5d3ab343e64a607529742690b144f212d Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 1 Apr 2019 19:42:30 +0200 Subject: [PATCH 123/884] Split iterators into separate types --- vm/src/obj/objbytearray.rs | 47 +++++++++++++++++++++++++++++++++++- vm/src/obj/objbytes.rs | 44 ++++++++++++++++++++++++++++++---- vm/src/obj/objdict.rs | 24 +++++++++++-------- vm/src/obj/objiter.rs | 49 ++------------------------------------ vm/src/obj/objlist.rs | 44 ++++++++++++++++++++++++++++++---- vm/src/obj/objrange.rs | 49 ++++++++++++++++++++++++++++++++------ vm/src/obj/objset.rs | 9 +++---- vm/src/obj/objtuple.rs | 44 ++++++++++++++++++++++++++++++---- vm/src/pyobject.rs | 35 +++++++++++++++++++++++++++ 9 files changed, 264 insertions(+), 81 deletions(-) diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index c609fdb2fe..f6244399b2 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -1,6 +1,6 @@ //! Implementation of the python bytearray object. -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::fmt::Write; use std::ops::{Deref, DerefMut}; @@ -11,6 +11,7 @@ use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint; +use super::objiter; use super::objtype::PyClassRef; #[derive(Debug)] @@ -67,6 +68,7 @@ pub fn init(context: &PyContext) { "__eq__" => context.new_rustfunc(PyByteArrayRef::eq), "__len__" => context.new_rustfunc(PyByteArrayRef::len), "__repr__" => context.new_rustfunc(PyByteArrayRef::repr), + "__iter__" => context.new_rustfunc(PyByteArrayRef::iter), "clear" => context.new_rustfunc(PyByteArrayRef::clear), "isalnum" => context.new_rustfunc(PyByteArrayRef::isalnum), "isalpha" => context.new_rustfunc(PyByteArrayRef::isalpha), @@ -80,6 +82,12 @@ pub fn init(context: &PyContext) { "pop" => context.new_rustfunc(PyByteArrayRef::pop), "upper" => context.new_rustfunc(PyByteArrayRef::upper) }); + + let bytearrayiterator_type = &context.bytearrayiterator_type; + extend_class!(context, bytearrayiterator_type, { + "__next__" => context.new_rustfunc(PyByteArrayIteratorRef::next), + "__iter__" => context.new_rustfunc(PyByteArrayIteratorRef::iter), + }); } fn bytearray_new( @@ -225,6 +233,13 @@ impl PyByteArrayRef { value: RefCell::new(bytes), } } + + fn iter(self, _vm: &VirtualMachine) -> PyByteArrayIterator { + PyByteArrayIterator { + position: Cell::new(0), + bytearray: self, + } + } } // helper function for istitle @@ -266,3 +281,33 @@ mod tests { assert_eq!(&to_hex(&[11u8, 222u8]), "\\x0b\\xde"); } } + +#[derive(Debug)] +pub struct PyByteArrayIterator { + position: Cell, + bytearray: PyByteArrayRef, +} + +impl PyValue for PyByteArrayIterator { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.bytearrayiterator_type() + } +} + +type PyByteArrayIteratorRef = PyRef; + +impl PyByteArrayIteratorRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + if self.position.get() < self.bytearray.value.borrow().len() { + let ret = self.bytearray.value.borrow()[self.position.get()]; + self.position.set(self.position.get() + 1); + Ok(ret) + } else { + Err(objiter::new_stop_iteration(vm)) + } + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } +} diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 2f5bdc0d92..4119d688f9 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -10,7 +10,7 @@ use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint; -use super::objiter::PyIteratorValue; +use super::objiter; use super::objtype::PyClassRef; #[derive(Debug)] @@ -69,6 +69,12 @@ pub fn init(context: &PyContext) { "__iter__" => context.new_rustfunc(PyBytesRef::iter), "__doc__" => context.new_str(bytes_doc.to_string()) }); + + let bytesiterator_type = &context.bytesiterator_type; + extend_class!(context, bytesiterator_type, { + "__next__" => context.new_rustfunc(PyBytesIteratorRef::next), + "__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), + }); } fn bytes_new( @@ -150,10 +156,10 @@ impl PyBytesRef { format!("b'{}'", data) } - fn iter(obj: PyBytesRef, _vm: &VirtualMachine) -> PyIteratorValue { - PyIteratorValue { + fn iter(self, _vm: &VirtualMachine) -> PyBytesIterator { + PyBytesIterator { position: Cell::new(0), - iterated_obj: obj.into_object(), + bytes: self, } } } @@ -161,3 +167,33 @@ impl PyBytesRef { pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { &obj.payload::().unwrap().value } + +#[derive(Debug)] +pub struct PyBytesIterator { + position: Cell, + bytes: PyBytesRef, +} + +impl PyValue for PyBytesIterator { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.bytesiterator_type() + } +} + +type PyBytesIteratorRef = PyRef; + +impl PyBytesIteratorRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + if self.position.get() < self.bytes.value.len() { + let ret = self.bytes[self.position.get()]; + self.position.set(self.position.get() + 1); + Ok(ret) + } else { + Err(objiter::new_stop_iteration(vm)) + } + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } +} diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 5a06fbf996..79c98c2ddd 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -10,7 +10,7 @@ use crate::pyobject::{ use crate::vm::{ReprGuard, VirtualMachine}; use super::objiter; -use super::objiter::PyIteratorValue; +use super::objlist::PyListIterator; use super::objstr::{self, PyStringRef}; use super::objtype; use crate::obj::objtype::PyClassRef; @@ -211,7 +211,8 @@ impl PyDictRef { } /// When iterating over a dictionary, we iterate over the keys of it. - fn iter(self, vm: &VirtualMachine) -> PyIteratorValue { + fn iter(self, vm: &VirtualMachine) -> PyListIterator { + // TODO: separate type, not a list iterator let keys = self .entries .borrow() @@ -220,13 +221,14 @@ impl PyDictRef { .collect(); let key_list = vm.ctx.new_list(keys); - PyIteratorValue { + PyListIterator { position: Cell::new(0), - iterated_obj: key_list, + list: key_list.downcast().unwrap(), } } - fn values(self, vm: &VirtualMachine) -> PyIteratorValue { + fn values(self, vm: &VirtualMachine) -> PyListIterator { + // TODO: separate type. `values` should be a live view over the collection, not an iterator. let values = self .entries .borrow() @@ -235,13 +237,14 @@ impl PyDictRef { .collect(); let values_list = vm.ctx.new_list(values); - PyIteratorValue { + PyListIterator { position: Cell::new(0), - iterated_obj: values_list, + list: values_list.downcast().unwrap(), } } - fn items(self, vm: &VirtualMachine) -> PyIteratorValue { + fn items(self, vm: &VirtualMachine) -> PyListIterator { + // TODO: separate type. `items` should be a live view over the collection, not an iterator. let items = self .entries .borrow() @@ -250,9 +253,9 @@ impl PyDictRef { .collect(); let items_list = vm.ctx.new_list(items); - PyIteratorValue { + PyListIterator { position: Cell::new(0), - iterated_obj: items_list, + list: items_list.downcast().unwrap(), } } @@ -333,6 +336,7 @@ pub fn init(context: &PyContext) { "clear" => context.new_rustfunc(PyDictRef::clear), "values" => context.new_rustfunc(PyDictRef::values), "items" => context.new_rustfunc(PyDictRef::items), + // TODO: separate type. `keys` should be a live view over the collection, not an iterator. "keys" => context.new_rustfunc(PyDictRef::iter), "get" => context.new_rustfunc(PyDictRef::get), }); diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index c1ffa77737..a99fb0a87f 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -7,10 +7,6 @@ use std::cell::Cell; use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; -use super::objbytearray::PyByteArray; -use super::objbytes::PyBytes; -use super::objrange::PyRange; -use super::objsequence; use super::objtype; use super::objtype::PyClassRef; @@ -92,42 +88,8 @@ impl PyValue for PyIteratorValue { type PyIteratorValueRef = PyRef; impl PyIteratorValueRef { - fn next(self, vm: &VirtualMachine) -> PyResult { - let position = &self.position; - let iterated_obj_ref = &self.iterated_obj; - if let Some(range) = iterated_obj_ref.payload::() { - if let Some(int) = range.get(position.get()) { - position.set(position.get() + 1); - Ok(vm.ctx.new_int(int)) - } else { - Err(new_stop_iteration(vm)) - } - } else if let Some(bytes) = iterated_obj_ref.payload::() { - if position.get() < bytes.len() { - let obj_ref = vm.ctx.new_int(bytes[position.get()]); - position.set(position.get() + 1); - Ok(obj_ref) - } else { - Err(new_stop_iteration(vm)) - } - } else if let Some(bytes) = iterated_obj_ref.payload::() { - if position.get() < bytes.value.borrow().len() { - let obj_ref = vm.ctx.new_int(bytes.value.borrow()[position.get()]); - position.set(position.get() + 1); - Ok(obj_ref) - } else { - Err(new_stop_iteration(vm)) - } - } else { - let elements = objsequence::get_elements(iterated_obj_ref); - if position.get() < elements.len() { - let obj_ref = elements[position.get()].clone(); - position.set(position.get() + 1); - Ok(obj_ref) - } else { - Err(new_stop_iteration(vm)) - } - } + fn next(self, _vm: &VirtualMachine) -> PyResult { + unimplemented!() } fn iter(self, _vm: &VirtualMachine) -> Self { @@ -138,15 +100,8 @@ impl PyIteratorValueRef { pub fn init(context: &PyContext) { let iter_type = &context.iter_type; - let iter_doc = "iter(iterable) -> iterator\n\ - iter(callable, sentinel) -> iterator\n\n\ - Get an iterator from an object. In the first form, the argument must\n\ - supply its own iterator, or be a sequence.\n\ - In the second form, the callable is called until it returns the sentinel."; - extend_class!(context, iter_type, { "__next__" => context.new_rustfunc(PyIteratorValueRef::next), "__iter__" => context.new_rustfunc(PyIteratorValueRef::iter), - "__doc__" => context.new_str(iter_doc.to_string()), }); } diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 444b5ad9d3..8657072018 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -9,7 +9,7 @@ use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objint; -use super::objiter::PyIteratorValue; +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, @@ -122,10 +122,10 @@ impl PyListRef { ) } - fn iter(self, _vm: &VirtualMachine) -> PyIteratorValue { - PyIteratorValue { + fn iter(self, _vm: &VirtualMachine) -> PyListIterator { + PyListIterator { position: Cell::new(0), - iterated_obj: self.into_object(), + list: self, } } @@ -411,6 +411,36 @@ fn list_sort(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } +#[derive(Debug)] +pub struct PyListIterator { + pub position: Cell, + pub list: PyListRef, +} + +impl PyValue for PyListIterator { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.listiterator_type() + } +} + +type PyListIteratorRef = PyRef; + +impl PyListIteratorRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + if self.position.get() < self.list.elements.borrow().len() { + let ret = self.list.elements.borrow()[self.position.get()].clone(); + self.position.set(self.position.get() + 1); + Ok(ret) + } else { + Err(objiter::new_stop_iteration(vm)) + } + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } +} + #[rustfmt::skip] // to avoid line splitting pub fn init(context: &PyContext) { let list_type = &context.list_type; @@ -449,4 +479,10 @@ pub fn init(context: &PyContext) { "pop" => context.new_rustfunc(PyListRef::pop), "remove" => context.new_rustfunc(PyListRef::remove) }); + + let listiterator_type = &context.listiterator_type; + extend_class!(context, listiterator_type, { + "__next__" => context.new_rustfunc(PyListIteratorRef::next), + "__iter__" => context.new_rustfunc(PyListIteratorRef::iter), + }); } diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 5af576bfc3..ae946d05eb 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -10,7 +10,7 @@ use crate::pyobject::{Either, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint::{PyInt, PyIntRef}; -use super::objiter::PyIteratorValue; +use super::objiter; use super::objslice::PySliceRef; use super::objtype::PyClassRef; @@ -114,6 +114,12 @@ pub fn init(context: &PyContext) { "stop" => context.new_property(PyRangeRef::stop), "step" => context.new_property(PyRangeRef::step), }); + + let rangeiterator_type = &context.rangeiterator_type; + extend_class!(context, rangeiterator_type, { + "__next__" => context.new_rustfunc(PyRangeIteratorRef::next), + "__iter__" => context.new_rustfunc(PyRangeIteratorRef::iter), + }); } type PyRangeRef = PyRef; @@ -157,14 +163,14 @@ impl PyRangeRef { self.step.clone() } - fn iter(self: PyRangeRef, _vm: &VirtualMachine) -> PyIteratorValue { - PyIteratorValue { + fn iter(self: PyRangeRef, _vm: &VirtualMachine) -> PyRangeIterator { + PyRangeIterator { position: Cell::new(0), - iterated_obj: self.into_object(), + range: self, } } - fn reversed(self: PyRangeRef, vm: &VirtualMachine) -> PyIteratorValue { + fn reversed(self: PyRangeRef, vm: &VirtualMachine) -> PyRangeIterator { let start = self.start.as_bigint(); let stop = self.stop.as_bigint(); let step = self.step.as_bigint(); @@ -190,9 +196,9 @@ impl PyRangeRef { step: PyInt::new(-step).into_ref(vm), }; - PyIteratorValue { + PyRangeIterator { position: Cell::new(0), - iterated_obj: reversed.into_ref(vm).into_object(), + range: reversed.into_ref(vm), } } @@ -314,3 +320,32 @@ fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(range.into_object()) } + +#[derive(Debug)] +pub struct PyRangeIterator { + position: Cell, + range: PyRangeRef, +} + +impl PyValue for PyRangeIterator { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.rangeiterator_type() + } +} + +type PyRangeIteratorRef = PyRef; + +impl PyRangeIteratorRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + if let Some(int) = self.range.get(self.position.get()) { + self.position.set(self.position.get() + 1); + Ok(int) + } else { + Err(objiter::new_stop_iteration(vm)) + } + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } +} diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index c048686d7e..fb558c9906 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -14,7 +14,7 @@ use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objint; use super::objiter; -use super::objiter::PyIteratorValue; +use super::objlist::PyListIterator; use super::objtype; use super::objtype::PyClassRef; @@ -560,12 +560,13 @@ fn set_ixor(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(zelf.clone()) } -fn set_iter(zelf: PySetRef, vm: &VirtualMachine) -> PyIteratorValue { +fn set_iter(zelf: PySetRef, vm: &VirtualMachine) -> PyListIterator { + // TODO: separate type let items = zelf.elements.borrow().values().cloned().collect(); let set_list = vm.ctx.new_list(items); - PyIteratorValue { + PyListIterator { position: Cell::new(0), - iterated_obj: set_list, + list: set_list.downcast().unwrap(), } } diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index e7dc5d0ea0..2169452845 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -8,7 +8,7 @@ use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objint; -use super::objiter::PyIteratorValue; +use super::objiter; use super::objsequence::{ get_elements, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, }; @@ -139,10 +139,10 @@ impl PyTupleRef { Ok(hasher.finish()) } - fn iter(self, _vm: &VirtualMachine) -> PyIteratorValue { - PyIteratorValue { + fn iter(self, _vm: &VirtualMachine) -> PyTupleIterator { + PyTupleIterator { position: Cell::new(0), - iterated_obj: self.into_object(), + tuple: self, } } @@ -224,6 +224,36 @@ fn tuple_new( PyTuple::from(elements).into_ref_with_type(vm, cls) } +#[derive(Debug)] +pub struct PyTupleIterator { + position: Cell, + tuple: PyTupleRef, +} + +impl PyValue for PyTupleIterator { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.tupleiterator_type() + } +} + +type PyTupleIteratorRef = PyRef; + +impl PyTupleIteratorRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + if self.position.get() < self.tuple.elements.borrow().len() { + let ret = self.tuple.elements.borrow()[self.position.get()].clone(); + self.position.set(self.position.get() + 1); + Ok(ret) + } else { + Err(objiter::new_stop_iteration(vm)) + } + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } +} + #[rustfmt::skip] // to avoid line splitting pub fn init(context: &PyContext) { let tuple_type = &context.tuple_type; @@ -251,4 +281,10 @@ If the argument is a tuple, the return value is the same object."; "__doc__" => context.new_str(tuple_doc.to_string()), "index" => context.new_rustfunc(PyTupleRef::index) }); + + let tupleiterator_type = &context.tupleiterator_type; + extend_class!(context, tupleiterator_type, { + "__next__" => context.new_rustfunc(PyTupleIteratorRef::next), + "__iter__" => context.new_rustfunc(PyTupleIteratorRef::iter), + }); } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 3574d71d9d..291d92e25b 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -108,7 +108,9 @@ impl fmt::Display for PyObject { #[derive(Debug)] pub struct PyContext { pub bytes_type: PyClassRef, + pub bytesiterator_type: PyClassRef, pub bytearray_type: PyClassRef, + pub bytearrayiterator_type: PyClassRef, pub bool_type: PyClassRef, pub classmethod_type: PyClassRef, pub code_type: PyClassRef, @@ -126,17 +128,20 @@ pub struct PyContext { pub true_value: PyIntRef, pub false_value: PyIntRef, pub list_type: PyClassRef, + pub listiterator_type: PyClassRef, pub map_type: PyClassRef, pub memoryview_type: PyClassRef, pub none: PyNoneRef, pub ellipsis: PyEllipsisRef, pub not_implemented: PyNotImplementedRef, pub tuple_type: PyClassRef, + pub tupleiterator_type: PyClassRef, pub set_type: PyClassRef, pub staticmethod_type: PyClassRef, pub super_type: PyClassRef, pub str_type: PyClassRef, pub range_type: PyClassRef, + pub rangeiterator_type: PyClassRef, pub slice_type: PyClassRef, pub type_type: PyClassRef, pub zip_type: PyClassRef, @@ -248,6 +253,7 @@ impl PyContext { let bound_method_type = create_type("method", &type_type, &object_type); let str_type = create_type("str", &type_type, &object_type); let list_type = create_type("list", &type_type, &object_type); + let listiterator_type = create_type("list_iterator", &type_type, &object_type); let set_type = create_type("set", &type_type, &object_type); let frozenset_type = create_type("frozenset", &type_type, &object_type); let int_type = create_type("int", &type_type, &object_type); @@ -255,8 +261,11 @@ impl PyContext { let frame_type = create_type("frame", &type_type, &object_type); let complex_type = create_type("complex", &type_type, &object_type); let bytes_type = create_type("bytes", &type_type, &object_type); + let bytesiterator_type = create_type("bytes_iterator", &type_type, &object_type); let bytearray_type = create_type("bytearray", &type_type, &object_type); + let bytearrayiterator_type = create_type("bytearray_iterator", &type_type, &object_type); let tuple_type = create_type("tuple", &type_type, &object_type); + let tupleiterator_type = create_type("tuple_iterator", &type_type, &object_type); let iter_type = create_type("iter", &type_type, &object_type); let enumerate_type = create_type("enumerate", &type_type, &object_type); let filter_type = create_type("filter", &type_type, &object_type); @@ -266,6 +275,7 @@ impl PyContext { let memoryview_type = create_type("memoryview", &type_type, &object_type); let code_type = create_type("code", &type_type, &int_type); let range_type = create_type("range", &type_type, &object_type); + let rangeiterator_type = create_type("range_iterator", &type_type, &object_type); let slice_type = create_type("slice", &type_type, &object_type); let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type); @@ -291,7 +301,9 @@ impl PyContext { bool_type, memoryview_type, bytearray_type, + bytearrayiterator_type, bytes_type, + bytesiterator_type, code_type, complex_type, classmethod_type, @@ -300,11 +312,13 @@ impl PyContext { frame_type, staticmethod_type, list_type, + listiterator_type, set_type, frozenset_type, true_value, false_value, tuple_type, + tupleiterator_type, iter_type, ellipsis_type, enumerate_type, @@ -317,6 +331,7 @@ impl PyContext { not_implemented, str_type, range_type, + rangeiterator_type, slice_type, object: object_type, function_type, @@ -373,10 +388,18 @@ impl PyContext { self.bytearray_type.clone() } + pub fn bytearrayiterator_type(&self) -> PyClassRef { + self.bytearrayiterator_type.clone() + } + pub fn bytes_type(&self) -> PyClassRef { self.bytes_type.clone() } + pub fn bytesiterator_type(&self) -> PyClassRef { + self.bytesiterator_type.clone() + } + pub fn code_type(&self) -> PyClassRef { self.code_type.clone() } @@ -405,6 +428,10 @@ impl PyContext { self.list_type.clone() } + pub fn listiterator_type(&self) -> PyClassRef { + self.listiterator_type.clone() + } + pub fn module_type(&self) -> PyClassRef { self.module_type.clone() } @@ -417,6 +444,10 @@ impl PyContext { self.range_type.clone() } + pub fn rangeiterator_type(&self) -> PyClassRef { + self.rangeiterator_type.clone() + } + pub fn slice_type(&self) -> PyClassRef { self.slice_type.clone() } @@ -437,6 +468,10 @@ impl PyContext { self.tuple_type.clone() } + pub fn tupleiterator_type(&self) -> PyClassRef { + self.tupleiterator_type.clone() + } + pub fn iter_type(&self) -> PyClassRef { self.iter_type.clone() } From de5b1c405573e8fa6ef3611ee53a08c234466ee4 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 1 Apr 2019 19:42:30 +0200 Subject: [PATCH 124/884] Support iter() for types with only __getitem__ --- vm/src/obj/objiter.rs | 55 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index a99fb0a87f..6e1dc1d064 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -4,7 +4,7 @@ use std::cell::Cell; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; use super::objtype; @@ -16,14 +16,19 @@ use super::objtype::PyClassRef; * function 'iter' is called. */ pub fn get_iter(vm: &VirtualMachine, iter_target: &PyObjectRef) -> PyResult { - vm.call_method(iter_target, "__iter__", vec![]) - // let type_str = objstr::get_value(&vm.to_str(iter_target.class()).unwrap()); - // let type_error = vm.new_type_error(format!("Cannot iterate over {}", type_str)); - // return Err(type_error); - - // TODO: special case when iter_target only has __getitem__ - // see: https://docs.python.org/3/library/functions.html#iter - // also https://docs.python.org/3.8/reference/datamodel.html#special-method-names + if let Ok(method) = vm.get_method(iter_target.clone(), "__iter__") { + vm.invoke(method, vec![]) + } else if vm.get_method(iter_target.clone(), "__getitem__").is_ok() { + Ok(PySequenceIterator { + position: Cell::new(0), + obj: iter_target.clone(), + } + .into_ref(vm) + .into_object()) + } else { + let message = format!("Cannot iterate over {}", iter_target.class().name); + return Err(vm.new_type_error(message)); + } } pub fn call_next(vm: &VirtualMachine, iter_obj: &PyObjectRef) -> PyResult { @@ -70,26 +75,34 @@ pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef { vm.new_exception(stop_iteration_type, "End of iterator".to_string()) } -// TODO: This is a workaround and shouldn't exist. -// Each iterable type should have its own distinct iterator type. -// (however, this boilerplate can be reused for "generic iterator" for types with only __getiter__) #[derive(Debug)] -pub struct PyIteratorValue { +pub struct PySequenceIterator { pub position: Cell, - pub iterated_obj: PyObjectRef, + pub obj: PyObjectRef, } -impl PyValue for PyIteratorValue { +impl PyValue for PySequenceIterator { fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.iter_type() } } -type PyIteratorValueRef = PyRef; +type PySequenceIteratorRef = PyRef; -impl PyIteratorValueRef { - fn next(self, _vm: &VirtualMachine) -> PyResult { - unimplemented!() +impl PySequenceIteratorRef { + 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) + } + 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), + } } fn iter(self, _vm: &VirtualMachine) -> Self { @@ -101,7 +114,7 @@ pub fn init(context: &PyContext) { let iter_type = &context.iter_type; extend_class!(context, iter_type, { - "__next__" => context.new_rustfunc(PyIteratorValueRef::next), - "__iter__" => context.new_rustfunc(PyIteratorValueRef::iter), + "__next__" => context.new_rustfunc(PySequenceIteratorRef::next), + "__iter__" => context.new_rustfunc(PySequenceIteratorRef::iter), }); } From 2f2a8438b55acba2cc98cbc9de8eb9cfb79a583e Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Mon, 1 Apr 2019 19:42:30 +0200 Subject: [PATCH 125/884] Add iterable tests --- tests/snippets/iterable.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 tests/snippets/iterable.py diff --git a/tests/snippets/iterable.py b/tests/snippets/iterable.py new file mode 100644 index 0000000000..45bb1cf4dc --- /dev/null +++ b/tests/snippets/iterable.py @@ -0,0 +1,32 @@ +from testutils import assert_raises + +def test_container(x): + assert 3 in x + assert 4 not in x + assert list(x) == list(iter(x)) + assert list(x) == [0, 1, 2, 3] + assert [*x] == [0, 1, 2, 3] + lst = [] + lst.extend(x) + assert lst == [0, 1, 2, 3] + +class C: + def __iter__(self): + return iter([0, 1, 2, 3]) +test_container(C()) + +class C: + def __getitem__(self, x): + return (0, 1, 2, 3)[x] # raises IndexError on x==4 +test_container(C()) + +class C: + def __getitem__(self, x): + if x > 3: + raise StopIteration + return x +test_container(C()) + +class C: pass +assert_raises(TypeError, lambda: 5 in C()) +assert_raises(TypeError, lambda: iter(C)) From 6276241d59f085965a6ab02ff72171be8c4bd109 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 1 Apr 2019 18:11:37 -0500 Subject: [PATCH 126/884] Redo much of whats_left_to_implement.py --- tests/snippets/whats_left_to_implement.py | 1743 +++++++++++---------- whats_left.sh | 5 + 2 files changed, 879 insertions(+), 869 deletions(-) create mode 100755 whats_left.sh diff --git a/tests/snippets/whats_left_to_implement.py b/tests/snippets/whats_left_to_implement.py index b6c8cbe42b..41b092506a 100644 --- a/tests/snippets/whats_left_to_implement.py +++ b/tests/snippets/whats_left_to_implement.py @@ -1,878 +1,883 @@ -expected_methods = [] +import platform -# TODO: using tuple could have been better -expected_methods.append({'name': 'bool', 'methods': [ - '__abs__', - '__add__', - '__and__', - '__bool__', - '__ceil__', - '__class__', - '__delattr__', - '__dir__', - '__divmod__', - '__doc__', - '__eq__', - '__float__', - '__floor__', - '__floordiv__', - '__format__', - '__ge__', - '__getattribute__', - '__getnewargs__', - '__gt__', - '__hash__', - '__index__', - '__init__', - '__init_subclass__', - '__int__', - '__invert__', - '__le__', - '__lshift__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__neg__', - '__new__', - '__or__', - '__pos__', - '__pow__', - '__radd__', - '__rand__', - '__rdivmod__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rfloordiv__', - '__rlshift__', - '__rmod__', - '__rmul__', - '__ror__', - '__round__', - '__rpow__', - '__rrshift__', - '__rshift__', - '__rsub__', - '__rtruediv__', - '__rxor__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__truediv__', - '__trunc__', - '__xor__', - 'bit_length', - 'conjugate', - 'denominator', - 'from_bytes', - 'imag', - 'numerator', - 'real', - 'to_bytes', -], 'type': bool}) -expected_methods.append({'name': 'bytearray', 'type': bytearray, 'methods': [ - '__add__', - '__alloc__', - '__class__', - '__contains__', - '__delattr__', - '__delitem__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__gt__', - '__hash__', - '__iadd__', - '__imul__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rmod__', - '__rmul__', - '__setattr__', - '__setitem__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'append', - 'capitalize', - 'center', - 'clear', - 'copy', - 'count', - 'decode', - 'endswith', - 'expandtabs', - 'extend', - 'find', - 'fromhex', - 'hex', - 'index', - 'insert', - 'isalnum', - 'isalpha', - 'isascii', - 'isdigit', - 'islower', - 'isspace', - 'istitle', - 'isupper', - 'join', - 'ljust', - 'lower', - 'lstrip', - 'maketrans', - 'partition', - 'pop', - 'remove', - 'replace', - 'reverse', - 'rfind', - 'rindex', - 'rjust', - 'rpartition', - 'rsplit', - 'rstrip', - 'split', - 'splitlines', - 'startswith', - 'strip', - 'swapcase', - 'title', - 'translate', - 'upper', - 'zfill', -]}) -expected_methods.append({'name': 'bytes', 'type': bytes, 'methods': [ - '__add__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__getnewargs__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rmod__', - '__rmul__', - '__setattr__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'capitalize', - 'center', - 'count', - 'decode', - 'endswith', - 'expandtabs', - 'find', - 'fromhex', - 'hex', - 'index', - 'isalnum', - 'isalpha', - 'isascii', - 'isdigit', - 'islower', - 'isspace', - 'istitle', - 'isupper', - 'join', - 'ljust', - 'lower', - 'lstrip', - 'maketrans', - 'partition', - 'replace', - 'rfind', - 'rindex', - 'rjust', - 'rpartition', - 'rsplit', - 'rstrip', - 'split', - 'splitlines', - 'startswith', - 'strip', - 'swapcase', - 'title', - 'translate', - 'upper', - 'zfill', -]}) -expected_methods.append({'name': 'complex', 'type': complex, 'methods': [ - '__abs__', - '__add__', - '__bool__', - '__class__', - '__delattr__', - '__dir__', - '__divmod__', - '__doc__', - '__eq__', - '__float__', - '__floordiv__', - '__format__', - '__ge__', - '__getattribute__', - '__getnewargs__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__int__', - '__le__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__neg__', - '__new__', - '__pos__', - '__pow__', - '__radd__', - '__rdivmod__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rfloordiv__', - '__rmod__', - '__rmul__', - '__rpow__', - '__rsub__', - '__rtruediv__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__truediv__', - 'conjugate', - 'imag', - 'real', -]}) -expected_methods.append({'name': 'dict', 'type': dict, 'methods': [ - '__class__', - '__contains__', - '__delattr__', - '__delitem__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__setattr__', - '__setitem__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'clear', - 'copy', - 'fromkeys', - 'get', - 'items', - 'keys', - 'pop', - 'popitem', - 'setdefault', - 'update', - 'values', -]}) -expected_methods.append({'name': 'float','type':float,'methods':[ - '__abs__', - '__add__', - '__bool__', - '__class__', - '__delattr__', - '__dir__', - '__divmod__', - '__doc__', - '__eq__', - '__float__', - '__floordiv__', - '__format__', - '__ge__', - '__getattribute__', - '__getformat__', - '__getnewargs__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__int__', - '__le__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__neg__', - '__new__', - '__pos__', - '__pow__', - '__radd__', - '__rdivmod__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rfloordiv__', - '__rmod__', - '__rmul__', - '__round__', - '__rpow__', - '__rsub__', - '__rtruediv__', - '__set_format__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__truediv__', - '__trunc__', - 'as_integer_ratio', - 'conjugate', - 'fromhex', - 'hex', - 'imag', - 'is_integer', - 'real', -]}) -expected_methods.append({'name': 'frozenset','type':frozenset, 'methods': [ - '__and__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__ne__', - '__new__', - '__or__', - '__rand__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__ror__', - '__rsub__', - '__rxor__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__xor__', - 'copy', - 'difference', - 'intersection', - 'isdisjoint', - 'issubset', - 'issuperset', - 'symmetric_difference', - 'union', -]}) -expected_methods.append({'name': 'int', 'type':int, 'methods': [ - '__abs__', - '__add__', - '__and__', - '__bool__', - '__ceil__', - '__class__', - '__delattr__', - '__dir__', - '__divmod__', - '__doc__', - '__eq__', - '__float__', - '__floor__', - '__floordiv__', - '__format__', - '__ge__', - '__getattribute__', - '__getnewargs__', - '__gt__', - '__hash__', - '__index__', - '__init__', - '__init_subclass__', - '__int__', - '__invert__', - '__le__', - '__lshift__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__neg__', - '__new__', - '__or__', - '__pos__', - '__pow__', - '__radd__', - '__rand__', - '__rdivmod__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rfloordiv__', - '__rlshift__', - '__rmod__', - '__rmul__', - '__ror__', - '__round__', - '__rpow__', - '__rrshift__', - '__rshift__', - '__rsub__', - '__rtruediv__', - '__rxor__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__truediv__', - '__trunc__', - '__xor__', - 'bit_length', - 'conjugate', - 'denominator', - 'from_bytes', - 'imag', - 'numerator', - 'real', - 'to_bytes', -]}) -expected_methods.append({'name': 'iter','type':iter,'methods':[ - '__class__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__length_hint__', - '__lt__', - '__ne__', - '__new__', - '__next__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__setattr__', - '__setstate__', - '__sizeof__', - '__str__', - '__subclasshook__', -]}) -expected_methods.append({'name': 'list','type':list,'methods':[ - '__add__', - '__class__', - '__contains__', - '__delattr__', - '__delitem__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__gt__', - '__hash__', - '__iadd__', - '__imul__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__reversed__', - '__rmul__', - '__setattr__', - '__setitem__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'append', - 'clear', - 'copy', - 'count', - 'extend', - 'index', - 'insert', - 'pop', - 'remove', - 'reverse', - 'sort', -]}) -expected_methods.append({'name': 'memoryview','type':memoryview,'methods':[ - '__class__', - '__delattr__', - '__delitem__', - '__dir__', - '__doc__', - '__enter__', - '__eq__', - '__exit__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__le__', - '__len__', - '__lt__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__setattr__', - '__setitem__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'c_contiguous', - 'cast', - 'contiguous', - 'f_contiguous', - 'format', - 'hex', - 'itemsize', - 'nbytes', - 'ndim', - 'obj', - 'readonly', - 'release', - 'shape', - 'strides', - 'suboffsets', - 'tobytes', - 'tolist', -]}) -expected_methods.append({'name': 'range','type':range,'methods':[ - '__bool__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__reversed__', - '__setattr__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'count', - 'index', - 'start', - 'step', - 'stop', -]}) -expected_methods.append({'name': 'set','type':set,'methods':[ - '__and__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__gt__', - '__hash__', - '__iand__', - '__init__', - '__init_subclass__', - '__ior__', - '__isub__', - '__iter__', - '__ixor__', - '__le__', - '__len__', - '__lt__', - '__ne__', - '__new__', - '__or__', - '__rand__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__ror__', - '__rsub__', - '__rxor__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__xor__', - 'add', - 'clear', - 'copy', - 'difference', - 'difference_update', - 'discard', - 'intersection', - 'intersection_update', - 'isdisjoint', - 'issubset', - 'issuperset', - 'pop', - 'remove', - 'symmetric_difference', - 'symmetric_difference_update', - 'union', - 'update', -]}) -expected_methods.append({'name': 'string','type':str,'methods':[ - '__add__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__getnewargs__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rmod__', - '__rmul__', - '__setattr__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'capitalize', - 'casefold', - 'center', - 'count', - 'encode', - 'endswith', - 'expandtabs', - 'find', - 'format', - 'format_map', - 'index', - 'isalnum', - 'isalpha', - 'isascii', - 'isdecimal', - 'isdigit', - 'isidentifier', - 'islower', - 'isnumeric', - 'isprintable', - 'isspace', - 'istitle', - 'isupper', - 'join', - 'ljust', - 'lower', - 'lstrip', - 'maketrans', - 'partition', - 'replace', - 'rfind', - 'rindex', - 'rjust', - 'rpartition', - 'rsplit', - 'rstrip', - 'split', - 'splitlines', - 'startswith', - 'strip', - 'swapcase', - 'title', - 'translate', - 'upper', - 'zfill' -]}) -expected_methods.append({'name': 'tuple','type':tuple, 'methods': [ - '__add__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__getnewargs__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rmul__', - '__setattr__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'count', - 'index', -]}) -expected_methods.append({'name': 'object', 'type':object, 'methods':[ - '__repr__', - '__hash__', - '__str__', - '__getattribute__', - '__setattr__', - '__delattr__', - '__lt__', - '__le__', - '__eq__', - '__ne__', - '__gt__', - '__ge__', - '__init__', - '__new__', - '__reduce_ex__', - '__reduce__', - '__subclasshook__', - '__init_subclass__', - '__format__', - '__sizeof__', - '__dir__', - '__class__', - '__doc__' -]}) +expected_methods = { + 'bool': (bool, [ + '__abs__', + '__add__', + '__and__', + '__bool__', + '__ceil__', + '__class__', + '__delattr__', + '__dir__', + '__divmod__', + '__doc__', + '__eq__', + '__float__', + '__floor__', + '__floordiv__', + '__format__', + '__ge__', + '__getattribute__', + '__getnewargs__', + '__gt__', + '__hash__', + '__index__', + '__init__', + '__init_subclass__', + '__int__', + '__invert__', + '__le__', + '__lshift__', + '__lt__', + '__mod__', + '__mul__', + '__ne__', + '__neg__', + '__new__', + '__or__', + '__pos__', + '__pow__', + '__radd__', + '__rand__', + '__rdivmod__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__rfloordiv__', + '__rlshift__', + '__rmod__', + '__rmul__', + '__ror__', + '__round__', + '__rpow__', + '__rrshift__', + '__rshift__', + '__rsub__', + '__rtruediv__', + '__rxor__', + '__setattr__', + '__sizeof__', + '__str__', + '__sub__', + '__subclasshook__', + '__truediv__', + '__trunc__', + '__xor__', + 'bit_length', + 'conjugate', + 'denominator', + 'from_bytes', + 'imag', + 'numerator', + 'real', + 'to_bytes', + ]), + 'bytearray': (bytearray, [ + '__add__', + '__alloc__', + '__class__', + '__contains__', + '__delattr__', + '__delitem__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__getitem__', + '__gt__', + '__hash__', + '__iadd__', + '__imul__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__len__', + '__lt__', + '__mod__', + '__mul__', + '__ne__', + '__new__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__rmod__', + '__rmul__', + '__setattr__', + '__setitem__', + '__sizeof__', + '__str__', + '__subclasshook__', + 'append', + 'capitalize', + 'center', + 'clear', + 'copy', + 'count', + 'decode', + 'endswith', + 'expandtabs', + 'extend', + 'find', + 'fromhex', + 'hex', + 'index', + 'insert', + 'isalnum', + 'isalpha', + 'isascii', + 'isdigit', + 'islower', + 'isspace', + 'istitle', + 'isupper', + 'join', + 'ljust', + 'lower', + 'lstrip', + 'maketrans', + 'partition', + 'pop', + 'remove', + 'replace', + 'reverse', + 'rfind', + 'rindex', + 'rjust', + 'rpartition', + 'rsplit', + 'rstrip', + 'split', + 'splitlines', + 'startswith', + 'strip', + 'swapcase', + 'title', + 'translate', + 'upper', + 'zfill', + ]), + 'bytes': (bytes, [ + '__add__', + '__class__', + '__contains__', + '__delattr__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__getitem__', + '__getnewargs__', + '__gt__', + '__hash__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__len__', + '__lt__', + '__mod__', + '__mul__', + '__ne__', + '__new__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__rmod__', + '__rmul__', + '__setattr__', + '__sizeof__', + '__str__', + '__subclasshook__', + 'capitalize', + 'center', + 'count', + 'decode', + 'endswith', + 'expandtabs', + 'find', + 'fromhex', + 'hex', + 'index', + 'isalnum', + 'isalpha', + 'isascii', + 'isdigit', + 'islower', + 'isspace', + 'istitle', + 'isupper', + 'join', + 'ljust', + 'lower', + 'lstrip', + 'maketrans', + 'partition', + 'replace', + 'rfind', + 'rindex', + 'rjust', + 'rpartition', + 'rsplit', + 'rstrip', + 'split', + 'splitlines', + 'startswith', + 'strip', + 'swapcase', + 'title', + 'translate', + 'upper', + 'zfill', + ]), + 'complex': (complex, [ + '__abs__', + '__add__', + '__bool__', + '__class__', + '__delattr__', + '__dir__', + '__divmod__', + '__doc__', + '__eq__', + '__float__', + '__floordiv__', + '__format__', + '__ge__', + '__getattribute__', + '__getnewargs__', + '__gt__', + '__hash__', + '__init__', + '__init_subclass__', + '__int__', + '__le__', + '__lt__', + '__mod__', + '__mul__', + '__ne__', + '__neg__', + '__new__', + '__pos__', + '__pow__', + '__radd__', + '__rdivmod__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__rfloordiv__', + '__rmod__', + '__rmul__', + '__rpow__', + '__rsub__', + '__rtruediv__', + '__setattr__', + '__sizeof__', + '__str__', + '__sub__', + '__subclasshook__', + '__truediv__', + 'conjugate', + 'imag', + 'real', + ]), + 'dict': (dict, [ + '__class__', + '__contains__', + '__delattr__', + '__delitem__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__getitem__', + '__gt__', + '__hash__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__len__', + '__lt__', + '__ne__', + '__new__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__setattr__', + '__setitem__', + '__sizeof__', + '__str__', + '__subclasshook__', + 'clear', + 'copy', + 'fromkeys', + 'get', + 'items', + 'keys', + 'pop', + 'popitem', + 'setdefault', + 'update', + 'values', + ]), + 'float': (float, [ + '__abs__', + '__add__', + '__bool__', + '__class__', + '__delattr__', + '__dir__', + '__divmod__', + '__doc__', + '__eq__', + '__float__', + '__floordiv__', + '__format__', + '__ge__', + '__getattribute__', + '__getformat__', + '__getnewargs__', + '__gt__', + '__hash__', + '__init__', + '__init_subclass__', + '__int__', + '__le__', + '__lt__', + '__mod__', + '__mul__', + '__ne__', + '__neg__', + '__new__', + '__pos__', + '__pow__', + '__radd__', + '__rdivmod__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__rfloordiv__', + '__rmod__', + '__rmul__', + '__round__', + '__rpow__', + '__rsub__', + '__rtruediv__', + '__set_format__', + '__setattr__', + '__sizeof__', + '__str__', + '__sub__', + '__subclasshook__', + '__truediv__', + '__trunc__', + 'as_integer_ratio', + 'conjugate', + 'fromhex', + 'hex', + 'imag', + 'is_integer', + 'real', + ]), + 'frozenset': (frozenset, [ + '__and__', + '__class__', + '__contains__', + '__delattr__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__gt__', + '__hash__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__len__', + '__lt__', + '__ne__', + '__new__', + '__or__', + '__rand__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__ror__', + '__rsub__', + '__rxor__', + '__setattr__', + '__sizeof__', + '__str__', + '__sub__', + '__subclasshook__', + '__xor__', + 'copy', + 'difference', + 'intersection', + 'isdisjoint', + 'issubset', + 'issuperset', + 'symmetric_difference', + 'union', + ]), + 'int': (int, [ + '__abs__', + '__add__', + '__and__', + '__bool__', + '__ceil__', + '__class__', + '__delattr__', + '__dir__', + '__divmod__', + '__doc__', + '__eq__', + '__float__', + '__floor__', + '__floordiv__', + '__format__', + '__ge__', + '__getattribute__', + '__getnewargs__', + '__gt__', + '__hash__', + '__index__', + '__init__', + '__init_subclass__', + '__int__', + '__invert__', + '__le__', + '__lshift__', + '__lt__', + '__mod__', + '__mul__', + '__ne__', + '__neg__', + '__new__', + '__or__', + '__pos__', + '__pow__', + '__radd__', + '__rand__', + '__rdivmod__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__rfloordiv__', + '__rlshift__', + '__rmod__', + '__rmul__', + '__ror__', + '__round__', + '__rpow__', + '__rrshift__', + '__rshift__', + '__rsub__', + '__rtruediv__', + '__rxor__', + '__setattr__', + '__sizeof__', + '__str__', + '__sub__', + '__subclasshook__', + '__truediv__', + '__trunc__', + '__xor__', + 'bit_length', + 'conjugate', + 'denominator', + 'from_bytes', + 'imag', + 'numerator', + 'real', + 'to_bytes', + ]), + 'iter': (iter, [ + '__class__', + '__delattr__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__gt__', + '__hash__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__length_hint__', + '__lt__', + '__ne__', + '__new__', + '__next__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__setattr__', + '__setstate__', + '__sizeof__', + '__str__', + '__subclasshook__', + ]), + 'list': (list, [ + '__add__', + '__class__', + '__contains__', + '__delattr__', + '__delitem__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__getitem__', + '__gt__', + '__hash__', + '__iadd__', + '__imul__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__len__', + '__lt__', + '__mul__', + '__ne__', + '__new__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__reversed__', + '__rmul__', + '__setattr__', + '__setitem__', + '__sizeof__', + '__str__', + '__subclasshook__', + 'append', + 'clear', + 'copy', + 'count', + 'extend', + 'index', + 'insert', + 'pop', + 'remove', + 'reverse', + 'sort', + ]), + 'memoryview': (memoryview, [ + '__class__', + '__delattr__', + '__delitem__', + '__dir__', + '__doc__', + '__enter__', + '__eq__', + '__exit__', + '__format__', + '__ge__', + '__getattribute__', + '__getitem__', + '__gt__', + '__hash__', + '__init__', + '__init_subclass__', + '__le__', + '__len__', + '__lt__', + '__ne__', + '__new__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__setattr__', + '__setitem__', + '__sizeof__', + '__str__', + '__subclasshook__', + 'c_contiguous', + 'cast', + 'contiguous', + 'f_contiguous', + 'format', + 'hex', + 'itemsize', + 'nbytes', + 'ndim', + 'obj', + 'readonly', + 'release', + 'shape', + 'strides', + 'suboffsets', + 'tobytes', + 'tolist', + ]), + 'range': (range, [ + '__bool__', + '__class__', + '__contains__', + '__delattr__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__getitem__', + '__gt__', + '__hash__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__len__', + '__lt__', + '__ne__', + '__new__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__reversed__', + '__setattr__', + '__sizeof__', + '__str__', + '__subclasshook__', + 'count', + 'index', + 'start', + 'step', + 'stop', + ]), + 'set': (set, [ + '__and__', + '__class__', + '__contains__', + '__delattr__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__gt__', + '__hash__', + '__iand__', + '__init__', + '__init_subclass__', + '__ior__', + '__isub__', + '__iter__', + '__ixor__', + '__le__', + '__len__', + '__lt__', + '__ne__', + '__new__', + '__or__', + '__rand__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__ror__', + '__rsub__', + '__rxor__', + '__setattr__', + '__sizeof__', + '__str__', + '__sub__', + '__subclasshook__', + '__xor__', + 'add', + 'clear', + 'copy', + 'difference', + 'difference_update', + 'discard', + 'intersection', + 'intersection_update', + 'isdisjoint', + 'issubset', + 'issuperset', + 'pop', + 'remove', + 'symmetric_difference', + 'symmetric_difference_update', + 'union', + 'update', + ]), + 'str': (str, [ + '__add__', + '__class__', + '__contains__', + '__delattr__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__getitem__', + '__getnewargs__', + '__gt__', + '__hash__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__len__', + '__lt__', + '__mod__', + '__mul__', + '__ne__', + '__new__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__rmod__', + '__rmul__', + '__setattr__', + '__sizeof__', + '__str__', + '__subclasshook__', + 'capitalize', + 'casefold', + 'center', + 'count', + 'encode', + 'endswith', + 'expandtabs', + 'find', + 'format', + 'format_map', + 'index', + 'isalnum', + 'isalpha', + 'isascii', + 'isdecimal', + 'isdigit', + 'isidentifier', + 'islower', + 'isnumeric', + 'isprintable', + 'isspace', + 'istitle', + 'isupper', + 'join', + 'ljust', + 'lower', + 'lstrip', + 'maketrans', + 'partition', + 'replace', + 'rfind', + 'rindex', + 'rjust', + 'rpartition', + 'rsplit', + 'rstrip', + 'split', + 'splitlines', + 'startswith', + 'strip', + 'swapcase', + 'title', + 'translate', + 'upper', + 'zfill' + ]), + 'tuple': (tuple, [ + '__add__', + '__class__', + '__contains__', + '__delattr__', + '__dir__', + '__doc__', + '__eq__', + '__format__', + '__ge__', + '__getattribute__', + '__getitem__', + '__getnewargs__', + '__gt__', + '__hash__', + '__init__', + '__init_subclass__', + '__iter__', + '__le__', + '__len__', + '__lt__', + '__mul__', + '__ne__', + '__new__', + '__reduce__', + '__reduce_ex__', + '__repr__', + '__rmul__', + '__setattr__', + '__sizeof__', + '__str__', + '__subclasshook__', + 'count', + 'index', + ]), + 'object': (object, [ + '__repr__', + '__hash__', + '__str__', + '__getattribute__', + '__setattr__', + '__delattr__', + '__lt__', + '__le__', + '__eq__', + '__ne__', + '__gt__', + '__ge__', + '__init__', + '__new__', + '__reduce_ex__', + '__reduce__', + '__subclasshook__', + '__init_subclass__', + '__format__', + '__sizeof__', + '__dir__', + '__class__', + '__doc__' + ]), +} not_implemented = [] -for item in expected_methods: - for method in item['methods']: +for name, (ty, methods) in expected_methods.items(): + for method in methods: try: - if not hasattr(item['type'], method): - not_implemented.append((item['name'], method)) + if not hasattr(ty, method): + not_implemented.append((name, method)) except NameError: - not_implemented.append((item['name'], method)) + not_implemented.append((name, method)) -for r in not_implemented: - print(r[0], ".", r[1]) +if not_implemented: + for r in not_implemented: + print(r[0], ".", r[1], sep="") else: print("Not much \\o/") + +if platform.python_implementation() == "CPython": + assert len(not_implemented) == 0, "CPython should have all the methods" diff --git a/whats_left.sh b/whats_left.sh new file mode 100755 index 0000000000..c6f191bd58 --- /dev/null +++ b/whats_left.sh @@ -0,0 +1,5 @@ +#!/bin/sh + +cd "$(dirname "$0")" || exit + +cargo run -- tests/snippets/whats_left_to_implement.py From 9ff1e41422578fd7566fba4b15d8fdec0a43b9dc Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 1 Apr 2019 18:28:15 -0500 Subject: [PATCH 127/884] Give values instead of types, list comprehension, remove methods not in CPython --- tests/snippets/whats_left_to_implement.py | 49 +++++++++-------------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/tests/snippets/whats_left_to_implement.py b/tests/snippets/whats_left_to_implement.py index 41b092506a..a9a2d4de93 100644 --- a/tests/snippets/whats_left_to_implement.py +++ b/tests/snippets/whats_left_to_implement.py @@ -1,7 +1,7 @@ import platform expected_methods = { - 'bool': (bool, [ + 'bool': (True, [ '__abs__', '__add__', '__and__', @@ -73,7 +73,7 @@ 'real', 'to_bytes', ]), - 'bytearray': (bytearray, [ + 'bytearray': (bytearray(), [ '__add__', '__alloc__', '__class__', @@ -128,7 +128,6 @@ 'insert', 'isalnum', 'isalpha', - 'isascii', 'isdigit', 'islower', 'isspace', @@ -160,7 +159,7 @@ 'upper', 'zfill', ]), - 'bytes': (bytes, [ + 'bytes': (b'', [ '__add__', '__class__', '__contains__', @@ -206,7 +205,6 @@ 'index', 'isalnum', 'isalpha', - 'isascii', 'isdigit', 'islower', 'isspace', @@ -235,7 +233,7 @@ 'upper', 'zfill', ]), - 'complex': (complex, [ + 'complex': (0j, [ '__abs__', '__add__', '__bool__', @@ -286,7 +284,7 @@ 'imag', 'real', ]), - 'dict': (dict, [ + 'dict': ({}, [ '__class__', '__contains__', '__delattr__', @@ -328,7 +326,7 @@ 'update', 'values', ]), - 'float': (float, [ + 'float': (0.0, [ '__abs__', '__add__', '__bool__', @@ -371,7 +369,6 @@ '__rpow__', '__rsub__', '__rtruediv__', - '__set_format__', '__setattr__', '__sizeof__', '__str__', @@ -387,7 +384,7 @@ 'is_integer', 'real', ]), - 'frozenset': (frozenset, [ + 'frozenset': (frozenset(), [ '__and__', '__class__', '__contains__', @@ -431,7 +428,7 @@ 'symmetric_difference', 'union', ]), - 'int': (int, [ + 'int': (0, [ '__abs__', '__add__', '__and__', @@ -503,7 +500,7 @@ 'real', 'to_bytes', ]), - 'iter': (iter, [ + 'iter': ([].__iter__(), [ '__class__', '__delattr__', '__dir__', @@ -532,7 +529,7 @@ '__str__', '__subclasshook__', ]), - 'list': (list, [ + 'list': ([], [ '__add__', '__class__', '__contains__', @@ -580,7 +577,7 @@ 'reverse', 'sort', ]), - 'memoryview': (memoryview, [ + 'memoryview': (memoryview(b''), [ '__class__', '__delattr__', '__delitem__', @@ -628,7 +625,7 @@ 'tobytes', 'tolist', ]), - 'range': (range, [ + 'range': (range(0), [ '__bool__', '__class__', '__contains__', @@ -664,7 +661,7 @@ 'step', 'stop', ]), - 'set': (set, [ + 'set': (set(), [ '__and__', '__class__', '__contains__', @@ -721,7 +718,7 @@ 'union', 'update', ]), - 'str': (str, [ + 'str': ('', [ '__add__', '__class__', '__contains__', @@ -768,7 +765,6 @@ 'index', 'isalnum', 'isalpha', - 'isascii', 'isdecimal', 'isdigit', 'isidentifier', @@ -801,7 +797,7 @@ 'upper', 'zfill' ]), - 'tuple': (tuple, [ + 'tuple': ((), [ '__add__', '__class__', '__contains__', @@ -836,7 +832,7 @@ 'count', 'index', ]), - 'object': (object, [ + 'object': (object(), [ '__repr__', '__hash__', '__str__', @@ -863,15 +859,10 @@ ]), } -not_implemented = [] - -for name, (ty, methods) in expected_methods.items(): - for method in methods: - try: - if not hasattr(ty, method): - not_implemented.append((name, method)) - except NameError: - not_implemented.append((name, method)) +not_implemented = [(name, method) + for name, (val, methods) in expected_methods.items() + for method in methods + if not hasattr(val, method)] if not_implemented: for r in not_implemented: From 98889d5339f8ec2e5c17ca58becf6a66dfbcd6cb Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Mon, 1 Apr 2019 13:38:06 -0700 Subject: [PATCH 128/884] match_class! macro --- vm/src/macros.rs | 87 ++++++++++++++++++++++++++++++++++++++++++ vm/src/obj/objfloat.rs | 6 +++ 2 files changed, 93 insertions(+) diff --git a/vm/src/macros.rs b/vm/src/macros.rs index a631867976..601151a05e 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -147,3 +147,90 @@ macro_rules! extend_class { )* } } + +/// Macro to match on the built-in class of a Python object. +/// +/// Like `match`, `match_object!` is an expression, and so must have a default +/// arm that will be evaluated when the object was not an instance of any of +/// the classes specified in other arms. +/// +/// # Examples +/// +/// ``` +/// use num_bigint::ToBigInt; +/// use num_traits::Zero; +/// +/// use rustpython_vm::VirtualMachine; +/// use rustpython_vm::match_class; +/// use rustpython_vm::obj::objfloat::PyFloat; +/// use rustpython_vm::obj::objint::PyInt; +/// use rustpython_vm::pyobject::PyValue; +/// +/// let vm = VirtualMachine::new(); +/// let obj = PyInt::new(0).into_ref(&vm).into_object(); +/// assert_eq!( +/// "int", +/// match_class!(obj.clone(), +/// PyInt => "int", +/// PyFloat => "float", +/// _ => "neither" +/// ) +/// ); +/// +/// ``` +/// +/// With a binding to the downcasted type: +/// +/// ``` +/// use num_bigint::ToBigInt; +/// use num_traits::Zero; +/// +/// use rustpython_vm::VirtualMachine; +/// use rustpython_vm::match_class; +/// use rustpython_vm::obj::objfloat::PyFloat; +/// use rustpython_vm::obj::objint::PyInt; +/// use rustpython_vm::pyobject::PyValue; +/// +/// let vm = VirtualMachine::new(); +/// let obj = PyInt::new(0).into_ref(&vm).into_object(); +/// +/// let int_value = match_class!(obj, +/// i @ PyInt => i.as_bigint().clone(), +/// f @ PyFloat => f.to_f64().to_bigint().unwrap(), +/// obj => panic!("non-numeric object {}", obj) +/// ); +/// +/// assert!(int_value.is_zero()); +/// ``` +#[macro_export] +macro_rules! match_class { + // The default arm. + ($obj:expr, _ => $default:expr) => { + $default + }; + + // The default arm, binding the original object to the specified identifier. + ($obj:expr, $binding:ident => $default:expr) => {{ + let $binding = $obj; + $default + }}; + + // An arm taken when the object is an instance of the specified built-in + // class and binding the downcasted object to the specified identifier. + ($obj:expr, $binding:ident @ $class:ty => $expr:expr, $($rest:tt)*) => { + match $obj.downcast::<$class>() { + Ok($binding) => $expr, + Err(_obj) => match_class!(_obj, $($rest)*), + } + }; + + // An arm taken when the object is an instance of the specified built-in + // class. + ($obj:expr, $class:ty => $expr:expr, $($rest:tt)*) => { + if $obj.payload_is::<$class>() { + $expr + } else { + match_class!($obj, $($rest)*) + } + }; +} diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index ca6bad9d22..4ef7123eee 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -16,6 +16,12 @@ pub struct PyFloat { value: f64, } +impl PyFloat { + pub fn to_f64(&self) -> f64 { + self.value + } +} + impl PyValue for PyFloat { fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.float_type() From 8549ea22a3d280498d72b10b0fbfa8378f3dd146 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Mon, 1 Apr 2019 20:22:11 -0700 Subject: [PATCH 129/884] Use match_class! in objint::to_int --- vm/src/obj/objint.rs | 37 +++++++++++++++---------------------- vm/src/obj/objstr.rs | 7 +++++++ 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 08175dc07f..c26cdf4c04 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -12,8 +12,8 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objfloat; -use super::objstr; +use super::objfloat::{self, PyFloat}; +use super::objstr::{PyString, PyStringRef}; use super::objtype; use crate::obj::objtype::PyClassRef; @@ -351,7 +351,7 @@ impl PyIntRef { self.value.to_string() } - fn format(self, spec: PyRef, vm: &VirtualMachine) -> PyResult { + fn format(self, spec: PyStringRef, vm: &VirtualMachine) -> PyResult { let format_spec = FormatSpec::parse(&spec.value); match format_spec.format_int(&self.value) { Ok(string) => Ok(string), @@ -408,29 +408,22 @@ fn int_new(cls: PyClassRef, options: IntOptions, vm: &VirtualMachine) -> PyResul // Casting function: pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult { - let val = if objtype::isinstance(obj, &vm.ctx.int_type()) { - get_value(obj).clone() - } else if objtype::isinstance(obj, &vm.ctx.float_type()) { - objfloat::get_value(obj).to_bigint().unwrap() - } else if objtype::isinstance(obj, &vm.ctx.str_type()) { - let s = objstr::get_value(obj); - match i32::from_str_radix(&s, base) { - Ok(v) => v.to_bigint().unwrap(), - Err(err) => { - trace!("Error occurred during int conversion {:?}", err); - return Err(vm.new_value_error(format!( + match_class!(obj.clone(), + i @ PyInt => Ok(i.as_bigint().clone()), + f @ PyFloat => Ok(f.to_f64().to_bigint().unwrap()), + s @ PyString => { + i32::from_str_radix(s.as_str(), base) + .map(|i| BigInt::from(i)) + .map_err(|_|vm.new_value_error(format!( "invalid literal for int() with base {}: '{}'", base, s - ))); - } - } - } else { - return Err(vm.new_type_error(format!( + ))) + }, + obj => Err(vm.new_type_error(format!( "int() argument must be a string or a number, not '{}'", obj.class().name - ))); - }; - Ok(val) + ))) + ) } // Retrieve inner int value: diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index aa62812a24..8ac23c1607 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -25,6 +25,13 @@ pub struct PyString { // TODO: shouldn't be public pub value: String, } + +impl PyString { + pub fn as_str(&self) -> &str { + &self.value + } +} + pub type PyStringRef = PyRef; impl fmt::Display for PyString { From ea2622ee7b820dc4fb787aa47817b293fcb69c57 Mon Sep 17 00:00:00 2001 From: ben Date: Mon, 1 Apr 2019 19:38:20 +1300 Subject: [PATCH 130/884] Make slice.stop not an option --- vm/src/frame.rs | 11 ++++++++--- vm/src/obj/objslice.rs | 16 ++++++---------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 3a5f4c5abe..2cd080824e 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -407,10 +407,15 @@ impl Frame { } else { None }; - let stop = Some(self.pop_value()); - let start = Some(self.pop_value()); + let stop = self.pop_value(); + let start = self.pop_value(); - let obj = PySlice { start, stop, step }.into_ref(vm); + let obj = PySlice { + start: Some(start), + stop, + step, + } + .into_ref(vm); self.push_value(obj.into_object()); Ok(None) } diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index f6f02c8db2..82e4cef8cd 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -9,7 +9,7 @@ use num_bigint::BigInt; #[derive(Debug)] pub struct PySlice { pub start: Option, - pub stop: Option, + pub stop: PyObjectRef, pub step: Option, } @@ -30,7 +30,7 @@ fn slice_new(cls: PyClassRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult let stop = args.bind(vm)?; PySlice { start: None, - stop: Some(stop), + stop, step: None, } } @@ -39,7 +39,7 @@ fn slice_new(cls: PyClassRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult args.bind(vm)?; PySlice { start: Some(start), - stop: Some(stop), + stop, step: step.into_option(), } } @@ -60,8 +60,8 @@ impl PySliceRef { get_property_value(vm, &self.start) } - fn stop(self, vm: &VirtualMachine) -> PyObjectRef { - get_property_value(vm, &self.stop) + fn stop(self, _vm: &VirtualMachine) -> PyObjectRef { + self.stop.clone() } fn step(self, vm: &VirtualMachine) -> PyObjectRef { @@ -77,11 +77,7 @@ impl PySliceRef { } pub fn stop_index(&self, vm: &VirtualMachine) -> PyResult> { - if let Some(obj) = &self.stop { - to_index_value(vm, obj) - } else { - Ok(None) - } + to_index_value(vm, &self.stop) } pub fn step_index(&self, vm: &VirtualMachine) -> PyResult> { From 75d02a1725ff3b050b395ec79287ca0d85e6948b Mon Sep 17 00:00:00 2001 From: Joey Date: Tue, 2 Apr 2019 09:30:18 -0700 Subject: [PATCH 131/884] complex: move to impl block --- vm/src/obj/objcomplex.rs | 244 ++++++++++++++++++++------------------- 1 file changed, 123 insertions(+), 121 deletions(-) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 296830e437..9501ee7f36 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -35,17 +35,17 @@ pub fn init(context: &PyContext) { This is equivalent to (real + imag*1j) where imag defaults to 0."; extend_class!(context, complex_type, { - "__abs__" => context.new_rustfunc(complex_abs), - "__add__" => context.new_rustfunc(complex_add), "__doc__" => context.new_str(complex_doc.to_string()), - "__eq__" => context.new_rustfunc(complex_eq), - "__neg__" => context.new_rustfunc(complex_neg), - "__new__" => context.new_rustfunc(complex_new), - "__radd__" => context.new_rustfunc(complex_radd), - "__repr__" => context.new_rustfunc(complex_repr), - "conjugate" => context.new_rustfunc(complex_conjugate), - "imag" => context.new_property(complex_imag), - "real" => context.new_property(complex_real) + "__abs__" => context.new_rustfunc(PyComplexRef::abs), + "__add__" => context.new_rustfunc(PyComplexRef::add), + "__eq__" => context.new_rustfunc(PyComplexRef::eq), + "__neg__" => context.new_rustfunc(PyComplexRef::neg), + "__new__" => context.new_rustfunc(PyComplexRef::new), + "__radd__" => context.new_rustfunc(PyComplexRef::radd), + "__repr__" => context.new_rustfunc(PyComplexRef::repr), + "conjugate" => context.new_rustfunc(PyComplexRef::conjugate), + "imag" => context.new_property(PyComplexRef::imag), + "real" => context.new_property(PyComplexRef::real) }); } @@ -53,128 +53,130 @@ pub fn get_value(obj: &PyObjectRef) -> Complex64 { obj.payload::().unwrap().value } -fn complex_new( - cls: PyClassRef, - real: OptionalArg, - imag: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { - let real = match real { - OptionalArg::Missing => 0.0, - OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?, - }; - - let imag = match imag { - OptionalArg::Missing => 0.0, - OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?, - }; - - let value = Complex64::new(real, imag); - PyComplex { value }.into_ref_with_type(vm, cls) -} - -fn complex_real(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); - let Complex64 { re, .. } = get_value(zelf); - Ok(vm.ctx.new_float(re)) -} +impl PyComplexRef { + fn new( + cls: PyClassRef, + real: OptionalArg, + imag: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let real = match real { + OptionalArg::Missing => 0.0, + OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?, + }; + + let imag = match imag { + OptionalArg::Missing => 0.0, + OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?, + }; + + let value = Complex64::new(real, imag); + PyComplex { value }.into_ref_with_type(vm, cls) + } -fn complex_imag(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); - let Complex64 { im, .. } = get_value(zelf); - Ok(vm.ctx.new_float(im)) -} + fn real(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); + let Complex64 { re, .. } = get_value(zelf); + Ok(vm.ctx.new_float(re)) + } -fn complex_abs(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); + fn imag(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); + let Complex64 { im, .. } = get_value(zelf); + Ok(vm.ctx.new_float(im)) + } - let Complex64 { re, im } = get_value(zelf); - Ok(vm.ctx.new_float(re.hypot(im))) -} + fn abs(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); -fn complex_add(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.complex_type())), (i2, None)] - ); - - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.complex_type()) { - Ok(vm.ctx.new_complex(v1 + get_value(i2))) - } else if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm.ctx.new_complex(Complex64::new( - v1.re + objint::get_value(i2).to_f64().unwrap(), - v1.im, - ))) - } else { - Err(vm.new_type_error(format!("Cannot add {} and {}", i, i2))) + let Complex64 { re, im } = get_value(zelf); + Ok(vm.ctx.new_float(re.hypot(im))) } -} -fn complex_radd(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.complex_type())), (i2, None)] - ); - - let v1 = get_value(i); - - if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm.ctx.new_complex(Complex64::new( - v1.re + objint::get_value(i2).to_f64().unwrap(), - v1.im, - ))) - } else { - Err(vm.new_type_error(format!("Cannot add {} and {}", i, i2))) + fn add(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(i, Some(vm.ctx.complex_type())), (i2, None)] + ); + + let v1 = get_value(i); + if objtype::isinstance(i2, &vm.ctx.complex_type()) { + Ok(vm.ctx.new_complex(v1 + get_value(i2))) + } else if objtype::isinstance(i2, &vm.ctx.int_type()) { + Ok(vm.ctx.new_complex(Complex64::new( + v1.re + objint::get_value(i2).to_f64().unwrap(), + v1.im, + ))) + } else { + Err(vm.new_type_error(format!("Cannot add {} and {}", i, i2))) + } } -} -fn complex_conjugate(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.complex_type()))]); + fn radd(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(i, Some(vm.ctx.complex_type())), (i2, None)] + ); + + let v1 = get_value(i); + + if objtype::isinstance(i2, &vm.ctx.int_type()) { + Ok(vm.ctx.new_complex(Complex64::new( + v1.re + objint::get_value(i2).to_f64().unwrap(), + v1.im, + ))) + } else { + Err(vm.new_type_error(format!("Cannot add {} and {}", i, i2))) + } + } - let v1 = get_value(i); - Ok(vm.ctx.new_complex(v1.conj())) -} + fn conjugate(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(i, Some(vm.ctx.complex_type()))]); -fn complex_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.complex_type())), (other, None)] - ); - - let z = get_value(zelf); - - let result = if objtype::isinstance(other, &vm.ctx.complex_type()) { - z == get_value(other) - } else if objtype::isinstance(other, &vm.ctx.int_type()) { - match objint::get_value(other).to_f64() { - Some(f) => z.im == 0.0f64 && z.re == f, - None => false, - } - } else if objtype::isinstance(other, &vm.ctx.float_type()) { - z.im == 0.0 && z.re == objfloat::get_value(other) - } else { - return Ok(vm.ctx.not_implemented()); - }; + let v1 = get_value(i); + Ok(vm.ctx.new_complex(v1.conj())) + } - Ok(vm.ctx.new_bool(result)) -} + fn eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(zelf, Some(vm.ctx.complex_type())), (other, None)] + ); + + let z = get_value(zelf); + + let result = if objtype::isinstance(other, &vm.ctx.complex_type()) { + z == get_value(other) + } else if objtype::isinstance(other, &vm.ctx.int_type()) { + match objint::get_value(other).to_f64() { + Some(f) => z.im == 0.0f64 && z.re == f, + None => false, + } + } else if objtype::isinstance(other, &vm.ctx.float_type()) { + z.im == 0.0 && z.re == objfloat::get_value(other) + } else { + return Ok(vm.ctx.not_implemented()); + }; + + Ok(vm.ctx.new_bool(result)) + } -fn complex_neg(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); - Ok(vm.ctx.new_complex(-get_value(zelf))) -} + fn neg(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); + Ok(vm.ctx.new_complex(-get_value(zelf))) + } -fn complex_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.complex_type()))]); - let v = get_value(obj); - let repr = if v.re == 0. { - format!("{}j", v.im) - } else { - format!("({}+{}j)", v.re, v.im) - }; - Ok(vm.new_str(repr)) + fn repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(obj, Some(vm.ctx.complex_type()))]); + let v = get_value(obj); + let repr = if v.re == 0. { + format!("{}j", v.im) + } else { + format!("({}+{}j)", v.re, v.im) + }; + Ok(vm.new_str(repr)) + } } From 6863c19fb7aa8a87eae9e022080b586f092fcfcb Mon Sep 17 00:00:00 2001 From: Joey Date: Tue, 2 Apr 2019 09:51:01 -0700 Subject: [PATCH 132/884] complex: convert to new args style --- vm/src/obj/objcomplex.rs | 127 ++++++++++++++------------------------- 1 file changed, 46 insertions(+), 81 deletions(-) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 9501ee7f36..9f88dd6a41 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -1,11 +1,11 @@ use num_complex::Complex64; use num_traits::ToPrimitive; -use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::function::OptionalArg; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; -use super::objfloat; +use super::objfloat::{self, PyFloat}; use super::objint; use super::objtype::{self, PyClassRef}; @@ -74,109 +74,74 @@ impl PyComplexRef { PyComplex { value }.into_ref_with_type(vm, cls) } - fn real(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); - let Complex64 { re, .. } = get_value(zelf); - Ok(vm.ctx.new_float(re)) + fn real(self, _vm: &VirtualMachine) -> PyFloat { + self.value.re.into() } - fn imag(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); - let Complex64 { im, .. } = get_value(zelf); - Ok(vm.ctx.new_float(im)) + fn imag(self, _vm: &VirtualMachine) -> PyFloat { + self.value.im.into() } - fn abs(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); - - let Complex64 { re, im } = get_value(zelf); - Ok(vm.ctx.new_float(re.hypot(im))) + fn abs(self, _vm: &VirtualMachine) -> PyFloat { + let Complex64 { im, re } = self.value; + re.hypot(im).into() } - fn add(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.complex_type())), (i2, None)] - ); - - let v1 = get_value(i); - if objtype::isinstance(i2, &vm.ctx.complex_type()) { - Ok(vm.ctx.new_complex(v1 + get_value(i2))) - } else if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm.ctx.new_complex(Complex64::new( - v1.re + objint::get_value(i2).to_f64().unwrap(), - v1.im, - ))) + fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.complex_type()) { + vm.ctx.new_complex(self.value + get_value(&other)) + } else if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_complex(Complex64::new( + self.value.re + objint::get_value(&other).to_f64().unwrap(), + self.value.im, + )) } else { - Err(vm.new_type_error(format!("Cannot add {} and {}", i, i2))) + vm.ctx.not_implemented() } } - fn radd(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(i, Some(vm.ctx.complex_type())), (i2, None)] - ); - - let v1 = get_value(i); - - if objtype::isinstance(i2, &vm.ctx.int_type()) { - Ok(vm.ctx.new_complex(Complex64::new( - v1.re + objint::get_value(i2).to_f64().unwrap(), - v1.im, - ))) + fn radd(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + vm.ctx.new_complex(Complex64::new( + self.value.re + objint::get_value(&other).to_f64().unwrap(), + self.value.im, + )) } else { - Err(vm.new_type_error(format!("Cannot add {} and {}", i, i2))) + vm.ctx.not_implemented() } } - fn conjugate(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(i, Some(vm.ctx.complex_type()))]); - - let v1 = get_value(i); - Ok(vm.ctx.new_complex(v1.conj())) + fn conjugate(self, _vm: &VirtualMachine) -> PyComplex { + self.value.conj().into() } - fn eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.complex_type())), (other, None)] - ); - - let z = get_value(zelf); - - let result = if objtype::isinstance(other, &vm.ctx.complex_type()) { - z == get_value(other) - } else if objtype::isinstance(other, &vm.ctx.int_type()) { - match objint::get_value(other).to_f64() { - Some(f) => z.im == 0.0f64 && z.re == f, + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + let result = if objtype::isinstance(&other, &vm.ctx.complex_type()) { + self.value == get_value(&other) + } else if objtype::isinstance(&other, &vm.ctx.int_type()) { + match objint::get_value(&other).to_f64() { + Some(f) => self.value.im == 0.0f64 && self.value.re == f, None => false, } - } else if objtype::isinstance(other, &vm.ctx.float_type()) { - z.im == 0.0 && z.re == objfloat::get_value(other) + } else if objtype::isinstance(&other, &vm.ctx.float_type()) { + self.value.im == 0.0 && self.value.re == objfloat::get_value(&other) } else { - return Ok(vm.ctx.not_implemented()); + return vm.ctx.not_implemented(); }; - Ok(vm.ctx.new_bool(result)) + vm.ctx.new_bool(result) } - fn neg(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, Some(vm.ctx.complex_type()))]); - Ok(vm.ctx.new_complex(-get_value(zelf))) + fn neg(self, _vm: &VirtualMachine) -> PyComplex { + PyComplex::from(-self.value) } - fn repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(obj, Some(vm.ctx.complex_type()))]); - let v = get_value(obj); - let repr = if v.re == 0. { - format!("{}j", v.im) + fn repr(self, _vm: &VirtualMachine) -> String { + let Complex64 { re, im } = self.value; + if re == 0.0 { + format!("{}j", im) } else { - format!("({}+{}j)", v.re, v.im) - }; - Ok(vm.new_str(repr)) + format!("({}+{}j)", re, im) + } } } From 2fb3fc92ecaa13d031bdd3dd2e83ccf778660b46 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Tue, 2 Apr 2019 20:50:14 +0200 Subject: [PATCH 133/884] Support chained comparisons --- parser/src/ast.rs | 5 +- parser/src/parser.rs | 36 ++++++++------ parser/src/python.lalrpop | 10 +++- tests/snippets/ast_snippet.py | 7 +++ tests/snippets/comparisons.py | 13 +++++ vm/src/compile.rs | 93 ++++++++++++++++++++++++++++------- vm/src/stdlib/ast.rs | 20 ++++++-- 7 files changed, 141 insertions(+), 43 deletions(-) create mode 100644 tests/snippets/comparisons.py diff --git a/parser/src/ast.rs b/parser/src/ast.rs index 257ceadf2a..ece0e97988 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -159,9 +159,8 @@ pub enum Expression { value: Box, }, Compare { - a: Box, - op: Comparison, - b: Box, + vals: Vec, + ops: Vec, }, Attribute { value: Box, diff --git a/parser/src/parser.rs b/parser/src/parser.rs index a119740ec5..4c37f28423 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -460,26 +460,30 @@ mod tests { }, ifs: vec![ ast::Expression::Compare { - a: Box::new(ast::Expression::Identifier { - name: "a".to_string() - }), - op: ast::Comparison::Less, - b: Box::new(ast::Expression::Number { - value: ast::Number::Integer { - value: BigInt::from(5) + vals: vec![ + ast::Expression::Identifier { + name: "a".to_string() + }, + ast::Expression::Number { + value: ast::Number::Integer { + value: BigInt::from(5) + } } - }), + ], + ops: vec![ast::Comparison::Less], }, ast::Expression::Compare { - a: Box::new(ast::Expression::Identifier { - name: "a".to_string() - }), - op: ast::Comparison::Greater, - b: Box::new(ast::Expression::Number { - value: ast::Number::Integer { - value: BigInt::from(10) + vals: vec![ + ast::Expression::Identifier { + name: "a".to_string() + }, + ast::Expression::Number { + value: ast::Number::Integer { + value: BigInt::from(10) + } } - }), + ], + ops: vec![ast::Comparison::Greater], }, ], } diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 83020e926c..f4c81f1d8d 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -680,7 +680,15 @@ NotTest: ast::Expression = { }; Comparison: ast::Expression = { - => ast::Expression::Compare { a: Box::new(e1), op: op, b: Box::new(e2) }, + => { + let mut vals = vec![e]; + let mut ops = vec![]; + for x in comparisons { + ops.push(x.0); + vals.push(x.1); + } + ast::Expression::Compare { vals, ops } + }, => e, }; diff --git a/tests/snippets/ast_snippet.py b/tests/snippets/ast_snippet.py index 43bf74756b..dad767b45b 100644 --- a/tests/snippets/ast_snippet.py +++ b/tests/snippets/ast_snippet.py @@ -21,3 +21,10 @@ def foo(): assert foo.body[0].value.func.id == 'print' assert foo.body[0].lineno == 3 assert foo.body[1].lineno == 4 + +n = ast.parse("3 < 4 > 5\n") +assert n.body[0].value.left.n == 3 +assert 'Lt' in str(n.body[0].value.ops[0]) +assert 'Gt' in str(n.body[0].value.ops[1]) +assert n.body[0].value.comparators[0].n == 4 +assert n.body[0].value.comparators[1].n == 5 diff --git a/tests/snippets/comparisons.py b/tests/snippets/comparisons.py new file mode 100644 index 0000000000..1a4597ef6e --- /dev/null +++ b/tests/snippets/comparisons.py @@ -0,0 +1,13 @@ + +assert 1 < 2 +assert 1 < 2 < 3 +assert 5 == 5 == 5 +assert (5 == 5) == True +assert 5 == 5 != 4 == 4 > 3 > 2 < 3 <= 3 != 0 == 0 + +assert not 1 > 2 +assert not 5 == 5 == True +assert not 5 == 5 != 5 == 5 +assert not 1 < 2 < 3 > 4 +assert not 1 < 2 > 3 < 4 +assert not 1 > 2 < 3 < 4 diff --git a/vm/src/compile.rs b/vm/src/compile.rs index f79980af9f..fe295f4249 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -840,6 +840,79 @@ impl Compiler { Ok(()) } + fn compile_chained_comparison( + &mut self, + vals: &[ast::Expression], + ops: &[ast::Comparison], + ) -> Result<(), CompileError> { + assert!(ops.len() > 0); + assert!(vals.len() == ops.len() + 1); + + let to_operator = |op: &ast::Comparison| match op { + ast::Comparison::Equal => bytecode::ComparisonOperator::Equal, + ast::Comparison::NotEqual => bytecode::ComparisonOperator::NotEqual, + ast::Comparison::Less => bytecode::ComparisonOperator::Less, + ast::Comparison::LessOrEqual => bytecode::ComparisonOperator::LessOrEqual, + ast::Comparison::Greater => bytecode::ComparisonOperator::Greater, + ast::Comparison::GreaterOrEqual => bytecode::ComparisonOperator::GreaterOrEqual, + ast::Comparison::In => bytecode::ComparisonOperator::In, + ast::Comparison::NotIn => bytecode::ComparisonOperator::NotIn, + ast::Comparison::Is => bytecode::ComparisonOperator::Is, + ast::Comparison::IsNot => bytecode::ComparisonOperator::IsNot, + }; + + // a == b == c == d + // compile into (pseudocode): + // result = a == b + // if result: + // result = b == c + // if result: + // result = c == d + + // initialize lhs outside of loop + self.compile_expression(&vals[0])?; + + let break_label = self.new_label(); + let last_label = self.new_label(); + + // for all comparisons except the last (as the last one doesn't need a conditional jump) + let ops_slice = &ops[0..ops.len()]; + let vals_slice = &vals[1..ops.len()]; + for (op, val) in ops_slice.iter().zip(vals_slice.iter()) { + self.compile_expression(val)?; + // store rhs for the next comparison in chain + self.emit(Instruction::Duplicate); + self.emit(Instruction::Rotate { amount: 3 }); + + self.emit(Instruction::CompareOperation { + op: to_operator(op), + }); + + // if comparison result is false, we break with this value; if true, try the next one. + // (CPython compresses these three opcodes into JUMP_IF_FALSE_OR_POP) + self.emit(Instruction::Duplicate); + self.emit(Instruction::JumpIfFalse { + target: break_label, + }); + self.emit(Instruction::Pop); + } + + // handle the last comparison + self.compile_expression(vals.last().unwrap())?; + self.emit(Instruction::CompareOperation { + op: to_operator(ops.last().unwrap()), + }); + self.emit(Instruction::Jump { target: last_label }); + + // early exit left us with stack: `rhs, comparison_result`. We need to clean up rhs. + self.set_label(break_label); + self.emit(Instruction::Rotate { amount: 2 }); + self.emit(Instruction::Pop); + + self.set_label(last_label); + Ok(()) + } + fn compile_store(&mut self, target: &ast::Expression) -> Result<(), CompileError> { match target { ast::Expression::Identifier { name } => { @@ -1022,24 +1095,8 @@ impl Compiler { name: name.to_string(), }); } - ast::Expression::Compare { a, op, b } => { - self.compile_expression(a)?; - self.compile_expression(b)?; - - let i = match op { - ast::Comparison::Equal => bytecode::ComparisonOperator::Equal, - ast::Comparison::NotEqual => bytecode::ComparisonOperator::NotEqual, - ast::Comparison::Less => bytecode::ComparisonOperator::Less, - ast::Comparison::LessOrEqual => bytecode::ComparisonOperator::LessOrEqual, - ast::Comparison::Greater => bytecode::ComparisonOperator::Greater, - ast::Comparison::GreaterOrEqual => bytecode::ComparisonOperator::GreaterOrEqual, - ast::Comparison::In => bytecode::ComparisonOperator::In, - ast::Comparison::NotIn => bytecode::ComparisonOperator::NotIn, - ast::Comparison::Is => bytecode::ComparisonOperator::Is, - ast::Comparison::IsNot => bytecode::ComparisonOperator::IsNot, - }; - let i = Instruction::CompareOperation { op: i }; - self.emit(i); + ast::Expression::Compare { vals, ops } => { + self.compile_chained_comparison(vals, ops)?; } ast::Expression::Number { value } => { let const_value = match value { diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 0011407aba..91631b0c90 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -327,14 +327,14 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj node } - ast::Expression::Compare { a, op, b } => { + ast::Expression::Compare { vals, ops } => { let node = create_node(vm, "Compare"); - let py_a = expression_to_ast(vm, a); + let py_a = expression_to_ast(vm, &vals[0]); vm.ctx.set_attr(&node, "left", py_a); // Operator: - let str_op = match op { + let to_operator = |op: &ast::Comparison| match op { ast::Comparison::Equal => "Eq", ast::Comparison::NotEqual => "NotEq", ast::Comparison::Less => "Lt", @@ -346,10 +346,20 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj ast::Comparison::Is => "Is", ast::Comparison::IsNot => "IsNot", }; - let py_ops = vm.ctx.new_list(vec![vm.ctx.new_str(str_op.to_string())]); + let py_ops = vm.ctx.new_list( + ops.iter() + .map(|x| vm.ctx.new_str(to_operator(x).to_string())) + .collect(), + ); + vm.ctx.set_attr(&node, "ops", py_ops); - let py_b = vm.ctx.new_list(vec![expression_to_ast(vm, b)]); + let py_b = vm.ctx.new_list( + vals.iter() + .skip(1) + .map(|x| expression_to_ast(vm, x)) + .collect(), + ); vm.ctx.set_attr(&node, "comparators", py_b); node } From 23f57f757ccb5074e4c1e0c557fcf48bffaf9870 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 3 Apr 2019 14:08:12 +0200 Subject: [PATCH 134/884] Improve trailing comma situation --- parser/src/ast.rs | 6 ++-- parser/src/python.lalrpop | 61 +++++++++++++++--------------------- tests/snippets/function.py | 4 +++ tests/snippets/generators.py | 7 +++++ vm/src/compile.rs | 21 +++---------- vm/src/stdlib/ast.rs | 5 ++- 6 files changed, 45 insertions(+), 59 deletions(-) diff --git a/parser/src/ast.rs b/parser/src/ast.rs index ece0e97988..21c8dd736b 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -47,7 +47,7 @@ pub enum Statement { Break, Continue, Return { - value: Option>, + value: Option>, }, Import { import_parts: Vec, @@ -94,7 +94,7 @@ pub enum Statement { }, For { target: Expression, - iter: Vec, + iter: Expression, body: Vec, orelse: Option>, }, @@ -114,12 +114,10 @@ pub enum Statement { bases: Vec, keywords: Vec, decorator_list: Vec, - // TODO: docstring: String, }, FunctionDef { name: String, args: Parameters, - // docstring: String, body: Vec, decorator_list: Vec, returns: Option, diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index f4c81f1d8d..151e06de21 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -102,7 +102,7 @@ ExpressionStatement: ast::LocatedStatement = { } } }, - => { + => { // TODO: this works in most cases: let rhs = e2.into_iter().next().unwrap(); ast::LocatedStatement { @@ -117,15 +117,7 @@ ExpressionStatement: ast::LocatedStatement = { }; AssignSuffix: ast::Expression = { - "=" => { - if e.len() > 1 { - ast::Expression::Tuple { - elements: e - } - } else { - e.into_iter().next().unwrap() - } - }, + "=" => e, "=" => e, }; @@ -182,7 +174,7 @@ FlowStatement: ast::LocatedStatement = { "return" => { ast::LocatedStatement { location: loc, - node: ast::Statement::Return { value: t }, + node: ast::Statement::Return { value: t.map(Box::new) }, } }, => { @@ -622,22 +614,8 @@ Decorator: ast::Expression = { }; YieldExpr: ast::Expression = { - "yield" => { - ast::Expression::Yield { - value: ex.map(|expr| Box::new( - if expr.len() > 1 { - ast::Expression::Tuple { elements: expr } - } else { - expr.into_iter().next().unwrap() - }) - ) - } - }, - "yield" "from" => { - ast::Expression::YieldFrom { - value: Box::new(e), - } - }, + "yield" => ast::Expression::Yield { value: value.map(Box::new) }, + "yield" "from" => ast::Expression::YieldFrom { value: Box::new(e) }, }; Test: ast::Expression = { @@ -660,7 +638,7 @@ LambdaDef: ast::Expression = { "lambda" ?> ":" => ast::Expression::Lambda { args: p.unwrap_or(Default::default()), - body:Box::new(b) + body: Box::new(b) } } @@ -817,7 +795,7 @@ Atom: ast::Expression = { // List comprehension: e }, - "(" ")" => { + "(" ")" => { match e { None => ast::Expression::Tuple { elements: Vec::new() }, Some(elements) => { @@ -913,13 +891,26 @@ ExpressionList2: Vec = { }, }; +// TODO: this TestList2 should be renamed or removed. #[inline] -TestList: Vec = { - => { - let mut l = vec![e1]; - l.extend(e2.into_iter().map(|x| x.1)); - l - } +TestList2: Vec = { + > => elements, +}; + +// A test list is one of: +// - a list of expressions +// - a single expression +// - a single expression followed by a trailing comma +TestList: ast::Expression = { + > => { + if e.len() == 1 && trailing_comma.is_none() { + e.into_iter().next().unwrap() + } else { + ast::Expression::Tuple { + elements: e + } + } + } }; // Test diff --git a/tests/snippets/function.py b/tests/snippets/function.py index 7d3f25709a..aafc9b53c0 100644 --- a/tests/snippets/function.py +++ b/tests/snippets/function.py @@ -10,6 +10,10 @@ def my_func(a,): assert my_func(2) == 4 +def fubar(): + return 42, + +assert fubar() == (42,) def f1(): diff --git a/tests/snippets/generators.py b/tests/snippets/generators.py index 5f64c49050..65044c0da9 100644 --- a/tests/snippets/generators.py +++ b/tests/snippets/generators.py @@ -35,3 +35,10 @@ def g3(): # print(r) assert r == [23, 1, 2, 3, 44] +def g4(): + yield + yield 2, + +r = list(g4()) +assert r == [None, (2,)] + diff --git a/vm/src/compile.rs b/vm/src/compile.rs index fe295f4249..88818f52f0 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -353,19 +353,8 @@ impl Compiler { return Err(CompileError::InvalidReturn); } match value { - Some(e) => { - let size = e.len(); - for v in e { - self.compile_expression(v)?; - } - - // If we have more than 1 return value, make it a tuple: - if size > 1 { - self.emit(Instruction::BuildTuple { - size, - unpack: false, - }); - } + Some(v) => { + self.compile_expression(v)?; } None => { self.emit(Instruction::LoadConst { @@ -796,7 +785,7 @@ impl Compiler { fn compile_for( &mut self, target: &ast::Expression, - iter: &[ast::Expression], + iter: &ast::Expression, body: &[ast::LocatedStatement], orelse: &Option>, ) -> Result<(), CompileError> { @@ -810,9 +799,7 @@ impl Compiler { }); // The thing iterated: - for i in iter { - self.compile_expression(i)?; - } + self.compile_expression(iter)?; // Retrieve Iterator self.emit(Instruction::GetIter); diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 91631b0c90..65fcdf660b 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -151,8 +151,7 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P let node = create_node(vm, "Return"); let py_value = if let Some(value) = value { - vm.ctx - .new_tuple(value.iter().map(|v| expression_to_ast(vm, v)).collect()) + expression_to_ast(vm, value) } else { vm.ctx.none() }; @@ -189,7 +188,7 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P let py_target = expression_to_ast(vm, target); vm.ctx.set_attr(&node, "target", py_target); - let py_iter = expressions_to_ast(vm, iter); + let py_iter = expression_to_ast(vm, iter); vm.ctx.set_attr(&node, "iter", py_iter); let py_body = statements_to_ast(vm, body); From e078614e05b25b2a6dc08e1c81d1f0804512c6cb Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 3 Apr 2019 08:50:55 -0500 Subject: [PATCH 135/884] Change to classes and remove unnecesary if --- tests/snippets/whats_left_to_implement.py | 37 +++++++++++------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/snippets/whats_left_to_implement.py b/tests/snippets/whats_left_to_implement.py index a9a2d4de93..6abade9df2 100644 --- a/tests/snippets/whats_left_to_implement.py +++ b/tests/snippets/whats_left_to_implement.py @@ -1,7 +1,7 @@ import platform expected_methods = { - 'bool': (True, [ + 'bool': (bool, [ '__abs__', '__add__', '__and__', @@ -73,7 +73,7 @@ 'real', 'to_bytes', ]), - 'bytearray': (bytearray(), [ + 'bytearray': (bytearray, [ '__add__', '__alloc__', '__class__', @@ -159,7 +159,7 @@ 'upper', 'zfill', ]), - 'bytes': (b'', [ + 'bytes': (bytes, [ '__add__', '__class__', '__contains__', @@ -233,7 +233,7 @@ 'upper', 'zfill', ]), - 'complex': (0j, [ + 'complex': (complex, [ '__abs__', '__add__', '__bool__', @@ -284,7 +284,7 @@ 'imag', 'real', ]), - 'dict': ({}, [ + 'dict': (dict, [ '__class__', '__contains__', '__delattr__', @@ -326,7 +326,7 @@ 'update', 'values', ]), - 'float': (0.0, [ + 'float': (float, [ '__abs__', '__add__', '__bool__', @@ -384,7 +384,7 @@ 'is_integer', 'real', ]), - 'frozenset': (frozenset(), [ + 'frozenset': (frozenset, [ '__and__', '__class__', '__contains__', @@ -428,7 +428,7 @@ 'symmetric_difference', 'union', ]), - 'int': (0, [ + 'int': (int, [ '__abs__', '__add__', '__and__', @@ -529,7 +529,7 @@ '__str__', '__subclasshook__', ]), - 'list': ([], [ + 'list': (list, [ '__add__', '__class__', '__contains__', @@ -577,7 +577,7 @@ 'reverse', 'sort', ]), - 'memoryview': (memoryview(b''), [ + 'memoryview': (memoryview, [ '__class__', '__delattr__', '__delitem__', @@ -625,7 +625,7 @@ 'tobytes', 'tolist', ]), - 'range': (range(0), [ + 'range': (range, [ '__bool__', '__class__', '__contains__', @@ -661,7 +661,7 @@ 'step', 'stop', ]), - 'set': (set(), [ + 'set': (set, [ '__and__', '__class__', '__contains__', @@ -718,7 +718,7 @@ 'union', 'update', ]), - 'str': ('', [ + 'str': (str, [ '__add__', '__class__', '__contains__', @@ -797,7 +797,7 @@ 'upper', 'zfill' ]), - 'tuple': ((), [ + 'tuple': (tuple, [ '__add__', '__class__', '__contains__', @@ -832,7 +832,7 @@ 'count', 'index', ]), - 'object': (object(), [ + 'object': (object, [ '__repr__', '__hash__', '__str__', @@ -864,10 +864,9 @@ for method in methods if not hasattr(val, method)] -if not_implemented: - for r in not_implemented: - print(r[0], ".", r[1], sep="") -else: +for r in not_implemented: + print(r[0], ".", r[1], sep="") +if not not_implemented: print("Not much \\o/") if platform.python_implementation() == "CPython": From 29a137bf0f8722fd449e18997ced1dc964c088ea Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 3 Apr 2019 22:24:19 +0200 Subject: [PATCH 136/884] Drop testlist2. Also add test cases for trailing comma in for statement and tuple addition example. --- parser/src/python.lalrpop | 182 ++++++++++++++------------------------ tests/snippets/for.py | 12 +++ tests/snippets/tuple.py | 4 + 3 files changed, 81 insertions(+), 117 deletions(-) diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 151e06de21..305f97c10b 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -102,9 +102,7 @@ ExpressionStatement: ast::LocatedStatement = { } } }, - => { - // TODO: this works in most cases: - let rhs = e2.into_iter().next().unwrap(); + => { ast::LocatedStatement { location: loc, node: ast::Statement::AugAssign { @@ -122,18 +120,12 @@ AssignSuffix: ast::Expression = { }; TestOrStarExprList: ast::Expression = { - => { - let mut res = vec![e]; - res.extend(e2.into_iter().map(|x| x.1)); - - // First build tuple from first item: - let expr = if (res.len() > 1) || comma.is_some() { - ast::Expression::Tuple { elements: res } + > => { + if elements.len() == 1 && comma.is_none() { + elements.into_iter().next().unwrap() } else { - res.into_iter().next().unwrap() - }; - - expr + ast::Expression::Tuple { elements } + } } }; @@ -301,15 +293,11 @@ NonlocalStatement: ast::LocatedStatement = { }; AssertStatement: ast::LocatedStatement = { - "assert" => { + "assert" => { ast::LocatedStatement { location: loc, node: ast::Statement::Assert { - test: t, - msg: match m { - Some(e) => Some(e.1), - None => None, - } + test, msg: msg.map(|e| e.1) } } }, @@ -326,7 +314,7 @@ CompoundStatement: ast::LocatedStatement = { }; IfStatement: ast::LocatedStatement = { - "if" ":" => { + "if" ":" => { // Determine last else: let mut last = s3.map(|s| s.2); @@ -341,17 +329,17 @@ IfStatement: ast::LocatedStatement = { ast::LocatedStatement { location: loc, - node: ast::Statement::If { test: t, body: s1, orelse: last } + node: ast::Statement::If { test, body: s1, orelse: last } } }, }; WhileStatement: ast::LocatedStatement = { - "while" ":" => { + "while" ":" => { let or_else = s2.map(|s| s.2); ast::LocatedStatement { location: loc, - node: ast::Statement::While { test: e, body: s, orelse: or_else }, + node: ast::Statement::While { test, body, orelse: or_else }, } }, }; @@ -434,9 +422,7 @@ FuncDef: ast::LocatedStatement = { }; Parameters: ast::Parameters = { - "(" )?> ")" => { - a.unwrap_or_else(Default::default) - }, + "(" )?> ")" => a.unwrap_or_else(Default::default), }; // Note that this is a macro which is used once for function defs, and @@ -619,42 +605,41 @@ YieldExpr: ast::Expression = { }; Test: ast::Expression = { - => { - match c { - Some(c) => { - ast::Expression::IfExpression { - test: Box::new(c.1), - body: Box::new(e), - orelse: Box::new(c.3), - } - }, - None => e, + => { + if let Some(c) = condition { + ast::Expression::IfExpression { + test: Box::new(c.1), + body: Box::new(expr), + orelse: Box::new(c.3), + } + } else { + expr } }, - => e, + LambdaDef, }; LambdaDef: ast::Expression = { - "lambda" ?> ":" => + "lambda" ?> ":" => ast::Expression::Lambda { args: p.unwrap_or(Default::default()), - body: Box::new(b) + body: Box::new(body) } } OrTest: ast::Expression = { - => e, + AndTest, "or" => ast::Expression::BoolOp { a: Box::new(e1), op: ast::BooleanOperator::Or, b: Box::new(e2) }, }; AndTest: ast::Expression = { - => e, + NotTest, "and" => ast::Expression::BoolOp { a: Box::new(e1), op: ast::BooleanOperator::And, b: Box::new(e2) }, }; NotTest: ast::Expression = { "not" => ast::Expression::Unop { a: Box::new(e), op: ast::UnaryOperator::Not }, - => e, + Comparison, }; Comparison: ast::Expression = { @@ -667,7 +652,7 @@ Comparison: ast::Expression = { } ast::Expression::Compare { vals, ops } }, - => e, + Expression, }; CompOp: ast::Comparison = { @@ -685,7 +670,7 @@ CompOp: ast::Comparison = { Expression: ast::Expression = { "|" => ast::Expression::Binop { a: Box::new(e1), op: ast::Operator::BitOr, b: Box::new(e2) }, - => e, + XorExpression, }; XorExpression: ast::Expression = { @@ -735,7 +720,7 @@ Factor: ast::Expression = { "+" => ast::Expression::Unop { a: Box::new(e), op: ast::UnaryOperator::Pos }, "-" => ast::Expression::Unop { a: Box::new(e), op: ast::UnaryOperator::Neg }, "~" => ast::Expression::Unop { a: Box::new(e), op: ast::UnaryOperator::Inv }, - => e, + Power, }; Power: ast::Expression = { @@ -748,7 +733,7 @@ Power: ast::Expression = { }; AtomExpr: ast::Expression = { - => e, + Atom, "(" ")" => ast::Expression::Call { function: Box::new(f), args: a.0, keywords: a.1 }, "[" "]" => ast::Expression::Subscript { a: Box::new(e), b: Box::new(s) }, "." => ast::Expression::Attribute { value: Box::new(e), name: n }, @@ -769,7 +754,7 @@ SubscriptList: ast::Expression = { }; Subscript: ast::Expression = { - => e, + Test, ":" => { let s1 = e1.unwrap_or(ast::Expression::None); let s2 = e2.unwrap_or(ast::Expression::None); @@ -783,30 +768,17 @@ SliceOp: ast::Expression = { } Atom: ast::Expression = { - => ast::Expression::String { value: s }, - => ast::Expression::Bytes { value: b }, - => ast::Expression::Number { value: n }, - => ast::Expression::Identifier { name: i }, + => ast::Expression::String { value }, + => ast::Expression::Bytes { value }, + => ast::Expression::Number { value }, + => ast::Expression::Identifier { name }, "[" "]" => { let elements = e.unwrap_or(Vec::new()); ast::Expression::List { elements } }, - "[" "]" => { - // List comprehension: - e - }, - "(" ")" => { - match e { - None => ast::Expression::Tuple { elements: Vec::new() }, - Some(elements) => { - if elements.len() == 1 && trailing_comma.is_none() { - // This is "(e)", which is equivalent to "e" - elements.into_iter().next().unwrap() - } else { - ast::Expression::Tuple { elements } - } - } - } + "[" "]" => e, + "(" ")" => { + elements.unwrap_or(ast::Expression::Tuple { elements: Vec::new() }) }, "(" ")" => { ast::Expression::Comprehension { @@ -825,9 +797,7 @@ Atom: ast::Expression = { }; TestListComp: Vec = { - > <_trailing_comma:","?> => { - e - }, + > <_trailing_comma:","?> => e, }; TestListComp2: ast::Expression = { @@ -840,9 +810,7 @@ TestListComp2: ast::Expression = { }; TestDict: Vec<(ast::Expression, ast::Expression)> = { - > <_trailing_comma:","?> => { - e1 - } + > <_trailing_comma:","?> => elements, }; TestDictComp: ast::Expression = { @@ -859,9 +827,7 @@ DictEntry: (ast::Expression, ast::Expression) = { }; TestSet: Vec = { - > ","? => { - e1 - } + > ","? => e1 }; TestSetComp: ast::Expression = { @@ -873,28 +839,23 @@ TestSetComp: ast::Expression = { } }; +ExpressionOrStarExpression = { + Expression, + StarExpr +}; + ExpressionList: ast::Expression = { - => { - if e.len() == 1 { - e.into_iter().next().unwrap() + > => { + if elements.len() == 1 && trailing_comma.is_none() { + elements.into_iter().next().unwrap() } else { - ast::Expression::Tuple { elements: e } + ast::Expression::Tuple { elements } } }, }; ExpressionList2: Vec = { - ","? => { - let mut l = vec![e1]; - l.extend(e2.into_iter().map(|x| x.1)); - l - }, -}; - -// TODO: this TestList2 should be renamed or removed. -#[inline] -TestList2: Vec = { - > => elements, + > ","? => elements, }; // A test list is one of: @@ -902,13 +863,11 @@ TestList2: Vec = { // - a single expression // - a single expression followed by a trailing comma TestList: ast::Expression = { - > => { - if e.len() == 1 && trailing_comma.is_none() { - e.into_iter().next().unwrap() + > => { + if elements.len() == 1 && trailing_comma.is_none() { + elements.into_iter().next().unwrap() } else { - ast::Expression::Tuple { - elements: e - } + ast::Expression::Tuple { elements } } } }; @@ -919,27 +878,16 @@ StarExpr: ast::Expression = { }; // Comprehensions: -CompFor: Vec = { - => c, -}; +CompFor: Vec = => c; SingleForComprehension: ast::Comprehension = { - "for" "in" => { - ast::Comprehension { - target: e, - iter: i, - ifs: c2, - } + "for" "in" => { + ast::Comprehension { target, iter, ifs: c2 } } }; -ExpressionNoCond: ast::Expression = { - OrTest, -}; - -ComprehensionIf: ast::Expression = { - "if" => c, -}; +ExpressionNoCond: ast::Expression = OrTest; +ComprehensionIf: ast::Expression = "if" => c; ArgumentList: (Vec, Vec) = { > => { @@ -952,7 +900,7 @@ ArgumentList: (Vec, Vec) = { }, None => { if keywords.len() > 0 { - panic!("positional argument follows keyword argument"); + panic!("positional argument follows keyword argument {:?}", keywords); }; args.push(value); }, @@ -996,8 +944,8 @@ OneOrMore: Vec = { }; Number: ast::Number = { - => { ast::Number::Integer { value: s } }, - => { ast::Number::Float { value: s } }, + => { ast::Number::Integer { value } }, + => { ast::Number::Float { value } }, => { ast::Number::Complex { real: s.0, imag: s.1 } }, }; diff --git a/tests/snippets/for.py b/tests/snippets/for.py index 07e138e3c0..e913ac907f 100644 --- a/tests/snippets/for.py +++ b/tests/snippets/for.py @@ -10,3 +10,15 @@ x = 3 assert x == 3 + +y = [] +for x, in [(9,), [2]]: + y.append(x) + +assert y == [9, 2], str(y) + +y = [] +for x, *z in [(9,88,'b'), [2, 'bla'], [None]*4]: + y.append(z) + +assert y == [[88, 'b'], ['bla'], [None]*3], str(y) diff --git a/tests/snippets/tuple.py b/tests/snippets/tuple.py index d50f552dc0..b7393fe00e 100644 --- a/tests/snippets/tuple.py +++ b/tests/snippets/tuple.py @@ -34,3 +34,7 @@ def __eq__(self, x): foo = Foo() assert (foo,) == (foo,) + +a = (1, 2, 3) +a += 1, +assert a == (1, 2, 3, 1) From 88bf2a32c4452a92bb09c4a9b5513701c55f9aa3 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Wed, 3 Apr 2019 17:30:04 -0700 Subject: [PATCH 137/884] address feedback --- vm/src/macros.rs | 13 ++++++------- vm/src/obj/objint.rs | 1 + 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 601151a05e..47d3c531ab 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -150,9 +150,8 @@ macro_rules! extend_class { /// Macro to match on the built-in class of a Python object. /// -/// Like `match`, `match_object!` is an expression, and so must have a default -/// arm that will be evaluated when the object was not an instance of any of -/// the classes specified in other arms. +/// Like `match`, `match_class!` must be exhaustive, so a default arm with +/// the uncasted object is required. /// /// # Examples /// @@ -173,7 +172,7 @@ macro_rules! extend_class { /// match_class!(obj.clone(), /// PyInt => "int", /// PyFloat => "float", -/// _ => "neither" +/// _ => "neither", /// ) /// ); /// @@ -197,7 +196,7 @@ macro_rules! extend_class { /// let int_value = match_class!(obj, /// i @ PyInt => i.as_bigint().clone(), /// f @ PyFloat => f.to_f64().to_bigint().unwrap(), -/// obj => panic!("non-numeric object {}", obj) +/// obj => panic!("non-numeric object {}", obj), /// ); /// /// assert!(int_value.is_zero()); @@ -205,12 +204,12 @@ macro_rules! extend_class { #[macro_export] macro_rules! match_class { // The default arm. - ($obj:expr, _ => $default:expr) => { + ($obj:expr, _ => $default:expr $(,)?) => { $default }; // The default arm, binding the original object to the specified identifier. - ($obj:expr, $binding:ident => $default:expr) => {{ + ($obj:expr, $binding:ident => $default:expr $(,)?) => {{ let $binding = $obj; $default }}; diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index c26cdf4c04..d7b6a04206 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -407,6 +407,7 @@ fn int_new(cls: PyClassRef, options: IntOptions, vm: &VirtualMachine) -> PyResul } // Casting function: +// TODO: this should just call `__int__` on the object pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult { match_class!(obj.clone(), i @ PyInt => Ok(i.as_bigint().clone()), From fa53d5cd655be6956fa0086d9441a866938e19f6 Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Wed, 3 Apr 2019 21:11:03 -0600 Subject: [PATCH 138/884] impl list.__delitem__ using list.__setitem__ as a guide while it works as well as __setitem__ currently does this impliments no support for slice indexing or negative indexing, adtionaly a bad index panics insted of giving a vm type error! --- vm/src/obj/objlist.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 8657072018..1558db6aae 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -306,6 +306,25 @@ impl PyListRef { Ok(vm.ctx.not_implemented()) } } + + fn delitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let mut elements = self.elements.borrow_mut(); + if objtype::isinstance(&key, &vm.ctx.int_type()) { + let idx = objint::get_value(&key).to_i32().unwrap(); + if let Some(_) = elements.get_pos(idx) { + elements.remove(idx as usize); + 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?)", + elements, key + ) + } + } } fn list_new( @@ -454,6 +473,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), From 39d4d64a4cb1f15af9a5900df6af76c98bbffbb4 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 3 Apr 2019 17:11:26 +1300 Subject: [PATCH 139/884] Copy abc and dependent python files over from CPython 3.7 branch --- Lib/_py_abc.py | 147 +++++++++++++++++++++ Lib/_weakrefset.py | 196 ++++++++++++++++++++++++++++ Lib/abc.py | 170 +++++++++++++++++++++++++ PSF-LICENSE | 254 +++++++++++++++++++++++++++++++++++++ tests/snippets/abc_test.py | 4 + 5 files changed, 771 insertions(+) create mode 100644 Lib/_py_abc.py create mode 100644 Lib/_weakrefset.py create mode 100644 Lib/abc.py create mode 100644 PSF-LICENSE create mode 100644 tests/snippets/abc_test.py diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py new file mode 100644 index 0000000000..3c3aa8e3d6 --- /dev/null +++ b/Lib/_py_abc.py @@ -0,0 +1,147 @@ +from _weakrefset import WeakSet + + +def get_cache_token(): + """Returns the current ABC cache token. + + The token is an opaque object (supporting equality testing) identifying the + current version of the ABC cache for virtual subclasses. The token changes + with every call to ``register()`` on any ABC. + """ + return ABCMeta._abc_invalidation_counter + + +class ABCMeta(type): + """Metaclass for defining Abstract Base Classes (ABCs). + + Use this metaclass to create an ABC. An ABC can be subclassed + directly, and then acts as a mix-in class. You can also register + unrelated concrete classes (even built-in classes) and unrelated + ABCs as 'virtual subclasses' -- these and their descendants will + be considered subclasses of the registering ABC by the built-in + issubclass() function, but the registering ABC won't show up in + their MRO (Method Resolution Order) nor will method + implementations defined by the registering ABC be callable (not + even via super()). + """ + + # A global counter that is incremented each time a class is + # registered as a virtual subclass of anything. It forces the + # negative cache to be cleared before its next use. + # Note: this counter is private. Use `abc.get_cache_token()` for + # external code. + _abc_invalidation_counter = 0 + + def __new__(mcls, name, bases, namespace, **kwargs): + cls = super().__new__(mcls, name, bases, namespace, **kwargs) + # Compute set of abstract method names + abstracts = {name + for name, value in namespace.items() + if getattr(value, "__isabstractmethod__", False)} + for base in bases: + for name in getattr(base, "__abstractmethods__", set()): + value = getattr(cls, name, None) + if getattr(value, "__isabstractmethod__", False): + abstracts.add(name) + cls.__abstractmethods__ = frozenset(abstracts) + # Set up inheritance registry + cls._abc_registry = WeakSet() + cls._abc_cache = WeakSet() + cls._abc_negative_cache = WeakSet() + cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + return cls + + def register(cls, subclass): + """Register a virtual subclass of an ABC. + + Returns the subclass, to allow usage as a class decorator. + """ + if not isinstance(subclass, type): + raise TypeError("Can only register classes") + if issubclass(subclass, cls): + return subclass # Already a subclass + # Subtle: test for cycles *after* testing for "already a subclass"; + # this means we allow X.register(X) and interpret it as a no-op. + if issubclass(cls, subclass): + # This would create a cycle, which is bad for the algorithm below + raise RuntimeError("Refusing to create an inheritance cycle") + cls._abc_registry.add(subclass) + ABCMeta._abc_invalidation_counter += 1 # Invalidate negative cache + return subclass + + def _dump_registry(cls, file=None): + """Debug helper to print the ABC registry.""" + print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file) + print(f"Inv. counter: {get_cache_token()}", file=file) + for name in cls.__dict__: + if name.startswith("_abc_"): + value = getattr(cls, name) + if isinstance(value, WeakSet): + value = set(value) + print(f"{name}: {value!r}", file=file) + + def _abc_registry_clear(cls): + """Clear the registry (for debugging or testing).""" + cls._abc_registry.clear() + + def _abc_caches_clear(cls): + """Clear the caches (for debugging or testing).""" + cls._abc_cache.clear() + cls._abc_negative_cache.clear() + + def __instancecheck__(cls, instance): + """Override for isinstance(instance, cls).""" + # Inline the cache checking + subclass = instance.__class__ + if subclass in cls._abc_cache: + return True + subtype = type(instance) + if subtype is subclass: + if (cls._abc_negative_cache_version == + ABCMeta._abc_invalidation_counter and + subclass in cls._abc_negative_cache): + return False + # Fall back to the subclass check. + return cls.__subclasscheck__(subclass) + return any(cls.__subclasscheck__(c) for c in (subclass, subtype)) + + def __subclasscheck__(cls, subclass): + """Override for issubclass(subclass, cls).""" + if not isinstance(subclass, type): + raise TypeError('issubclass() arg 1 must be a class') + # Check cache + if subclass in cls._abc_cache: + return True + # Check negative cache; may have to invalidate + if cls._abc_negative_cache_version < ABCMeta._abc_invalidation_counter: + # Invalidate the negative cache + cls._abc_negative_cache = WeakSet() + cls._abc_negative_cache_version = ABCMeta._abc_invalidation_counter + elif subclass in cls._abc_negative_cache: + return False + # Check the subclass hook + ok = cls.__subclasshook__(subclass) + if ok is not NotImplemented: + assert isinstance(ok, bool) + if ok: + cls._abc_cache.add(subclass) + else: + cls._abc_negative_cache.add(subclass) + return ok + # Check if it's a direct subclass + if cls in getattr(subclass, '__mro__', ()): + cls._abc_cache.add(subclass) + return True + # Check if it's a subclass of a registered class (recursive) + for rcls in cls._abc_registry: + if issubclass(subclass, rcls): + cls._abc_cache.add(subclass) + return True + # Check if it's a subclass of a subclass (recursive) + for scls in cls.__subclasses__(): + if issubclass(subclass, scls): + cls._abc_cache.add(subclass) + return True + # No dice; update negative cache + cls._abc_negative_cache.add(subclass) + return False diff --git a/Lib/_weakrefset.py b/Lib/_weakrefset.py new file mode 100644 index 0000000000..304c66f59b --- /dev/null +++ b/Lib/_weakrefset.py @@ -0,0 +1,196 @@ +# Access WeakSet through the weakref module. +# This code is separated-out because it is needed +# by abc.py to load everything else at startup. + +from _weakref import ref + +__all__ = ['WeakSet'] + + +class _IterationGuard: + # This context manager registers itself in the current iterators of the + # weak container, such as to delay all removals until the context manager + # exits. + # This technique should be relatively thread-safe (since sets are). + + def __init__(self, weakcontainer): + # Don't create cycles + self.weakcontainer = ref(weakcontainer) + + def __enter__(self): + w = self.weakcontainer() + if w is not None: + w._iterating.add(self) + return self + + def __exit__(self, e, t, b): + w = self.weakcontainer() + if w is not None: + s = w._iterating + s.remove(self) + if not s: + w._commit_removals() + + +class WeakSet: + def __init__(self, data=None): + self.data = set() + def _remove(item, selfref=ref(self)): + self = selfref() + if self is not None: + if self._iterating: + self._pending_removals.append(item) + else: + self.data.discard(item) + self._remove = _remove + # A list of keys to be removed + self._pending_removals = [] + self._iterating = set() + if data is not None: + self.update(data) + + def _commit_removals(self): + l = self._pending_removals + discard = self.data.discard + while l: + discard(l.pop()) + + def __iter__(self): + with _IterationGuard(self): + for itemref in self.data: + item = itemref() + if item is not None: + # Caveat: the iterator will keep a strong reference to + # `item` until it is resumed or closed. + yield item + + def __len__(self): + return len(self.data) - len(self._pending_removals) + + def __contains__(self, item): + try: + wr = ref(item) + except TypeError: + return False + return wr in self.data + + def __reduce__(self): + return (self.__class__, (list(self),), + getattr(self, '__dict__', None)) + + def add(self, item): + if self._pending_removals: + self._commit_removals() + self.data.add(ref(item, self._remove)) + + def clear(self): + if self._pending_removals: + self._commit_removals() + self.data.clear() + + def copy(self): + return self.__class__(self) + + def pop(self): + if self._pending_removals: + self._commit_removals() + while True: + try: + itemref = self.data.pop() + except KeyError: + raise KeyError('pop from empty WeakSet') from None + item = itemref() + if item is not None: + return item + + def remove(self, item): + if self._pending_removals: + self._commit_removals() + self.data.remove(ref(item)) + + def discard(self, item): + if self._pending_removals: + self._commit_removals() + self.data.discard(ref(item)) + + def update(self, other): + if self._pending_removals: + self._commit_removals() + for element in other: + self.add(element) + + def __ior__(self, other): + self.update(other) + return self + + def difference(self, other): + newset = self.copy() + newset.difference_update(other) + return newset + __sub__ = difference + + def difference_update(self, other): + self.__isub__(other) + def __isub__(self, other): + if self._pending_removals: + self._commit_removals() + if self is other: + self.data.clear() + else: + self.data.difference_update(ref(item) for item in other) + return self + + def intersection(self, other): + return self.__class__(item for item in other if item in self) + __and__ = intersection + + def intersection_update(self, other): + self.__iand__(other) + def __iand__(self, other): + if self._pending_removals: + self._commit_removals() + self.data.intersection_update(ref(item) for item in other) + return self + + def issubset(self, other): + return self.data.issubset(ref(item) for item in other) + __le__ = issubset + + def __lt__(self, other): + return self.data < set(map(ref, other)) + + def issuperset(self, other): + return self.data.issuperset(ref(item) for item in other) + __ge__ = issuperset + + def __gt__(self, other): + return self.data > set(map(ref, other)) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return self.data == set(map(ref, other)) + + def symmetric_difference(self, other): + newset = self.copy() + newset.symmetric_difference_update(other) + return newset + __xor__ = symmetric_difference + + def symmetric_difference_update(self, other): + self.__ixor__(other) + def __ixor__(self, other): + if self._pending_removals: + self._commit_removals() + if self is other: + self.data.clear() + else: + self.data.symmetric_difference_update(ref(item, self._remove) for item in other) + return self + + def union(self, other): + return self.__class__(e for s in (self, other) for e in s) + __or__ = union + + def isdisjoint(self, other): + return len(self.intersection(other)) == 0 diff --git a/Lib/abc.py b/Lib/abc.py new file mode 100644 index 0000000000..7094141277 --- /dev/null +++ b/Lib/abc.py @@ -0,0 +1,170 @@ +# Copyright 2007 Google, Inc. All Rights Reserved. +# Licensed to PSF under a Contributor Agreement. + +"""Abstract Base Classes (ABCs) according to PEP 3119.""" + + +def abstractmethod(funcobj): + """A decorator indicating abstract methods. + + Requires that the metaclass is ABCMeta or derived from it. A + class that has a metaclass derived from ABCMeta cannot be + instantiated unless all of its abstract methods are overridden. + The abstract methods can be called using any of the normal + 'super' call mechanisms. + + Usage: + + class C(metaclass=ABCMeta): + @abstractmethod + def my_abstract_method(self, ...): + ... + """ + funcobj.__isabstractmethod__ = True + return funcobj + + +class abstractclassmethod(classmethod): + """A decorator indicating abstract classmethods. + + Similar to abstractmethod. + + Usage: + + class C(metaclass=ABCMeta): + @abstractclassmethod + def my_abstract_classmethod(cls, ...): + ... + + 'abstractclassmethod' is deprecated. Use 'classmethod' with + 'abstractmethod' instead. + """ + + __isabstractmethod__ = True + + def __init__(self, callable): + callable.__isabstractmethod__ = True + super().__init__(callable) + + +class abstractstaticmethod(staticmethod): + """A decorator indicating abstract staticmethods. + + Similar to abstractmethod. + + Usage: + + class C(metaclass=ABCMeta): + @abstractstaticmethod + def my_abstract_staticmethod(...): + ... + + 'abstractstaticmethod' is deprecated. Use 'staticmethod' with + 'abstractmethod' instead. + """ + + __isabstractmethod__ = True + + def __init__(self, callable): + callable.__isabstractmethod__ = True + super().__init__(callable) + + +class abstractproperty(property): + """A decorator indicating abstract properties. + + Requires that the metaclass is ABCMeta or derived from it. A + class that has a metaclass derived from ABCMeta cannot be + instantiated unless all of its abstract properties are overridden. + The abstract properties can be called using any of the normal + 'super' call mechanisms. + + Usage: + + class C(metaclass=ABCMeta): + @abstractproperty + def my_abstract_property(self): + ... + + This defines a read-only property; you can also define a read-write + abstract property using the 'long' form of property declaration: + + class C(metaclass=ABCMeta): + def getx(self): ... + def setx(self, value): ... + x = abstractproperty(getx, setx) + + 'abstractproperty' is deprecated. Use 'property' with 'abstractmethod' + instead. + """ + + __isabstractmethod__ = True + + +try: + from _abc import (get_cache_token, _abc_init, _abc_register, + _abc_instancecheck, _abc_subclasscheck, _get_dump, + _reset_registry, _reset_caches) +except ImportError: + from _py_abc import ABCMeta, get_cache_token + ABCMeta.__module__ = 'abc' +else: + class ABCMeta(type): + """Metaclass for defining Abstract Base Classes (ABCs). + + Use this metaclass to create an ABC. An ABC can be subclassed + directly, and then acts as a mix-in class. You can also register + unrelated concrete classes (even built-in classes) and unrelated + ABCs as 'virtual subclasses' -- these and their descendants will + be considered subclasses of the registering ABC by the built-in + issubclass() function, but the registering ABC won't show up in + their MRO (Method Resolution Order) nor will method + implementations defined by the registering ABC be callable (not + even via super()). + """ + def __new__(mcls, name, bases, namespace, **kwargs): + cls = super().__new__(mcls, name, bases, namespace, **kwargs) + _abc_init(cls) + return cls + + def register(cls, subclass): + """Register a virtual subclass of an ABC. + + Returns the subclass, to allow usage as a class decorator. + """ + return _abc_register(cls, subclass) + + def __instancecheck__(cls, instance): + """Override for isinstance(instance, cls).""" + return _abc_instancecheck(cls, instance) + + def __subclasscheck__(cls, subclass): + """Override for issubclass(subclass, cls).""" + return _abc_subclasscheck(cls, subclass) + + def _dump_registry(cls, file=None): + """Debug helper to print the ABC registry.""" + print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file) + print(f"Inv. counter: {get_cache_token()}", file=file) + (_abc_registry, _abc_cache, _abc_negative_cache, + _abc_negative_cache_version) = _get_dump(cls) + print(f"_abc_registry: {_abc_registry!r}", file=file) + print(f"_abc_cache: {_abc_cache!r}", file=file) + print(f"_abc_negative_cache: {_abc_negative_cache!r}", file=file) + print(f"_abc_negative_cache_version: {_abc_negative_cache_version!r}", + file=file) + + def _abc_registry_clear(cls): + """Clear the registry (for debugging or testing).""" + _reset_registry(cls) + + def _abc_caches_clear(cls): + """Clear the caches (for debugging or testing).""" + _reset_caches(cls) + + +class ABC(metaclass=ABCMeta): + """Helper class that provides a standard way to create an ABC using + inheritance. + """ + __slots__ = () diff --git a/PSF-LICENSE b/PSF-LICENSE new file mode 100644 index 0000000000..1afbedba92 --- /dev/null +++ b/PSF-LICENSE @@ -0,0 +1,254 @@ +A. HISTORY OF THE SOFTWARE +========================== + +Python was created in the early 1990s by Guido van Rossum at Stichting +Mathematisch Centrum (CWI, see http://www.cwi.nl) in the Netherlands +as a successor of a language called ABC. Guido remains Python's +principal author, although it includes many contributions from others. + +In 1995, Guido continued his work on Python at the Corporation for +National Research Initiatives (CNRI, see http://www.cnri.reston.va.us) +in Reston, Virginia where he released several versions of the +software. + +In May 2000, Guido and the Python core development team moved to +BeOpen.com to form the BeOpen PythonLabs team. In October of the same +year, the PythonLabs team moved to Digital Creations, which became +Zope Corporation. In 2001, the Python Software Foundation (PSF, see +https://www.python.org/psf/) was formed, a non-profit organization +created specifically to own Python-related Intellectual Property. +Zope Corporation was a sponsoring member of the PSF. + +All Python releases are Open Source (see http://www.opensource.org for +the Open Source Definition). Historically, most, but not all, Python +releases have also been GPL-compatible; the table below summarizes +the various releases. + + Release Derived Year Owner GPL- + from compatible? (1) + + 0.9.0 thru 1.2 1991-1995 CWI yes + 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes + 1.6 1.5.2 2000 CNRI no + 2.0 1.6 2000 BeOpen.com no + 1.6.1 1.6 2001 CNRI yes (2) + 2.1 2.0+1.6.1 2001 PSF no + 2.0.1 2.0+1.6.1 2001 PSF yes + 2.1.1 2.1+2.0.1 2001 PSF yes + 2.1.2 2.1.1 2002 PSF yes + 2.1.3 2.1.2 2002 PSF yes + 2.2 and above 2.1.1 2001-now PSF yes + +Footnotes: + +(1) GPL-compatible doesn't mean that we're distributing Python under + the GPL. All Python licenses, unlike the GPL, let you distribute + a modified version without making your changes open source. The + GPL-compatible licenses make it possible to combine Python with + other software that is released under the GPL; the others don't. + +(2) According to Richard Stallman, 1.6.1 is not GPL-compatible, + because its license has a choice of law clause. According to + CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1 + is "not incompatible" with the GPL. + +Thanks to the many outside volunteers who have worked under Guido's +direction to make these releases possible. + + +B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON +=============================================================== + +PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation +("PSF"), and the Individual or Organization ("Licensee") accessing and +otherwise using this software ("Python") in source or binary form and +its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python alone or in any derivative version, +provided, however, that PSF's License Agreement and PSF's notice of copyright, +i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, +2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 Python Software Foundation; All +Rights Reserved" are retained in Python alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python. + +4. PSF is making Python available to Licensee on an "AS IS" +basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any +relationship of agency, partnership, or joint venture between PSF and +Licensee. This License Agreement does not grant permission to use PSF +trademarks or trade name in a trademark sense to endorse or promote +products or services of Licensee, or any third party. + +8. By copying, installing or otherwise using Python, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0 +------------------------------------------- + +BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1 + +1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an +office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the +Individual or Organization ("Licensee") accessing and otherwise using +this software in source or binary form and its associated +documentation ("the Software"). + +2. Subject to the terms and conditions of this BeOpen Python License +Agreement, BeOpen hereby grants Licensee a non-exclusive, +royalty-free, world-wide license to reproduce, analyze, test, perform +and/or display publicly, prepare derivative works, distribute, and +otherwise use the Software alone or in any derivative version, +provided, however, that the BeOpen Python License is retained in the +Software, alone or in any derivative version prepared by Licensee. + +3. BeOpen is making the Software available to Licensee on an "AS IS" +basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE +SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS +AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY +DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +5. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +6. This License Agreement shall be governed by and interpreted in all +respects by the law of the State of California, excluding conflict of +law provisions. Nothing in this License Agreement shall be deemed to +create any relationship of agency, partnership, or joint venture +between BeOpen and Licensee. This License Agreement does not grant +permission to use BeOpen trademarks or trade names in a trademark +sense to endorse or promote products or services of Licensee, or any +third party. As an exception, the "BeOpen Python" logos available at +http://www.pythonlabs.com/logos.html may be used according to the +permissions granted on that web page. + +7. By copying, installing or otherwise using the software, Licensee +agrees to be bound by the terms and conditions of this License +Agreement. + + +CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1 +--------------------------------------- + +1. This LICENSE AGREEMENT is between the Corporation for National +Research Initiatives, having an office at 1895 Preston White Drive, +Reston, VA 20191 ("CNRI"), and the Individual or Organization +("Licensee") accessing and otherwise using Python 1.6.1 software in +source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, CNRI +hereby grants Licensee a nonexclusive, royalty-free, world-wide +license to reproduce, analyze, test, perform and/or display publicly, +prepare derivative works, distribute, and otherwise use Python 1.6.1 +alone or in any derivative version, provided, however, that CNRI's +License Agreement and CNRI's notice of copyright, i.e., "Copyright (c) +1995-2001 Corporation for National Research Initiatives; All Rights +Reserved" are retained in Python 1.6.1 alone or in any derivative +version prepared by Licensee. Alternately, in lieu of CNRI's License +Agreement, Licensee may substitute the following text (omitting the +quotes): "Python 1.6.1 is made available subject to the terms and +conditions in CNRI's License Agreement. This Agreement together with +Python 1.6.1 may be located on the Internet using the following +unique, persistent identifier (known as a handle): 1895.22/1013. This +Agreement may also be obtained from a proxy server on the Internet +using the following URL: http://hdl.handle.net/1895.22/1013". + +3. In the event Licensee prepares a derivative work that is based on +or incorporates Python 1.6.1 or any part thereof, and wants to make +the derivative work available to others as provided herein, then +Licensee hereby agrees to include in any such work a brief summary of +the changes made to Python 1.6.1. + +4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS" +basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR +IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND +DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS +FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT +INFRINGE ANY THIRD PARTY RIGHTS. + +5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON +1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS +A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1, +OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material +breach of its terms and conditions. + +7. This License Agreement shall be governed by the federal +intellectual property law of the United States, including without +limitation the federal copyright law, and, to the extent such +U.S. federal law does not apply, by the law of the Commonwealth of +Virginia, excluding Virginia's conflict of law provisions. +Notwithstanding the foregoing, with regard to derivative works based +on Python 1.6.1 that incorporate non-separable material that was +previously distributed under the GNU General Public License (GPL), the +law of the Commonwealth of Virginia shall govern this License +Agreement only as to issues arising under or with respect to +Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this +License Agreement shall be deemed to create any relationship of +agency, partnership, or joint venture between CNRI and Licensee. This +License Agreement does not grant permission to use CNRI trademarks or +trade name in a trademark sense to endorse or promote products or +services of Licensee, or any third party. + +8. By clicking on the "ACCEPT" button where indicated, or by copying, +installing or otherwise using Python 1.6.1, Licensee agrees to be +bound by the terms and conditions of this License Agreement. + + ACCEPT + + +CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2 +-------------------------------------------------- + +Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam, +The Netherlands. All rights reserved. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, +provided that the above copyright notice appear in all copies and that +both that copyright notice and this permission notice appear in +supporting documentation, and that the name of Stichting Mathematisch +Centrum or CWI not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE +FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/tests/snippets/abc_test.py b/tests/snippets/abc_test.py new file mode 100644 index 0000000000..a5b53ea61b --- /dev/null +++ b/tests/snippets/abc_test.py @@ -0,0 +1,4 @@ +import sys +sys.path.append('Lib') + +import abc From 0bd08618afc87e5b787ca3a0b1efe3622bad2e7e Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 3 Apr 2019 20:36:53 +1300 Subject: [PATCH 140/884] Change _py_abc to workaround current issue, and run test with PYTHONPATH linking to Lib directory. --- Lib/_py_abc.py | 2 +- tests/snippets/abc_test.py | 3 --- tests/test_snippets.py | 3 ++- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py index 3c3aa8e3d6..60adace9b8 100644 --- a/Lib/_py_abc.py +++ b/Lib/_py_abc.py @@ -33,7 +33,7 @@ class ABCMeta(type): _abc_invalidation_counter = 0 def __new__(mcls, name, bases, namespace, **kwargs): - cls = super().__new__(mcls, name, bases, namespace, **kwargs) + cls = type.__new__(mcls, name, bases, namespace, **kwargs) # Compute set of abstract method names abstracts = {name for name, value in namespace.items() diff --git a/tests/snippets/abc_test.py b/tests/snippets/abc_test.py index a5b53ea61b..494f967be1 100644 --- a/tests/snippets/abc_test.py +++ b/tests/snippets/abc_test.py @@ -1,4 +1 @@ -import sys -sys.path.append('Lib') - import abc diff --git a/tests/test_snippets.py b/tests/test_snippets.py index 7edbd7b7e5..b3bc79ee74 100644 --- a/tests/test_snippets.py +++ b/tests/test_snippets.py @@ -27,7 +27,7 @@ class _TestType(enum.Enum): } CPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR, 'py_code_object')) RUSTPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR)) - +RUSTPYTHON_LIB_DIR = os.path.abspath(os.path.join(ROOT_DIR, "Lib")) @contextlib.contextmanager def pushd(path): @@ -73,6 +73,7 @@ def run_via_rustpython(filename, test_type): env = os.environ.copy() env['RUST_LOG'] = 'info,cargo=error,jobserver=error' env['RUST_BACKTRACE'] = '1' + env['PYTHONPATH'] = RUSTPYTHON_LIB_DIR target = 'release' if env.get('CODE_COVERAGE', 'false') == 'true': From ea872521d6e114cc99cbd7dc0f08ac2f4b2467bf Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 4 Apr 2019 19:45:20 +1300 Subject: [PATCH 141/884] Make object by default hashable --- Lib/_py_abc.py | 2 +- tests/snippets/abc_test.py | 1 - tests/snippets/hash.py | 23 +++++++++++++++++++++++ tests/snippets/test_abc.py | 34 ++++++++++++++++++++++++++++++++++ vm/src/obj/objdict.rs | 5 +++++ vm/src/obj/objlist.rs | 5 +++++ vm/src/obj/objobject.rs | 4 ++-- vm/src/obj/objset.rs | 5 +++++ 8 files changed, 75 insertions(+), 4 deletions(-) delete mode 100644 tests/snippets/abc_test.py create mode 100644 tests/snippets/hash.py create mode 100644 tests/snippets/test_abc.py diff --git a/Lib/_py_abc.py b/Lib/_py_abc.py index 60adace9b8..07044f2f75 100644 --- a/Lib/_py_abc.py +++ b/Lib/_py_abc.py @@ -43,7 +43,7 @@ def __new__(mcls, name, bases, namespace, **kwargs): value = getattr(cls, name, None) if getattr(value, "__isabstractmethod__", False): abstracts.add(name) - cls.__abstractmethods__ = frozenset(abstracts) + cls.__abstractmethods__ = set(abstracts) # Set up inheritance registry cls._abc_registry = WeakSet() cls._abc_cache = WeakSet() diff --git a/tests/snippets/abc_test.py b/tests/snippets/abc_test.py deleted file mode 100644 index 494f967be1..0000000000 --- a/tests/snippets/abc_test.py +++ /dev/null @@ -1 +0,0 @@ -import abc diff --git a/tests/snippets/hash.py b/tests/snippets/hash.py new file mode 100644 index 0000000000..b108db7055 --- /dev/null +++ b/tests/snippets/hash.py @@ -0,0 +1,23 @@ + +from testutils import assertRaises + + +class A: + pass + + +assert type(hash(None)) is int +assert type(hash(object())) is int +assert type(hash(A())) is int +assert type(hash(1)) is int +assert type(hash(1.1)) is int +assert type(hash("")) is int + +with assertRaises(TypeError): + hash({}) + +with assertRaises(TypeError): + hash(set()) + +with assertRaises(TypeError): + hash([]) diff --git a/tests/snippets/test_abc.py b/tests/snippets/test_abc.py new file mode 100644 index 0000000000..6f0243fcdd --- /dev/null +++ b/tests/snippets/test_abc.py @@ -0,0 +1,34 @@ +import abc + +from testutils import assertRaises + + +class CustomInterface(abc.ABC): + @abc.abstractmethod + def a(self): + pass + + @classmethod + def __subclasshook__(cls, subclass): + return NotImplemented + + +# with assertRaises(TypeError): +# CustomInterface() + + +class Concrete: + def a(self): + pass + + +CustomInterface.register(Concrete) + + +class SubConcrete(Concrete): + pass + + +assert issubclass(Concrete, CustomInterface) +assert issubclass(SubConcrete, CustomInterface) + diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 79c98c2ddd..28b3f4210b 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -295,6 +295,10 @@ impl PyDictRef { } } } + + fn hash(self, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("unhashable type".to_string())) + } } impl DictProtocol for PyDictRef { @@ -333,6 +337,7 @@ pub fn init(context: &PyContext) { "__new__" => context.new_rustfunc(PyDictRef::new), "__repr__" => context.new_rustfunc(PyDictRef::repr), "__setitem__" => context.new_rustfunc(PyDictRef::setitem), + "__hash__" => context.new_rustfunc(PyDictRef::hash), "clear" => context.new_rustfunc(PyDictRef::clear), "values" => context.new_rustfunc(PyDictRef::values), "items" => context.new_rustfunc(PyDictRef::items), diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 8657072018..6f059b5926 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -162,6 +162,10 @@ impl PyListRef { Ok(s) } + fn hash(self, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("unhashable type".to_string())) + } + fn mul(self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { let new_elements = seq_mul(&self.elements.borrow(), counter); vm.ctx.new_list(new_elements) @@ -466,6 +470,7 @@ pub fn init(context: &PyContext) { "__len__" => context.new_rustfunc(PyListRef::len), "__new__" => context.new_rustfunc(list_new), "__repr__" => context.new_rustfunc(PyListRef::repr), + "__hash__" => context.new_rustfunc(PyListRef::hash), "__doc__" => context.new_str(list_doc.to_string()), "append" => context.new_rustfunc(PyListRef::append), "clear" => context.new_rustfunc(PyListRef::clear), diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index c43e31a969..df164f49b3 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -55,8 +55,8 @@ fn object_ge(_zelf: PyObjectRef, _other: PyObjectRef, vm: &VirtualMachine) -> Py vm.ctx.not_implemented() } -fn object_hash(_zelf: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Err(vm.new_type_error("unhashable type".to_string())) +fn object_hash(zelf: PyObjectRef, _vm: &VirtualMachine) -> u64 { + zelf.get_id() as u64 } fn object_setattr( diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index fb558c9906..34d487fbb9 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -588,6 +588,10 @@ fn frozenset_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.new_str(s)) } +fn set_hash(_zelf: PySetRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("unhashable type".to_string())) +} + pub fn init(context: &PyContext) { let set_type = &context.set_type; @@ -600,6 +604,7 @@ pub fn init(context: &PyContext) { "__len__" => context.new_rustfunc(set_len), "__new__" => context.new_rustfunc(set_new), "__repr__" => context.new_rustfunc(set_repr), + "__hash__" => context.new_rustfunc(set_hash), "__eq__" => context.new_rustfunc(set_eq), "__ge__" => context.new_rustfunc(set_ge), "__gt__" => context.new_rustfunc(set_gt), From c01dc5310a45fee5b43b98455be66d8d0d88b630 Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 4 Apr 2019 20:10:47 +1300 Subject: [PATCH 142/884] Add callback parameter to weakref, at the moment this callback isn't yet implement though. --- tests/snippets/test_abc.py | 5 +++++ vm/src/obj/objweakref.rs | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/snippets/test_abc.py b/tests/snippets/test_abc.py index 6f0243fcdd..391932c36a 100644 --- a/tests/snippets/test_abc.py +++ b/tests/snippets/test_abc.py @@ -13,6 +13,7 @@ def __subclasshook__(cls, subclass): return NotImplemented +# TODO raise an error if there are in any abstract methods not fulfilled # with assertRaises(TypeError): # CustomInterface() @@ -31,4 +32,8 @@ class SubConcrete(Concrete): assert issubclass(Concrete, CustomInterface) assert issubclass(SubConcrete, CustomInterface) +assert not issubclass(tuple, CustomInterface) +assert isinstance(Concrete(), CustomInterface) +assert isinstance(SubConcrete(), CustomInterface) +assert not isinstance((), CustomInterface) diff --git a/vm/src/obj/objweakref.rs b/vm/src/obj/objweakref.rs index 685e32b704..05d6147ce7 100644 --- a/vm/src/obj/objweakref.rs +++ b/vm/src/obj/objweakref.rs @@ -3,6 +3,7 @@ use crate::pyobject::PyValue; use crate::pyobject::{PyContext, PyObject, PyObjectPayload, PyObjectRef, PyRef, PyResult}; use crate::vm::VirtualMachine; +use crate::function::OptionalArg; use std::rc::{Rc, Weak}; #[derive(Debug)] @@ -32,7 +33,12 @@ pub type PyWeakRef = PyRef; impl PyWeakRef { // TODO callbacks - fn create(cls: PyClassRef, referent: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn create( + cls: PyClassRef, + referent: PyObjectRef, + _callback: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { PyWeak::downgrade(&referent).into_ref_with_type(vm, cls) } From aba059b81ae9d579134cbd521e624a44e3af3b93 Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Thu, 4 Apr 2019 02:52:51 -0600 Subject: [PATCH 143/884] impl PySliceableSequenceMut & objseqence::del_item used to impl list.__delitem__ --- vm/src/obj/objlist.rs | 39 +++++++---- vm/src/obj/objsequence.rs | 139 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 15 deletions(-) diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 1558db6aae..97a798f35a 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -1,8 +1,11 @@ use std::cell::{Cell, RefCell}; use std::fmt; +use std::ops::DerefMut; + use num_traits::ToPrimitive; + use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::{ReprGuard, VirtualMachine}; @@ -12,7 +15,7 @@ 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, del_item, }; use super::objtype; use crate::obj::objtype::PyClassRef; @@ -309,21 +312,27 @@ impl PyListRef { fn delitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { let mut elements = self.elements.borrow_mut(); - if objtype::isinstance(&key, &vm.ctx.int_type()) { - let idx = objint::get_value(&key).to_i32().unwrap(); - if let Some(_) = elements.get_pos(idx) { - elements.remove(idx as usize); - Ok(vm.get_none()) - } else { - Err(vm.new_index_error("list index out of range".to_string())) - } + // if objtype::isinstance(&key, &vm.ctx.int_type()) { + // let idx = objint::get_value(&key).to_i32().unwrap(); + // if let Some(_) = elements.get_pos(idx) { + // elements.remove(idx as usize); + // 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?)", - elements, key - ) - } + // } else { + // panic!( + // "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", + // elements, key + // ) + // } + del_item( + vm, + self.as_object(), + elements.deref_mut(), + key, + ) } } diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index a07b2454de..1d700e312f 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -137,6 +137,108 @@ impl PySliceableSequence for Vec { } } +pub trait PySliceableSequenceMut : PySliceableSequence { + //fn set_slice(&self, range: Range, seq: T) -> Self; + //fn set_slice_reverse(&self, range: Range, seq: T) -> Self; + //fn set_stepped_slice(&self, range: Range, step: usize, seq: T) -> Self; + //fn set_stepped_slice_reverse(&self, range: Range, step: usize, seq: T) -> Self; + + fn del_slice(&mut self, range: Range); + fn del_stepped_slice(&mut self, range: Range, step: usize); + fn del_index(&mut self, index: usize); + + fn del_slice_items(&mut self, vm: &VirtualMachine, slice: &PyObjectRef) -> PyResult { + // TODO: we could potentially avoid this copy and use slice + match slice.payload() { + Some(PySlice { start, stop, step }) => { + let step = 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 { + // TODO what to delete here? + 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(range, num as usize); + Ok(vm.get_none()) + }, + None => { + self.del_slice(range.end - 1..range.end); + Ok(vm.get_none()) + }, + } + } else { + // TODO what to del here? + Ok(vm.get_none()) + } + } + } + payload => panic!("del_slice_items called with non-slice: {:?}", payload), + } + } +} + +impl PySliceableSequenceMut for Vec { + fn del_slice(&mut self, range: Range) { + self.drain(range); + } + + fn del_stepped_slice(&mut self, range: Range, step: usize) { + // no easy way to delete steped indexes so heres what we'll do + let len = self.len(); + let mut deleted = 0; + let mut indexes = range.step_by(step).peekable(); + for i in 0..len { + // is this an index to delete? + if indexes.peek() == Some(&i) { + // record and move on + indexes.next(); + deleted += 1; + } else { + // swap towards front + self.swap(i - deleted, i); + } + } + // then truncate the vec (the values to delete should now be the last elements and thus choped off) + self.truncate(len - deleted); + } + + fn del_index(&mut self, index: usize) { + self.remove(index); + } +} + + pub fn get_item( vm: &VirtualMachine, sequence: &PyObjectRef, @@ -183,6 +285,43 @@ pub fn get_item( } } +pub fn del_item ( + vm: &VirtualMachine, + sequence: &PyObjectRef, + elements: &mut T, + subscript: PyObjectRef, +) -> PyResult { + if let Some(i) = subscript.payload::() { + return match i.as_bigint().to_i32() { + Some(value) => { + if let Some(pos_index) = elements.get_pos(value) { + elements.del_index(pos_index); + Ok(vm.get_none()) + } else { + Err(vm.new_index_error("Index out of bounds!".to_string())) + } + } + None => { + Err(vm.new_index_error("cannot fit 'int' into an index-sized integer".to_string())) + } + }; + } + + if subscript.payload::().is_some() { + + if sequence.payload::().is_some() { + elements.del_slice_items(vm, &subscript) + } else { + panic!("sequence del_item called for non-mutable-sequence") + } + } else { + Err(vm.new_type_error(format!( + "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", + sequence, subscript + ))) + } +} + pub fn seq_equal( vm: &VirtualMachine, zelf: &[PyObjectRef], From c97f342ef772f998cabed97baf6ebc8d916d46fc Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Thu, 4 Apr 2019 03:18:58 -0600 Subject: [PATCH 144/884] cleanup and rustfmt --- vm/src/obj/objlist.rs | 27 +++------------------------ vm/src/obj/objsequence.rs | 18 ++++++++---------- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 97a798f35a..6d7dc770cd 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -5,7 +5,6 @@ use std::ops::DerefMut; use num_traits::ToPrimitive; - use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::{ReprGuard, VirtualMachine}; @@ -14,8 +13,8 @@ use super::objbool; 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, del_item, + del_item, get_elements, get_elements_cell, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, + seq_mul, PySliceableSequence, }; use super::objtype; use crate::obj::objtype::PyClassRef; @@ -312,27 +311,7 @@ impl PyListRef { fn delitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { let mut elements = self.elements.borrow_mut(); - // if objtype::isinstance(&key, &vm.ctx.int_type()) { - // let idx = objint::get_value(&key).to_i32().unwrap(); - // if let Some(_) = elements.get_pos(idx) { - // elements.remove(idx as usize); - // 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?)", - // elements, key - // ) - // } - del_item( - vm, - self.as_object(), - elements.deref_mut(), - key, - ) + del_item(vm, self.as_object(), elements.deref_mut(), key) } } diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index 1d700e312f..2168166e8c 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -137,7 +137,7 @@ impl PySliceableSequence for Vec { } } -pub trait PySliceableSequenceMut : PySliceableSequence { +pub trait PySliceableSequenceMut: PySliceableSequence { //fn set_slice(&self, range: Range, seq: T) -> Self; //fn set_slice_reverse(&self, range: Range, seq: T) -> Self; //fn set_stepped_slice(&self, range: Range, step: usize, seq: T) -> Self; @@ -162,15 +162,15 @@ pub trait PySliceableSequenceMut : PySliceableSequence { 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 { // TODO what to delete here? @@ -187,15 +187,15 @@ pub trait PySliceableSequenceMut : PySliceableSequence { 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.end - 1..range.end); Ok(vm.get_none()) - }, + } } } else { // TODO what to del here? @@ -238,7 +238,6 @@ impl PySliceableSequenceMut for Vec { } } - pub fn get_item( vm: &VirtualMachine, sequence: &PyObjectRef, @@ -285,7 +284,7 @@ pub fn get_item( } } -pub fn del_item ( +pub fn del_item( vm: &VirtualMachine, sequence: &PyObjectRef, elements: &mut T, @@ -308,7 +307,6 @@ pub fn del_item ( } if subscript.payload::().is_some() { - if sequence.payload::().is_some() { elements.del_slice_items(vm, &subscript) } else { From e3994010f3595bc39508d1beb8b9bf9c25aaaacd Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Thu, 4 Apr 2019 04:04:26 -0600 Subject: [PATCH 145/884] add list.__delitem__ test snippits --- tests/snippets/list.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 6b9839321d..6efa52cc0f 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -170,3 +170,30 @@ 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] + +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) From d0e6c3bd9266ce0c70e9b8d8d4ffdbc414a8c8e8 Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Thu, 4 Apr 2019 04:52:33 -0600 Subject: [PATCH 146/884] convert to PySliceRef as it is the only option --- vm/src/obj/objsequence.rs | 113 ++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 54 deletions(-) diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index 2168166e8c..9593e57fe1 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -11,7 +11,7 @@ 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 { @@ -147,64 +147,64 @@ pub trait PySliceableSequenceMut: PySliceableSequence { fn del_stepped_slice(&mut self, range: Range, step: usize); fn del_index(&mut self, index: usize); - fn del_slice_items(&mut self, vm: &VirtualMachine, slice: &PyObjectRef) -> PyResult { - // TODO: we could potentially avoid this copy and use slice - match slice.payload() { - Some(PySlice { start, stop, step }) => { - let step = 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 { - // TODO what to delete here? + fn del_slice_items(&mut self, vm: &VirtualMachine, slice: &PySliceRef) -> 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()) } - } 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(range, num as usize); - Ok(vm.get_none()) - } - None => { - self.del_slice(range.end - 1..range.end); - Ok(vm.get_none()) - } - } - } else { - // TODO what to del here? + 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(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()) } - payload => panic!("del_slice_items called with non-slice: {:?}", payload), } + } } @@ -305,10 +305,15 @@ pub fn del_item( } }; } - + if subscript.payload::().is_some() { if sequence.payload::().is_some() { - elements.del_slice_items(vm, &subscript) + if let Ok(slice) = subscript.downcast::() { + elements.del_slice_items(vm, &slice) + } else { + panic!("PySlice is not a slice? this should not be") + } + } else { panic!("sequence del_item called for non-mutable-sequence") } From 05ede30c519f117998e9282bfed219e049cfd673 Mon Sep 17 00:00:00 2001 From: Joey Date: Thu, 4 Apr 2019 07:52:53 -0700 Subject: [PATCH 147/884] range index --- vm/src/obj/objrange.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index ae946d05eb..71adb41189 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -6,12 +6,14 @@ use num_integer::Integer; use num_traits::{One, Signed, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{Either, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{ + PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, +}; use crate::vm::VirtualMachine; use super::objint::{PyInt, PyIntRef}; use super::objiter; -use super::objslice::PySliceRef; +use super::objslice::{PySlice, PySliceRef}; use super::objtype::PyClassRef; #[derive(Debug, Clone)] @@ -261,16 +263,16 @@ impl PyRangeRef { } } - fn getitem(self, subscript: Either, vm: &VirtualMachine) -> PyResult { + fn getitem(self, subscript: RangeIndex, vm: &VirtualMachine) -> PyResult { match subscript { - Either::A(index) => { + RangeIndex::Int(index) => { if let Some(value) = self.get(index.as_bigint()) { Ok(PyInt::new(value).into_ref(vm).into_object()) } else { Err(vm.new_index_error("range object index out of range".to_string())) } } - Either::B(slice) => { + RangeIndex::Slice(slice) => { let new_start = if let Some(int) = slice.start.as_ref() { if let Some(i) = self.get(int) { PyInt::new(i).into_ref(vm) @@ -349,3 +351,21 @@ impl PyRangeIteratorRef { self } } + +pub enum RangeIndex { + Int(PyIntRef), + Slice(PySliceRef), +} + +impl TryFromObject for RangeIndex { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + match_class!(obj, + i @ PyInt => Ok(RangeIndex::Int(i)), + s @ PySlice => Ok(RangeIndex::Slice(s)), + obj => Err(vm.new_type_error(format!( + "sequence indices be integers or slices, not {}", + obj.class(), + ))) + ) + } +} From d96a5b11f7574d7214509c750d94e043a79424d3 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 4 Apr 2019 21:32:04 +0200 Subject: [PATCH 148/884] add resources to snippets --- .gitignore | 1 + tests/snippets/builtin_slice.py | 72 +- tests/snippets/slice_res.py | 2252 ------------------------------- tests/test_snippets.py | 112 +- 4 files changed, 107 insertions(+), 2330 deletions(-) delete mode 100644 tests/snippets/slice_res.py diff --git a/.gitignore b/.gitignore index 967a51a834..e9dd6d220a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ __pycache__ .vscode wasm-pack.log .idea/ +tests/snippets/resources \ No newline at end of file diff --git a/tests/snippets/builtin_slice.py b/tests/snippets/builtin_slice.py index 619356211e..d616980341 100644 --- a/tests/snippets/builtin_slice.py +++ b/tests/snippets/builtin_slice.py @@ -78,53 +78,35 @@ def test_all_slices(): """ test all possible slices except big number """ - MODE = None # set to "build" to rebuild slice_res.py - ll = [0, 1, 2, 3] - start = list(range(-7, 7)) - end = list(range(-7, 7)) - step = list(range(-5, 5)) - step.pop(step.index(0)) - - for i in [start, end, step]: - i.append(None) - - def build(): - # loop used to build slices_res.py with cpython - with open("slice_res.py", "wt") as f: - for s in start: - for e in end: - for t in step: - f.write(str(ll[s:e:t]) + "\n") - - def run(): - # test utility - from slice_res import SLICES_RES - - count = 0 - failures = [] - for s in start: - for e in end: - for t in step: - lhs = ll[s:e:t] - try: - assert lhs == SLICES_RES[count] - except AssertionError: - failures.append( - "start: {} ,stop: {}, step {}. Expected: {}, found: {}".format( - s, e, t, lhs, SLICES_RES[count] - ) - ) - count += 1 - if failures: - for f in failures: - print(f) - print(len(failures), "slices failed") + import resources + from resources.cpython_generated_slices import SLICES_RES, START, END, STEP, LL + + ll = LL + start = START + end = END + step = STEP + + count = 0 + failures = [] + for s in start: + for e in end: + for t in step: + lhs = ll[s:e:t] + try: + assert lhs == SLICES_RES[count] + except AssertionError: + failures.append( + "start: {} ,stop: {}, step {}. Expected: {}, found: {}".format( + s, e, t, lhs, SLICES_RES[count] + ) + ) + count += 1 - if MODE == "build": - build() - else: - run() + if failures: + for f in failures: + print(f) + print(len(failures), "slices failed") test_all_slices() diff --git a/tests/snippets/slice_res.py b/tests/snippets/slice_res.py deleted file mode 100644 index 38eb87729c..0000000000 --- a/tests/snippets/slice_res.py +++ /dev/null @@ -1,2252 +0,0 @@ -SLICES_RES = [ - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [0], - [0], - [0], - [0], - [0], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [1], - [1], - [1], - [1], - [1, 0], - [], - [], - [], - [], - [], - [1], - [1], - [1], - [1], - [1, 0], - [], - [], - [], - [], - [], - [1], - [1], - [1], - [1], - [1, 0], - [], - [], - [], - [], - [], - [1], - [1], - [1], - [1], - [1], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [1], - [1], - [1], - [1], - [1], - [], - [], - [], - [], - [], - [1, 2], - [1], - [1], - [1], - [1, 2], - [1], - [1], - [1], - [1], - [1], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [1], - [1], - [1], - [1], - [1], - [], - [], - [], - [], - [], - [1, 2], - [1], - [1], - [1], - [1, 2], - [], - [], - [], - [], - [], - [1, 2, 3], - [1, 3], - [1], - [1], - [1, 2, 3], - [], - [], - [], - [], - [], - [1, 2, 3], - [1, 3], - [1], - [1], - [1, 2, 3], - [], - [], - [], - [], - [], - [1, 2, 3], - [1, 3], - [1], - [1], - [1, 2, 3], - [1], - [1], - [1], - [1], - [1, 0], - [1, 2, 3], - [1, 3], - [1], - [1], - [1, 2, 3], - [2], - [2], - [2], - [2, 0], - [2, 1, 0], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2, 0], - [2, 1, 0], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2, 0], - [2, 1, 0], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2], - [2, 1], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2], - [2], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2], - [2], - [2], - [2], - [2], - [2], - [2, 1], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2], - [2], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2], - [2], - [], - [], - [], - [], - [], - [2, 3], - [2], - [2], - [2], - [2, 3], - [], - [], - [], - [], - [], - [2, 3], - [2], - [2], - [2], - [2, 3], - [], - [], - [], - [], - [], - [2, 3], - [2], - [2], - [2], - [2, 3], - [2], - [2], - [2], - [2, 0], - [2, 1, 0], - [2, 3], - [2], - [2], - [2], - [2, 3], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [3], - [3], - [3], - [3], - [3], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [0], - [0], - [0], - [0], - [0], - [], - [], - [], - [], - [], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [0], - [0], - [0], - [0], - [0], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [1], - [1], - [1], - [1], - [1, 0], - [], - [], - [], - [], - [], - [1], - [1], - [1], - [1], - [1, 0], - [], - [], - [], - [], - [], - [1], - [1], - [1], - [1], - [1, 0], - [], - [], - [], - [], - [], - [1], - [1], - [1], - [1], - [1], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [1], - [1], - [1], - [1], - [1], - [], - [], - [], - [], - [], - [1, 2], - [1], - [1], - [1], - [1, 2], - [1], - [1], - [1], - [1], - [1], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [1], - [1], - [1], - [1], - [1], - [], - [], - [], - [], - [], - [1, 2], - [1], - [1], - [1], - [1, 2], - [], - [], - [], - [], - [], - [1, 2, 3], - [1, 3], - [1], - [1], - [1, 2, 3], - [], - [], - [], - [], - [], - [1, 2, 3], - [1, 3], - [1], - [1], - [1, 2, 3], - [], - [], - [], - [], - [], - [1, 2, 3], - [1, 3], - [1], - [1], - [1, 2, 3], - [1], - [1], - [1], - [1], - [1, 0], - [1, 2, 3], - [1, 3], - [1], - [1], - [1, 2, 3], - [2], - [2], - [2], - [2, 0], - [2, 1, 0], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2, 0], - [2, 1, 0], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2, 0], - [2, 1, 0], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2], - [2, 1], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2], - [2], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2], - [2], - [2], - [2], - [2], - [2], - [2, 1], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2], - [2], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [2], - [2], - [2], - [2], - [2], - [], - [], - [], - [], - [], - [2, 3], - [2], - [2], - [2], - [2, 3], - [], - [], - [], - [], - [], - [2, 3], - [2], - [2], - [2], - [2, 3], - [], - [], - [], - [], - [], - [2, 3], - [2], - [2], - [2], - [2, 3], - [2], - [2], - [2], - [2, 0], - [2, 1, 0], - [2, 3], - [2], - [2], - [2], - [2, 3], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [3], - [3], - [3], - [3], - [3], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [0], - [0], - [0], - [0], - [0], - [3], - [3], - [3], - [3], - [3], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [3], - [3], - [3], - [3, 1], - [3, 2, 1], - [], - [], - [], - [], - [], - [3], - [3], - [3], - [3], - [3, 2], - [0], - [0], - [0], - [0], - [0], - [3], - [3], - [3], - [3], - [3], - [0, 1], - [0], - [0], - [0], - [0, 1], - [], - [], - [], - [], - [], - [0, 1, 2], - [0, 2], - [0], - [0], - [0, 1, 2], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [], - [], - [], - [], - [], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], - [3], - [3], - [3, 0], - [3, 1], - [3, 2, 1, 0], - [0, 1, 2, 3], - [0, 2], - [0, 3], - [0], - [0, 1, 2, 3], -] diff --git a/tests/test_snippets.py b/tests/test_snippets.py index 7edbd7b7e5..7a888f0935 100644 --- a/tests/test_snippets.py +++ b/tests/test_snippets.py @@ -1,4 +1,3 @@ - # This is a python unittest class automatically populating with all tests # in the tests folder. @@ -11,6 +10,8 @@ import subprocess import contextlib import enum +from pathlib import Path +import shutil import compile_code @@ -19,14 +20,15 @@ class _TestType(enum.Enum): functional = 1 -logger = logging.getLogger('tests') -ROOT_DIR = '..' -TEST_ROOT = os.path.abspath(os.path.join(ROOT_DIR, 'tests')) -TEST_DIRS = { - _TestType.functional: os.path.join(TEST_ROOT, 'snippets'), -} -CPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR, 'py_code_object')) +logger = logging.getLogger("tests") +ROOT_DIR = ".." +TEST_ROOT = os.path.abspath(os.path.join(ROOT_DIR, "tests")) +TEST_DIRS = {_TestType.functional: os.path.join(TEST_ROOT, "snippets")} +CPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR, "py_code_object")) RUSTPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR)) +RESOURCES_DIR = os.path.abspath( + os.path.join(TEST_DIRS[_TestType.functional], "resources") +) @contextlib.contextmanager @@ -38,12 +40,12 @@ def pushd(path): def perform_test(filename, method, test_type): - logger.info('Running %s via %s', filename, method) - if method == 'cpython': + logger.info("Running %s via %s", filename, method) + if method == "cpython": run_via_cpython(filename) - elif method == 'cpython_bytecode': + elif method == "cpython_bytecode": run_via_cpython_bytecode(filename, test_type) - elif method == 'rustpython': + elif method == "rustpython": run_via_rustpython(filename, test_type) else: raise NotImplementedError(method) @@ -57,27 +59,27 @@ def run_via_cpython(filename): def run_via_cpython_bytecode(filename, test_type): # Step1: Create bytecode file: - bytecode_filename = filename + '.bytecode' - with open(bytecode_filename, 'w') as f: + bytecode_filename = filename + ".bytecode" + with open(bytecode_filename, "w") as f: compile_code.compile_to_bytecode(filename, out_file=f) # Step2: run cpython bytecode: env = os.environ.copy() - env['RUST_LOG'] = 'info,cargo=error,jobserver=error' - env['RUST_BACKTRACE'] = '1' + env["RUST_LOG"] = "info,cargo=error,jobserver=error" + env["RUST_BACKTRACE"] = "1" with pushd(CPYTHON_RUNNER_DIR): - subprocess.check_call(['cargo', 'run', bytecode_filename], env=env) + subprocess.check_call(["cargo", "run", bytecode_filename], env=env) def run_via_rustpython(filename, test_type): env = os.environ.copy() - env['RUST_LOG'] = 'info,cargo=error,jobserver=error' - env['RUST_BACKTRACE'] = '1' + env["RUST_LOG"] = "info,cargo=error,jobserver=error" + env["RUST_BACKTRACE"] = "1" - target = 'release' - if env.get('CODE_COVERAGE', 'false') == 'true': - target = 'debug' - binary = os.path.abspath(os.path.join(ROOT_DIR, 'target', target, 'rustpython')) + target = "release" + if env.get("CODE_COVERAGE", "false") == "true": + target = "debug" + binary = os.path.abspath(os.path.join(ROOT_DIR, "target", target, "rustpython")) subprocess.check_call([binary, filename], env=env) @@ -85,15 +87,15 @@ def run_via_rustpython(filename, test_type): def create_test_function(cls, filename, method, test_type): """ Create a test function for a single snippet """ core_test_directory, snippet_filename = os.path.split(filename) - test_function_name = 'test_{}_'.format(method) \ - + os.path.splitext(snippet_filename)[0] \ - .replace('.', '_').replace('-', '_') + test_function_name = "test_{}_".format(method) + os.path.splitext(snippet_filename)[ + 0 + ].replace(".", "_").replace("-", "_") def test_function(self): perform_test(filename, method, test_type) if hasattr(cls, test_function_name): - raise ValueError('Duplicate test case {}'.format(test_function_name)) + raise ValueError("Duplicate test case {}".format(test_function_name)) setattr(cls, test_function_name, test_function) @@ -103,25 +105,69 @@ def wrapper(cls): for test_type, filename in get_test_files(): create_test_function(cls, filename, method, test_type) return cls + return wrapper def get_test_files(): """ Retrieve test files """ for test_type, test_dir in TEST_DIRS.items(): - for filepath in sorted(glob.iglob(os.path.join(test_dir, '*.py'))): + for filepath in sorted(glob.iglob(os.path.join(test_dir, "*.py"))): filename = os.path.split(filepath)[1] - if filename.startswith('xfail_'): + if filename.startswith("xfail_"): continue yield test_type, os.path.abspath(filepath) -@populate('cpython') +def generate_slices(path): + # loop used to build slices_res.py with cpython + ll = [0, 1, 2, 3] + start = list(range(-7, 7)) + end = list(range(-7, 7)) + step = list(range(-5, 5)) + step.pop(step.index(0)) + for i in [start, end, step]: + i.append(None) + + slices_res = [] + for s in start: + for e in end: + for t in step: + slices_res.append(ll[s:e:t]) + + path.write_text( + "SLICES_RES={}\nSTART= {}\nEND= {}\nSTEP= {}\nLL={}\n".format( + slices_res, start, end, step, ll + ) + ) + + +@populate("cpython") # @populate('cpython_bytecode') -@populate('rustpython') +@populate("rustpython") class SampleTestCase(unittest.TestCase): @classmethod def setUpClass(cls): - subprocess.check_call(['cargo', 'build']) - subprocess.check_call(['cargo', 'build', '--release']) + # setup resource dir + cls.resources = Path(RESOURCES_DIR) + if cls.resources.exists(): + shutil.rmtree( + RESOURCES_DIR, ignore_errors=True + ) # we don't care if dir doesnt exist + cls.resources.mkdir() + (cls.resources / "__init__.py").touch() + + # Here add resource files + cls.slices_resource_path = Path(RESOURCES_DIR) / "cpython_generated_slices.py" + generate_slices(cls.slices_resource_path) + + # cargo stuff + subprocess.check_call(["cargo", "build"]) + subprocess.check_call(["cargo", "build", "--release"]) + + @classmethod + def tearDownClass(cls): + shutil.rmtree( + RESOURCES_DIR, ignore_errors=True + ) # we don't care if dir doesnt exist From 79caaa5a0b025ab18462667f1b6bf3fe14a9ebe9 Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 5 Apr 2019 19:19:55 +1300 Subject: [PATCH 149/884] Move PSF-LICENSE and add Lib README.md --- PSF-LICENSE => Lib/PSF-LICENSE | 0 Lib/README.md | 10 ++++++++++ Lib/re.py | 11 ----------- 3 files changed, 10 insertions(+), 11 deletions(-) rename PSF-LICENSE => Lib/PSF-LICENSE (100%) create mode 100644 Lib/README.md delete mode 100644 Lib/re.py diff --git a/PSF-LICENSE b/Lib/PSF-LICENSE similarity index 100% rename from PSF-LICENSE rename to Lib/PSF-LICENSE diff --git a/Lib/README.md b/Lib/README.md new file mode 100644 index 0000000000..b3aade7bfa --- /dev/null +++ b/Lib/README.md @@ -0,0 +1,10 @@ +Standard Library for RustPython +=============================== + +This directory contains all of the Python files that make up the standard library for RustPython. + +Most of these files are copied over from the CPython repository(the 3.7 branch), with slight modifications to allow them +to work under RustPython. The current goal is to complete the standard library with as few modifications as possible. +Current modifications are just temporary workarounds for bugs/missing feature within the RustPython implementation. + +The first target is to run the ``unittest`` module, so we can leverage the CPython test suite. diff --git a/Lib/re.py b/Lib/re.py deleted file mode 100644 index 8511222be3..0000000000 --- a/Lib/re.py +++ /dev/null @@ -1,11 +0,0 @@ - -""" Regular expressions """ - - -def match(pattern, string, flags=0): - return _compile(pattern, flags).match(string) - - -def _compile(pattern, flags): - p = sre_compile.compile(pattern, flags) - return p From bce4f1e48373f6d4bcdc21fce17c6002505ab6fa Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Wed, 3 Apr 2019 17:23:27 +0100 Subject: [PATCH 150/884] Simplify/shrink the dict interface. --- vm/src/frame.rs | 15 ++--- vm/src/import.rs | 6 +- vm/src/obj/objdict.rs | 120 +++++++++------------------------------- vm/src/obj/objobject.rs | 4 +- vm/src/obj/objstr.rs | 6 ++ vm/src/obj/objtype.rs | 8 +-- vm/src/pyobject.rs | 67 ++++++++-------------- vm/src/stdlib/json.rs | 11 ++-- 8 files changed, 77 insertions(+), 160 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index b3cf2149bd..6a09f085af 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -385,21 +385,21 @@ impl Frame { Ok(None) } bytecode::Instruction::BuildMap { size, unpack } => { - let map_obj = vm.ctx.new_dict().into_object(); + let map_obj = vm.ctx.new_dict(); for _x in 0..*size { let obj = self.pop_value(); if *unpack { // Take all key-value pairs from the dict: let dict_elements = objdict::get_key_value_pairs(&obj); for (key, value) in dict_elements.iter() { - objdict::set_item(&map_obj, vm, key, value); + map_obj.set_item(&vm.ctx, &objstr::get_value(key), value.clone()); } } else { let key = self.pop_value(); - objdict::set_item(&map_obj, vm, &key, &obj); + map_obj.set_item(&vm.ctx, &objstr::get_value(&key), obj) } } - self.push_value(map_obj); + self.push_value(map_obj.into_object()); Ok(None) } bytecode::Instruction::BuildSlice { size } => { @@ -613,10 +613,10 @@ impl Frame { bytecode::CallType::Ex(has_kwargs) => { let kwargs = if *has_kwargs { let kw_dict = self.pop_value(); - let dict_elements = objdict::get_elements(&kw_dict).clone(); + let dict_elements = objdict::get_key_value_pairs(&kw_dict).clone(); dict_elements .into_iter() - .map(|elem| (elem.0, (elem.1).1)) + .map(|elem| (objstr::get_value(&elem.0), elem.1)) .collect() } else { vec![] @@ -1210,7 +1210,8 @@ impl fmt::Debug for Frame { .map(|elem| format!("\n > {:?}", elem)) .collect::(); let dict = self.scope.get_locals(); - let local_str = objdict::get_key_value_pairs_from_content(&dict.entries.borrow()) + let local_str = dict + .get_key_value_pairs() .iter() .map(|elem| format!("\n {:?} = {:?}", elem.0, elem.1)) .collect::(); diff --git a/vm/src/import.rs b/vm/src/import.rs index 936b896805..179dd4321f 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; use crate::compile; use crate::frame::Scope; use crate::obj::{objsequence, objstr}; -use crate::pyobject::{DictProtocol, PyResult}; +use crate::pyobject::{DictProtocol, ItemProtocol, PyResult}; use crate::util; use crate::vm::VirtualMachine; @@ -47,11 +47,11 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &str) -> PyResult { // First, see if we already loaded the module: let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules")?; - if let Some(module) = sys_modules.get_item(module_name) { + if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) { return Ok(module); } let module = import_uncached_module(vm, current_path, module_name)?; - sys_modules.set_item(&vm.ctx, module_name, module.clone()); + sys_modules.set_item(module_name, module.clone(), vm)?; Ok(module) } diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 79c98c2ddd..15046a405f 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -1,7 +1,6 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::fmt; -use std::ops::{Deref, DerefMut}; use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ @@ -37,94 +36,16 @@ impl PyValue for PyDict { } } -pub fn get_elements<'a>(obj: &'a PyObjectRef) -> impl Deref + 'a { - obj.payload::().unwrap().entries.borrow() -} - -pub fn get_mut_elements<'a>(obj: &'a PyObjectRef) -> impl DerefMut + 'a { - obj.payload::().unwrap().entries.borrow_mut() -} - -pub fn set_item( - dict: &PyObjectRef, - _vm: &VirtualMachine, - needle: &PyObjectRef, - value: &PyObjectRef, -) { - // TODO: use vm to call eventual __hash__ and __eq__methods. - let mut elements = get_mut_elements(dict); - set_item_in_content(&mut elements, needle, value); -} - -pub fn set_item_in_content( - elements: &mut DictContentType, - needle: &PyObjectRef, - value: &PyObjectRef, -) { - // XXX: Currently, we only support String keys, so we have to unwrap the - // PyObject (and ensure it is a String). - - // TODO: invoke __hash__ function here! - let needle_str = objstr::get_value(needle); - elements.insert(needle_str, (needle.clone(), value.clone())); -} - pub fn get_key_value_pairs(dict: &PyObjectRef) -> Vec<(PyObjectRef, PyObjectRef)> { - let dict_elements = get_elements(dict); - get_key_value_pairs_from_content(&dict_elements) -} - -pub fn get_key_value_pairs_from_content( - dict_content: &DictContentType, -) -> Vec<(PyObjectRef, PyObjectRef)> { + let dict_elements = dict.payload::().unwrap().entries.borrow(); let mut pairs: Vec<(PyObjectRef, PyObjectRef)> = Vec::new(); - for (_str_key, pair) in dict_content.iter() { + for (_str_key, pair) in dict_elements.iter() { let (key, obj) = pair; pairs.push((key.clone(), obj.clone())); } pairs } -pub fn get_item(dict: &PyObjectRef, key: &PyObjectRef) -> Option { - let needle_str = objstr::get_value(key); - get_key_str(dict, &needle_str) -} - -// Special case for the case when requesting a str key from a dict: -pub fn get_key_str(dict: &PyObjectRef, key: &str) -> Option { - let elements = get_elements(dict); - content_get_key_str(&elements, key) -} - -/// Retrieve a key from dict contents: -pub fn content_get_key_str(elements: &DictContentType, key: &str) -> Option { - // TODO: let hash: usize = key; - match elements.get(key) { - Some(v) => Some(v.1.clone()), - None => None, - } -} - -pub fn contains_key_str(dict: &PyObjectRef, key: &str) -> bool { - let elements = get_elements(dict); - content_contains_key_str(&elements, key) -} - -pub fn content_contains_key_str(elements: &DictContentType, key: &str) -> bool { - // TODO: let hash: usize = key; - elements.get(key).is_some() -} - -/// Take a python dictionary and convert it to attributes. -pub fn py_dict_to_attributes(dict: &PyObjectRef) -> PyAttributes { - let mut attrs = PyAttributes::new(); - for (key, value) in get_key_value_pairs(dict) { - let key = objstr::get_value(&key); - attrs.insert(key, value); - } - attrs -} - // Python dict methods: impl PyDictRef { fn new( @@ -137,7 +58,7 @@ impl PyDictRef { if let OptionalArg::Present(dict_obj) = dict_obj { if objtype::isinstance(&dict_obj, &vm.ctx.dict_type()) { for (needle, value) in get_key_value_pairs(&dict_obj) { - set_item(dict.as_object(), vm, &needle, &value); + dict.set_item(&vm.ctx, &objstr::get_value(&needle), value); } } else { let iter = objiter::get_iter(vm, &dict_obj)?; @@ -156,13 +77,13 @@ impl PyDictRef { if objiter::get_next_object(vm, &elem_iter)?.is_some() { return Err(err(vm)); } - set_item(dict.as_object(), vm, &needle, &value); + dict.set_item(&vm.ctx, &objstr::get_value(&needle), value); } } } for (needle, value) in kwargs.into_iter() { let py_needle = vm.new_str(needle); - set_item(&dict.as_object(), vm, &py_needle, &value); + dict.set_item(&vm.ctx, &objstr::get_value(&py_needle), value); } Ok(dict) } @@ -259,9 +180,8 @@ impl PyDictRef { } } - fn setitem(self, needle: PyObjectRef, value: PyObjectRef, _vm: &VirtualMachine) { - let mut elements = self.entries.borrow_mut(); - set_item_in_content(&mut elements, &needle, &value) + fn setitem(self, needle: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) { + self.set_item(&vm.ctx, &objstr::get_value(&needle), value) } fn getitem(self, key: PyStringRef, vm: &VirtualMachine) -> PyResult { @@ -295,15 +215,28 @@ impl PyDictRef { } } } + + /// Take a python dictionary and convert it to attributes. + pub fn to_attributes(self) -> PyAttributes { + let mut attrs = PyAttributes::new(); + for (key, value) in self.get_key_value_pairs() { + let key = objstr::get_value(&key); + attrs.insert(key, value); + } + attrs + } } impl DictProtocol for PyDictRef { fn contains_key(&self, k: &str) -> bool { - content_contains_key_str(&self.entries.borrow(), k) + self.entries.borrow().get(k).is_some() } fn get_item(&self, k: &str) -> Option { - content_get_key_str(&self.entries.borrow(), k) + match self.entries.borrow().get(k) { + Some(v) => Some(v.1.clone()), + None => None, + } } fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { @@ -311,13 +244,14 @@ impl DictProtocol for PyDictRef { } // Item set/get: - fn set_item(&self, ctx: &PyContext, key: &str, v: PyObjectRef) { - let key = ctx.new_str(key.to_string()); - set_item_in_content(&mut self.entries.borrow_mut(), &key, &v); + fn set_item(&self, ctx: &PyContext, key_str: &str, v: PyObjectRef) { + let key = ctx.new_str(key_str.to_string()); + let elements = &mut self.entries.borrow_mut(); + elements.insert(key_str.to_string(), (key.clone(), v.clone())); } fn del_item(&self, key: &str) { - let mut elements = get_mut_elements(self.as_object()); + let elements = &mut self.entries.borrow_mut(); elements.remove(key).unwrap(); } } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index c43e31a969..11a8309abe 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -1,4 +1,4 @@ -use super::objdict::{self, PyDictRef}; +use super::objdict::PyDictRef; use super::objlist::PyList; use super::objstr::PyStringRef; use super::objtype; @@ -233,7 +233,7 @@ pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes { // Get instance attributes: if let Some(dict) = &obj.dict { - for (key, value) in objdict::get_key_value_pairs(dict.as_object()) { + for (key, value) in dict.get_key_value_pairs() { attributes.insert(key.to_string(), value.clone()); } } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index a8264b2de0..592a4950f3 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -756,6 +756,12 @@ impl IntoPyObject for String { } } +impl IntoPyObject for &str { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_str(self.to_string())) + } +} + pub fn init(ctx: &PyContext) { PyStringRef::extend_class(ctx, &ctx.str_type); } diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 14958300bf..16cfbaa5ee 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -9,7 +9,6 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objdict; use super::objdict::PyDictRef; use super::objlist::PyList; use super::objproperty::PropertyBuilder; @@ -244,12 +243,7 @@ pub fn type_new_class( ) -> PyResult { let mut bases: Vec = bases.iter(vm)?.collect::, _>>()?; bases.push(vm.ctx.object()); - new( - typ.clone(), - &name.value, - bases, - objdict::py_dict_to_attributes(dict.as_object()), - ) + new(typ.clone(), &name.value, bases, dict.to_attributes()) } pub fn type_call(class: PyClassRef, args: Args, kwargs: KwArgs, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index d6982aff69..12d6fa1cae 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -684,16 +684,6 @@ impl PyContext { .into_ref() } - // Item set/get: - pub fn set_item(&self, obj: &PyObjectRef, key: &str, v: PyObjectRef) { - if let Some(dict) = obj.payload::() { - let key = self.new_str(key.to_string()); - objdict::set_item_in_content(&mut dict.entries.borrow_mut(), &key, &v); - } else { - unimplemented!() - }; - } - pub fn set_attr<'a, T: Into<&'a PyObjectRef>, V: Into>( &'a self, obj: T, @@ -948,44 +938,35 @@ pub trait DictProtocol { fn del_item(&self, key: &str); } -impl DictProtocol for PyObjectRef { - fn contains_key(&self, k: &str) -> bool { - if let Some(dict) = self.payload::() { - objdict::content_contains_key_str(&dict.entries.borrow(), k) - } else { - unimplemented!() - } - } - - fn get_item(&self, k: &str) -> Option { - if let Some(dict) = self.payload::() { - objdict::content_get_key_str(&dict.entries.borrow(), k) - } else { - panic!("TODO {:?}", k) - } - } +pub trait ItemProtocol { + // // Move: doesn't really belong in this protocol. + // fn get_key_value_pairs(&self, vm: &VirtualMachine) -> PyResult; + fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult; + fn set_item( + &self, + key: T, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult; + fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult; +} - fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { - if self.payload_is::() { - objdict::get_key_value_pairs(self) - } else { - panic!("TODO") - } +impl ItemProtocol for PyObjectRef { + fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult { + vm.call_method(self, "__getitem__", key.into_pyobject(vm)?) } - // Item set/get: - fn set_item(&self, ctx: &PyContext, key: &str, v: PyObjectRef) { - if let Some(dict) = self.payload::() { - let key = ctx.new_str(key.to_string()); - objdict::set_item_in_content(&mut dict.entries.borrow_mut(), &key, &v); - } else { - panic!("TODO {:?}", self); - } + fn set_item( + &self, + key: T, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + vm.call_method(self, "__setitem__", vec![key.into_pyobject(vm)?, value]) } - fn del_item(&self, key: &str) { - let mut elements = objdict::get_mut_elements(self); - elements.remove(key).unwrap(); + fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult { + vm.call_method(self, "__delitem__", key.into_pyobject(vm)?) } } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index fbe7ea6b1e..80f0f45b62 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -12,7 +12,8 @@ use crate::obj::{ objtype, }; use crate::pyobject::{ - create_type, DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol, + create_type, DictProtocol, IdProtocol, ItemProtocol, PyContext, PyObjectRef, PyResult, + TypeProtocol, }; use crate::VirtualMachine; use num_traits::cast::ToPrimitive; @@ -63,10 +64,10 @@ impl<'s> serde::Serialize for PyObjectSerializer<'s> { let elements = objsequence::get_elements(self.pyobject); serialize_seq_elements(serializer, &elements) } else if objtype::isinstance(self.pyobject, &self.vm.ctx.dict_type()) { - let pairs = objdict::get_elements(self.pyobject); + let pairs = objdict::get_key_value_pairs(self.pyobject); let mut map = serializer.serialize_map(Some(pairs.len()))?; for (key, e) in pairs.iter() { - map.serialize_entry(&key, &self.clone_with_object(&e.1))?; + map.serialize_entry(&self.clone_with_object(key), &self.clone_with_object(&e))?; } map.end() } else if self.pyobject.is(&self.vm.get_none()) { @@ -201,9 +202,9 @@ pub fn de_pyobject(vm: &VirtualMachine, s: &str) -> PyResult { let json_decode_error = vm .get_attribute(vm.sys_module.clone(), "modules") .unwrap() - .get_item("json") + .get_item("json", vm) .unwrap() - .get_item("JSONDecodeError") + .get_item("JSONDecodeError", vm) .unwrap(); let json_decode_error = json_decode_error.downcast().unwrap(); let exc = vm.new_exception(json_decode_error, format!("{}", err)); From f840bdcd7fa8b47434e0470f617d8b488c9dccd3 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 4 Apr 2019 14:47:44 +0100 Subject: [PATCH 151/884] Generalise set_attr. --- vm/src/vm.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 2fad29124b..1649d4efc9 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -568,13 +568,16 @@ impl VirtualMachine { self.call_method(&obj, "__getattribute__", vec![attr_name.into_object()]) } - pub fn set_attr( - &self, - obj: &PyObjectRef, - attr_name: PyObjectRef, - attr_value: PyObjectRef, - ) -> PyResult { - self.call_method(&obj, "__setattr__", vec![attr_name, attr_value]) + pub fn set_attr(&self, obj: &PyObjectRef, attr_name: K, attr_value: PyObjectRef) -> PyResult + where + K: TryIntoRef, + { + let attr_name = attr_name.try_into_ref(self)?; + self.call_method( + obj, + "__setattr__", + vec![attr_name.into_object(), attr_value], + ) } pub fn del_attr(&self, obj: &PyObjectRef, attr_name: PyObjectRef) -> PyResult<()> { From 25c17b482985f3641134f7a06b109845ae19ba49 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 4 Apr 2019 14:48:23 +0100 Subject: [PATCH 152/884] Change all the ctx.set_attr in ast. --- vm/src/stdlib/ast.rs | 158 +++++++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 75 deletions(-) diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 65fcdf660b..f6aa93aadc 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -50,7 +50,7 @@ fn program_to_ast(vm: &VirtualMachine, program: &ast::Program) -> PyObjectRef { // let ast_node = ctx.new_instance(this.Module); let ast_node = create_node(vm, "program"); let py_body = vm.ctx.new_list(body); - vm.ctx.set_attr(&ast_node, "body", py_body); + vm.set_attr(&ast_node, "body", py_body).unwrap(); ast_node } @@ -80,15 +80,16 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P let node = create_node(vm, "ClassDef"); // Set name: - vm.ctx - .set_attr(&node, "name", vm.ctx.new_str(name.to_string())); + vm.set_attr(&node, "name", vm.ctx.new_str(name.to_string())) + .unwrap(); // Set body: let py_body = statements_to_ast(vm, body); - vm.ctx.set_attr(&node, "body", py_body); + vm.set_attr(&node, "body", py_body).unwrap(); let py_decorator_list = expressions_to_ast(vm, decorator_list); - vm.ctx.set_attr(&node, "decorator_list", py_decorator_list); + vm.set_attr(&node, "decorator_list", py_decorator_list) + .unwrap(); node } ast::Statement::FunctionDef { @@ -101,24 +102,26 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P let node = create_node(vm, "FunctionDef"); // Set name: - vm.ctx - .set_attr(&node, "name", vm.ctx.new_str(name.to_string())); + vm.set_attr(&node, "name", vm.ctx.new_str(name.to_string())) + .unwrap(); - vm.ctx.set_attr(&node, "args", parameters_to_ast(vm, args)); + vm.set_attr(&node, "args", parameters_to_ast(vm, args)) + .unwrap(); // Set body: let py_body = statements_to_ast(vm, body); - vm.ctx.set_attr(&node, "body", py_body); + vm.set_attr(&node, "body", py_body).unwrap(); let py_decorator_list = expressions_to_ast(vm, decorator_list); - vm.ctx.set_attr(&node, "decorator_list", py_decorator_list); + vm.set_attr(&node, "decorator_list", py_decorator_list) + .unwrap(); let py_returns = if let Some(hint) = returns { expression_to_ast(vm, hint) } else { vm.ctx.none() }; - vm.ctx.set_attr(&node, "returns", py_returns); + vm.set_attr(&node, "returns", py_returns).unwrap(); node } ast::Statement::Continue => create_node(vm, "Continue"), @@ -127,13 +130,14 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P ast::Statement::Assert { test, msg } => { let node = create_node(vm, "Pass"); - vm.ctx.set_attr(&node, "test", expression_to_ast(vm, test)); + vm.set_attr(&node, "test", expression_to_ast(vm, test)) + .unwrap(); let py_msg = match msg { Some(msg) => expression_to_ast(vm, msg), None => vm.ctx.none(), }; - vm.ctx.set_attr(&node, "msg", py_msg); + vm.set_attr(&node, "msg", py_msg).unwrap(); node } @@ -143,7 +147,7 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P let py_targets = vm .ctx .new_tuple(targets.iter().map(|v| expression_to_ast(vm, v)).collect()); - vm.ctx.set_attr(&node, "targets", py_targets); + vm.set_attr(&node, "targets", py_targets).unwrap(); node } @@ -155,7 +159,7 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P } else { vm.ctx.none() }; - vm.ctx.set_attr(&node, "value", py_value); + vm.set_attr(&node, "value", py_value).unwrap(); node } @@ -163,17 +167,17 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P let node = create_node(vm, "If"); let py_test = expression_to_ast(vm, test); - vm.ctx.set_attr(&node, "test", py_test); + vm.set_attr(&node, "test", py_test).unwrap(); let py_body = statements_to_ast(vm, body); - vm.ctx.set_attr(&node, "body", py_body); + vm.set_attr(&node, "body", py_body).unwrap(); let py_orelse = if let Some(orelse) = orelse { statements_to_ast(vm, orelse) } else { vm.ctx.none() }; - vm.ctx.set_attr(&node, "orelse", py_orelse); + vm.set_attr(&node, "orelse", py_orelse).unwrap(); node } @@ -186,20 +190,20 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P let node = create_node(vm, "For"); let py_target = expression_to_ast(vm, target); - vm.ctx.set_attr(&node, "target", py_target); + vm.set_attr(&node, "target", py_target).unwrap(); let py_iter = expression_to_ast(vm, iter); - vm.ctx.set_attr(&node, "iter", py_iter); + vm.set_attr(&node, "iter", py_iter).unwrap(); let py_body = statements_to_ast(vm, body); - vm.ctx.set_attr(&node, "body", py_body); + vm.set_attr(&node, "body", py_body).unwrap(); let py_orelse = if let Some(orelse) = orelse { statements_to_ast(vm, orelse) } else { vm.ctx.none() }; - vm.ctx.set_attr(&node, "orelse", py_orelse); + vm.set_attr(&node, "orelse", py_orelse).unwrap(); node } @@ -207,17 +211,17 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P let node = create_node(vm, "While"); let py_test = expression_to_ast(vm, test); - vm.ctx.set_attr(&node, "test", py_test); + vm.set_attr(&node, "test", py_test).unwrap(); let py_body = statements_to_ast(vm, body); - vm.ctx.set_attr(&node, "body", py_body); + vm.set_attr(&node, "body", py_body).unwrap(); let py_orelse = if let Some(orelse) = orelse { statements_to_ast(vm, orelse) } else { vm.ctx.none() }; - vm.ctx.set_attr(&node, "orelse", py_orelse); + vm.set_attr(&node, "orelse", py_orelse).unwrap(); node } @@ -225,7 +229,7 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P let node = create_node(vm, "Expr"); let value = expression_to_ast(vm, expression); - vm.ctx.set_attr(&node, "value", value); + vm.set_attr(&node, "value", value).unwrap(); node } @@ -236,7 +240,7 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P // set lineno on node: let lineno = vm.ctx.new_int(statement.location.get_row()); - vm.ctx.set_attr(&node, "lineno", lineno); + vm.set_attr(&node, "lineno", lineno).unwrap(); node } @@ -255,10 +259,10 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let node = create_node(vm, "Call"); let py_func_ast = expression_to_ast(vm, function); - vm.ctx.set_attr(&node, "func", py_func_ast); + vm.set_attr(&node, "func", py_func_ast).unwrap(); let py_args = expressions_to_ast(vm, args); - vm.ctx.set_attr(&node, "args", py_args); + vm.set_attr(&node, "args", py_args).unwrap(); node } @@ -266,7 +270,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let node = create_node(vm, "BinOp"); let py_a = expression_to_ast(vm, a); - vm.ctx.set_attr(&node, "left", py_a); + vm.set_attr(&node, "left", py_a).unwrap(); // Operator: let str_op = match op { @@ -285,10 +289,10 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj ast::Operator::FloorDiv => "FloorDiv", }; let py_op = vm.ctx.new_str(str_op.to_string()); - vm.ctx.set_attr(&node, "op", py_op); + vm.set_attr(&node, "op", py_op).unwrap(); let py_b = expression_to_ast(vm, b); - vm.ctx.set_attr(&node, "right", py_b); + vm.set_attr(&node, "right", py_b).unwrap(); node } ast::Expression::Unop { op, a } => { @@ -301,10 +305,10 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj ast::UnaryOperator::Pos => "UAdd", }; let py_op = vm.ctx.new_str(str_op.to_string()); - vm.ctx.set_attr(&node, "op", py_op); + vm.set_attr(&node, "op", py_op).unwrap(); let py_a = expression_to_ast(vm, a); - vm.ctx.set_attr(&node, "operand", py_a); + vm.set_attr(&node, "operand", py_a).unwrap(); node } @@ -315,14 +319,14 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let py_a = expression_to_ast(vm, a); let py_b = expression_to_ast(vm, b); let py_values = vm.ctx.new_tuple(vec![py_a, py_b]); - vm.ctx.set_attr(&node, "values", py_values); + vm.set_attr(&node, "values", py_values).unwrap(); let str_op = match op { ast::BooleanOperator::And => "And", ast::BooleanOperator::Or => "Or", }; let py_op = vm.ctx.new_str(str_op.to_string()); - vm.ctx.set_attr(&node, "op", py_op); + vm.set_attr(&node, "op", py_op).unwrap(); node } @@ -330,7 +334,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let node = create_node(vm, "Compare"); let py_a = expression_to_ast(vm, &vals[0]); - vm.ctx.set_attr(&node, "left", py_a); + vm.set_attr(&node, "left", py_a).unwrap(); // Operator: let to_operator = |op: &ast::Comparison| match op { @@ -351,7 +355,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj .collect(), ); - vm.ctx.set_attr(&node, "ops", py_ops); + vm.set_attr(&node, "ops", py_ops).unwrap(); let py_b = vm.ctx.new_list( vals.iter() @@ -359,7 +363,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj .map(|x| expression_to_ast(vm, x)) .collect(), ); - vm.ctx.set_attr(&node, "comparators", py_b); + vm.set_attr(&node, "comparators", py_b).unwrap(); node } ast::Expression::Identifier { name } => { @@ -367,16 +371,17 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj // Id: let py_name = vm.ctx.new_str(name.clone()); - vm.ctx.set_attr(&node, "id", py_name); + vm.set_attr(&node, "id", py_name).unwrap(); node } ast::Expression::Lambda { args, body } => { let node = create_node(vm, "Lambda"); - vm.ctx.set_attr(&node, "args", parameters_to_ast(vm, args)); + vm.set_attr(&node, "args", parameters_to_ast(vm, args)) + .unwrap(); let py_body = expression_to_ast(vm, body); - vm.ctx.set_attr(&node, "body", py_body); + vm.set_attr(&node, "body", py_body).unwrap(); node } @@ -384,13 +389,13 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let node = create_node(vm, "IfExp"); let py_test = expression_to_ast(vm, test); - vm.ctx.set_attr(&node, "test", py_test); + vm.set_attr(&node, "test", py_test).unwrap(); let py_body = expression_to_ast(vm, body); - vm.ctx.set_attr(&node, "body", py_body); + vm.set_attr(&node, "body", py_body).unwrap(); let py_orelse = expression_to_ast(vm, orelse); - vm.ctx.set_attr(&node, "orelse", py_orelse); + vm.set_attr(&node, "orelse", py_orelse).unwrap(); node } @@ -404,28 +409,28 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj vm.ctx.new_complex(Complex64::new(*real, *imag)) } }; - vm.ctx.set_attr(&node, "n", py_n); + vm.set_attr(&node, "n", py_n).unwrap(); node } ast::Expression::True => { let node = create_node(vm, "NameConstant"); - vm.ctx.set_attr(&node, "value", vm.ctx.new_bool(true)); + vm.set_attr(&node, "value", vm.ctx.new_bool(true)).unwrap(); node } ast::Expression::False => { let node = create_node(vm, "NameConstant"); - vm.ctx.set_attr(&node, "value", vm.ctx.new_bool(false)); + vm.set_attr(&node, "value", vm.ctx.new_bool(false)).unwrap(); node } ast::Expression::None => { let node = create_node(vm, "NameConstant"); - vm.ctx.set_attr(&node, "value", vm.ctx.none()); + vm.set_attr(&node, "value", vm.ctx.none()).unwrap(); node } @@ -435,7 +440,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let elts = elements.iter().map(|e| expression_to_ast(vm, e)).collect(); let py_elts = vm.ctx.new_list(elts); - vm.ctx.set_attr(&node, "elts", py_elts); + vm.set_attr(&node, "elts", py_elts).unwrap(); node } @@ -444,7 +449,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let elts = elements.iter().map(|e| expression_to_ast(vm, e)).collect(); let py_elts = vm.ctx.new_list(elts); - vm.ctx.set_attr(&node, "elts", py_elts); + vm.set_attr(&node, "elts", py_elts).unwrap(); node } @@ -453,7 +458,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let elts = elements.iter().map(|e| expression_to_ast(vm, e)).collect(); let py_elts = vm.ctx.new_list(elts); - vm.ctx.set_attr(&node, "elts", py_elts); + vm.set_attr(&node, "elts", py_elts).unwrap(); node } @@ -468,10 +473,10 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj } let py_keys = vm.ctx.new_list(keys); - vm.ctx.set_attr(&node, "keys", py_keys); + vm.set_attr(&node, "keys", py_keys).unwrap(); let py_values = vm.ctx.new_list(values); - vm.ctx.set_attr(&node, "values", py_values); + vm.set_attr(&node, "values", py_values).unwrap(); node } @@ -490,7 +495,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj .map(|g| comprehension_to_ast(vm, g)) .collect(); let py_generators = vm.ctx.new_list(g); - vm.ctx.set_attr(&node, "generators", py_generators); + vm.set_attr(&node, "generators", py_generators).unwrap(); node } @@ -501,7 +506,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj Some(value) => expression_to_ast(vm, value), None => vm.ctx.none(), }; - vm.ctx.set_attr(&node, "value", py_value); + vm.set_attr(&node, "value", py_value).unwrap(); node } @@ -509,7 +514,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let node = create_node(vm, "YieldFrom"); let py_value = expression_to_ast(vm, value); - vm.ctx.set_attr(&node, "value", py_value); + vm.set_attr(&node, "value", py_value).unwrap(); node } @@ -517,10 +522,10 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let node = create_node(vm, "Subscript"); let py_value = expression_to_ast(vm, a); - vm.ctx.set_attr(&node, "value", py_value); + vm.set_attr(&node, "value", py_value).unwrap(); let py_slice = expression_to_ast(vm, b); - vm.ctx.set_attr(&node, "slice", py_slice); + vm.set_attr(&node, "slice", py_slice).unwrap(); node } @@ -528,10 +533,10 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let node = create_node(vm, "Attribute"); let py_value = expression_to_ast(vm, value); - vm.ctx.set_attr(&node, "value", py_value); + vm.set_attr(&node, "value", py_value).unwrap(); let py_attr = vm.ctx.new_str(name.to_string()); - vm.ctx.set_attr(&node, "attr", py_attr); + vm.set_attr(&node, "attr", py_attr).unwrap(); node } @@ -539,7 +544,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let node = create_node(vm, "Starred"); let py_value = expression_to_ast(vm, value); - vm.ctx.set_attr(&node, "value", py_value); + vm.set_attr(&node, "value", py_value).unwrap(); node } @@ -547,21 +552,22 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let node = create_node(vm, "Slice"); let py_value = expressions_to_ast(vm, elements); - vm.ctx.set_attr(&node, "bounds", py_value); + vm.set_attr(&node, "bounds", py_value).unwrap(); node } ast::Expression::String { value } => string_to_ast(vm, value), ast::Expression::Bytes { value } => { let node = create_node(vm, "Bytes"); - vm.ctx.set_attr(&node, "s", vm.ctx.new_bytes(value.clone())); + vm.set_attr(&node, "s", vm.ctx.new_bytes(value.clone())) + .unwrap(); node } }; // TODO: retrieve correct lineno: let lineno = vm.ctx.new_int(1); - vm.ctx.set_attr(&node, "lineno", lineno); + vm.set_attr(&node, "lineno", lineno).unwrap(); node } @@ -569,12 +575,13 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj fn parameters_to_ast(vm: &VirtualMachine, args: &ast::Parameters) -> PyObjectRef { let node = create_node(vm, "arguments"); - vm.ctx.set_attr( + vm.set_attr( &node, "args", vm.ctx .new_list(args.args.iter().map(|a| parameter_to_ast(vm, a)).collect()), - ); + ) + .unwrap(); node } @@ -583,14 +590,14 @@ fn parameter_to_ast(vm: &VirtualMachine, parameter: &ast::Parameter) -> PyObject let node = create_node(vm, "arg"); let py_arg = vm.ctx.new_str(parameter.arg.to_string()); - vm.ctx.set_attr(&node, "arg", py_arg); + vm.set_attr(&node, "arg", py_arg).unwrap(); let py_annotation = if let Some(annotation) = ¶meter.annotation { expression_to_ast(vm, annotation) } else { vm.ctx.none() }; - vm.ctx.set_attr(&node, "annotation", py_annotation); + vm.set_attr(&node, "annotation", py_annotation).unwrap(); node } @@ -599,13 +606,13 @@ fn comprehension_to_ast(vm: &VirtualMachine, comprehension: &ast::Comprehension) let node = create_node(vm, "comprehension"); let py_target = expression_to_ast(vm, &comprehension.target); - vm.ctx.set_attr(&node, "target", py_target); + vm.set_attr(&node, "target", py_target).unwrap(); let py_iter = expression_to_ast(vm, &comprehension.iter); - vm.ctx.set_attr(&node, "iter", py_iter); + vm.set_attr(&node, "iter", py_iter).unwrap(); let py_ifs = expressions_to_ast(vm, &comprehension.ifs); - vm.ctx.set_attr(&node, "ifs", py_ifs); + vm.set_attr(&node, "ifs", py_ifs).unwrap(); node } @@ -614,13 +621,14 @@ fn string_to_ast(vm: &VirtualMachine, string: &ast::StringGroup) -> PyObjectRef match string { ast::StringGroup::Constant { value } => { let node = create_node(vm, "Str"); - vm.ctx.set_attr(&node, "s", vm.ctx.new_str(value.clone())); + vm.set_attr(&node, "s", vm.ctx.new_str(value.clone())) + .unwrap(); node } ast::StringGroup::FormattedValue { value, .. } => { let node = create_node(vm, "FormattedValue"); let py_value = expression_to_ast(vm, value); - vm.ctx.set_attr(&node, "value", py_value); + vm.set_attr(&node, "value", py_value).unwrap(); node } ast::StringGroup::Joined { values } => { @@ -631,7 +639,7 @@ fn string_to_ast(vm: &VirtualMachine, string: &ast::StringGroup) -> PyObjectRef .map(|value| string_to_ast(vm, value)) .collect(), ); - vm.ctx.set_attr(&node, "values", py_values); + vm.set_attr(&node, "values", py_values).unwrap(); node } } From c8eda3733d211a2b77b5a40f70bc1ce8fbc61821 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 4 Apr 2019 14:49:10 +0100 Subject: [PATCH 153/884] Eliminate ctx.set_attr. --- derive/src/pyclass.rs | 2 +- vm/src/builtins.rs | 17 ++++++++------- vm/src/exceptions.rs | 4 ++-- vm/src/frame.rs | 16 +++++++------- vm/src/import.rs | 2 +- vm/src/macros.rs | 22 +++++++++---------- vm/src/obj/objbytes.rs | 4 +--- vm/src/obj/objcode.rs | 3 +-- vm/src/obj/objdict.rs | 45 ++++++++++++++++++++++++++++----------- vm/src/obj/objellipsis.rs | 3 +-- vm/src/obj/objmemory.rs | 2 +- vm/src/obj/objobject.rs | 8 +++---- vm/src/obj/objstr.rs | 6 ++++++ vm/src/obj/objsuper.rs | 2 +- vm/src/obj/objtype.rs | 7 ++++++ vm/src/pyobject.rs | 28 ++++-------------------- vm/src/stdlib/io.rs | 16 +++++++------- vm/src/stdlib/json.rs | 8 ++++--- vm/src/stdlib/os.rs | 20 ++++++++--------- vm/src/sysmodule.rs | 11 +++++----- vm/src/vm.rs | 24 +++++++++------------ wasm/lib/src/convert.rs | 2 +- wasm/lib/src/vm_class.rs | 3 +-- 23 files changed, 129 insertions(+), 126 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index ad4ebf66ea..e533e8009a 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -118,7 +118,7 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { }| { let constructor_fn = kind.to_ctx_constructor_fn(); quote! { - ctx.set_attr(class, #py_name, ctx.#constructor_fn(Self::#fn_name)); + class.set_str_attr(#py_name, ctx.#constructor_fn(Self::#fn_name)); } }, ); diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 205d0c66e0..7264c9c6e4 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -669,7 +669,12 @@ fn builtin_import(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // builtin_vars pub fn make_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = py_module!(ctx, "__builtins__", { + #[cfg(target_arch = "wasm32")] + let open = ctx.none(); + #[cfg(not(target_arch = "wasm32"))] + let open = ctx.new_rustfunc(io_open); + + py_module!(ctx, "__builtins__", { //set __name__ fixes: https://github.com/RustPython/RustPython/issues/146 "__name__" => ctx.new_str(String::from("__main__")), @@ -715,6 +720,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "min" => ctx.new_rustfunc(builtin_min), "object" => ctx.object(), "oct" => ctx.new_rustfunc(builtin_oct), + "open" => open, "ord" => ctx.new_rustfunc(builtin_ord), "next" => ctx.new_rustfunc(builtin_next), "pow" => ctx.new_rustfunc(builtin_pow), @@ -759,12 +765,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone(), "KeyError" => ctx.exceptions.key_error.clone(), "OSError" => ctx.exceptions.os_error.clone(), - }); - - #[cfg(not(target_arch = "wasm32"))] - ctx.set_attr(&py_mod, "open", ctx.new_rustfunc(io_open)); - - py_mod + }) } pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { @@ -801,6 +802,6 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu "__call__", vec![name_arg, bases, namespace.into_object()], )?; - cells.set_item(&vm.ctx, "__class__", class.clone()); + cells.set_item("__class__", class.clone(), vm); Ok(class) } diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index fb757cd0f3..cec4b3125e 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -13,8 +13,8 @@ fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { vm.new_str("No msg".to_string()) }; let traceback = vm.ctx.new_list(Vec::new()); - vm.ctx.set_attr(&zelf, "msg", msg); - vm.ctx.set_attr(&zelf, "__traceback__", traceback); + vm.set_attr(&zelf, "msg", msg)?; + vm.set_attr(&zelf, "__traceback__", traceback)?; Ok(vm.get_none()) } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 6a09f085af..1a0cc4de7c 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -132,21 +132,21 @@ pub trait NameProtocol { impl NameProtocol for Scope { fn load_name(&self, vm: &VirtualMachine, name: &str) -> Option { for dict in self.locals.iter() { - if let Some(value) = dict.get_item(name) { + if let Some(value) = dict.get_item(name, vm) { return Some(value); } } - if let Some(value) = self.globals.get_item(name) { + if let Some(value) = self.globals.get_item(name, vm) { return Some(value); } vm.get_attribute(vm.builtins.clone(), name).ok() } - fn load_cell(&self, _vm: &VirtualMachine, name: &str) -> Option { + fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option { for dict in self.locals.iter().skip(1) { - if let Some(value) = dict.get_item(name) { + if let Some(value) = dict.get_item(name, vm) { return Some(value); } } @@ -154,7 +154,7 @@ impl NameProtocol for Scope { } fn store_name(&self, vm: &VirtualMachine, key: &str, value: PyObjectRef) { - self.get_locals().set_item(&vm.ctx, key, value) + self.get_locals().set_item(key, value, vm) } fn delete_name(&self, _vm: &VirtualMachine, key: &str) { @@ -392,11 +392,11 @@ impl Frame { // Take all key-value pairs from the dict: let dict_elements = objdict::get_key_value_pairs(&obj); for (key, value) in dict_elements.iter() { - map_obj.set_item(&vm.ctx, &objstr::get_value(key), value.clone()); + map_obj.set_item(key.clone(), value.clone(), vm); } } else { let key = self.pop_value(); - map_obj.set_item(&vm.ctx, &objstr::get_value(&key), obj) + map_obj.set_item(key, obj, vm) } } self.push_value(map_obj.into_object()); @@ -585,7 +585,7 @@ impl Frame { let scope = self.scope.clone(); let obj = vm.ctx.new_function(code_obj, scope, defaults); - vm.ctx.set_attr(&obj, "__annotations__", annotations); + vm.set_attr(&obj, "__annotations__", annotations)?; self.push_value(obj); Ok(None) diff --git a/vm/src/import.rs b/vm/src/import.rs index 179dd4321f..28b3d02ef2 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -39,7 +39,7 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s // trace!("Code object: {:?}", code_obj); let attrs = vm.ctx.new_dict(); - attrs.set_item(&vm.ctx, "__name__", vm.new_str(module.to_string())); + attrs.set_item("__name__", vm.new_str(module.to_string()), vm); vm.run_code_obj(code_obj, Scope::new(None, attrs.clone()))?; Ok(vm.ctx.new_module(module, attrs)) } diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 47d3c531ab..0c37e0dfc8 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -114,15 +114,15 @@ macro_rules! no_kwargs { #[macro_export] macro_rules! py_module { - ( $ctx:expr, $module_name:expr, { $($name:expr => $value:expr),* $(,)* }) => { - { - let py_mod = $ctx.new_module($module_name, $ctx.new_dict()); - $( - $ctx.set_attr(&py_mod, $name, $value); - )* - py_mod - } - } + ( $ctx:expr, $module_name:expr, { $($name:expr => $value:expr),* $(,)* }) => {{ + let mut attributes = $crate::pyobject::PyAttributes::new(); + $( + let value: PyObjectRef = $value.into(); + attributes.insert($name.to_string(), value); + )* + let module_dict = $crate::obj::objdict::PyDictRef::from_attributes($ctx, attributes); + $ctx.new_module($module_name, module_dict) + }}; } #[macro_export] @@ -131,7 +131,7 @@ macro_rules! py_class { { let py_class = $ctx.new_class($class_name, $class_base); $( - $ctx.set_attr(&py_class, $name, $value); + py_class.set_str_attr($name, $value); )* py_class } @@ -143,7 +143,7 @@ macro_rules! extend_class { ( $ctx:expr, $class:expr, { $($name:expr => $value:expr),* $(,)* }) => { let class = $class; $( - $ctx.set_attr(class, $name, $value); + class.set_str_attr($name, $value); )* } } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 4119d688f9..f24c9273c5 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -43,8 +43,6 @@ impl PyValue for PyBytes { // Fill bytes class methods: pub fn init(context: &PyContext) { - let bytes_type = context.bytes_type.as_object(); - let bytes_doc = "bytes(iterable_of_ints) -> bytes\n\ bytes(string, encoding[, errors]) -> bytes\n\ @@ -56,7 +54,7 @@ pub fn init(context: &PyContext) { - any object implementing the buffer API.\n \ - an integer"; - extend_class!(context, bytes_type, { + extend_class!(context, &context.bytes_type, { "__new__" => context.new_rustfunc(bytes_new), "__eq__" => context.new_rustfunc(PyBytesRef::eq), "__lt__" => context.new_rustfunc(PyBytesRef::lt), diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index a26645b913..60549cd3ac 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -80,8 +80,7 @@ impl PyCodeRef { } pub fn init(context: &PyContext) { - let code_type = context.code_type.as_object(); - extend_class!(context, code_type, { + extend_class!(context, &context.code_type, { "__new__" => context.new_rustfunc(PyCodeRef::new), "__repr__" => context.new_rustfunc(PyCodeRef::repr), diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 15046a405f..e110e71f82 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -4,7 +4,7 @@ use std::fmt; use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ - DictProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + DictProtocol, IntoPyObject, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, PyValue, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -58,7 +58,7 @@ impl PyDictRef { if let OptionalArg::Present(dict_obj) = dict_obj { if objtype::isinstance(&dict_obj, &vm.ctx.dict_type()) { for (needle, value) in get_key_value_pairs(&dict_obj) { - dict.set_item(&vm.ctx, &objstr::get_value(&needle), value); + dict.set_item(needle, value, vm); } } else { let iter = objiter::get_iter(vm, &dict_obj)?; @@ -77,13 +77,12 @@ impl PyDictRef { if objiter::get_next_object(vm, &elem_iter)?.is_some() { return Err(err(vm)); } - dict.set_item(&vm.ctx, &objstr::get_value(&needle), value); + dict.set_item(needle, value, vm); } } } for (needle, value) in kwargs.into_iter() { - let py_needle = vm.new_str(needle); - dict.set_item(&vm.ctx, &objstr::get_value(&py_needle), value); + dict.set_item(vm.new_str(needle), value, vm); } Ok(dict) } @@ -181,7 +180,7 @@ impl PyDictRef { } fn setitem(self, needle: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) { - self.set_item(&vm.ctx, &objstr::get_value(&needle), value) + self.set_item(needle, value, vm) } fn getitem(self, key: PyStringRef, vm: &VirtualMachine) -> PyResult { @@ -216,6 +215,23 @@ impl PyDictRef { } } + // Used during module initialisation when vm isn't available. + pub fn from_attributes(ctx: &PyContext, attributes: PyAttributes) -> PyDictRef { + let dict = ctx.new_dict(); + for (key_str, value) in attributes.into_iter() { + dict.unsafe_str_insert(&key_str, value, ctx); + } + dict + } + + // Pub needed for some nasty edge cases. + // It will be unsafe if there are entries in the dictionary that compare equal. + pub fn unsafe_str_insert(&self, key: &str, value: PyObjectRef, ctx: &PyContext) { + self.entries + .borrow_mut() + .insert(key.to_string(), (ctx.new_str(key.to_string()), value)); + } + /// Take a python dictionary and convert it to attributes. pub fn to_attributes(self) -> PyAttributes { let mut attrs = PyAttributes::new(); @@ -228,12 +244,14 @@ impl PyDictRef { } impl DictProtocol for PyDictRef { - fn contains_key(&self, k: &str) -> bool { - self.entries.borrow().get(k).is_some() + fn contains_key(&self, key: T, vm: &VirtualMachine) -> bool { + let key_str = &objstr::get_value(&key.into_pyobject(vm).unwrap()); + self.entries.borrow().get(key_str).is_some() } - fn get_item(&self, k: &str) -> Option { - match self.entries.borrow().get(k) { + fn get_item(&self, key: T, vm: &VirtualMachine) -> Option { + let key_str = &objstr::get_value(&key.into_pyobject(vm).unwrap()); + match self.entries.borrow().get(key_str) { Some(v) => Some(v.1.clone()), None => None, } @@ -244,10 +262,11 @@ impl DictProtocol for PyDictRef { } // Item set/get: - fn set_item(&self, ctx: &PyContext, key_str: &str, v: PyObjectRef) { - let key = ctx.new_str(key_str.to_string()); + fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine) { + let key = key.into_pyobject(vm).unwrap(); + let key_str = &objstr::get_value(&key); let elements = &mut self.entries.borrow_mut(); - elements.insert(key_str.to_string(), (key.clone(), v.clone())); + elements.insert(key_str.to_string(), (key.clone(), value)); } fn del_item(&self, key: &str) { diff --git a/vm/src/obj/objellipsis.rs b/vm/src/obj/objellipsis.rs index 8ce843352f..6591d81738 100644 --- a/vm/src/obj/objellipsis.rs +++ b/vm/src/obj/objellipsis.rs @@ -3,8 +3,7 @@ use crate::pyobject::{PyContext, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; pub fn init(context: &PyContext) { - let ellipsis_type = context.ellipsis_type.as_object(); - extend_class!(context, ellipsis_type, { + extend_class!(context, &context.ellipsis_type, { "__new__" => context.new_rustfunc(ellipsis_new), "__repr__" => context.new_rustfunc(ellipsis_repr) }); diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index 7f53fb5ae6..cfe44dea38 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -20,7 +20,7 @@ pub fn new_memory_view( bytes_object: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - vm.ctx.set_attr(&cls, "obj", bytes_object.clone()); + vm.set_attr(cls.as_object(), "obj", bytes_object.clone())?; PyMemoryView { obj: bytes_object }.into_ref_with_type(vm, cls) } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 11a8309abe..86a56de5d8 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -77,7 +77,7 @@ fn object_setattr( } if let Some(ref dict) = obj.clone().dict { - dict.set_item(&vm.ctx, &attr_name.value, value); + dict.set_item(attr_name, value, vm); Ok(()) } else { Err(vm.new_attribute_error(format!( @@ -208,7 +208,7 @@ fn object_getattribute(obj: PyObjectRef, name_str: PyStringRef, vm: &VirtualMach } } - if let Some(obj_attr) = object_getattr(&obj, &name) { + if let Some(obj_attr) = object_getattr(&obj, &name, &vm) { Ok(obj_attr) } else if let Some(attr) = objtype::class_get_attr(&cls, &name) { vm.call_get_descriptor(attr, obj) @@ -219,9 +219,9 @@ fn object_getattribute(obj: PyObjectRef, name_str: PyStringRef, vm: &VirtualMach } } -fn object_getattr(obj: &PyObjectRef, attr_name: &str) -> Option { +fn object_getattr(obj: &PyObjectRef, attr_name: &str, vm: &VirtualMachine) -> Option { if let Some(ref dict) = obj.dict { - dict.get_item(attr_name) + dict.get_item(attr_name, vm) } else { None } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 592a4950f3..62cc923b1e 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -762,6 +762,12 @@ impl IntoPyObject for &str { } } +impl IntoPyObject for &String { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_str(self.clone())) + } +} + pub fn init(ctx: &PyContext) { PyStringRef::extend_class(ctx, &ctx.str_type); } diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 24424d265f..19c7c2ea88 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -124,7 +124,7 @@ fn super_new( } else { let frame = vm.current_frame().expect("no current frame for super()"); if let Some(first_arg) = frame.code.arg_names.get(0) { - match vm.get_locals().get_item(first_arg) { + match vm.get_locals().get_item(first_arg, vm) { Some(obj) => obj.clone(), _ => { return Err(vm diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 16cfbaa5ee..b1b50dfbd0 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -162,6 +162,13 @@ impl PyClassRef { Ok(()) } + // This is used for class initialisation where the vm is not yet available. + pub fn set_str_attr>(&self, attr_name: &str, value: V) { + self.attributes + .borrow_mut() + .insert(attr_name.to_string(), value.into()); + } + fn subclasses(self, _vm: &VirtualMachine) -> PyList { let mut subclasses = self.subclasses.borrow_mut(); subclasses.retain(|x| x.upgrade().is_some()); diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 12d6fa1cae..0c7d4955b2 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -684,24 +684,6 @@ impl PyContext { .into_ref() } - pub fn set_attr<'a, T: Into<&'a PyObjectRef>, V: Into>( - &'a self, - obj: T, - attr_name: &str, - value: V, - ) { - let obj = obj.into(); - if let Some(PyClass { ref attributes, .. }) = obj.payload::() { - attributes - .borrow_mut() - .insert(attr_name.to_string(), value.into()); - } else if let Some(ref dict) = obj.dict { - dict.set_item(self, attr_name, value.into()); - } else { - unimplemented!("set_attr unimplemented for: {:?}", obj); - }; - } - pub fn unwrap_constant(&self, value: &bytecode::Constant) -> PyObjectRef { match *value { bytecode::Constant::Integer { ref value } => self.new_int(value.clone()), @@ -931,16 +913,14 @@ impl TypeProtocol for PyRef { } pub trait DictProtocol { - fn contains_key(&self, k: &str) -> bool; - fn get_item(&self, k: &str) -> Option; + fn contains_key(&self, key: T, vm: &VirtualMachine) -> bool; + fn get_item(&self, key: T, vm: &VirtualMachine) -> Option; fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)>; - fn set_item(&self, ctx: &PyContext, key: &str, v: PyObjectRef); + fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine); fn del_item(&self, key: &str); } pub trait ItemProtocol { - // // Move: doesn't really belong in this protocol. - // fn get_key_value_pairs(&self, vm: &VirtualMachine) -> PyResult; fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult; fn set_item( &self, @@ -1280,7 +1260,7 @@ pub trait PyClassImpl: PyClassDef { fn extend_class(ctx: &PyContext, class: &PyClassRef) { Self::impl_extend_class(ctx, class); if let Some(doc) = Self::DOC { - ctx.set_attr(class, "__doc__", ctx.new_str(doc.into())); + class.set_str_attr("__doc__", ctx.new_str(doc.into())); } } diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 9daaf95d2e..c6dbbc161d 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -99,7 +99,7 @@ fn io_base_cm_exit(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn buffered_io_base_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(buffered, None), (raw, None)]); - vm.ctx.set_attr(buffered, "raw", raw.clone()); + vm.set_attr(buffered, "raw", raw.clone())?; Ok(vm.get_none()) } @@ -148,10 +148,10 @@ fn file_io_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let args = vec![name.clone(), vm.ctx.new_int(os_mode)]; let file_no = os::os_open(vm, PyFuncArgs::new(args, vec![]))?; - vm.ctx.set_attr(file_io, "name", name.clone()); - vm.ctx.set_attr(file_io, "fileno", file_no); - vm.ctx.set_attr(file_io, "closefd", vm.new_bool(false)); - vm.ctx.set_attr(file_io, "closed", vm.new_bool(false)); + vm.set_attr(file_io, "name", name.clone())?; + vm.set_attr(file_io, "fileno", file_no)?; + vm.set_attr(file_io, "closefd", vm.new_bool(false))?; + vm.set_attr(file_io, "closed", vm.new_bool(false))?; Ok(vm.get_none()) } @@ -215,7 +215,7 @@ fn file_io_readinto(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { }; let updated = os::raw_file_number(f.into_inner()); - vm.ctx.set_attr(file_io, "fileno", vm.ctx.new_int(updated)); + vm.set_attr(file_io, "fileno", vm.ctx.new_int(updated))?; Ok(vm.get_none()) } @@ -241,7 +241,7 @@ fn file_io_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(len) => { //reset raw fd on the FileIO object let updated = os::raw_file_number(handle); - vm.ctx.set_attr(file_io, "fileno", vm.ctx.new_int(updated)); + vm.set_attr(file_io, "fileno", vm.ctx.new_int(updated))?; //return number of bytes written Ok(vm.ctx.new_int(len)) @@ -273,7 +273,7 @@ fn text_io_wrapper_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(text_io_wrapper, None), (buffer, None)] ); - vm.ctx.set_attr(text_io_wrapper, "buffer", buffer.clone()); + vm.set_attr(text_io_wrapper, "buffer", buffer.clone())?; Ok(vm.get_none()) } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 80f0f45b62..4695b7acbf 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -176,7 +176,7 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { Some(PyString { ref value }) => value.clone(), _ => unimplemented!("map keys must be strings"), }; - dict.set_item(&self.vm.ctx, &key, value); + dict.set_item(&key, value, self.vm); } Ok(dict.into_object()) } @@ -208,8 +208,10 @@ pub fn de_pyobject(vm: &VirtualMachine, s: &str) -> PyResult { .unwrap(); let json_decode_error = json_decode_error.downcast().unwrap(); let exc = vm.new_exception(json_decode_error, format!("{}", err)); - vm.ctx.set_attr(&exc, "lineno", vm.ctx.new_int(err.line())); - vm.ctx.set_attr(&exc, "colno", vm.ctx.new_int(err.column())); + vm.set_attr(&exc, "lineno", vm.ctx.new_int(err.line())) + .unwrap(); + vm.set_attr(&exc, "colno", vm.ctx.new_int(err.column())) + .unwrap(); exc }) } diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 42b4160ca8..31b3ee6449 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -114,24 +114,22 @@ fn os_error(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } pub fn make_module(ctx: &PyContext) -> PyObjectRef { - let py_mod = py_module!(ctx, "os", { + let os_name = if cfg!(windows) { + "nt".to_string() + } else { + "posix".to_string() + }; + + py_module!(ctx, "os", { "open" => ctx.new_rustfunc(os_open), "close" => ctx.new_rustfunc(os_close), "error" => ctx.new_rustfunc(os_error), + "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), "O_RDWR" => ctx.new_int(2), "O_NONBLOCK" => ctx.new_int(4), "O_APPEND" => ctx.new_int(8), "O_CREAT" => ctx.new_int(512) - }); - - if cfg!(windows) { - ctx.set_attr(&py_mod, "name", ctx.new_str("nt".to_string())); - } else { - // Assume we're on a POSIX system - ctx.set_attr(&py_mod, "name", ctx.new_str("posix".to_string())); - } - - py_mod + }) } diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index a4a37b350c..c803daaad5 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -3,7 +3,7 @@ use std::{env, mem}; use crate::frame::FrameRef; use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{DictProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; /* @@ -124,8 +124,7 @@ setrecursionlimit() -- set the max recursion depth for the interpreter settrace() -- set the global debug tracing function "; let modules = ctx.new_dict(); - let sys_name = "sys"; - let sys_mod = py_module!(ctx, sys_name, { + let sys_mod = py_module!(ctx, "sys", { "argv" => argv(ctx), "getrefcount" => ctx.new_rustfunc(sys_getrefcount), "getsizeof" => ctx.new_rustfunc(sys_getsizeof), @@ -135,11 +134,11 @@ settrace() -- set the global debug tracing function "ps2" => ctx.new_str("..... ".to_string()), "__doc__" => ctx.new_str(sys_doc.to_string()), "_getframe" => ctx.new_rustfunc(getframe), + "modules" => modules.clone(), }); - modules.set_item(&ctx, sys_name, sys_mod.clone()); - modules.set_item(&ctx, "builtins", builtins); - ctx.set_attr(&sys_mod, "modules", modules); + modules.unsafe_str_insert("sys", sys_mod.clone(), &ctx); + modules.unsafe_str_insert("builtins", builtins, &ctx); sys_mod } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 1649d4efc9..6c35983e72 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -419,7 +419,7 @@ impl VirtualMachine { for i in 0..n { let arg_name = &code_object.arg_names[i]; let arg = &args.args[i]; - locals.set_item(&self.ctx, arg_name, arg.clone()); + locals.set_item(arg_name, arg.clone(), self); } // Pack other positional arguments in to *args: @@ -432,7 +432,7 @@ impl VirtualMachine { } let vararg_value = self.ctx.new_tuple(last_args); - locals.set_item(&self.ctx, vararg_name, vararg_value); + locals.set_item(vararg_name, vararg_value, self); } bytecode::Varargs::Unnamed => { // just ignore the rest of the args @@ -452,7 +452,7 @@ impl VirtualMachine { let kwargs = match code_object.varkeywords { bytecode::Varargs::Named(ref kwargs_name) => { let d = self.ctx.new_dict(); - locals.set_item(&self.ctx, kwargs_name, d.as_object().clone()); + locals.set_item(kwargs_name, d.as_object().clone(), self); Some(d) } bytecode::Varargs::Unnamed => Some(self.ctx.new_dict()), @@ -464,15 +464,15 @@ impl VirtualMachine { // Check if we have a parameter with this name: if code_object.arg_names.contains(&name) || code_object.kwonlyarg_names.contains(&name) { - if locals.contains_key(&name) { + if locals.contains_key(&name, self) { return Err( self.new_type_error(format!("Got multiple values for argument '{}'", name)) ); } - locals.set_item(&self.ctx, &name, value); + locals.set_item(&name, value, self); } else if let Some(d) = &kwargs { - d.set_item(&self.ctx, &name, value); + d.set_item(&name, value, self); } else { return Err( self.new_type_error(format!("Got an unexpected keyword argument '{}'", name)) @@ -499,7 +499,7 @@ impl VirtualMachine { let mut missing = vec![]; for i in 0..required_args { let variable_name = &code_object.arg_names[i]; - if !locals.contains_key(variable_name) { + if !locals.contains_key(variable_name, self) { missing.push(variable_name) } } @@ -515,12 +515,8 @@ impl VirtualMachine { // the default if we don't already have a value for (default_index, i) in (required_args..nexpected_args).enumerate() { let arg_name = &code_object.arg_names[i]; - if !locals.contains_key(arg_name) { - locals.set_item( - &self.ctx, - arg_name, - available_defaults[default_index].clone(), - ); + if !locals.contains_key(arg_name, self) { + locals.set_item(arg_name, available_defaults[default_index].clone(), self); } } }; @@ -528,7 +524,7 @@ impl VirtualMachine { // Check if kw only arguments are all present: let kwdefs: HashMap = HashMap::new(); for arg_name in &code_object.kwonlyarg_names { - if !locals.contains_key(arg_name) { + if !locals.contains_key(arg_name, self) { if kwdefs.contains_key(arg_name) { // If not yet specified, take the default value unimplemented!(); diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index af6ad029e7..717c9a9730 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -192,7 +192,7 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { for pair in object_entries(&Object::from(js_val)) { let (key, val) = pair.expect("iteration over object to not fail"); let py_val = js_to_py(vm, val); - dict.set_item(&vm.ctx, &String::from(js_sys::JsString::from(key)), py_val); + dict.set_item(&String::from(js_sys::JsString::from(key)), py_val, vm); } dict.into_object() } diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index c7e637a9f5..6e4ceaee29 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -243,8 +243,7 @@ impl WASMVirtualMachine { } else { return Err(error()); }; - let rustfunc = vm.ctx.new_rustfunc(print_fn); - vm.ctx.set_attr(&vm.builtins, "print", rustfunc); + vm.set_attr(&vm.builtins, "print", vm.ctx.new_rustfunc(print_fn))?; Ok(()) })? } From 7438b0685c6d923921f0a615021898cae6693d62 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 4 Apr 2019 15:03:56 +0100 Subject: [PATCH 154/884] Changes suggested by code review. --- vm/src/obj/objdict.rs | 5 +---- vm/src/pyobject.rs | 12 ++++++------ 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index e110e71f82..6062e07e66 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -251,10 +251,7 @@ impl DictProtocol for PyDictRef { fn get_item(&self, key: T, vm: &VirtualMachine) -> Option { let key_str = &objstr::get_value(&key.into_pyobject(vm).unwrap()); - match self.entries.borrow().get(key_str) { - Some(v) => Some(v.1.clone()), - None => None, - } + self.entries.borrow().get(key_str).map(|v| v.1.clone()) } fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 0c7d4955b2..139b2a1e11 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -921,18 +921,18 @@ pub trait DictProtocol { } pub trait ItemProtocol { - fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult; + fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult; fn set_item( &self, key: T, value: PyObjectRef, vm: &VirtualMachine, - ) -> PyResult; - fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult; + ) -> PyResult; + fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult; } impl ItemProtocol for PyObjectRef { - fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult { + fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult { vm.call_method(self, "__getitem__", key.into_pyobject(vm)?) } @@ -941,11 +941,11 @@ impl ItemProtocol for PyObjectRef { key: T, value: PyObjectRef, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult { vm.call_method(self, "__setitem__", vec![key.into_pyobject(vm)?, value]) } - fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult { + fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult { vm.call_method(self, "__delitem__", key.into_pyobject(vm)?) } } From 9ed051e3b7d7cbe7140d628f06cae4c80c90dafc Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 4 Apr 2019 17:56:39 +0100 Subject: [PATCH 155/884] Module Initialisation takes VirtualMachine rather than PyContext. --- vm/src/builtins.rs | 14 ++++++++------ vm/src/dictdatatype.rs | 15 +++++++++------ vm/src/import.rs | 2 +- vm/src/macros.rs | 19 +++++++++++++------ vm/src/obj/objdict.rs | 17 ----------------- vm/src/stdlib/ast.rs | 8 +++++--- vm/src/stdlib/dis.rs | 8 +++++--- vm/src/stdlib/io.rs | 10 +++++----- vm/src/stdlib/json.rs | 9 +++++---- vm/src/stdlib/keyword.rs | 8 +++++--- vm/src/stdlib/math.rs | 8 +++++--- vm/src/stdlib/mod.rs | 6 ++++-- vm/src/stdlib/os.rs | 8 +++++--- vm/src/stdlib/platform.rs | 7 ++++--- vm/src/stdlib/pystruct.rs | 8 +++++--- vm/src/stdlib/random.rs | 8 +++++--- vm/src/stdlib/re.rs | 8 +++++--- vm/src/stdlib/socket.rs | 10 +++++----- vm/src/stdlib/string.rs | 9 ++++++--- vm/src/stdlib/time_module.rs | 8 +++++--- vm/src/stdlib/tokenize.rs | 8 +++++--- vm/src/stdlib/types.rs | 8 +++++--- vm/src/stdlib/weakref.rs | 9 ++++++--- vm/src/sysmodule.rs | 14 +++++++------- vm/src/vm.rs | 21 +++++++++++++-------- wasm/lib/src/browser_module.rs | 10 +++++----- wasm/lib/src/vm_class.rs | 10 +++++----- 27 files changed, 151 insertions(+), 119 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 7264c9c6e4..5aa5ee226d 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -20,7 +20,7 @@ use crate::obj::objtype::{self, PyClassRef}; use crate::frame::Scope; use crate::function::{Args, OptionalArg, PyFuncArgs}; use crate::pyobject::{ - DictProtocol, IdProtocol, PyContext, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, + DictProtocol, IdProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -668,13 +668,15 @@ fn builtin_import(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // builtin_vars -pub fn make_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { + let ctx = &vm.ctx; + #[cfg(target_arch = "wasm32")] - let open = ctx.none(); + let open = vm.ctx.none(); #[cfg(not(target_arch = "wasm32"))] - let open = ctx.new_rustfunc(io_open); + let open = vm.ctx.new_rustfunc(io_open); - py_module!(ctx, "__builtins__", { + extend_module!(vm, module, { //set __name__ fixes: https://github.com/RustPython/RustPython/issues/146 "__name__" => ctx.new_str(String::from("__main__")), @@ -765,7 +767,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone(), "KeyError" => ctx.exceptions.key_error.clone(), "OSError" => ctx.exceptions.os_error.clone(), - }) + }); } pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 436923ea95..92a93a4e9e 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -9,6 +9,7 @@ use num_traits::ToPrimitive; /// And: http://code.activestate.com/recipes/578375/ use std::collections::HashMap; +#[derive(Default)] pub struct Dict { size: usize, indices: HashMap, @@ -88,6 +89,12 @@ impl Dict { } } + pub fn clear(&mut self) { + self.entries.clear(); + self.indices.clear(); + self.size = 0 + } + /// Delete a key pub fn delete(&mut self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult<()> { if let LookupResult::Existing(index) = self.lookup(vm, key)? { @@ -175,12 +182,8 @@ fn calc_hash(vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { } /// Invoke __eq__ on two keys -fn do_eq( - vm: &VirtualMachine, - key1: &PyObjectRef, - key2: &PyObjectRef, -) -> Result { - let result = vm._eq(key1, key2.clone())?; +fn do_eq(vm: &VirtualMachine, key1: &PyObjectRef, key2: &PyObjectRef) -> Result { + let result = vm._eq(key1.clone(), key2.clone())?; Ok(objbool::get_value(&result)) } diff --git a/vm/src/import.rs b/vm/src/import.rs index 28b3d02ef2..713393debf 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -15,7 +15,7 @@ use crate::vm::VirtualMachine; fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &str) -> PyResult { // Check for Rust-native modules if let Some(module) = vm.stdlib_inits.borrow().get(module) { - return Ok(module(&vm.ctx).clone()); + return Ok(module(vm).clone()); } let notfound_error = vm.context().exceptions.module_not_found_error.clone(); diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 0c37e0dfc8..27d1647184 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -114,17 +114,24 @@ macro_rules! no_kwargs { #[macro_export] macro_rules! py_module { - ( $ctx:expr, $module_name:expr, { $($name:expr => $value:expr),* $(,)* }) => {{ - let mut attributes = $crate::pyobject::PyAttributes::new(); + ( $vm:expr, $module_name:expr, { $($name:expr => $value:expr),* $(,)* }) => {{ + let module = $vm.ctx.new_module($module_name, $vm.ctx.new_dict()); $( - let value: PyObjectRef = $value.into(); - attributes.insert($name.to_string(), value); + $vm.set_attr(&module, $name, $value).unwrap(); )* - let module_dict = $crate::obj::objdict::PyDictRef::from_attributes($ctx, attributes); - $ctx.new_module($module_name, module_dict) + module }}; } +#[macro_export] +macro_rules! extend_module { + ( $vm:expr, $module:expr, { $($name:expr => $value:expr),* $(,)* }) => { + $( + $vm.set_attr(&$module, $name, $value).unwrap(); + )* + } +} + #[macro_export] macro_rules! py_class { ( $ctx:expr, $class_name:expr, $class_base:expr, { $($name:expr => $value:expr),* $(,)* }) => { diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 6062e07e66..6ebe5aa2e4 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -215,23 +215,6 @@ impl PyDictRef { } } - // Used during module initialisation when vm isn't available. - pub fn from_attributes(ctx: &PyContext, attributes: PyAttributes) -> PyDictRef { - let dict = ctx.new_dict(); - for (key_str, value) in attributes.into_iter() { - dict.unsafe_str_insert(&key_str, value, ctx); - } - dict - } - - // Pub needed for some nasty edge cases. - // It will be unsafe if there are entries in the dictionary that compare equal. - pub fn unsafe_str_insert(&self, key: &str, value: PyObjectRef, ctx: &PyContext) { - self.entries - .borrow_mut() - .insert(key.to_string(), (ctx.new_str(key.to_string()), value)); - } - /// Take a python dictionary and convert it to attributes. pub fn to_attributes(self) -> PyAttributes { let mut attrs = PyAttributes::new(); diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index f6aa93aadc..e7ac5308cd 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -12,7 +12,7 @@ use rustpython_parser::{ast, parser}; use crate::function::PyFuncArgs; use crate::obj::objstr; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; #[derive(Debug)] @@ -656,8 +656,10 @@ fn ast_parse(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(ast_node) } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - py_module!(ctx, "ast", { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "ast", { "parse" => ctx.new_rustfunc(ast_parse), "Module" => py_class!(ctx, "_ast.Module", ctx.object(), {}), "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ctx.object(), {}), diff --git a/vm/src/stdlib/dis.rs b/vm/src/stdlib/dis.rs index 222f23383d..4c35f45e99 100644 --- a/vm/src/stdlib/dis.rs +++ b/vm/src/stdlib/dis.rs @@ -1,5 +1,5 @@ use crate::obj::objcode::PyCodeRef; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TryFromObject}; +use crate::pyobject::{PyObjectRef, PyResult, TryFromObject}; use crate::vm::VirtualMachine; fn dis_dis(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -17,8 +17,10 @@ fn dis_disassemble(co: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(vm.get_none()) } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - py_module!(ctx, "dis", { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "dis", { "dis" => ctx.new_rustfunc(dis_dis), "disassemble" => ctx.new_rustfunc(dis_disassemble) }) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index c6dbbc161d..bd33eac753 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -20,9 +20,7 @@ use crate::obj::objbytes; use crate::obj::objint; use crate::obj::objstr; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{ - BufferProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, -}; +use crate::pyobject::{BufferProtocol, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; fn compute_c_flag(mode: &str) -> u16 { @@ -365,7 +363,9 @@ pub fn io_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + //IOBase the abstract base class of the IO Module let io_base = py_class!(ctx, "IOBase", ctx.object(), { "__enter__" => ctx.new_rustfunc(io_base_cm_enter), @@ -421,7 +421,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "getvalue" => ctx.new_rustfunc(bytes_io_getvalue) }); - py_module!(ctx, "io", { + py_module!(vm, "io", { "open" => ctx.new_rustfunc(io_open), "IOBase" => io_base, "RawIOBase" => raw_io_base, diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 4695b7acbf..631f8257f2 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -12,8 +12,7 @@ use crate::obj::{ objtype, }; use crate::pyobject::{ - create_type, DictProtocol, IdProtocol, ItemProtocol, PyContext, PyObjectRef, PyResult, - TypeProtocol, + create_type, DictProtocol, IdProtocol, ItemProtocol, PyObjectRef, PyResult, TypeProtocol, }; use crate::VirtualMachine; use num_traits::cast::ToPrimitive; @@ -232,7 +231,9 @@ fn json_loads(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { de_pyobject(vm, &objstr::get_value(&string)) } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + // TODO: Make this a proper type with a constructor let json_decode_error = create_type( "JSONDecodeError", @@ -240,7 +241,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { &ctx.exceptions.exception_type, ); - py_module!(ctx, "json", { + py_module!(vm, "json", { "dumps" => ctx.new_rustfunc(json_dumps), "loads" => ctx.new_rustfunc(json_loads), "JSONDecodeError" => json_decode_error diff --git a/vm/src/stdlib/keyword.rs b/vm/src/stdlib/keyword.rs index fe27399c55..3143af21aa 100644 --- a/vm/src/stdlib/keyword.rs +++ b/vm/src/stdlib/keyword.rs @@ -6,7 +6,7 @@ use rustpython_parser::lexer; use crate::function::PyFuncArgs; use crate::obj::objstr; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; fn keyword_iskeyword(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -18,7 +18,9 @@ fn keyword_iskeyword(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(value) } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + let keyword_kwlist = ctx.new_list( lexer::get_keywords() .keys() @@ -26,7 +28,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { .collect(), ); - py_module!(ctx, "keyword", { + py_module!(vm, "keyword", { "iskeyword" => ctx.new_rustfunc(keyword_iskeyword), "kwlist" => keyword_kwlist }) diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index 5af599cb7d..dc7981ab6d 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -8,7 +8,7 @@ use statrs::function::gamma::{gamma, ln_gamma}; use crate::function::PyFuncArgs; use crate::obj::objfloat; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; // Helper macro: @@ -172,8 +172,10 @@ fn math_lgamma(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - py_module!(ctx, "math", { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "math", { // Number theory functions: "fabs" => ctx.new_rustfunc(math_fabs), "isfinite" => ctx.new_rustfunc(math_isfinite), diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index b0b61119c3..a69a80d118 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -15,14 +15,16 @@ mod types; mod weakref; use std::collections::HashMap; +use crate::vm::VirtualMachine; + #[cfg(not(target_arch = "wasm32"))] pub mod io; #[cfg(not(target_arch = "wasm32"))] mod os; -use crate::pyobject::{PyContext, PyObjectRef}; +use crate::pyobject::PyObjectRef; -pub type StdlibInitFunc = Box PyObjectRef>; +pub type StdlibInitFunc = Box PyObjectRef>; pub fn get_module_inits() -> HashMap { let mut modules = HashMap::new(); diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 31b3ee6449..3f0f557f1c 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -7,7 +7,7 @@ use num_traits::cast::ToPrimitive; use crate::function::PyFuncArgs; use crate::obj::objint; use crate::obj::objstr; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; #[cfg(unix)] @@ -113,14 +113,16 @@ fn os_error(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Err(vm.new_os_error(msg)) } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + let os_name = if cfg!(windows) { "nt".to_string() } else { "posix".to_string() }; - py_module!(ctx, "os", { + py_module!(vm, "os", { "open" => ctx.new_rustfunc(os_open), "close" => ctx.new_rustfunc(os_close), "error" => ctx.new_rustfunc(os_error), diff --git a/vm/src/stdlib/platform.rs b/vm/src/stdlib/platform.rs index a87383a723..6d4751a35f 100644 --- a/vm/src/stdlib/platform.rs +++ b/vm/src/stdlib/platform.rs @@ -1,9 +1,10 @@ use crate::function::PyFuncArgs; -use crate::pyobject::{PyContext, PyObjectRef, PyResult}; +use crate::pyobject::{PyObjectRef, PyResult}; use crate::vm::VirtualMachine; -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - py_module!(ctx, "platform", { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + py_module!(vm, "platform", { "python_compiler" => ctx.new_rustfunc(platform_python_compiler), "python_implementation" => ctx.new_rustfunc(platform_python_implementation), "python_version" => ctx.new_rustfunc(platform_python_version), diff --git a/vm/src/stdlib/pystruct.rs b/vm/src/stdlib/pystruct.rs index 9dadc520a9..2ff61d630a 100644 --- a/vm/src/stdlib/pystruct.rs +++ b/vm/src/stdlib/pystruct.rs @@ -15,7 +15,7 @@ use num_traits::ToPrimitive; use crate::function::PyFuncArgs; use crate::obj::{objbool, objbytes, objfloat, objint, objstr, objtype}; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; use crate::VirtualMachine; #[derive(Debug)] @@ -297,8 +297,10 @@ fn struct_unpack(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_tuple(items)) } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - py_module!(ctx, "struct", { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "struct", { "pack" => ctx.new_rustfunc(struct_pack), "unpack" => ctx.new_rustfunc(struct_unpack) }) diff --git a/vm/src/stdlib/random.rs b/vm/src/stdlib/random.rs index 82c1f410c3..c6cc44a363 100644 --- a/vm/src/stdlib/random.rs +++ b/vm/src/stdlib/random.rs @@ -4,11 +4,13 @@ use rand::distributions::{Distribution, Normal}; use crate::function::PyFuncArgs; use crate::obj::objfloat; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - py_module!(ctx, "random", { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "random", { "guass" => ctx.new_rustfunc(random_gauss), "normalvariate" => ctx.new_rustfunc(random_normalvariate), "random" => ctx.new_rustfunc(random_random), diff --git a/vm/src/stdlib/re.rs b/vm/src/stdlib/re.rs index fee100bfeb..b9f9215020 100644 --- a/vm/src/stdlib/re.rs +++ b/vm/src/stdlib/re.rs @@ -8,7 +8,7 @@ use regex::{Match, Regex}; use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; impl PyValue for Regex { @@ -98,7 +98,9 @@ impl PyMatchRef { } /// Create the python `re` module with all its members. -pub fn make_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + let match_type = py_class!(ctx, "Match", ctx.object(), { "start" => ctx.new_rustfunc(PyMatchRef::start), "end" => ctx.new_rustfunc(PyMatchRef::end) @@ -109,7 +111,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "search" => ctx.new_rustfunc(PyRegexRef::search) }); - py_module!(ctx, "re", { + py_module!(vm, "re", { "compile" => ctx.new_rustfunc(re_compile), "Match" => match_type, "match" => ctx.new_rustfunc(re_match), diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index fe0ce29a06..ab5072f7c1 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -10,9 +10,7 @@ use crate::obj::objbytes; use crate::obj::objint; use crate::obj::objsequence::get_elements; use crate::obj::objstr; -use crate::pyobject::{ - PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, -}; +use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol}; use crate::vm::VirtualMachine; use crate::obj::objtype::PyClassRef; @@ -411,7 +409,9 @@ fn get_addr_tuple(vm: &VirtualMachine, addr: SocketAddr) -> PyResult { Ok(vm.ctx.new_tuple(vec![ip, port])) } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + let socket = py_class!(ctx, "socket", ctx.object(), { "__new__" => ctx.new_rustfunc(socket_new), "connect" => ctx.new_rustfunc(socket_connect), @@ -426,7 +426,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "recvfrom" => ctx.new_rustfunc(socket_recvfrom), }); - py_module!(ctx, "socket", { + py_module!(vm, "socket", { "AF_INET" => ctx.new_int(AddressFamily::Inet as i32), "SOCK_STREAM" => ctx.new_int(SocketKind::Stream as i32), "SOCK_DGRAM" => ctx.new_int(SocketKind::Dgram as i32), diff --git a/vm/src/stdlib/string.rs b/vm/src/stdlib/string.rs index 157dae035f..c255ffc935 100644 --- a/vm/src/stdlib/string.rs +++ b/vm/src/stdlib/string.rs @@ -3,9 +3,12 @@ * */ -use crate::pyobject::{PyContext, PyObjectRef}; +use crate::pyobject::PyObjectRef; +use crate::vm::VirtualMachine; + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; -pub fn make_module(ctx: &PyContext) -> PyObjectRef { let ascii_lowercase = "abcdefghijklmnopqrstuvwxyz".to_string(); let ascii_uppercase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".to_string(); let ascii_letters = format!("{}{}", ascii_lowercase, ascii_uppercase); @@ -19,7 +22,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { */ // Constants: - py_module!(ctx, "string", { + py_module!(vm, "string", { "ascii_letters" => ctx.new_str(ascii_letters), "ascii_lowercase" => ctx.new_str(ascii_lowercase), "ascii_uppercase" => ctx.new_str(ascii_uppercase), diff --git a/vm/src/stdlib/time_module.rs b/vm/src/stdlib/time_module.rs index dfe3ab8e35..93beabf8ac 100644 --- a/vm/src/stdlib/time_module.rs +++ b/vm/src/stdlib/time_module.rs @@ -5,7 +5,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::function::PyFuncArgs; use crate::obj::objfloat; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; fn time_sleep(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -32,8 +32,10 @@ fn time_time(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(value) } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - py_module!(ctx, "time", { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "time", { "sleep" => ctx.new_rustfunc(time_sleep), "time" => ctx.new_rustfunc(time_time) }) diff --git a/vm/src/stdlib/tokenize.rs b/vm/src/stdlib/tokenize.rs index 0aa5df3d2a..88a26881cc 100644 --- a/vm/src/stdlib/tokenize.rs +++ b/vm/src/stdlib/tokenize.rs @@ -8,7 +8,7 @@ use rustpython_parser::lexer; use crate::function::PyFuncArgs; use crate::obj::objstr; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; fn tokenize_tokenize(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -25,8 +25,10 @@ fn tokenize_tokenize(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: create main function when called with -m -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - py_module!(ctx, "tokenize", { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "tokenize", { "tokenize" => ctx.new_rustfunc(tokenize_tokenize) }) } diff --git a/vm/src/stdlib/types.rs b/vm/src/stdlib/types.rs index 8a5ab2939c..ad76a80a42 100644 --- a/vm/src/stdlib/types.rs +++ b/vm/src/stdlib/types.rs @@ -7,7 +7,7 @@ use crate::obj::objdict::PyDict; use crate::obj::objstr::PyStringRef; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject}; +use crate::pyobject::{PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject}; use crate::VirtualMachine; fn types_new_class( @@ -25,8 +25,10 @@ fn types_new_class( objtype::type_new_class(vm, vm.ctx.type_type(), name, bases, dict) } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - py_module!(ctx, "types", { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "types", { "new_class" => ctx.new_rustfunc(types_new_class), "FunctionType" => ctx.function_type(), "LambdaType" => ctx.function_type(), diff --git a/vm/src/stdlib/weakref.rs b/vm/src/stdlib/weakref.rs index 5616c3ddfe..b5f7b756ac 100644 --- a/vm/src/stdlib/weakref.rs +++ b/vm/src/stdlib/weakref.rs @@ -5,10 +5,13 @@ //! - [rust weak struct](https://doc.rust-lang.org/std/rc/struct.Weak.html) //! -use super::super::pyobject::{PyContext, PyObjectRef}; +use super::super::pyobject::PyObjectRef; +use crate::vm::VirtualMachine; -pub fn make_module(ctx: &PyContext) -> PyObjectRef { - py_module!(ctx, "_weakref", { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "_weakref", { "ref" => ctx.weakref_type() }) } diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index c803daaad5..772dfe198e 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -3,7 +3,7 @@ use std::{env, mem}; use crate::frame::FrameRef; use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{DictProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; /* @@ -39,7 +39,9 @@ fn sys_getsizeof(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_int(size)) } -pub fn make_module(ctx: &PyContext, builtins: PyObjectRef) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectRef) { + let ctx = &vm.ctx; + let path_list = match env::var_os("PYTHONPATH") { Some(paths) => env::split_paths(&paths) .map(|path| { @@ -124,7 +126,7 @@ setrecursionlimit() -- set the max recursion depth for the interpreter settrace() -- set the global debug tracing function "; let modules = ctx.new_dict(); - let sys_mod = py_module!(ctx, "sys", { + extend_module!(vm, module, { "argv" => argv(ctx), "getrefcount" => ctx.new_rustfunc(sys_getrefcount), "getsizeof" => ctx.new_rustfunc(sys_getsizeof), @@ -137,8 +139,6 @@ settrace() -- set the global debug tracing function "modules" => modules.clone(), }); - modules.unsafe_str_insert("sys", sys_mod.clone(), &ctx); - modules.unsafe_str_insert("builtins", builtins, &ctx); - - sys_mod + modules.set_item("sys", module.clone(), vm); + modules.set_item("builtins", builtins.clone(), vm); } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 6c35983e72..0bd2555742 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -58,18 +58,22 @@ impl VirtualMachine { let ctx = PyContext::new(); // Hard-core modules: - let builtins = builtins::make_module(&ctx); - let sysmod = sysmodule::make_module(&ctx, builtins.clone()); + let builtins = ctx.new_module("builtins", ctx.new_dict()); + let sysmod = ctx.new_module("sys", ctx.new_dict()); let stdlib_inits = RefCell::new(stdlib::get_module_inits()); - VirtualMachine { - builtins, - sys_module: sysmod, + let vm = VirtualMachine { + builtins: builtins.clone(), + sys_module: sysmod.clone(), stdlib_inits, ctx, frames: RefCell::new(vec![]), wasm_id: None, - } + }; + + builtins::make_module(&vm, builtins.clone()); + sysmodule::make_module(&vm, sysmod, builtins); + vm } pub fn run_code_obj(&self, code: PyCodeRef, scope: Scope) -> PyResult { @@ -564,15 +568,16 @@ impl VirtualMachine { self.call_method(&obj, "__getattribute__", vec![attr_name.into_object()]) } - pub fn set_attr(&self, obj: &PyObjectRef, attr_name: K, attr_value: PyObjectRef) -> PyResult + pub fn set_attr(&self, obj: &PyObjectRef, attr_name: K, attr_value: V) -> PyResult where K: TryIntoRef, + V: Into, { let attr_name = attr_name.try_into_ref(self)?; self.call_method( obj, "__setattr__", - vec![attr_name.into_object(), attr_value], + vec![attr_name.into_object(), attr_value.into()], ) } diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 0e13aeb3ab..cb00d175c6 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -12,9 +12,7 @@ use rustpython_vm::obj::{ objstr::{self, PyStringRef}, objtype::PyClassRef, }; -use rustpython_vm::pyobject::{ - PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, -}; +use rustpython_vm::pyobject::{PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use rustpython_vm::VirtualMachine; use crate::{convert, vm_class::AccessibleVM, wasm_builtins::window}; @@ -348,7 +346,9 @@ fn browser_prompt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(result) } -pub fn make_module(ctx: &PyContext) -> PyObjectRef { +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + let promise = py_class!(ctx, "Promise", ctx.object(), { "then" => ctx.new_rustfunc(PyPromise::then), "catch" => ctx.new_rustfunc(PyPromise::catch), @@ -371,7 +371,7 @@ pub fn make_module(ctx: &PyContext) -> PyObjectRef { "set_attr" => ctx.new_rustfunc(Element::set_attr), }); - py_module!(ctx, "browser", { + py_module!(vm, "browser", { "fetch" => ctx.new_rustfunc(browser_fetch), "request_animation_frame" => ctx.new_rustfunc(browser_request_animation_frame), "cancel_animation_frame" => ctx.new_rustfunc(browser_cancel_animation_frame), diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 6e4ceaee29..dbf148f2ea 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -8,7 +8,7 @@ use wasm_bindgen::prelude::*; use rustpython_vm::compile; use rustpython_vm::frame::{NameProtocol, Scope}; use rustpython_vm::function::PyFuncArgs; -use rustpython_vm::pyobject::{PyContext, PyObject, PyObjectPayload, PyObjectRef, PyResult}; +use rustpython_vm::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult}; use rustpython_vm::VirtualMachine; use crate::browser_module::setup_browser_module; @@ -260,12 +260,12 @@ impl WASMVirtualMachine { let mod_name = name.clone(); - let stdlib_init_fn = move |ctx: &PyContext| { - let py_mod = ctx.new_module(&name, ctx.new_dict()); + let stdlib_init_fn = move |vm: &VirtualMachine| { + let module = vm.ctx.new_module(&name, vm.ctx.new_dict()); for (key, value) in module_items.clone() { - ctx.set_attr(&py_mod, &key, value); + vm.set_attr(&module, key, value).unwrap(); } - py_mod + module }; vm.stdlib_inits From b943f4a4bb465d88af212461dfc7406259247b83 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 4 Apr 2019 17:58:14 +0100 Subject: [PATCH 156/884] Historic real proper dictionary support. --- tests/snippets/dict.py | 4 ++ vm/src/frame.rs | 4 +- vm/src/lib.rs | 1 + vm/src/obj/objdict.rs | 90 ++++++++++++++++------------------------ vm/src/obj/objobject.rs | 2 +- vm/src/pyobject.rs | 2 +- wasm/lib/src/vm_class.rs | 3 +- 7 files changed, 47 insertions(+), 59 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 170a6492dc..a80b5b9e20 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -34,3 +34,7 @@ def dict_eq(d1, d2): for key in a.keys(): res.add(key) assert res == set(['a','b']) + +x = {} +x[1] = 1 +assert x[1] == 1 diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 1a0cc4de7c..0b735df87c 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -157,8 +157,8 @@ impl NameProtocol for Scope { self.get_locals().set_item(key, value, vm) } - fn delete_name(&self, _vm: &VirtualMachine, key: &str) { - self.get_locals().del_item(key) + fn delete_name(&self, vm: &VirtualMachine, key: &str) { + self.get_locals().del_item(key, vm) } } diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 4a300dceff..89b4dd8720 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -44,6 +44,7 @@ pub mod macros; mod builtins; pub mod bytecode; pub mod compile; +mod dictdatatype; pub mod error; pub mod eval; mod exceptions; diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 6ebe5aa2e4..22e4fb5188 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -1,20 +1,22 @@ use std::cell::{Cell, RefCell}; -use std::collections::HashMap; use std::fmt; use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ - DictProtocol, IntoPyObject, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + DictProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, + PyResult, PyValue, }; use crate::vm::{ReprGuard, VirtualMachine}; +use crate::dictdatatype; + use super::objiter; use super::objlist::PyListIterator; -use super::objstr::{self, PyStringRef}; +use super::objstr; use super::objtype; use crate::obj::objtype::PyClassRef; -pub type DictContentType = HashMap; +pub type DictContentType = dictdatatype::Dict; #[derive(Default)] pub struct PyDict { @@ -37,13 +39,11 @@ impl PyValue for PyDict { } pub fn get_key_value_pairs(dict: &PyObjectRef) -> Vec<(PyObjectRef, PyObjectRef)> { - let dict_elements = dict.payload::().unwrap().entries.borrow(); - let mut pairs: Vec<(PyObjectRef, PyObjectRef)> = Vec::new(); - for (_str_key, pair) in dict_elements.iter() { - let (key, obj) = pair; - pairs.push((key.clone(), obj.clone())); - } - pairs + dict.payload::() + .unwrap() + .entries + .borrow() + .get_items() } // Python dict methods: @@ -112,18 +112,12 @@ impl PyDictRef { Ok(vm.new_str(s)) } - fn contains(self, key: PyStringRef, _vm: &VirtualMachine) -> bool { - self.entries.borrow().contains_key(&key.value) + fn contains(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.entries.borrow().contains(vm, &key) } - fn delitem(self, key: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { - let key = &key.value; - // Delete the item: - let mut elements = self.entries.borrow_mut(); - match elements.remove(key) { - Some(_) => Ok(()), - None => Err(vm.new_value_error(format!("Key not found: {}", key))), - } + fn delitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + self.entries.borrow_mut().delete(vm, &key) } fn clear(self, _vm: &VirtualMachine) { @@ -136,7 +130,8 @@ impl PyDictRef { let keys = self .entries .borrow() - .values() + .get_items() + .iter() .map(|(k, _v)| k.clone()) .collect(); let key_list = vm.ctx.new_list(keys); @@ -152,7 +147,8 @@ impl PyDictRef { let values = self .entries .borrow() - .values() + .get_items() + .iter() .map(|(_k, v)| v.clone()) .collect(); let values_list = vm.ctx.new_list(values); @@ -168,7 +164,8 @@ impl PyDictRef { let items = self .entries .borrow() - .values() + .get_items() + .iter() .map(|(k, v)| vm.ctx.new_tuple(vec![k.clone(), v.clone()])) .collect(); let items_list = vm.ctx.new_list(items); @@ -183,35 +180,22 @@ impl PyDictRef { self.set_item(needle, value, vm) } - fn getitem(self, key: PyStringRef, vm: &VirtualMachine) -> PyResult { - let key = &key.value; - - // What we are looking for: - let elements = self.entries.borrow(); - if elements.contains_key(key) { - Ok(elements[key].1.clone()) - } else { - Err(vm.new_value_error(format!("Key not found: {}", key))) - } + fn getitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.entries.borrow().get(vm, &key) } fn get( self, - key: PyStringRef, + key: PyObjectRef, default: OptionalArg, vm: &VirtualMachine, ) -> PyObjectRef { - // What we are looking for: - let key = &key.value; - - let elements = self.entries.borrow(); - if elements.contains_key(key) { - elements[key].1.clone() - } else { - match default { + match self.into_object().get_item(key, vm) { + Ok(value) => value, + Err(_) => match default { OptionalArg::Present(value) => value, OptionalArg::Missing => vm.ctx.none(), - } + }, } } @@ -228,13 +212,13 @@ impl PyDictRef { impl DictProtocol for PyDictRef { fn contains_key(&self, key: T, vm: &VirtualMachine) -> bool { - let key_str = &objstr::get_value(&key.into_pyobject(vm).unwrap()); - self.entries.borrow().get(key_str).is_some() + let key = key.into_pyobject(vm).unwrap(); + self.entries.borrow().contains(vm, &key).unwrap() } fn get_item(&self, key: T, vm: &VirtualMachine) -> Option { - let key_str = &objstr::get_value(&key.into_pyobject(vm).unwrap()); - self.entries.borrow().get(key_str).map(|v| v.1.clone()) + let key = key.into_pyobject(vm).unwrap(); + self.entries.borrow().get(vm, &key).ok() } fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { @@ -244,14 +228,12 @@ impl DictProtocol for PyDictRef { // Item set/get: fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine) { let key = key.into_pyobject(vm).unwrap(); - let key_str = &objstr::get_value(&key); - let elements = &mut self.entries.borrow_mut(); - elements.insert(key_str.to_string(), (key.clone(), value)); + self.entries.borrow_mut().insert(vm, &key, value).unwrap() } - fn del_item(&self, key: &str) { - let elements = &mut self.entries.borrow_mut(); - elements.remove(key).unwrap(); + fn del_item(&self, key: T, vm: &VirtualMachine) { + let key = key.into_pyobject(vm).unwrap(); + self.entries.borrow_mut().delete(vm, &key).unwrap(); } } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 86a56de5d8..1336e52465 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -98,7 +98,7 @@ fn object_delattr(obj: PyObjectRef, attr_name: PyStringRef, vm: &VirtualMachine) } if let Some(ref dict) = obj.dict { - dict.del_item(&attr_name.value); + dict.del_item(attr_name, vm); Ok(()) } else { Err(vm.new_attribute_error(format!( diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 139b2a1e11..de4d296285 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -917,7 +917,7 @@ pub trait DictProtocol { fn get_item(&self, key: T, vm: &VirtualMachine) -> Option; fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)>; fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine); - fn del_item(&self, key: &str); + fn del_item(&self, key: T, vm: &VirtualMachine); } pub trait ItemProtocol { diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index dbf148f2ea..d6f9ede0ec 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -243,7 +243,8 @@ impl WASMVirtualMachine { } else { return Err(error()); }; - vm.set_attr(&vm.builtins, "print", vm.ctx.new_rustfunc(print_fn))?; + vm.set_attr(&vm.builtins, "print", vm.ctx.new_rustfunc(print_fn)) + .unwrap(); Ok(()) })? } From 43347e2d8bb67395aacfde800dd68ddc9a6d2f7a Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 4 Apr 2019 18:00:14 +0100 Subject: [PATCH 157/884] Dictionary: KeyError -> ValueError. --- vm/src/dictdatatype.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 92a93a4e9e..ac1df994a9 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -85,7 +85,7 @@ impl Dict { } } else { let key_repr = vm.to_pystr(key)?; - Err(vm.new_value_error(format!("Key not found: {}", key_repr))) + Err(vm.new_key_error(format!("Key not found: {}", key_repr))) } } @@ -103,7 +103,7 @@ impl Dict { Ok(()) } else { let key_repr = vm.to_pystr(key)?; - Err(vm.new_value_error(format!("Key not found: {}", key_repr))) + Err(vm.new_key_error(format!("Key not found: {}", key_repr))) } } From 77d5f57df1dd1f63e5393d279c711d9eac8605ce Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Fri, 5 Apr 2019 14:36:19 +0100 Subject: [PATCH 158/884] Add some additional dictionary tests. --- tests/snippets/dict.py | 50 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index a80b5b9e20..5c5ef62b1e 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -1,3 +1,5 @@ +from testutils import assertRaises + def dict_eq(d1, d2): return (all(k in d2 and d1[k] == d2[k] for k in d1) and all(k in d1 and d1[k] == d2[k] for k in d2)) @@ -21,20 +23,60 @@ def dict_eq(d1, d2): a = {'a': 5, 'b': 6} res = set() for value in a.values(): - res.add(value) + res.add(value) assert res == set([5,6]) count = 0 for (key, value) in a.items(): - assert a[key] == value - count += 1 + assert a[key] == value + count += 1 assert count == len(a) res = set() for key in a.keys(): - res.add(key) + res.add(key) assert res == set(['a','b']) x = {} x[1] = 1 assert x[1] == 1 + + +x[7] = 7 +x[2] = 2 +x[(5, 6)] = 5 + +with assertRaises(KeyError): + x["not here"] + +with assertRaises(TypeError): + x[[]] # Unhashable type. + +x["here"] = "here" +assert x.get("not here", "default") == "default" +assert x.get("here", "default") == "here" +assert x.get("not here") == None + +# An object that hashes to the same value always, and compares equal if any its values match. +class Hashable(object): + def __init__(self, *args): + self.values = args + def __hash__(self): + return 1 + def __eq__(self, other): + for x in self.values: + for y in other.values: + if x == y: + return True + return False + +x = {} +x[Hashable(1,2)] = 8 + +assert x[Hashable(1,2)] == 8 +assert x[Hashable(3,1)] == 8 + +x[Hashable(8)] = 19 +x[Hashable(19,8)] = 1 +assert x[Hashable(8)] == 1 +assert len(x) == 2 From ec94168a15dedc80297659934623a1730a99ce6a Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Fri, 5 Apr 2019 15:00:10 +0100 Subject: [PATCH 159/884] Remove objdict::get_key_value_pairs. --- vm/src/frame.rs | 11 +++++++---- vm/src/obj/objdict.rs | 25 ++++++++----------------- vm/src/obj/objmodule.rs | 2 +- vm/src/pyobject.rs | 1 - vm/src/stdlib/json.rs | 7 +++++-- wasm/lib/src/browser_module.rs | 4 +++- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 0b735df87c..f6fc8e96e9 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -12,7 +12,7 @@ use crate::function::PyFuncArgs; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode::PyCodeRef; -use crate::obj::objdict::{self, PyDictRef}; +use crate::obj::objdict::PyDictRef; use crate::obj::objint::PyInt; use crate::obj::objiter; use crate::obj::objlist; @@ -390,7 +390,9 @@ impl Frame { let obj = self.pop_value(); if *unpack { // Take all key-value pairs from the dict: - let dict_elements = objdict::get_key_value_pairs(&obj); + let dict: PyDictRef = + obj.downcast().expect("Need a dictionary to build a map."); + let dict_elements = dict.get_key_value_pairs(); for (key, value) in dict_elements.iter() { map_obj.set_item(key.clone(), value.clone(), vm); } @@ -612,8 +614,9 @@ impl Frame { } bytecode::CallType::Ex(has_kwargs) => { let kwargs = if *has_kwargs { - let kw_dict = self.pop_value(); - let dict_elements = objdict::get_key_value_pairs(&kw_dict).clone(); + let kw_dict: PyDictRef = + self.pop_value().downcast().expect("Kwargs must be a dict."); + let dict_elements = kw_dict.get_key_value_pairs(); dict_elements .into_iter() .map(|elem| (objstr::get_value(&elem.0), elem.1)) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 4ac1431b3c..b2bf453041 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -13,7 +13,6 @@ use crate::dictdatatype; use super::objiter; use super::objlist::PyListIterator; use super::objstr; -use super::objtype; use crate::obj::objtype::PyClassRef; pub type DictContentType = dictdatatype::Dict; @@ -38,14 +37,6 @@ impl PyValue for PyDict { } } -pub fn get_key_value_pairs(dict: &PyObjectRef) -> Vec<(PyObjectRef, PyObjectRef)> { - dict.payload::() - .unwrap() - .entries - .borrow() - .get_items() -} - // Python dict methods: impl PyDictRef { fn new( @@ -56,8 +47,9 @@ impl PyDictRef { ) -> PyResult { let dict = vm.ctx.new_dict(); if let OptionalArg::Present(dict_obj) = dict_obj { - if objtype::isinstance(&dict_obj, &vm.ctx.dict_type()) { - for (needle, value) in get_key_value_pairs(&dict_obj) { + let dicted: PyResult = dict_obj.clone().downcast(); + if let Ok(dict_obj) = dicted { + for (needle, value) in dict_obj.get_key_value_pairs() { dict.set_item(needle, value, vm); } } else { @@ -97,9 +89,8 @@ impl PyDictRef { fn repr(self, vm: &VirtualMachine) -> PyResult { let s = if let Some(_guard) = ReprGuard::enter(self.as_object()) { - let elements = get_key_value_pairs(self.as_object()); let mut str_parts = vec![]; - for (key, value) in elements { + for (key, value) in self.get_key_value_pairs() { let key_repr = vm.to_repr(&key)?; let value_repr = vm.to_repr(&value)?; str_parts.push(format!("{}: {}", key_repr.value, value_repr.value)); @@ -176,6 +167,10 @@ impl PyDictRef { } } + pub fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { + self.entries.borrow().get_items() + } + fn setitem(self, needle: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) { self.set_item(needle, value, vm) } @@ -225,10 +220,6 @@ impl DictProtocol for PyDictRef { self.entries.borrow().get(vm, &key).ok() } - fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { - get_key_value_pairs(self.as_object()) - } - // Item set/get: fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine) { let key = key.into_pyobject(vm).unwrap(); diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs index b641f04268..75749d557e 100644 --- a/vm/src/obj/objmodule.rs +++ b/vm/src/obj/objmodule.rs @@ -1,5 +1,5 @@ use crate::obj::objtype::PyClassRef; -use crate::pyobject::{DictProtocol, PyContext, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyContext, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; #[derive(Debug)] diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index de4d296285..4df7e57fe5 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -915,7 +915,6 @@ impl TypeProtocol for PyRef { pub trait DictProtocol { fn contains_key(&self, key: T, vm: &VirtualMachine) -> bool; fn get_item(&self, key: T, vm: &VirtualMachine) -> Option; - fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)>; fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine); fn del_item(&self, key: T, vm: &VirtualMachine); } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 631f8257f2..b7cb704b1c 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -7,7 +7,9 @@ use serde_json; use crate::function::PyFuncArgs; use crate::obj::{ - objbool, objdict, objfloat, objint, objsequence, + objbool, + objdict::PyDictRef, + objfloat, objint, objsequence, objstr::{self, PyString}, objtype, }; @@ -63,7 +65,8 @@ impl<'s> serde::Serialize for PyObjectSerializer<'s> { let elements = objsequence::get_elements(self.pyobject); serialize_seq_elements(serializer, &elements) } else if objtype::isinstance(self.pyobject, &self.vm.ctx.dict_type()) { - let pairs = objdict::get_key_value_pairs(self.pyobject); + let dict: PyDictRef = self.pyobject.clone().downcast().unwrap(); + let pairs = dict.get_key_value_pairs(); let mut map = serializer.serialize_map(Some(pairs.len()))?; for (key, e) in pairs.iter() { map.serialize_entry(&self.clone_with_object(key), &self.clone_with_object(&e))?; diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index cb00d175c6..6f7c31964f 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -7,6 +7,7 @@ use wasm_bindgen_futures::{future_to_promise, JsFuture}; use rustpython_vm::function::{OptionalArg, PyFuncArgs}; use rustpython_vm::obj::{ + objdict::PyDictRef, objfunction::PyFunction, objint, objstr::{self, PyStringRef}, @@ -72,7 +73,8 @@ fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if let Some(headers) = headers { let h = request.headers(); - for (key, value) in rustpython_vm::obj::objdict::get_key_value_pairs(&headers) { + let headers: PyDictRef = headers.downcast().unwrap(); + for (key, value) in headers.get_key_value_pairs() { let key = &vm.to_str(&key)?.value; let value = &vm.to_str(&value)?.value; h.set(key, value) From 4212594c5b108afcd076fa56930ec861c559bf8a Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Fri, 5 Apr 2019 15:01:44 +0100 Subject: [PATCH 160/884] Prefer key to needle. --- vm/src/obj/objdict.rs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index b2bf453041..449c60726a 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -49,8 +49,8 @@ impl PyDictRef { if let OptionalArg::Present(dict_obj) = dict_obj { let dicted: PyResult = dict_obj.clone().downcast(); if let Ok(dict_obj) = dicted { - for (needle, value) in dict_obj.get_key_value_pairs() { - dict.set_item(needle, value, vm); + for (key, value) in dict_obj.get_key_value_pairs() { + dict.set_item(key, value, vm); } } else { let iter = objiter::get_iter(vm, &dict_obj)?; @@ -63,18 +63,17 @@ impl PyDictRef { None => break, }; let elem_iter = objiter::get_iter(vm, &element)?; - let needle = - objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?; + let key = objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?; let value = objiter::get_next_object(vm, &elem_iter)?.ok_or_else(|| err(vm))?; if objiter::get_next_object(vm, &elem_iter)?.is_some() { return Err(err(vm)); } - dict.set_item(needle, value, vm); + dict.set_item(key, value, vm); } } } - for (needle, value) in kwargs.into_iter() { - dict.set_item(vm.new_str(needle), value, vm); + for (key, value) in kwargs.into_iter() { + dict.set_item(vm.new_str(key), value, vm); } Ok(dict) } @@ -171,8 +170,8 @@ impl PyDictRef { self.entries.borrow().get_items() } - fn setitem(self, needle: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) { - self.set_item(needle, value, vm) + fn setitem(self, key: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) { + self.set_item(key, value, vm) } fn getitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { From 0dce9bb96aac9e446f0b87111e8b27c0135f2489 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Fri, 5 Apr 2019 15:10:52 +0100 Subject: [PATCH 161/884] Use item protocol when executing instructions. --- vm/src/frame.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index f6fc8e96e9..858b33a08b 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -21,8 +21,8 @@ use crate::obj::objstr; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - DictProtocol, IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, - TypeProtocol, + DictProtocol, IdProtocol, ItemProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -990,22 +990,18 @@ impl Frame { } } - fn subscript(&self, vm: &VirtualMachine, a: PyObjectRef, b: PyObjectRef) -> PyResult { - vm.call_method(&a, "__getitem__", vec![b]) - } - fn execute_store_subscript(&self, vm: &VirtualMachine) -> FrameResult { let idx = self.pop_value(); let obj = self.pop_value(); let value = self.pop_value(); - vm.call_method(&obj, "__setitem__", vec![idx, value])?; + obj.set_item(idx, value, vm)?; Ok(None) } fn execute_delete_subscript(&self, vm: &VirtualMachine) -> FrameResult { let idx = self.pop_value(); let obj = self.pop_value(); - vm.call_method(&obj, "__delitem__", vec![idx])?; + obj.del_item(idx, vm)?; Ok(None) } @@ -1040,7 +1036,7 @@ impl Frame { bytecode::BinaryOperator::FloorDivide => vm._floordiv(a_ref, b_ref), // TODO: Subscript should probably have its own op bytecode::BinaryOperator::Subscript if inplace => unreachable!(), - bytecode::BinaryOperator::Subscript => self.subscript(vm, a_ref, b_ref), + bytecode::BinaryOperator::Subscript => a_ref.get_item(b_ref, vm), bytecode::BinaryOperator::Modulo if inplace => vm._imod(a_ref, b_ref), bytecode::BinaryOperator::Modulo => vm._mod(a_ref, b_ref), bytecode::BinaryOperator::Lshift if inplace => vm._ilshift(a_ref, b_ref), From f354f4ce028cd86b63f33641dc0aa4c53d253274 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Fri, 5 Apr 2019 16:14:02 +0100 Subject: [PATCH 162/884] Move contains_key to PyDictRef. --- vm/src/obj/objdict.rs | 6 +++--- vm/src/pyobject.rs | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 449c60726a..541645d5f7 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -206,14 +206,14 @@ impl PyDictRef { fn hash(self, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error("unhashable type".to_string())) } -} -impl DictProtocol for PyDictRef { - fn contains_key(&self, key: T, vm: &VirtualMachine) -> bool { + pub fn contains_key(&self, key: T, vm: &VirtualMachine) -> bool { let key = key.into_pyobject(vm).unwrap(); self.entries.borrow().contains(vm, &key).unwrap() } +} +impl DictProtocol for PyDictRef { fn get_item(&self, key: T, vm: &VirtualMachine) -> Option { let key = key.into_pyobject(vm).unwrap(); self.entries.borrow().get(vm, &key).ok() diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 4df7e57fe5..bf5cf3c860 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -913,7 +913,6 @@ impl TypeProtocol for PyRef { } pub trait DictProtocol { - fn contains_key(&self, key: T, vm: &VirtualMachine) -> bool; fn get_item(&self, key: T, vm: &VirtualMachine) -> Option; fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine); fn del_item(&self, key: T, vm: &VirtualMachine); From 37e7972dcd438dfd0e0305236e4a0bbbc66a99eb Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 5 Apr 2019 17:51:03 +0300 Subject: [PATCH 163/884] Add os.read --- tests/snippets/stdlib_os.py | 9 ++++++++- vm/src/stdlib/os.rs | 22 +++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index ed9e91c708..d700504951 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -2,8 +2,15 @@ from testutils import assert_raises -assert os.open('README.md', 0) > 0 +fd = os.open('README.md', 0) +assert fd > 0 +assert len(os.read(fd, 10)) == 10 +assert len(os.read(fd, 5)) == 5 + +assert_raises(OSError, lambda: os.read(fd + 1, 10)) +os.close(fd) +assert_raises(OSError, lambda: os.read(fd, 10)) assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', 0)) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 3f0f557f1c..0f57620140 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,6 +1,6 @@ use std::fs::File; use std::fs::OpenOptions; -use std::io::ErrorKind; +use std::io::{ErrorKind, Read}; use num_traits::cast::ToPrimitive; @@ -113,6 +113,25 @@ fn os_error(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Err(vm.new_os_error(msg)) } +fn os_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(fd, Some(vm.ctx.int_type())), (n, Some(vm.ctx.int_type()))] + ); + + let mut buffer = vec![0u8; objint::get_value(n).to_usize().unwrap()]; + let mut file = rust_file(objint::get_value(fd).to_i64().unwrap()); + match file.read_exact(&mut buffer) { + Ok(_) => (), + Err(s) => return Err(vm.new_os_error(s.to_string())), + }; + + // Avoid closing the fd + raw_file_number(file); + Ok(vm.ctx.new_bytes(buffer)) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -126,6 +145,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "open" => ctx.new_rustfunc(os_open), "close" => ctx.new_rustfunc(os_close), "error" => ctx.new_rustfunc(os_error), + "read" => ctx.new_rustfunc(os_read), "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From 28c3ef1ae3c8a867c52f4306bd81e7587b484bff Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 5 Apr 2019 19:17:36 +0300 Subject: [PATCH 164/884] Add os.write --- vm/src/stdlib/os.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 0f57620140..b7e4280e50 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,10 +1,11 @@ use std::fs::File; use std::fs::OpenOptions; -use std::io::{ErrorKind, Read}; +use std::io::{ErrorKind, Read, Write}; use num_traits::cast::ToPrimitive; use crate::function::PyFuncArgs; +use crate::obj::objbytes; use crate::obj::objint; use crate::obj::objstr; use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; @@ -132,6 +133,27 @@ fn os_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bytes(buffer)) } +fn os_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [ + (fd, Some(vm.ctx.int_type())), + (data, Some(vm.ctx.bytes_type())) + ] + ); + + let mut file = rust_file(objint::get_value(fd).to_i64().unwrap()); + let written = match file.write(&objbytes::get_value(&data)) { + Ok(written) => written, + Err(s) => return Err(vm.new_os_error(s.to_string())), + }; + + // Avoid closing the fd + raw_file_number(file); + Ok(vm.ctx.new_int(written)) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -146,6 +168,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "close" => ctx.new_rustfunc(os_close), "error" => ctx.new_rustfunc(os_error), "read" => ctx.new_rustfunc(os_read), + "write" => ctx.new_rustfunc(os_write), "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From 1f19951ece36d1e229f0054abacdcfa4a2cf0907 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 5 Apr 2019 19:43:46 +0300 Subject: [PATCH 165/884] Add os.{remove,unlink} --- vm/src/stdlib/os.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index b7e4280e50..cd4c0709bb 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,3 +1,4 @@ +use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::io::{ErrorKind, Read, Write}; @@ -154,6 +155,18 @@ fn os_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_int(written)) } +fn os_remove(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(path, Some(vm.ctx.str_type()))]); + let fname = objstr::get_value(&path); + + match fs::remove_file(fname) { + Ok(_) => (), + Err(s) => return Err(vm.new_os_error(s.to_string())), + } + + Ok(vm.get_none()) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -169,6 +182,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "error" => ctx.new_rustfunc(os_error), "read" => ctx.new_rustfunc(os_read), "write" => ctx.new_rustfunc(os_write), + "remove" => ctx.new_rustfunc(os_remove), + "unlink" => ctx.new_rustfunc(os_remove), "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From cdd39bc50e7054b6ffbb9e12245b2e5140e59d83 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 5 Apr 2019 19:44:09 +0300 Subject: [PATCH 166/884] Add tests for os.{read, write} --- tests/snippets/stdlib_os.py | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index d700504951..29c8005dc8 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -5,13 +5,36 @@ fd = os.open('README.md', 0) assert fd > 0 -assert len(os.read(fd, 10)) == 10 -assert len(os.read(fd, 5)) == 5 - assert_raises(OSError, lambda: os.read(fd + 1, 10)) os.close(fd) assert_raises(OSError, lambda: os.read(fd, 10)) +FNAME = "test_file_that_no_one_will_have_on_disk" +CONTENT = b"testing" +CONTENT2 = b"rustpython" +CONTENT3 = b"BOYA" + +class TestWithFile(): + def __enter__(self): + open(FNAME, "wb") + return FNAME + + def __exit__(self, exc_type, exc_val, exc_tb): + os.remove(FNAME) + + +with TestWithFile() as fname: + fd = os.open(fname, 1) + assert os.write(fd, CONTENT2) == len(CONTENT2) + assert os.write(fd, CONTENT3) == len(CONTENT3) + os.close(fd) + + fd = os.open(fname, 0) + assert os.read(fd, len(CONTENT2)) == CONTENT2 + assert os.read(fd, len(CONTENT3)) == CONTENT3 + os.close(fd) + + assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', 0)) From ed8c1e1a955725093c1ab04ea6b4e2c9c52e94ac Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 29 Mar 2019 16:39:52 +0300 Subject: [PATCH 167/884] Implement set and frozenset with PySetInner --- tests/snippets/set.py | 7 + vm/src/obj/objset.rs | 1100 +++++++++++++++++++++++------------------ 2 files changed, 617 insertions(+), 490 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index 9e49dff89e..0117163350 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -205,6 +205,13 @@ def __hash__(self): assert_raises(TypeError, lambda: frozenset([[]])) +a = frozenset([1,2,3]) +b = set() +for e in a: + assert e == 1 or e == 2 or e == 3 + b.add(e) +assert a == b + # set and frozen set assert frozenset([1,2,3]).union(set([4,5])) == frozenset([1,2,3,4,5]) assert set([1,2,3]).union(frozenset([4,5])) == set([1,2,3,4,5]) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 34d487fbb9..eaa0cd09ca 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -7,7 +7,7 @@ use std::collections::{hash_map::DefaultHasher, HashMap}; use std::fmt; use std::hash::{Hash, Hasher}; -use crate::function::{OptionalArg, PyFuncArgs}; +use crate::function::OptionalArg; use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::{ReprGuard, VirtualMachine}; @@ -20,13 +20,13 @@ use super::objtype::PyClassRef; #[derive(Default)] pub struct PySet { - elements: RefCell>, + inner: RefCell, } pub type PySetRef = PyRef; #[derive(Default)] pub struct PyFrozenSet { - elements: HashMap, + inner: PySetInner, } pub type PyFrozenSetRef = PyRef; @@ -56,536 +56,655 @@ impl PyValue for PyFrozenSet { } } -pub fn get_elements(obj: &PyObjectRef) -> HashMap { - if let Some(set) = obj.payload::() { - return set.elements.borrow().clone(); - } else if let Some(frozenset) = obj.payload::() { - return frozenset.elements.clone(); - } - panic!("Not frozenset or set"); +#[derive(Default, Clone)] +struct PySetInner { + elements: HashMap, } -fn validate_set_or_frozenset(vm: &VirtualMachine, cls: PyClassRef) -> PyResult<()> { - if !(objtype::issubclass(&cls, &vm.ctx.set_type()) - || objtype::issubclass(&cls, &vm.ctx.frozenset_type())) - { - return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", cls))); +impl PySetInner { + fn len(&self) -> usize { + self.elements.len() } - Ok(()) -} - -fn create_set( - vm: &VirtualMachine, - elements: HashMap, - cls: PyClassRef, -) -> PyResult { - if objtype::issubclass(&cls, &vm.ctx.set_type()) { - Ok(PyObject::new( - PySet { - elements: RefCell::new(elements), - }, - PySet::class(vm), - None, - )) - } else if objtype::issubclass(&cls, &vm.ctx.frozenset_type()) { - Ok(PyObject::new( - PyFrozenSet { elements: elements }, - PyFrozenSet::class(vm), - None, - )) - } else { - Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", cls))) + fn copy(&self) -> PySetInner { + PySetInner { + elements: self.elements.clone(), + } } -} - -fn perform_action_with_hash( - vm: &VirtualMachine, - elements: &mut HashMap, - item: &PyObjectRef, - f: &Fn(&VirtualMachine, &mut HashMap, u64, &PyObjectRef) -> PyResult, -) -> PyResult { - let hash: PyObjectRef = vm.call_method(item, "__hash__", vec![])?; - let hash_value = objint::get_value(&hash); - let mut hasher = DefaultHasher::new(); - hash_value.hash(&mut hasher); - let key = hasher.finish(); - f(vm, elements, key, item) -} + fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + for element in self.elements.iter() { + let value = vm._eq(needle.clone(), element.1.clone())?; + if objbool::get_value(&value) { + return Ok(vm.new_bool(true)); + } + } + Ok(vm.new_bool(false)) + } -fn insert_into_set( - vm: &VirtualMachine, - elements: &mut HashMap, - item: &PyObjectRef, -) -> PyResult { - fn insert( + fn _compare_inner( + &self, + other: &PySetInner, + size_func: &Fn(usize, usize) -> bool, + swap: bool, vm: &VirtualMachine, - elements: &mut HashMap, - key: u64, - value: &PyObjectRef, ) -> PyResult { - elements.insert(key, value.clone()); - Ok(vm.get_none()) + let get_zelf = |swap: bool| -> &PySetInner { + if swap { + other + } else { + self + } + }; + let get_other = |swap: bool| -> &PySetInner { + if swap { + self + } else { + other + } + }; + + if size_func(get_zelf(swap).len(), get_other(swap).len()) { + return Ok(vm.new_bool(false)); + } + for element in get_other(swap).elements.iter() { + let value = get_zelf(swap).contains(element.1.clone(), vm)?; + if !objbool::get_value(&value) { + return Ok(vm.new_bool(false)); + } + } + Ok(vm.new_bool(true)) } - perform_action_with_hash(vm, elements, item, &insert) -} -fn set_add(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("set.add called with: {:?}", args); - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.set_type())), (item, None)] - ); - match zelf.payload::() { - Some(set) => insert_into_set(vm, &mut set.elements.borrow_mut(), item), - _ => Err(vm.new_type_error("set.add is called with no item".to_string())), + fn eq(&self, other: &PySetInner, vm: &VirtualMachine) -> PyResult { + self._compare_inner( + other, + &|zelf: usize, other: usize| -> bool { zelf != other }, + false, + vm, + ) } -} -fn set_remove(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("set.remove called with: {:?}", args); - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.set_type())), (item, None)] - ); - match s.payload::() { - Some(set) => { - fn remove( - vm: &VirtualMachine, - elements: &mut HashMap, - key: u64, - value: &PyObjectRef, - ) -> PyResult { - match elements.remove(&key) { - None => { - let item_str = format!("{:?}", value); - Err(vm.new_key_error(item_str)) - } - Some(_) => Ok(vm.get_none()), - } - } - perform_action_with_hash(vm, &mut set.elements.borrow_mut(), item, &remove) - } - _ => Err(vm.new_type_error("set.remove is called with no item".to_string())), + fn ge(&self, other: &PySetInner, vm: &VirtualMachine) -> PyResult { + self._compare_inner( + other, + &|zelf: usize, other: usize| -> bool { zelf < other }, + false, + vm, + ) } -} -fn set_discard(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("set.discard called with: {:?}", args); - arg_check!( - vm, - args, - required = [(s, Some(vm.ctx.set_type())), (item, None)] - ); - match s.payload::() { - Some(set) => { - fn discard( - vm: &VirtualMachine, - elements: &mut HashMap, - key: u64, - _value: &PyObjectRef, - ) -> PyResult { - elements.remove(&key); - Ok(vm.get_none()) - } - perform_action_with_hash(vm, &mut set.elements.borrow_mut(), item, &discard) - } - None => Err(vm.new_type_error("set.discard is called with no item".to_string())), + fn gt(&self, other: &PySetInner, vm: &VirtualMachine) -> PyResult { + self._compare_inner( + other, + &|zelf: usize, other: usize| -> bool { zelf <= other }, + false, + vm, + ) } -} -fn set_clear(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("set.clear called"); - arg_check!(vm, args, required = [(s, Some(vm.ctx.set_type()))]); - match s.payload::() { - Some(set) => { - set.elements.borrow_mut().clear(); - Ok(vm.get_none()) - } - None => Err(vm.new_type_error("".to_string())), + fn le(&self, other: &PySetInner, vm: &VirtualMachine) -> PyResult { + self._compare_inner( + other, + &|zelf: usize, other: usize| -> bool { zelf < other }, + true, + vm, + ) } -} -/* Create a new object of sub-type of set */ -fn set_new(cls: PyClassRef, iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { - validate_set_or_frozenset(vm, cls.clone())?; + fn lt(&self, other: &PySetInner, vm: &VirtualMachine) -> PyResult { + self._compare_inner( + other, + &|zelf: usize, other: usize| -> bool { zelf <= other }, + true, + vm, + ) + } - let elements: HashMap = match iterable { - OptionalArg::Missing => HashMap::new(), - OptionalArg::Present(iterable) => { - let mut elements = HashMap::new(); - let iterator = objiter::get_iter(vm, &iterable)?; - while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { - insert_into_set(vm, &mut elements, &v)?; + fn union(&self, other: &PySetInner, _vm: &VirtualMachine) -> PyResult { + let mut elements = self.elements.clone(); + elements.extend(other.elements.clone()); + + Ok(PySetInner { elements }) + } + + fn intersection(&self, other: &PySetInner, vm: &VirtualMachine) -> PyResult { + self._combine_inner(other, vm, SetCombineOperation::Intersection) + } + + fn difference(&self, other: &PySetInner, vm: &VirtualMachine) -> PyResult { + self._combine_inner(other, vm, SetCombineOperation::Difference) + } + + fn _combine_inner( + &self, + other: &PySetInner, + vm: &VirtualMachine, + op: SetCombineOperation, + ) -> PyResult { + let mut elements = HashMap::new(); + + for element in self.elements.iter() { + let value = other.contains(element.1.clone(), vm)?; + let should_add = match op { + SetCombineOperation::Intersection => objbool::get_value(&value), + SetCombineOperation::Difference => !objbool::get_value(&value), + }; + if should_add { + elements.insert(element.0.clone(), element.1.clone()); } - elements } - }; - create_set(vm, elements, cls.clone()) -} + Ok(PySetInner { elements }) + } -fn set_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - trace!("set.len called with: {:?}", args); - arg_check!(vm, args, required = [(s, None)]); - validate_set_or_frozenset(vm, s.class())?; - let elements = get_elements(s); - Ok(vm.context().new_int(elements.len())) -} + fn symmetric_difference( + &self, + other: &PySetInner, + vm: &VirtualMachine, + ) -> PyResult { + let mut elements = HashMap::new(); -fn set_copy(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - trace!("set.copy called with: {:?}", obj); - validate_set_or_frozenset(vm, obj.class())?; - let elements = get_elements(&obj).clone(); - create_set(vm, elements, obj.class()) -} + for element in self.elements.iter() { + let value = other.contains(element.1.clone(), vm)?; + if !objbool::get_value(&value) { + elements.insert(element.0.clone(), element.1.clone()); + } + } + + for element in other.elements.iter() { + let value = self.contains(element.1.clone(), vm)?; + if !objbool::get_value(&value) { + elements.insert(element.0.clone(), element.1.clone()); + } + } + + Ok(PySetInner { elements }) + } -fn set_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(o, Some(vm.ctx.set_type()))]); + fn iter(&self, vm: &VirtualMachine) -> PyListIterator { + let items = self.elements.values().cloned().collect(); + let set_list = vm.ctx.new_list(items); + PyListIterator { + position: Cell::new(0), + list: set_list.downcast().unwrap(), + } + } - let elements = get_elements(o); - let s = if elements.is_empty() { - "set()".to_string() - } else if let Some(_guard) = ReprGuard::enter(o) { + fn repr(&self, vm: &VirtualMachine) -> PyResult { let mut str_parts = vec![]; - for elem in elements.values() { + for elem in self.elements.values() { let part = vm.to_repr(elem)?; str_parts.push(part.value.clone()); } - format!("{{{}}}", str_parts.join(", ")) - } else { - "set(...)".to_string() - }; - Ok(vm.new_str(s)) -} + Ok(format!("{{{}}}", str_parts.join(", "))) + } + + fn add(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + insert_into_set(vm, &mut self.elements, &item) + } -pub fn set_contains(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(set, None), (needle, None)]); - validate_set_or_frozenset(vm, set.class())?; - for element in get_elements(set).iter() { - match vm._eq(needle.clone(), element.1.clone()) { - Ok(value) => { - if objbool::get_value(&value) { - return Ok(vm.new_bool(true)); + fn remove(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn remove( + vm: &VirtualMachine, + elements: &mut HashMap, + key: u64, + value: &PyObjectRef, + ) -> PyResult { + match elements.remove(&key) { + None => { + let item_str = format!("{:?}", value); + Err(vm.new_key_error(item_str)) } + Some(_) => Ok(vm.get_none()), } - Err(_) => return Err(vm.new_type_error("".to_string())), } + perform_action_with_hash(vm, &mut self.elements, &item, &remove) } - Ok(vm.new_bool(false)) -} + fn discard(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn discard( + vm: &VirtualMachine, + elements: &mut HashMap, + key: u64, + _value: &PyObjectRef, + ) -> PyResult { + elements.remove(&key); + Ok(vm.get_none()) + } + perform_action_with_hash(vm, &mut self.elements, &item, &discard) + } -fn set_eq(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_compare_inner( - vm, - args, - &|zelf: usize, other: usize| -> bool { zelf != other }, - false, - ) -} + fn clear(&mut self, vm: &VirtualMachine) -> PyResult { + self.elements.clear(); + Ok(vm.get_none()) + } -fn set_ge(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_compare_inner( - vm, - args, - &|zelf: usize, other: usize| -> bool { zelf < other }, - false, - ) -} + fn pop(&mut self, vm: &VirtualMachine) -> PyResult { + let elements = &mut self.elements; + match elements.clone().keys().next() { + Some(key) => Ok(elements.remove(key).unwrap()), + None => Err(vm.new_key_error("pop from an empty set".to_string())), + } + } -fn set_gt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_compare_inner( - vm, - args, - &|zelf: usize, other: usize| -> bool { zelf <= other }, - false, - ) -} + fn update(&mut self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let iterator = objiter::get_iter(vm, &iterable)?; + while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { + insert_into_set(vm, &mut self.elements, &v)?; + } + Ok(vm.get_none()) + } -fn set_le(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_compare_inner( - vm, - args, - &|zelf: usize, other: usize| -> bool { zelf < other }, - true, - ) -} + fn combine_update_inner( + &mut self, + iterable: &PyObjectRef, + vm: &VirtualMachine, + op: SetCombineOperation, + ) -> PyResult { + let elements = &mut self.elements; + for element in elements.clone().iter() { + let value = vm.call_method(iterable, "__contains__", vec![element.1.clone()])?; + let should_remove = match op { + SetCombineOperation::Intersection => !objbool::get_value(&value), + SetCombineOperation::Difference => objbool::get_value(&value), + }; + if should_remove { + elements.remove(&element.0.clone()); + } + } + Ok(vm.get_none()) + } + + fn ixor(&mut self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let elements_original = self.elements.clone(); + let iterator = objiter::get_iter(vm, &iterable)?; + while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { + insert_into_set(vm, &mut self.elements, &v)?; + } + for element in elements_original.iter() { + let value = vm.call_method(&iterable, "__contains__", vec![element.1.clone()])?; + if objbool::get_value(&value) { + self.elements.remove(&element.0.clone()); + } + } -fn set_lt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_compare_inner( - vm, - args, - &|zelf: usize, other: usize| -> bool { zelf <= other }, - true, - ) + Ok(vm.get_none()) + } } -fn set_compare_inner( - vm: &VirtualMachine, - args: PyFuncArgs, - size_func: &Fn(usize, usize) -> bool, - swap: bool, -) -> PyResult { - arg_check!(vm, args, required = [(zelf, None), (other, None)]); +impl PySetRef { + fn len(self, _vm: &VirtualMachine) -> usize { + self.inner.borrow().len() + } + fn copy(self, _vm: &VirtualMachine) -> PySet { + PySet { + inner: RefCell::new(self.inner.borrow().copy()), + } + } + fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().contains(needle, vm) + } - validate_set_or_frozenset(vm, zelf.class())?; - validate_set_or_frozenset(vm, other.class())?; + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().eq(&get_inner(vm, &other)?, vm) + } - let get_zelf = |swap: bool| -> &PyObjectRef { - if swap { - other - } else { - zelf - } - }; - let get_other = |swap: bool| -> &PyObjectRef { - if swap { - zelf + fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().ge(&get_inner(vm, &other)?, vm) + } + + fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().gt(&get_inner(vm, &other)?, vm) + } + + fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().le(&get_inner(vm, &other)?, vm) + } + + fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().lt(&get_inner(vm, &other)?, vm) + } + + fn union(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(PyObject::new( + PySet { + inner: RefCell::new(self.inner.borrow().union(&get_inner(vm, &other)?, vm)?), + }, + PySet::class(vm), + None, + )) + } + + fn intersection(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(PyObject::new( + PySet { + inner: RefCell::new( + self.inner + .borrow() + .intersection(&get_inner(vm, &other)?, vm)?, + ), + }, + PySet::class(vm), + None, + )) + } + + fn difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(PyObject::new( + PySet { + inner: RefCell::new( + self.inner + .borrow() + .difference(&get_inner(vm, &other)?, vm)?, + ), + }, + PySet::class(vm), + None, + )) + } + + fn symmetric_difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(PyObject::new( + PySet { + inner: RefCell::new( + self.inner + .borrow() + .symmetric_difference(&get_inner(vm, &other)?, vm)?, + ), + }, + PySet::class(vm), + None, + )) + } + + fn iter(self, vm: &VirtualMachine) -> PyListIterator { + self.inner.borrow().iter(vm) + } + + fn repr(self, vm: &VirtualMachine) -> PyResult { + let inner = self.inner.borrow(); + let s = if inner.len() == 0 { + format!("set()") + } else if let Some(_guard) = ReprGuard::enter(self.as_object()) { + inner.repr(vm)? } else { - other - } - }; + format!("set(...)") + }; + Ok(vm.new_str(s)) + } - let zelf_elements = get_elements(get_zelf(swap)); - let other_elements = get_elements(get_other(swap)); - if size_func(zelf_elements.len(), other_elements.len()) { - return Ok(vm.new_bool(false)); + fn add(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().add(&item, vm) } - for element in other_elements.iter() { - match vm.call_method(get_zelf(swap), "__contains__", vec![element.1.clone()]) { - Ok(value) => { - if !objbool::get_value(&value) { - return Ok(vm.new_bool(false)); - } - } - Err(_) => return Err(vm.new_type_error("".to_string())), - } + + fn remove(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().remove(&item, vm) } - Ok(vm.new_bool(true)) -} -fn set_union(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - validate_set_or_frozenset(vm, zelf.class())?; - validate_set_or_frozenset(vm, other.class())?; + fn discard(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().discard(&item, vm) + } - let mut elements = get_elements(&zelf).clone(); - elements.extend(get_elements(&other).clone()); + fn clear(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().clear(vm) + } - create_set(vm, elements, zelf.class()) -} + fn pop(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().pop(vm) + } -fn set_intersection(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - set_combine_inner(zelf, other, vm, SetCombineOperation::Intersection) -} + fn ior(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().update(iterable, vm)?; + Ok(self.as_object().clone()) + } -fn set_difference(zelf: PyObjectRef, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - set_combine_inner(zelf, other, vm, SetCombineOperation::Difference) -} + fn update(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().update(iterable, vm)?; + Ok(vm.get_none()) + } -fn set_symmetric_difference( - zelf: PyObjectRef, - other: PyObjectRef, - vm: &VirtualMachine, -) -> PyResult { - validate_set_or_frozenset(vm, zelf.class())?; - validate_set_or_frozenset(vm, other.class())?; - let mut elements = HashMap::new(); - - for element in get_elements(&zelf).iter() { - let value = vm.call_method(&other, "__contains__", vec![element.1.clone()])?; - if !objbool::get_value(&value) { - elements.insert(element.0.clone(), element.1.clone()); - } + fn intersection_update(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().combine_update_inner( + &iterable, + vm, + SetCombineOperation::Intersection, + )?; + Ok(vm.get_none()) } - for element in get_elements(&other).iter() { - let value = vm.call_method(&zelf, "__contains__", vec![element.1.clone()])?; - if !objbool::get_value(&value) { - elements.insert(element.0.clone(), element.1.clone()); - } + fn iand(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().combine_update_inner( + &iterable, + vm, + SetCombineOperation::Intersection, + )?; + Ok(self.as_object().clone()) } - create_set(vm, elements, zelf.class()) -} + fn difference_update(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().combine_update_inner( + &iterable, + vm, + SetCombineOperation::Difference, + )?; + Ok(vm.get_none()) + } -enum SetCombineOperation { - Intersection, - Difference, + fn isub(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().combine_update_inner( + &iterable, + vm, + SetCombineOperation::Difference, + )?; + Ok(self.as_object().clone()) + } + + fn symmetric_difference_update(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().ixor(iterable, vm)?; + Ok(vm.get_none()) + } + + fn ixor(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().ixor(iterable, vm)?; + Ok(self.as_object().clone()) + } } -fn set_combine_inner( - zelf: PyObjectRef, - other: PyObjectRef, - vm: &VirtualMachine, - op: SetCombineOperation, -) -> PyResult { - validate_set_or_frozenset(vm, zelf.class())?; - validate_set_or_frozenset(vm, other.class())?; - let mut elements = HashMap::new(); - - for element in get_elements(&zelf).iter() { - let value = vm.call_method(&other, "__contains__", vec![element.1.clone()])?; - let should_add = match op { - SetCombineOperation::Intersection => objbool::get_value(&value), - SetCombineOperation::Difference => !objbool::get_value(&value), - }; - if should_add { - elements.insert(element.0.clone(), element.1.clone()); +impl PyFrozenSetRef { + fn len(self, _vm: &VirtualMachine) -> usize { + self.inner.len() + } + fn copy(self, _vm: &VirtualMachine) -> PyFrozenSet { + PyFrozenSet { + inner: self.inner.copy(), } } + fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.contains(needle, vm) + } - create_set(vm, elements, zelf.class()) -} + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.eq(&get_inner(vm, &other)?, vm) + } -fn set_pop(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(s, Some(vm.ctx.set_type()))]); + fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.ge(&get_inner(vm, &other)?, vm) + } - match s.payload::() { - Some(set) => { - let mut elements = set.elements.borrow_mut(); - match elements.clone().keys().next() { - Some(key) => Ok(elements.remove(key).unwrap()), - None => Err(vm.new_key_error("pop from an empty set".to_string())), - } - } - _ => Err(vm.new_type_error("".to_string())), + fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.gt(&get_inner(vm, &other)?, vm) } -} -fn set_update(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_ior(vm, args)?; - Ok(vm.get_none()) -} + fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.le(&get_inner(vm, &other)?, vm) + } -fn set_ior(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.set_type())), (iterable, None)] - ); + fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.lt(&get_inner(vm, &other)?, vm) + } - match zelf.payload::() { - Some(set) => { - let iterator = objiter::get_iter(vm, iterable)?; - while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { - insert_into_set(vm, &mut set.elements.borrow_mut(), &v)?; - } - } - _ => return Err(vm.new_type_error("set.update is called with no other".to_string())), + fn union(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(PyObject::new( + PyFrozenSet { + inner: self.inner.union(&get_inner(vm, &other)?, vm)?, + }, + PyFrozenSet::class(vm), + None, + )) } - Ok(zelf.clone()) -} -fn set_intersection_update(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_combine_update_inner(vm, args, SetCombineOperation::Intersection)?; - Ok(vm.get_none()) -} + fn intersection(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(PyObject::new( + PyFrozenSet { + inner: self.inner.intersection(&get_inner(vm, &other)?, vm)?, + }, + PyFrozenSet::class(vm), + None, + )) + } -fn set_iand(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_combine_update_inner(vm, args, SetCombineOperation::Intersection) + fn difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(PyObject::new( + PyFrozenSet { + inner: self.inner.difference(&get_inner(vm, &other)?, vm)?, + }, + PyFrozenSet::class(vm), + None, + )) + } + + fn symmetric_difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(PyObject::new( + PyFrozenSet { + inner: self + .inner + .symmetric_difference(&get_inner(vm, &other)?, vm)?, + }, + PyFrozenSet::class(vm), + None, + )) + } + + fn iter(self, vm: &VirtualMachine) -> PyListIterator { + self.inner.iter(vm) + } + + fn repr(self, vm: &VirtualMachine) -> PyResult { + let inner = &self.inner; + let s = if inner.len() == 0 { + format!("frozenset()") + } else if let Some(_guard) = ReprGuard::enter(self.as_object()) { + format!("frozenset({})", inner.repr(vm)?) + } else { + format!("frozenset(...)") + }; + Ok(vm.new_str(s)) + } } -fn set_difference_update(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_combine_update_inner(vm, args, SetCombineOperation::Difference)?; - Ok(vm.get_none()) +fn get_inner(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { + if let Some(set) = obj.payload::() { + Ok(set.inner.borrow().clone()) + } else if let Some(frozenset) = obj.payload::() { + Ok(frozenset.inner.clone()) + } else { + Err(vm.new_type_error(format!( + "{} is not a subtype of set or frozenset", + obj.class() + ))) + } } -fn set_isub(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_combine_update_inner(vm, args, SetCombineOperation::Difference) +fn validate_set_or_frozenset(vm: &VirtualMachine, cls: PyClassRef) -> PyResult<()> { + if !(objtype::issubclass(&cls, &vm.ctx.set_type()) + || objtype::issubclass(&cls, &vm.ctx.frozenset_type())) + { + return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", cls))); + } + Ok(()) } -fn set_combine_update_inner( +fn create_set( vm: &VirtualMachine, - args: PyFuncArgs, - op: SetCombineOperation, + elements: HashMap, + cls: PyClassRef, ) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.set_type())), (iterable, None)] - ); - - match zelf.payload::() { - Some(set) => { - let mut elements = set.elements.borrow_mut(); - for element in elements.clone().iter() { - let value = vm.call_method(iterable, "__contains__", vec![element.1.clone()])?; - let should_remove = match op { - SetCombineOperation::Intersection => !objbool::get_value(&value), - SetCombineOperation::Difference => objbool::get_value(&value), - }; - if should_remove { - elements.remove(&element.0.clone()); - } - } - } - _ => return Err(vm.new_type_error("".to_string())), + if objtype::issubclass(&cls, &vm.ctx.set_type()) { + Ok(PyObject::new( + PySet { + inner: RefCell::new(PySetInner { elements: elements }), + }, + PySet::class(vm), + None, + )) + } else if objtype::issubclass(&cls, &vm.ctx.frozenset_type()) { + Ok(PyObject::new( + PyFrozenSet { + inner: PySetInner { elements: elements }, + }, + PyFrozenSet::class(vm), + None, + )) + } else { + Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", cls))) } - Ok(zelf.clone()) } -fn set_symmetric_difference_update(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - set_ixor(vm, args)?; - Ok(vm.get_none()) -} - -fn set_ixor(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, Some(vm.ctx.set_type())), (iterable, None)] - ); - - match zelf.payload::() { - Some(set) => { - let elements_original = set.elements.borrow().clone(); - let iterator = objiter::get_iter(vm, iterable)?; - while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { - insert_into_set(vm, &mut set.elements.borrow_mut(), &v)?; - } - for element in elements_original.iter() { - let value = vm.call_method(iterable, "__contains__", vec![element.1.clone()])?; - if objbool::get_value(&value) { - set.elements.borrow_mut().remove(&element.0.clone()); - } - } - } - _ => return Err(vm.new_type_error("".to_string())), - } +fn perform_action_with_hash( + vm: &VirtualMachine, + elements: &mut HashMap, + item: &PyObjectRef, + f: &Fn(&VirtualMachine, &mut HashMap, u64, &PyObjectRef) -> PyResult, +) -> PyResult { + let hash: PyObjectRef = vm.call_method(item, "__hash__", vec![])?; - Ok(zelf.clone()) + let hash_value = objint::get_value(&hash); + let mut hasher = DefaultHasher::new(); + hash_value.hash(&mut hasher); + let key = hasher.finish(); + f(vm, elements, key, item) } -fn set_iter(zelf: PySetRef, vm: &VirtualMachine) -> PyListIterator { - // TODO: separate type - let items = zelf.elements.borrow().values().cloned().collect(); - let set_list = vm.ctx.new_list(items); - PyListIterator { - position: Cell::new(0), - list: set_list.downcast().unwrap(), +fn insert_into_set( + vm: &VirtualMachine, + elements: &mut HashMap, + item: &PyObjectRef, +) -> PyResult { + fn insert( + vm: &VirtualMachine, + elements: &mut HashMap, + key: u64, + value: &PyObjectRef, + ) -> PyResult { + elements.insert(key, value.clone()); + Ok(vm.get_none()) } + perform_action_with_hash(vm, elements, item, &insert) } -fn frozenset_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(o, Some(vm.ctx.frozenset_type()))]); +/* Create a new object of sub-type of set */ +fn set_new(cls: PyClassRef, iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { + validate_set_or_frozenset(vm, cls.clone())?; - let elements = get_elements(o); - let s = if elements.is_empty() { - "frozenset()".to_string() - } else { - let mut str_parts = vec![]; - for elem in elements.values() { - let part = vm.to_repr(elem)?; - str_parts.push(part.value.clone()); + let elements: HashMap = match iterable { + OptionalArg::Missing => HashMap::new(), + OptionalArg::Present(iterable) => { + let mut elements = HashMap::new(); + let iterator = objiter::get_iter(vm, &iterable)?; + while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { + insert_into_set(vm, &mut elements, &v)?; + } + elements } - - format!("frozenset({{{}}})", str_parts.join(", ")) }; - Ok(vm.new_str(s)) + + create_set(vm, elements, cls.clone()) +} + +enum SetCombineOperation { + Intersection, + Difference, } fn set_hash(_zelf: PySetRef, vm: &VirtualMachine) -> PyResult { @@ -600,42 +719,42 @@ pub fn init(context: &PyContext) { Build an unordered collection of unique elements."; extend_class!(context, set_type, { - "__contains__" => context.new_rustfunc(set_contains), - "__len__" => context.new_rustfunc(set_len), + "__contains__" => context.new_rustfunc(PySetRef::contains), + "__len__" => context.new_rustfunc(PySetRef::len), "__new__" => context.new_rustfunc(set_new), - "__repr__" => context.new_rustfunc(set_repr), "__hash__" => context.new_rustfunc(set_hash), - "__eq__" => context.new_rustfunc(set_eq), - "__ge__" => context.new_rustfunc(set_ge), - "__gt__" => context.new_rustfunc(set_gt), - "__le__" => context.new_rustfunc(set_le), - "__lt__" => context.new_rustfunc(set_lt), - "issubset" => context.new_rustfunc(set_le), - "issuperset" => context.new_rustfunc(set_ge), - "union" => context.new_rustfunc(set_union), - "__or__" => context.new_rustfunc(set_union), - "intersection" => context.new_rustfunc(set_intersection), - "__and__" => context.new_rustfunc(set_intersection), - "difference" => context.new_rustfunc(set_difference), - "__sub__" => context.new_rustfunc(set_difference), - "symmetric_difference" => context.new_rustfunc(set_symmetric_difference), - "__xor__" => context.new_rustfunc(set_symmetric_difference), + "__repr__" => context.new_rustfunc(PySetRef::repr), + "__eq__" => context.new_rustfunc(PySetRef::eq), + "__ge__" => context.new_rustfunc(PySetRef::ge), + "__gt__" => context.new_rustfunc(PySetRef::gt), + "__le__" => context.new_rustfunc(PySetRef::le), + "__lt__" => context.new_rustfunc(PySetRef::lt), + "issubset" => context.new_rustfunc(PySetRef::le), + "issuperset" => context.new_rustfunc(PySetRef::ge), + "union" => context.new_rustfunc(PySetRef::union), + "__or__" => context.new_rustfunc(PySetRef::union), + "intersection" => context.new_rustfunc(PySetRef::intersection), + "__and__" => context.new_rustfunc(PySetRef::intersection), + "difference" => context.new_rustfunc(PySetRef::difference), + "__sub__" => context.new_rustfunc(PySetRef::difference), + "symmetric_difference" => context.new_rustfunc(PySetRef::symmetric_difference), + "__xor__" => context.new_rustfunc(PySetRef::symmetric_difference), "__doc__" => context.new_str(set_doc.to_string()), - "add" => context.new_rustfunc(set_add), - "remove" => context.new_rustfunc(set_remove), - "discard" => context.new_rustfunc(set_discard), - "clear" => context.new_rustfunc(set_clear), - "copy" => context.new_rustfunc(set_copy), - "pop" => context.new_rustfunc(set_pop), - "update" => context.new_rustfunc(set_update), - "__ior__" => context.new_rustfunc(set_ior), - "intersection_update" => context.new_rustfunc(set_intersection_update), - "__iand__" => context.new_rustfunc(set_iand), - "difference_update" => context.new_rustfunc(set_difference_update), - "__isub__" => context.new_rustfunc(set_isub), - "symmetric_difference_update" => context.new_rustfunc(set_symmetric_difference_update), - "__ixor__" => context.new_rustfunc(set_ixor), - "__iter__" => context.new_rustfunc(set_iter) + "add" => context.new_rustfunc(PySetRef::add), + "remove" => context.new_rustfunc(PySetRef::remove), + "discard" => context.new_rustfunc(PySetRef::discard), + "clear" => context.new_rustfunc(PySetRef::clear), + "copy" => context.new_rustfunc(PySetRef::copy), + "pop" => context.new_rustfunc(PySetRef::pop), + "update" => context.new_rustfunc(PySetRef::update), + "__ior__" => context.new_rustfunc(PySetRef::ior), + "intersection_update" => context.new_rustfunc(PySetRef::intersection_update), + "__iand__" => context.new_rustfunc(PySetRef::iand), + "difference_update" => context.new_rustfunc(PySetRef::difference_update), + "__isub__" => context.new_rustfunc(PySetRef::isub), + "symmetric_difference_update" => context.new_rustfunc(PySetRef::symmetric_difference_update), + "__ixor__" => context.new_rustfunc(PySetRef::ixor), + "__iter__" => context.new_rustfunc(PySetRef::iter) }); let frozenset_type = &context.frozenset_type; @@ -646,25 +765,26 @@ pub fn init(context: &PyContext) { extend_class!(context, frozenset_type, { "__new__" => context.new_rustfunc(set_new), - "__eq__" => context.new_rustfunc(set_eq), - "__ge__" => context.new_rustfunc(set_ge), - "__gt__" => context.new_rustfunc(set_gt), - "__le__" => context.new_rustfunc(set_le), - "__lt__" => context.new_rustfunc(set_lt), - "issubset" => context.new_rustfunc(set_le), - "issuperset" => context.new_rustfunc(set_ge), - "union" => context.new_rustfunc(set_union), - "__or__" => context.new_rustfunc(set_union), - "intersection" => context.new_rustfunc(set_intersection), - "__and__" => context.new_rustfunc(set_intersection), - "difference" => context.new_rustfunc(set_difference), - "__sub__" => context.new_rustfunc(set_difference), - "symmetric_difference" => context.new_rustfunc(set_symmetric_difference), - "__xor__" => context.new_rustfunc(set_symmetric_difference), - "__contains__" => context.new_rustfunc(set_contains), - "__len__" => context.new_rustfunc(set_len), + "__eq__" => context.new_rustfunc(PyFrozenSetRef::eq), + "__ge__" => context.new_rustfunc(PyFrozenSetRef::ge), + "__gt__" => context.new_rustfunc(PyFrozenSetRef::gt), + "__le__" => context.new_rustfunc(PyFrozenSetRef::le), + "__lt__" => context.new_rustfunc(PyFrozenSetRef::lt), + "issubset" => context.new_rustfunc(PyFrozenSetRef::le), + "issuperset" => context.new_rustfunc(PyFrozenSetRef::ge), + "union" => context.new_rustfunc(PyFrozenSetRef::union), + "__or__" => context.new_rustfunc(PyFrozenSetRef::union), + "intersection" => context.new_rustfunc(PyFrozenSetRef::intersection), + "__and__" => context.new_rustfunc(PyFrozenSetRef::intersection), + "difference" => context.new_rustfunc(PyFrozenSetRef::difference), + "__sub__" => context.new_rustfunc(PyFrozenSetRef::difference), + "symmetric_difference" => context.new_rustfunc(PyFrozenSetRef::symmetric_difference), + "__xor__" => context.new_rustfunc(PyFrozenSetRef::symmetric_difference), + "__contains__" => context.new_rustfunc(PyFrozenSetRef::contains), + "__len__" => context.new_rustfunc(PyFrozenSetRef::len), "__doc__" => context.new_str(frozenset_doc.to_string()), - "__repr__" => context.new_rustfunc(frozenset_repr), - "copy" => context.new_rustfunc(set_copy) + "__repr__" => context.new_rustfunc(PyFrozenSetRef::repr), + "copy" => context.new_rustfunc(PyFrozenSetRef::copy), + "__iter__" => context.new_rustfunc(PyFrozenSetRef::iter) }); } From a685216f29ecc75e2bc3f9637a42e5fe878c5b37 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 4 Apr 2019 20:47:29 +0300 Subject: [PATCH 168/884] Use new arg style for set and frozenset new --- vm/src/obj/objset.rs | 103 +++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 58 deletions(-) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index eaa0cd09ca..fd31044aa4 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -15,7 +15,6 @@ use super::objbool; use super::objint; use super::objiter; use super::objlist::PyListIterator; -use super::objtype; use super::objtype::PyClassRef; #[derive(Default)] @@ -62,6 +61,22 @@ struct PySetInner { } impl PySetInner { + fn new(iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { + let elements: HashMap = match iterable { + OptionalArg::Missing => HashMap::new(), + OptionalArg::Present(iterable) => { + let mut elements = HashMap::new(); + let iterator = objiter::get_iter(vm, &iterable)?; + while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { + insert_into_set(vm, &mut elements, &v)?; + } + elements + } + }; + + Ok(PySetInner { elements }) + } + fn len(&self) -> usize { self.elements.len() } @@ -334,14 +349,27 @@ impl PySetInner { } impl PySetRef { + fn new( + cls: PyClassRef, + iterable: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + PySet { + inner: RefCell::new(PySetInner::new(iterable, vm)?), + } + .into_ref_with_type(vm, cls) + } + fn len(self, _vm: &VirtualMachine) -> usize { self.inner.borrow().len() } + fn copy(self, _vm: &VirtualMachine) -> PySet { PySet { inner: RefCell::new(self.inner.borrow().copy()), } } + fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.inner.borrow().contains(needle, vm) } @@ -512,14 +540,27 @@ impl PySetRef { } impl PyFrozenSetRef { + fn new( + cls: PyClassRef, + iterable: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + PyFrozenSet { + inner: PySetInner::new(iterable, vm)?, + } + .into_ref_with_type(vm, cls) + } + fn len(self, _vm: &VirtualMachine) -> usize { self.inner.len() } + fn copy(self, _vm: &VirtualMachine) -> PyFrozenSet { PyFrozenSet { inner: self.inner.copy(), } } + fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.inner.contains(needle, vm) } @@ -616,41 +657,6 @@ fn get_inner(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { } } -fn validate_set_or_frozenset(vm: &VirtualMachine, cls: PyClassRef) -> PyResult<()> { - if !(objtype::issubclass(&cls, &vm.ctx.set_type()) - || objtype::issubclass(&cls, &vm.ctx.frozenset_type())) - { - return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", cls))); - } - Ok(()) -} - -fn create_set( - vm: &VirtualMachine, - elements: HashMap, - cls: PyClassRef, -) -> PyResult { - if objtype::issubclass(&cls, &vm.ctx.set_type()) { - Ok(PyObject::new( - PySet { - inner: RefCell::new(PySetInner { elements: elements }), - }, - PySet::class(vm), - None, - )) - } else if objtype::issubclass(&cls, &vm.ctx.frozenset_type()) { - Ok(PyObject::new( - PyFrozenSet { - inner: PySetInner { elements: elements }, - }, - PyFrozenSet::class(vm), - None, - )) - } else { - Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", cls))) - } -} - fn perform_action_with_hash( vm: &VirtualMachine, elements: &mut HashMap, @@ -683,25 +689,6 @@ fn insert_into_set( perform_action_with_hash(vm, elements, item, &insert) } -/* Create a new object of sub-type of set */ -fn set_new(cls: PyClassRef, iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { - validate_set_or_frozenset(vm, cls.clone())?; - - let elements: HashMap = match iterable { - OptionalArg::Missing => HashMap::new(), - OptionalArg::Present(iterable) => { - let mut elements = HashMap::new(); - let iterator = objiter::get_iter(vm, &iterable)?; - while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { - insert_into_set(vm, &mut elements, &v)?; - } - elements - } - }; - - create_set(vm, elements, cls.clone()) -} - enum SetCombineOperation { Intersection, Difference, @@ -719,10 +706,10 @@ pub fn init(context: &PyContext) { Build an unordered collection of unique elements."; extend_class!(context, set_type, { + "__hash__" => context.new_rustfunc(set_hash), "__contains__" => context.new_rustfunc(PySetRef::contains), "__len__" => context.new_rustfunc(PySetRef::len), - "__new__" => context.new_rustfunc(set_new), - "__hash__" => context.new_rustfunc(set_hash), + "__new__" => context.new_rustfunc(PySetRef::new), "__repr__" => context.new_rustfunc(PySetRef::repr), "__eq__" => context.new_rustfunc(PySetRef::eq), "__ge__" => context.new_rustfunc(PySetRef::ge), @@ -764,7 +751,7 @@ pub fn init(context: &PyContext) { Build an immutable unordered collection of unique elements."; extend_class!(context, frozenset_type, { - "__new__" => context.new_rustfunc(set_new), + "__new__" => context.new_rustfunc(PyFrozenSetRef::new), "__eq__" => context.new_rustfunc(PyFrozenSetRef::eq), "__ge__" => context.new_rustfunc(PyFrozenSetRef::ge), "__gt__" => context.new_rustfunc(PyFrozenSetRef::gt), From 2a3a88c2f2cf0f13f0ed89843f19680f43b8d403 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 4 Apr 2019 21:23:00 +0300 Subject: [PATCH 169/884] Use match_class macro to avoid unwanted clones --- vm/src/obj/objset.rs | 135 ++++++++++++++++++++++++++++--------------- 1 file changed, 90 insertions(+), 45 deletions(-) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index fd31044aa4..6c67312fb1 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -375,29 +375,53 @@ impl PySetRef { } fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow().eq(&get_inner(vm, &other)?, vm) + match_class!(other, + set @ PySet => self.inner.borrow().eq(&set.inner.borrow(), vm), + frozen @ PyFrozenSet => self.inner.borrow().eq(&frozen.inner, vm), + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ) } fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow().ge(&get_inner(vm, &other)?, vm) + match_class!(other, + set @ PySet => self.inner.borrow().ge(&set.inner.borrow(), vm), + frozen @ PyFrozenSet => self.inner.borrow().ge(&frozen.inner, vm), + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ) } fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow().gt(&get_inner(vm, &other)?, vm) + match_class!(other, + set @ PySet => self.inner.borrow().gt(&set.inner.borrow(), vm), + frozen @ PyFrozenSet => self.inner.borrow().gt(&frozen.inner, vm), + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ) } fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow().le(&get_inner(vm, &other)?, vm) + match_class!(other, + set @ PySet => self.inner.borrow().le(&set.inner.borrow(), vm), + frozen @ PyFrozenSet => self.inner.borrow().le(&frozen.inner, vm), + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ) } fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow().lt(&get_inner(vm, &other)?, vm) + match_class!(other, + set @ PySet => self.inner.borrow().lt(&set.inner.borrow(), vm), + frozen @ PyFrozenSet => self.inner.borrow().lt(&frozen.inner, vm), + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ) } fn union(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { - inner: RefCell::new(self.inner.borrow().union(&get_inner(vm, &other)?, vm)?), + inner: RefCell::new(match_class!(other, + set @ PySet => self.inner.borrow().union(&set.inner.borrow(), vm)?, + frozen @ PyFrozenSet => self.inner.borrow().union(&frozen.inner, vm)?, + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + )), }, PySet::class(vm), None, @@ -407,11 +431,11 @@ impl PySetRef { fn intersection(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { - inner: RefCell::new( - self.inner - .borrow() - .intersection(&get_inner(vm, &other)?, vm)?, - ), + inner: RefCell::new(match_class!(other, + set @ PySet => self.inner.borrow().intersection(&set.inner.borrow(), vm)?, + frozen @ PyFrozenSet => self.inner.borrow().intersection(&frozen.inner, vm)?, + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + )), }, PySet::class(vm), None, @@ -421,11 +445,11 @@ impl PySetRef { fn difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { - inner: RefCell::new( - self.inner - .borrow() - .difference(&get_inner(vm, &other)?, vm)?, - ), + inner: RefCell::new(match_class!(other, + set @ PySet => self.inner.borrow().difference(&set.inner.borrow(), vm)?, + frozen @ PyFrozenSet => self.inner.borrow().difference(&frozen.inner, vm)?, + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + )), }, PySet::class(vm), None, @@ -435,11 +459,11 @@ impl PySetRef { fn symmetric_difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { - inner: RefCell::new( - self.inner - .borrow() - .symmetric_difference(&get_inner(vm, &other)?, vm)?, - ), + inner: RefCell::new(match_class!(other, + set @ PySet => self.inner.borrow().symmetric_difference(&set.inner.borrow(), vm)?, + frozen @ PyFrozenSet => self.inner.borrow().symmetric_difference(&frozen.inner, vm)?, + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + )), }, PySet::class(vm), None, @@ -566,29 +590,53 @@ impl PyFrozenSetRef { } fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.eq(&get_inner(vm, &other)?, vm) + match_class!(other, + set @ PySet => self.inner.eq(&set.inner.borrow(), vm), + frozen @ PyFrozenSet => self.inner.eq(&frozen.inner, vm), + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ) } fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.ge(&get_inner(vm, &other)?, vm) + match_class!(other, + set @ PySet => self.inner.ge(&set.inner.borrow(), vm), + frozen @ PyFrozenSet => self.inner.ge(&frozen.inner, vm), + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ) } fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.gt(&get_inner(vm, &other)?, vm) + match_class!(other, + set @ PySet => self.inner.gt(&set.inner.borrow(), vm), + frozen @ PyFrozenSet => self.inner.gt(&frozen.inner, vm), + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ) } fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.le(&get_inner(vm, &other)?, vm) + match_class!(other, + set @ PySet => self.inner.le(&set.inner.borrow(), vm), + frozen @ PyFrozenSet => self.inner.le(&frozen.inner, vm), + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ) } fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.lt(&get_inner(vm, &other)?, vm) + match_class!(other, + set @ PySet => self.inner.lt(&set.inner.borrow(), vm), + frozen @ PyFrozenSet => self.inner.lt(&frozen.inner, vm), + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ) } fn union(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { - inner: self.inner.union(&get_inner(vm, &other)?, vm)?, + inner: match_class!(other, + set @ PySet => self.inner.union(&set.inner.borrow(), vm)?, + frozen @ PyFrozenSet => self.inner.union(&frozen.inner, vm)?, + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ), }, PyFrozenSet::class(vm), None, @@ -598,7 +646,11 @@ impl PyFrozenSetRef { fn intersection(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { - inner: self.inner.intersection(&get_inner(vm, &other)?, vm)?, + inner: match_class!(other, + set @ PySet => self.inner.intersection(&set.inner.borrow(), vm)?, + frozen @ PyFrozenSet => self.inner.intersection(&frozen.inner, vm)?, + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ), }, PyFrozenSet::class(vm), None, @@ -608,7 +660,11 @@ impl PyFrozenSetRef { fn difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { - inner: self.inner.difference(&get_inner(vm, &other)?, vm)?, + inner: match_class!(other, + set @ PySet => self.inner.difference(&set.inner.borrow(), vm)?, + frozen @ PyFrozenSet => self.inner.difference(&frozen.inner, vm)?, + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ), }, PyFrozenSet::class(vm), None, @@ -618,9 +674,11 @@ impl PyFrozenSetRef { fn symmetric_difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { - inner: self - .inner - .symmetric_difference(&get_inner(vm, &other)?, vm)?, + inner: match_class!(other, + set @ PySet => self.inner.symmetric_difference(&set.inner.borrow(), vm)?, + frozen @ PyFrozenSet => self.inner.symmetric_difference(&frozen.inner, vm)?, + other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, + ), }, PyFrozenSet::class(vm), None, @@ -644,19 +702,6 @@ impl PyFrozenSetRef { } } -fn get_inner(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { - if let Some(set) = obj.payload::() { - Ok(set.inner.borrow().clone()) - } else if let Some(frozenset) = obj.payload::() { - Ok(frozenset.inner.clone()) - } else { - Err(vm.new_type_error(format!( - "{} is not a subtype of set or frozenset", - obj.class() - ))) - } -} - fn perform_action_with_hash( vm: &VirtualMachine, elements: &mut HashMap, From ab3d004c4227d79ada563c72a65cb2ac569fd47c Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Fri, 5 Apr 2019 21:59:20 +0200 Subject: [PATCH 170/884] Fix syntax for float literals, statements seperated by semicolons and starargs after keyword arguments. --- parser/src/ast.rs | 2 +- parser/src/lexer.rs | 22 ++++++++++++--------- parser/src/parser.rs | 18 ++++++++--------- parser/src/python.lalrpop | 34 +++++++++++++++++++++----------- tests/snippets/comprehensions.py | 5 +++++ tests/snippets/floats.py | 4 ++++ tests/snippets/function_args.py | 9 +++++++++ tests/snippets/statements.py | 9 +++++++++ vm/src/compile.rs | 16 ++++++++------- 9 files changed, 81 insertions(+), 38 deletions(-) create mode 100644 tests/snippets/statements.py diff --git a/parser/src/ast.rs b/parser/src/ast.rs index 21c8dd736b..9fbe8153e9 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -16,7 +16,7 @@ pub struct Node { #[derive(Debug, PartialEq)] pub enum Top { Program(Program), - Statement(LocatedStatement), + Statement(Vec), Expression(Expression), } diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index edf21c973c..ebdb9555dd 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -1044,16 +1044,20 @@ where return Some(Ok((tok_start, Tok::Comma, tok_end))); } '.' => { - let tok_start = self.get_pos(); - self.next_char(); - if let (Some('.'), Some('.')) = (&self.chr0, &self.chr1) { - self.next_char(); - self.next_char(); - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Ellipsis, tok_end))); + if let Some('0'..='9') = self.chr1 { + return Some(self.lex_number()); } else { - let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Dot, tok_end))); + let tok_start = self.get_pos(); + self.next_char(); + if let (Some('.'), Some('.')) = (&self.chr0, &self.chr1) { + self.next_char(); + self.next_char(); + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Ellipsis, tok_end))); + } else { + let tok_end = self.get_pos(); + return Some(Ok((tok_start, Tok::Dot, tok_end))); + } } } '\n' => { diff --git a/parser/src/parser.rs b/parser/src/parser.rs index 4c37f28423..dd58bd11db 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -35,7 +35,7 @@ pub fn parse_program(source: &str) -> Result { do_lalr_parsing!(source, Program, StartProgram) } -pub fn parse_statement(source: &str) -> Result { +pub fn parse_statement(source: &str) -> Result, ParseError> { do_lalr_parsing!(source, Statement, StartStatement) } @@ -180,7 +180,7 @@ mod tests { let parse_ast = parse_statement(&source).unwrap(); assert_eq!( parse_ast, - ast::LocatedStatement { + vec![ast::LocatedStatement { location: ast::Location::new(1, 1), node: ast::Statement::If { test: ast::Expression::Number { @@ -229,7 +229,7 @@ mod tests { } },]), } - } + }] ); } @@ -239,7 +239,7 @@ mod tests { let parse_ast = parse_statement(&source); assert_eq!( parse_ast, - Ok(ast::LocatedStatement { + Ok(vec![ast::LocatedStatement { location: ast::Location::new(1, 1), node: ast::Statement::Expression { expression: ast::Expression::Lambda { @@ -271,7 +271,7 @@ mod tests { }) } } - }) + }]) ) } @@ -281,7 +281,7 @@ mod tests { assert_eq!( parse_statement(&source), - Ok(ast::LocatedStatement { + Ok(vec![ast::LocatedStatement { location: ast::Location::new(1, 1), node: ast::Statement::Assign { targets: vec![ast::Expression::Tuple { @@ -309,7 +309,7 @@ mod tests { ] } } - }) + }]) ) } @@ -320,7 +320,7 @@ mod tests { ); assert_eq!( parse_statement(&source), - Ok(ast::LocatedStatement { + Ok(vec![ast::LocatedStatement { location: ast::Location::new(1, 1), node: ast::Statement::ClassDef { name: String::from("Foo"), @@ -393,7 +393,7 @@ mod tests { ], decorator_list: vec![], } - }) + }]) ) } diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 305f97c10b..a9663943fb 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -24,29 +24,32 @@ pub Top: ast::Top = { Program: ast::Program = { => ast::Program { - statements: Vec::from_iter(lines.into_iter().filter_map(|e| e)) + statements: Vec::from_iter(lines.into_iter().flatten()) }, }; // A file line either has a declaration, or an empty newline: -FileLine: Option = { - => Some(s), - "\n" => None, +FileLine: Vec = { + Statement, + "\n" => vec![], }; Suite: Vec = { - => vec![s], - "\n" indent dedent => s, + SimpleStatement, + "\n" indent dedent => s.into_iter().flatten().collect(), }; -Statement: ast::LocatedStatement = { +Statement: Vec = { SimpleStatement, - CompoundStatement, + => vec![s], }; -SimpleStatement: ast::LocatedStatement = { - "\n" => s, - ";" => s, +SimpleStatement: Vec = { + ";"? "\n" => { + let mut statements = vec![s1]; + statements.extend(s2.into_iter().map(|e| e.1)); + statements + } }; SmallStatement: ast::LocatedStatement = { @@ -899,7 +902,14 @@ ArgumentList: (Vec, Vec) = { keywords.push(ast::Keyword { name: n, value: value }); }, None => { - if keywords.len() > 0 { + // Allow starred args after keyword arguments. + let is_starred = if let ast::Expression::Starred { .. } = &value { + true + } else { + false + }; + + if keywords.len() > 0 && !is_starred { panic!("positional argument follows keyword argument {:?}", keywords); }; args.push(value); diff --git a/tests/snippets/comprehensions.py b/tests/snippets/comprehensions.py index 14c545de30..132f3cdcb9 100644 --- a/tests/snippets/comprehensions.py +++ b/tests/snippets/comprehensions.py @@ -22,3 +22,8 @@ y = [a+2 for a in x if a % 2] print(y) assert y == [3, 5] + +z = [(9,), (10,)] +w = [x for x, in z] +assert w == [9, 10] + diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 6675615c56..08e7d4b2b9 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -106,3 +106,7 @@ assert_raises(OverflowError, float('-inf').as_integer_ratio) assert_raises(ValueError, float('nan').as_integer_ratio) +# Test special case for lexer, float starts with a dot: +a = .5 +assert a == 0.5 + diff --git a/tests/snippets/function_args.py b/tests/snippets/function_args.py index 745648f9d2..884cb47df1 100644 --- a/tests/snippets/function_args.py +++ b/tests/snippets/function_args.py @@ -42,3 +42,12 @@ def va2(*args, **kwargs): x = {'f': 42, 'e': 1337} y = {'d': 1337} va(1, 22, 3, 4, **x, **y) + +# star arg after keyword args: +def fubar(x, y, obj=None): + assert x == 4 + assert y == 5 + assert obj == 6 + +rest = [4, 5] +fubar(obj=6, *rest) diff --git a/tests/snippets/statements.py b/tests/snippets/statements.py new file mode 100644 index 0000000000..1921363f56 --- /dev/null +++ b/tests/snippets/statements.py @@ -0,0 +1,9 @@ + +# Test several statement types + +# small ones, seperated by ';': + +if True: + 5;4; + b=4 + b; diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 88818f52f0..5802d9e300 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -129,15 +129,17 @@ impl Compiler { // Compile statement in eval mode: fn compile_statement_eval( &mut self, - statement: &ast::LocatedStatement, + statements: &[ast::LocatedStatement], ) -> Result<(), CompileError> { - if let ast::Statement::Expression { ref expression } = statement.node { - self.compile_expression(expression)?; - self.emit(Instruction::ReturnValue); - Ok(()) - } else { - Err(CompileError::ExpectExpr) + for statement in statements { + if let ast::Statement::Expression { ref expression } = statement.node { + self.compile_expression(expression)?; + } else { + return Err(CompileError::ExpectExpr); + } } + self.emit(Instruction::ReturnValue); + Ok(()) } fn compile_statements( From dda0d561ac8f23da8f16951bc33891630e0be955 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 6 Apr 2019 10:43:39 +1300 Subject: [PATCH 171/884] Fix order of dict literal --- Cargo.lock | 1 + tests/snippets/dict.py | 6 ++++++ vm/Cargo.toml | 1 + vm/src/frame.rs | 14 ++++++++------ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 36e313e262..864e90ce5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -809,6 +809,7 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 5c5ef62b1e..2d369465cd 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -80,3 +80,9 @@ def __eq__(self, other): x[Hashable(19,8)] = 1 assert x[Hashable(8)] == 1 assert len(x) == 2 + +assert list({'a': 2, 'b': 10}) == ['a', 'b'] +x = {} +x['a'] = 2 +x['b'] = 10 +assert list(x) == ['a', 'b'] diff --git a/vm/Cargo.toml b/vm/Cargo.toml index e1b1cc9ffa..ef1a9cbc35 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -26,3 +26,4 @@ caseless = "0.2.1" unicode-segmentation = "1.2.1" lazy_static = "^1.0.1" lexical = "2.0.0" +itertools = "^0.8.0" diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 858b33a08b..2f525afdd1 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -25,6 +25,7 @@ use crate::pyobject::{ TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; +use itertools::Itertools; /* * So a scope is a linked list of scopes. @@ -386,9 +387,8 @@ impl Frame { } bytecode::Instruction::BuildMap { size, unpack } => { let map_obj = vm.ctx.new_dict(); - for _x in 0..*size { - let obj = self.pop_value(); - if *unpack { + if *unpack { + for obj in self.pop_multiple(*size) { // Take all key-value pairs from the dict: let dict: PyDictRef = obj.downcast().expect("Need a dictionary to build a map."); @@ -396,11 +396,13 @@ impl Frame { for (key, value) in dict_elements.iter() { map_obj.set_item(key.clone(), value.clone(), vm); } - } else { - let key = self.pop_value(); - map_obj.set_item(key, obj, vm) + } + } else { + for (key, value) in self.pop_multiple(2 * size).into_iter().tuples() { + map_obj.set_item(key, value, vm) } } + self.push_value(map_obj.into_object()); Ok(None) } From a7d5b0e9ac1ecd8153d5ceeaa796340755ccb692 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sat, 6 Apr 2019 01:44:00 +0200 Subject: [PATCH 172/884] start --- vm/src/obj/mod.rs | 1 + vm/src/obj/objbyteinner.rs | 9 +++ vm/src/obj/objbytes.rs | 155 ++++++++++++++++++++----------------- 3 files changed, 96 insertions(+), 69 deletions(-) create mode 100644 vm/src/obj/objbyteinner.rs diff --git a/vm/src/obj/mod.rs b/vm/src/obj/mod.rs index 0419d827f0..c824e726d8 100644 --- a/vm/src/obj/mod.rs +++ b/vm/src/obj/mod.rs @@ -3,6 +3,7 @@ pub mod objbool; pub mod objbuiltinfunc; pub mod objbytearray; +pub mod objbyteinner; pub mod objbytes; pub mod objclassmethod; pub mod objcode; diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs new file mode 100644 index 0000000000..0607621f1a --- /dev/null +++ b/vm/src/obj/objbyteinner.rs @@ -0,0 +1,9 @@ +#[derive(Debug, Default, Clone)] +pub struct PyByteInner { + pub elements: Vec, +} +impl PyByteInner { + pub fn new(data: Vec) -> Self { + PyByteInner { elements: data } + } +} diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index f24c9273c5..8f43cd05f6 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -6,22 +6,37 @@ use std::ops::Deref; use num_traits::ToPrimitive; use crate::function::OptionalArg; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{ + IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, +}; use crate::vm::VirtualMachine; +use super::objbyteinner::PyByteInner; use super::objint; use super::objiter; use super::objtype::PyClassRef; +/// "bytes(iterable_of_ints) -> bytes\n\ +/// bytes(string, encoding[, errors]) -> bytes\n\ +/// bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\ +/// bytes(int) -> bytes object of size given by the parameter initialized with null bytes\n\ +/// bytes() -> empty bytes object\n\nConstruct an immutable array of bytes from:\n \ +/// - an iterable yielding integers in range(256)\n \ +/// - a text string encoded using the specified encoding\n \ +/// - any object implementing the buffer API.\n \ +/// - an integer"; +#[pyclass(name = "bytes", __inside_vm)] #[derive(Debug)] pub struct PyBytes { - value: Vec, + inner: PyByteInner, } type PyBytesRef = PyRef; impl PyBytes { - pub fn new(data: Vec) -> Self { - PyBytes { value: data } + pub fn new(elements: Vec) -> Self { + PyBytes { + inner: PyByteInner { elements }, + } } } @@ -29,7 +44,7 @@ impl Deref for PyBytes { type Target = [u8]; fn deref(&self) -> &[u8] { - &self.value + &self.inner.elements } } @@ -42,62 +57,74 @@ impl PyValue for PyBytes { // Binary data support // Fill bytes class methods: -pub fn init(context: &PyContext) { - let bytes_doc = - "bytes(iterable_of_ints) -> bytes\n\ - bytes(string, encoding[, errors]) -> bytes\n\ - bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\ - bytes(int) -> bytes object of size given by the parameter initialized with null bytes\n\ - bytes() -> empty bytes object\n\nConstruct an immutable array of bytes from:\n \ - - an iterable yielding integers in range(256)\n \ - - a text string encoded using the specified encoding\n \ - - any object implementing the buffer API.\n \ - - an integer"; - - extend_class!(context, &context.bytes_type, { - "__new__" => context.new_rustfunc(bytes_new), - "__eq__" => context.new_rustfunc(PyBytesRef::eq), - "__lt__" => context.new_rustfunc(PyBytesRef::lt), - "__le__" => context.new_rustfunc(PyBytesRef::le), - "__gt__" => context.new_rustfunc(PyBytesRef::gt), - "__ge__" => context.new_rustfunc(PyBytesRef::ge), - "__hash__" => context.new_rustfunc(PyBytesRef::hash), - "__repr__" => context.new_rustfunc(PyBytesRef::repr), - "__len__" => context.new_rustfunc(PyBytesRef::len), - "__iter__" => context.new_rustfunc(PyBytesRef::iter), - "__doc__" => context.new_str(bytes_doc.to_string()) - }); - - let bytesiterator_type = &context.bytesiterator_type; - extend_class!(context, bytesiterator_type, { - "__next__" => context.new_rustfunc(PyBytesIteratorRef::next), - "__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), - }); -} - -fn bytes_new( - cls: PyClassRef, - val_option: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { - // Create bytes data: - let value = if let OptionalArg::Present(ival) = val_option { - let elements = vm.extract_elements(&ival)?; - let mut data_bytes = vec![]; - for elem in elements.iter() { - let v = objint::to_int(vm, elem, 10)?; - data_bytes.push(v.to_u8().unwrap()); - } - data_bytes - // return Err(vm.new_type_error("Cannot construct bytes".to_string())); - } else { - vec![] - }; - PyBytes::new(value).into_ref_with_type(vm, cls) +pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { + &obj.payload::().unwrap().inner.elements } +// pub fn init(context: &PyContext) { +// let bytes_doc = +pub fn init(ctx: &PyContext) { + PyBytesRef::extend_class(ctx, &ctx.bytes_type); +} +//extend_class!(context, &context.bytes_type, { +//"__new__" => context.new_rustfunc(bytes_new), +/* "__eq__" => context.new_rustfunc(PyBytesRef::eq), +"__lt__" => context.new_rustfunc(PyBytesRef::lt), +"__le__" => context.new_rustfunc(PyBytesRef::le), +"__gt__" => context.new_rustfunc(PyBytesRef::gt), +"__ge__" => context.new_rustfunc(PyBytesRef::ge), +"__hash__" => context.new_rustfunc(PyBytesRef::hash), +"__repr__" => context.new_rustfunc(PyBytesRef::repr), +"__len__" => context.new_rustfunc(PyBytesRef::len), +"__iter__" => context.new_rustfunc(PyBytesRef::iter), +"__doc__" => context.new_str(bytes_doc.to_string())*/ +// }); + +/* let bytesiterator_type = &context.bytesiterator_type; +extend_class!(context, bytesiterator_type, { + "__next__" => context.new_rustfunc(PyBytesIteratorRef::next), + "__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), +});*/ +//} +#[pyimpl(__inside_vm)] impl PyBytesRef { + #[pymethod(name = "__new__")] + fn bytes_new( + cls: PyClassRef, + val_option: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + // Create bytes data: + let value = if let OptionalArg::Present(ival) = val_option { + let elements = vm.extract_elements(&ival)?; + let mut data_bytes = vec![]; + for elem in elements.iter() { + let v = objint::to_int(vm, elem, 10)?; + data_bytes.push(v.to_u8().unwrap()); + } + data_bytes + // return Err(vm.new_type_error("Cannot construct bytes".to_string())); + } else { + vec![] + }; + + PyBytes::new(value).into_ref_with_type(vm, cls) + } + + #[pymethod(name = "__repr__")] + fn repr(self, _vm: &VirtualMachine) -> String { + // TODO: don't just unwrap + let data = self.inner.elements.clone(); + format!("b'{:?}'", data) + } + + #[pymethod(name = "__len__")] + fn len(self, _vm: &VirtualMachine) -> usize { + self.inner.elements.len() + } +} +/* fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if let Ok(other) = other.downcast::() { vm.ctx.new_bool(self.value == other.value) @@ -138,9 +165,6 @@ impl PyBytesRef { } } - fn len(self, _vm: &VirtualMachine) -> usize { - self.value.len() - } fn hash(self, _vm: &VirtualMachine) -> u64 { let mut hasher = DefaultHasher::new(); @@ -148,11 +172,6 @@ impl PyBytesRef { hasher.finish() } - fn repr(self, _vm: &VirtualMachine) -> String { - // TODO: don't just unwrap - let data = String::from_utf8(self.value.clone()).unwrap(); - format!("b'{}'", data) - } fn iter(self, _vm: &VirtualMachine) -> PyBytesIterator { PyBytesIterator { @@ -162,9 +181,7 @@ impl PyBytesRef { } } -pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { - &obj.payload::().unwrap().value -} + #[derive(Debug)] pub struct PyBytesIterator { @@ -194,4 +211,4 @@ impl PyBytesIteratorRef { fn iter(self, _vm: &VirtualMachine) -> Self { self } -} +}*/ From 7fffc572559d5d2b889af747f083f5dad16645f9 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 6 Apr 2019 12:46:48 +1300 Subject: [PATCH 173/884] Fix decorator on functions with defaults --- tests/snippets/decorators.py | 10 ++++++++++ vm/src/compile.rs | 5 +++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/tests/snippets/decorators.py b/tests/snippets/decorators.py index fcc196f936..2633308f18 100644 --- a/tests/snippets/decorators.py +++ b/tests/snippets/decorators.py @@ -15,6 +15,16 @@ def add(a, b): assert c == 14 +@logged +def add3(a, b, c=2): + return a + b + c + + +d = add3(12, 5) + +assert d == 20 + + def f(func): return lambda: 42 class A: pass a = A() diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 88818f52f0..d7f774b83a 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -585,6 +585,9 @@ impl Compiler { let was_in_function_def = self.in_function_def; self.in_loop = false; self.in_function_def = true; + + self.prepare_decorators(decorator_list)?; + let mut flags = self.enter_function(name, args)?; let (new_body, doc_str) = get_doc(body); @@ -598,8 +601,6 @@ impl Compiler { self.emit(Instruction::ReturnValue); let code = self.pop_code_object(); - self.prepare_decorators(decorator_list)?; - // Prepare type annotations: let mut num_annotations = 0; From 909d94ec233749d9bfc42fdab830e08eb3cfa4fd Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Fri, 5 Apr 2019 20:25:53 -0600 Subject: [PATCH 174/884] impl and use SequenceIndex for list.__delitem__ - removal of del_item - all del function impl on PyListRef - stepped deletion now loops only over the range insted of the whole list --- tests/snippets/list.py | 4 + vm/src/obj/objlist.rs | 176 ++++++++++++++++++++++++++++++++++++-- vm/src/obj/objsequence.rs | 152 +++----------------------------- 3 files changed, 188 insertions(+), 144 deletions(-) diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 6efa52cc0f..3d9743c0b4 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -190,6 +190,10 @@ def f(x): 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) diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index f3e812615e..60c14b1074 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -1,9 +1,10 @@ use std::cell::{Cell, RefCell}; use std::fmt; -use std::ops::DerefMut; +use std::ops::Range; -use num_traits::ToPrimitive; +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}; @@ -13,9 +14,10 @@ use super::objbool; use super::objint; use super::objiter; use super::objsequence::{ - del_item, get_elements, get_elements_cell, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, - seq_mul, PySliceableSequence, + get_elements, get_elements_cell, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, + PySliceableSequence, SequenceIndex, }; +use super::objslice::PySliceRef; use super::objtype; use crate::obj::objtype::PyClassRef; @@ -46,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 { + // 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, stop: &Option) -> Range { + 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; impl PyListRef { @@ -313,9 +363,123 @@ impl PyListRef { } } - fn delitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { + 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) { + self.elements.borrow_mut().drain(range); + } + + fn _del_stepped_slice(self, range: Range, 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, 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(); - del_item(vm, self.as_object(), elements.deref_mut(), key) + 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)); } } diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index 9593e57fe1..14f9c5c93f 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -5,7 +5,7 @@ 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; @@ -137,104 +137,21 @@ impl PySliceableSequence for Vec { } } -pub trait PySliceableSequenceMut: PySliceableSequence { - //fn set_slice(&self, range: Range, seq: T) -> Self; - //fn set_slice_reverse(&self, range: Range, seq: T) -> Self; - //fn set_stepped_slice(&self, range: Range, step: usize, seq: T) -> Self; - //fn set_stepped_slice_reverse(&self, range: Range, step: usize, seq: T) -> Self; - - fn del_slice(&mut self, range: Range); - fn del_stepped_slice(&mut self, range: Range, step: usize); - fn del_index(&mut self, index: usize); - - fn del_slice_items(&mut self, vm: &VirtualMachine, slice: &PySliceRef) -> 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(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()) - } - } - - } +pub enum SequenceIndex { + Int(i32), + Slice(PySliceRef), } -impl PySliceableSequenceMut for Vec { - fn del_slice(&mut self, range: Range) { - self.drain(range); - } - - fn del_stepped_slice(&mut self, range: Range, step: usize) { - // no easy way to delete steped indexes so heres what we'll do - let len = self.len(); - let mut deleted = 0; - let mut indexes = range.step_by(step).peekable(); - for i in 0..len { - // is this an index to delete? - if indexes.peek() == Some(&i) { - // record and move on - indexes.next(); - deleted += 1; - } else { - // swap towards front - self.swap(i - deleted, i); - } - } - // then truncate the vec (the values to delete should now be the last elements and thus choped off) - self.truncate(len - deleted); - } - - fn del_index(&mut self, index: usize) { - self.remove(index); +impl TryFromObject for SequenceIndex { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + 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(), + ))) + ) } } @@ -284,47 +201,6 @@ pub fn get_item( } } -pub fn del_item( - vm: &VirtualMachine, - sequence: &PyObjectRef, - elements: &mut T, - subscript: PyObjectRef, -) -> PyResult { - if let Some(i) = subscript.payload::() { - return match i.as_bigint().to_i32() { - Some(value) => { - if let Some(pos_index) = elements.get_pos(value) { - elements.del_index(pos_index); - Ok(vm.get_none()) - } else { - Err(vm.new_index_error("Index out of bounds!".to_string())) - } - } - None => { - Err(vm.new_index_error("cannot fit 'int' into an index-sized integer".to_string())) - } - }; - } - - if subscript.payload::().is_some() { - if sequence.payload::().is_some() { - if let Ok(slice) = subscript.downcast::() { - elements.del_slice_items(vm, &slice) - } else { - panic!("PySlice is not a slice? this should not be") - } - - } else { - panic!("sequence del_item called for non-mutable-sequence") - } - } else { - Err(vm.new_type_error(format!( - "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", - sequence, subscript - ))) - } -} - pub fn seq_equal( vm: &VirtualMachine, zelf: &[PyObjectRef], From 157204c61ad6f80503c413245019e1a4ffd88054 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 5 Apr 2019 23:24:26 -0500 Subject: [PATCH 175/884] Clean up the wasm crate --- derive/src/from_args.rs | 23 +++--- wasm/lib/src/browser_module.rs | 123 +++++++++++++++------------------ wasm/lib/src/convert.rs | 8 +-- wasm/lib/src/vm_class.rs | 58 +++++----------- 4 files changed, 89 insertions(+), 123 deletions(-) diff --git a/derive/src/from_args.rs b/derive/src/from_args.rs index 7845b28800..040ebf36ca 100644 --- a/derive/src/from_args.rs +++ b/derive/src/from_args.rs @@ -109,7 +109,7 @@ impl ArgAttribute { } } -fn generate_field(field: &Field) -> TokenStream2 { +fn generate_field(field: &Field, rp_path: &syn::Path) -> TokenStream2 { let mut pyarg_attrs = field .attrs .iter() @@ -132,7 +132,7 @@ fn generate_field(field: &Field) -> TokenStream2 { let name = &field.ident; let middle = quote! { - .map(|x| crate::pyobject::TryFromObject::try_from_object(vm, x)).transpose()? + .map(|x| #rp_path::pyobject::TryFromObject::try_from_object(vm, x)).transpose()? }; let ending = if let Some(default) = attr.default { quote! { @@ -140,16 +140,16 @@ fn generate_field(field: &Field) -> TokenStream2 { } } else if attr.optional { quote! { - .map(crate::function::OptionalArg::Present) - .unwrap_or(crate::function::OptionalArg::Missing) + .map(#rp_path::function::OptionalArg::Present) + .unwrap_or(#rp_path::function::OptionalArg::Missing) } } else { let err = match attr.kind { ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword => quote! { - crate::function::ArgumentError::TooFewArgs + #rp_path::function::ArgumentError::TooFewArgs }, ParameterKind::KeywordOnly => quote! { - crate::function::ArgumentError::RequiredKeywordArgument(tringify!(#name)) + #rp_path::function::ArgumentError::RequiredKeywordArgument(tringify!(#name)) }, }; quote! { @@ -181,7 +181,10 @@ pub fn impl_from_args(input: DeriveInput) -> TokenStream2 { let fields = match input.data { Data::Struct(ref data) => { match data.fields { - Fields::Named(ref fields) => fields.named.iter().map(generate_field), + Fields::Named(ref fields) => fields + .named + .iter() + .map(|field| generate_field(field, &rp_path)), Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message } } @@ -192,9 +195,9 @@ pub fn impl_from_args(input: DeriveInput) -> TokenStream2 { quote! { impl #rp_path::function::FromArgs for #name { fn from_args( - vm: &crate::vm::VirtualMachine, - args: &mut crate::function::PyFuncArgs - ) -> Result { + vm: &#rp_path::VirtualMachine, + args: &mut #rp_path::function::PyFuncArgs + ) -> Result { Ok(#name { #(#fields)* }) } } diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 6f7c31964f..607cfc886a 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -7,16 +7,13 @@ use wasm_bindgen_futures::{future_to_promise, JsFuture}; use rustpython_vm::function::{OptionalArg, PyFuncArgs}; use rustpython_vm::obj::{ - objdict::PyDictRef, - objfunction::PyFunction, - objint, - objstr::{self, PyStringRef}, + objdict::PyDictRef, objfunction::PyFunctionRef, objint::PyIntRef, objstr::PyStringRef, objtype::PyClassRef, }; -use rustpython_vm::pyobject::{PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use rustpython_vm::pyobject::{PyObject, PyObjectRef, PyRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; -use crate::{convert, vm_class::AccessibleVM, wasm_builtins::window}; +use crate::{convert, vm_class::weak_vm, wasm_builtins::window}; enum FetchResponseFormat { Json, @@ -42,25 +39,38 @@ impl FetchResponseFormat { } } -fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(url, Some(vm.ctx.str_type()))]); +#[derive(FromArgs)] +struct FetchArgs { + #[pyarg(keyword_only, default = "None")] + response_format: Option, + #[pyarg(keyword_only, default = "None")] + method: Option, + #[pyarg(keyword_only, default = "None")] + headers: Option, + #[pyarg(keyword_only, default = "None")] + body: Option, + #[pyarg(keyword_only, default = "None")] + content_type: Option, +} - let response_format = - args.get_optional_kwarg_with_type("response_format", vm.ctx.str_type(), vm)?; - let method = args.get_optional_kwarg_with_type("method", vm.ctx.str_type(), vm)?; - let headers = args.get_optional_kwarg_with_type("headers", vm.ctx.dict_type(), vm)?; - let body = args.get_optional_kwarg("body"); - let content_type = args.get_optional_kwarg_with_type("content_type", vm.ctx.str_type(), vm)?; +fn browser_fetch(url: PyStringRef, args: FetchArgs, vm: &VirtualMachine) -> PyResult { + let FetchArgs { + response_format, + method, + headers, + body, + content_type, + } = args; let response_format = match response_format { - Some(s) => FetchResponseFormat::from_str(vm, &objstr::get_value(&s))?, + Some(s) => FetchResponseFormat::from_str(vm, s.as_str())?, None => FetchResponseFormat::Text, }; let mut opts = web_sys::RequestInit::new(); match method { - Some(s) => opts.method(&objstr::get_value(&s)), + Some(s) => opts.method(s.as_str()), None => opts.method("GET"), }; @@ -68,16 +78,15 @@ fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { opts.body(Some(&convert::py_to_js(vm, body))); } - let request = web_sys::Request::new_with_str_and_init(&objstr::get_value(url), &opts) + let request = web_sys::Request::new_with_str_and_init(url.as_str(), &opts) .map_err(|err| convert::js_py_typeerror(vm, err))?; if let Some(headers) = headers { let h = request.headers(); - let headers: PyDictRef = headers.downcast().unwrap(); for (key, value) in headers.get_key_value_pairs() { - let key = &vm.to_str(&key)?.value; - let value = &vm.to_str(&value)?.value; - h.set(key, value) + let key = vm.to_str(&key)?; + let value = vm.to_str(&value)?; + h.set(key.as_str(), value.as_str()) .map_err(|err| convert::js_py_typeerror(vm, err))?; } } @@ -85,7 +94,7 @@ fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if let Some(content_type) = content_type { request .headers() - .set("Content-Type", &objstr::get_value(&content_type)) + .set("Content-Type", content_type.as_str()) .map_err(|err| convert::js_py_typeerror(vm, err))?; } @@ -104,9 +113,7 @@ fn browser_fetch(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(PyPromise::from_future(future).into_ref(vm).into_object()) } -fn browser_request_animation_frame(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(func, Some(vm.ctx.function_type()))]); - +fn browser_request_animation_frame(func: PyFunctionRef, vm: &VirtualMachine) -> PyResult { use std::{cell::RefCell, rc::Rc}; // this basic setup for request_animation_frame taken from: @@ -115,18 +122,16 @@ fn browser_request_animation_frame(vm: &VirtualMachine, args: PyFuncArgs) -> PyR let f = Rc::new(RefCell::new(None)); let g = f.clone(); - let func = func.clone(); - - let acc_vm = AccessibleVM::from(vm); + let weak_vm = weak_vm(vm); *g.borrow_mut() = Some(Closure::wrap(Box::new(move |time: f64| { - let stored_vm = acc_vm + let stored_vm = weak_vm .upgrade() .expect("that the vm is valid from inside of request_animation_frame"); let vm = &stored_vm.vm; let func = func.clone(); let args = vec![vm.ctx.new_float(time)]; - let _ = vm.invoke(func, args); + let _ = vm.invoke(func.into_object(), args); let closure = f.borrow_mut().take(); drop(closure); @@ -141,10 +146,8 @@ fn browser_request_animation_frame(vm: &VirtualMachine, args: PyFuncArgs) -> PyR Ok(vm.ctx.new_int(id)) } -fn browser_cancel_animation_frame(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(id, Some(vm.ctx.int_type()))]); - - let id = objint::get_value(id).to_i32().ok_or_else(|| { +fn browser_cancel_animation_frame(id: PyIntRef, vm: &VirtualMachine) -> PyResult { + let id = id.as_bigint().to_i32().ok_or_else(|| { vm.new_exception( vm.ctx.exceptions.value_error.clone(), "Integer too large to convert to i32 for animationFrame id".into(), @@ -186,14 +189,14 @@ impl PyPromise { fn then( zelf: PyPromiseRef, - on_fulfill: PyRef, - on_reject: OptionalArg>, + on_fulfill: PyFunctionRef, + on_reject: OptionalArg, vm: &VirtualMachine, ) -> PyPromiseRef { - let acc_vm = AccessibleVM::from(vm); + let weak_vm = weak_vm(vm); let ret_future = JsFuture::from(zelf.value.clone()).then(move |res| { - let stored_vm = &acc_vm + let stored_vm = &weak_vm .upgrade() .expect("that the vm is valid when the promise resolves"); let vm = &stored_vm.vm; @@ -217,16 +220,12 @@ impl PyPromise { PyPromise::from_future(ret_future).into_ref(vm) } - fn catch( - zelf: PyPromiseRef, - on_reject: PyRef, - vm: &VirtualMachine, - ) -> PyPromiseRef { - let acc_vm = AccessibleVM::from(vm); + fn catch(zelf: PyPromiseRef, on_reject: PyFunctionRef, vm: &VirtualMachine) -> PyPromiseRef { + let weak_vm = weak_vm(vm); let ret_future = JsFuture::from(zelf.value.clone()).then(move |res| { res.or_else(|err| { - let stored_vm = acc_vm + let stored_vm = weak_vm .upgrade() .expect("that the vm is valid when the promise resolves"); let vm = &stored_vm.vm; @@ -303,41 +302,31 @@ impl Element { } } -fn browser_alert(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(message, Some(vm.ctx.str_type()))]); - +fn browser_alert(message: PyStringRef, vm: &VirtualMachine) -> PyResult { window() - .alert_with_message(&objstr::get_value(message)) + .alert_with_message(message.as_str()) .expect("alert() not to fail"); Ok(vm.get_none()) } -fn browser_confirm(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(message, Some(vm.ctx.str_type()))]); - +fn browser_confirm(message: PyStringRef, vm: &VirtualMachine) -> PyResult { let result = window() - .confirm_with_message(&objstr::get_value(message)) + .confirm_with_message(message.as_str()) .expect("confirm() not to fail"); Ok(vm.new_bool(result)) } -fn browser_prompt(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(message, Some(vm.ctx.str_type()))], - optional = [(default, Some(vm.ctx.str_type()))] - ); - - let result = if let Some(default) = default { - window().prompt_with_message_and_default( - &objstr::get_value(message), - &objstr::get_value(default), - ) +fn browser_prompt( + message: PyStringRef, + default: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let result = if let OptionalArg::Present(default) = default { + window().prompt_with_message_and_default(message.as_str(), default.as_str()) } else { - window().prompt_with_message(&objstr::get_value(message)) + window().prompt_with_message(message.as_str()) }; let result = match result.expect("prompt() not to fail") { diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 717c9a9730..6fb527d76b 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -8,7 +8,7 @@ use rustpython_vm::pyobject::{DictProtocol, PyObjectRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; use crate::browser_module; -use crate::vm_class::{AccessibleVM, WASMVirtualMachine}; +use crate::vm_class::{stored_vm_from_wasm, WASMVirtualMachine}; pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { macro_rules! map_exceptions { @@ -81,11 +81,7 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { return Err(err); } }; - let acc_vm = AccessibleVM::from(wasm_vm.clone()); - let stored_vm = acc_vm - .upgrade() - .expect("acc. VM to be invalid when WASM vm is valid"); - let vm = &stored_vm.vm; + let vm = &stored_vm_from_wasm(&wasm_vm).vm; let mut py_func_args = PyFuncArgs::default(); if let Some(ref args) = args { for arg in args.values() { diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index d6f9ede0ec..3f7bdef8ae 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -43,8 +43,24 @@ impl StoredVirtualMachine { // probably gets compiled down to a normal-ish static varible, like Atomic* types do: // https://rustwasm.github.io/2018/10/24/multithreading-rust-and-wasm.html#atomic-instructions thread_local! { - static STORED_VMS: RefCell>> = - RefCell::default(); + static STORED_VMS: RefCell>> = RefCell::default(); +} + +pub(crate) fn stored_vm_from_wasm(wasm_vm: &WASMVirtualMachine) -> Rc { + STORED_VMS.with(|cell| { + cell.borrow() + .get(&wasm_vm.id) + .expect("VirtualMachine is not valid") + .clone() + }) +} +pub(crate) fn weak_vm(vm: &VirtualMachine) -> Weak { + let id = vm + .wasm_id + .as_ref() + .expect("VirtualMachine inside of WASM crate should have wasm_id set"); + STORED_VMS + .with(|cell| Rc::downgrade(cell.borrow().get(id).expect("VirtualMachine is not valid"))) } #[wasm_bindgen(js_name = vmStore)] @@ -104,44 +120,6 @@ impl VMStore { } } -#[derive(Clone)] -pub(crate) struct AccessibleVM { - weak: Weak, - id: String, -} - -impl AccessibleVM { - pub fn from_id(id: String) -> AccessibleVM { - let weak = STORED_VMS - .with(|cell| Rc::downgrade(cell.borrow().get(&id).expect("WASM VM to be valid"))); - AccessibleVM { weak, id } - } - - pub fn upgrade(&self) -> Option> { - self.weak.upgrade() - } -} - -impl From for AccessibleVM { - fn from(vm: WASMVirtualMachine) -> AccessibleVM { - AccessibleVM::from_id(vm.id) - } -} -impl From<&WASMVirtualMachine> for AccessibleVM { - fn from(vm: &WASMVirtualMachine) -> AccessibleVM { - AccessibleVM::from_id(vm.id.clone()) - } -} -impl From<&VirtualMachine> for AccessibleVM { - fn from(vm: &VirtualMachine) -> AccessibleVM { - AccessibleVM::from_id( - vm.wasm_id - .clone() - .expect("VM passed to from::() to have wasm_id be Some()"), - ) - } -} - #[wasm_bindgen(js_name = VirtualMachine)] #[derive(Clone)] pub struct WASMVirtualMachine { From 3e42edd2613791cd91cdc262d7573640bac33970 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 6 Apr 2019 08:51:44 +0100 Subject: [PATCH 176/884] dict.__new__ - support for dict subtypes. --- tests/snippets/dict.py | 7 +++++++ vm/src/obj/objdict.rs | 16 ++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 2d369465cd..d2aa9ed5bc 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -57,6 +57,13 @@ def dict_eq(d1, d2): assert x.get("here", "default") == "here" assert x.get("not here") == None +class LengthDict(dict): + pass + +x = LengthDict() +assert type(x) == LengthDict + + # An object that hashes to the same value always, and compares equal if any its values match. class Hashable(object): def __init__(self, *args): diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 541645d5f7..6388aa9ab4 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -40,17 +40,18 @@ impl PyValue for PyDict { // Python dict methods: impl PyDictRef { fn new( - _class: PyClassRef, // TODO Support subclasses of int. + class: PyClassRef, dict_obj: OptionalArg, kwargs: KwArgs, vm: &VirtualMachine, ) -> PyResult { - let dict = vm.ctx.new_dict(); + let mut dict = DictContentType::default(); + if let OptionalArg::Present(dict_obj) = dict_obj { let dicted: PyResult = dict_obj.clone().downcast(); if let Ok(dict_obj) = dicted { for (key, value) in dict_obj.get_key_value_pairs() { - dict.set_item(key, value, vm); + dict.insert(vm, &key, value)?; } } else { let iter = objiter::get_iter(vm, &dict_obj)?; @@ -68,14 +69,17 @@ impl PyDictRef { if objiter::get_next_object(vm, &elem_iter)?.is_some() { return Err(err(vm)); } - dict.set_item(key, value, vm); + dict.insert(vm, &key, value)?; } } } for (key, value) in kwargs.into_iter() { - dict.set_item(vm.new_str(key), value, vm); + dict.insert(vm, &vm.new_str(key), value)?; + } + PyDict { + entries: RefCell::new(dict), } - Ok(dict) + .into_ref_with_type(vm, class) } fn bool(self, _vm: &VirtualMachine) -> bool { From ee9066a713e0b81802e718b79a485eeee1bf06b3 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 6 Apr 2019 09:02:49 +0100 Subject: [PATCH 177/884] dict.get shouldn't call into __getitem__ --- tests/snippets/dict.py | 6 ++++-- vm/src/dictdatatype.rs | 7 +++---- vm/src/obj/objdict.rs | 22 ++++++++++++++-------- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index d2aa9ed5bc..e28fd9da45 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -58,11 +58,13 @@ def dict_eq(d1, d2): assert x.get("not here") == None class LengthDict(dict): - pass + def __getitem__(self, k): + return len(k) x = LengthDict() assert type(x) == LengthDict - +assert x['word'] == 4 +assert x.get('word') is None # An object that hashes to the same value always, and compares equal if any its values match. class Hashable(object): diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index ac1df994a9..65ad55110b 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -76,16 +76,15 @@ impl Dict { } /// Retrieve a key - pub fn get(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { + pub fn get(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult> { if let LookupResult::Existing(index) = self.lookup(vm, key)? { if let Some(entry) = &self.entries[index] { - Ok(entry.value.clone()) + Ok(Some(entry.value.clone())) } else { panic!("Lookup returned invalid index into entries!"); } } else { - let key_repr = vm.to_pystr(key)?; - Err(vm.new_key_error(format!("Key not found: {}", key_repr))) + Ok(None) } } diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 6388aa9ab4..0e1a2aedd7 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -179,7 +179,13 @@ impl PyDictRef { } fn getitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.entries.borrow().get(vm, &key) + match self.entries.borrow().get(vm, &key)? { + Some(value) => Ok(value), + None => { + let key_repr = vm.to_pystr(&key)?; + Err(vm.new_key_error(format!("Key not found: {}", key_repr))) + } + } } fn get( @@ -187,12 +193,12 @@ impl PyDictRef { key: PyObjectRef, default: OptionalArg, vm: &VirtualMachine, - ) -> PyObjectRef { - match self.into_object().get_item(key, vm) { - Ok(value) => value, - Err(_) => match default { - OptionalArg::Present(value) => value, - OptionalArg::Missing => vm.ctx.none(), + ) -> PyResult { + match self.entries.borrow().get(vm, &key)? { + Some(value) => Ok(value), + None => match default { + OptionalArg::Present(value) => Ok(value), + OptionalArg::Missing => Ok(vm.ctx.none()), }, } } @@ -220,7 +226,7 @@ impl PyDictRef { impl DictProtocol for PyDictRef { fn get_item(&self, key: T, vm: &VirtualMachine) -> Option { let key = key.into_pyobject(vm).unwrap(); - self.entries.borrow().get(vm, &key).ok() + self.entries.borrow().get(vm, &key).unwrap() } // Item set/get: From 7b2d92f495e037fd85c19398ee2ba37b42a79cae Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 6 Apr 2019 09:41:46 +0100 Subject: [PATCH 178/884] Delete DictProtocol. impl ItemProtocol for PyDictRef. --- tests/snippets/dict.py | 2 ++ vm/src/builtins.rs | 4 ++-- vm/src/frame.rs | 18 +++++++++--------- vm/src/import.rs | 4 ++-- vm/src/obj/objdict.rs | 34 +++++++++++++++++++--------------- vm/src/obj/objobject.rs | 18 +++++++++++------- vm/src/obj/objsuper.rs | 4 ++-- vm/src/pyobject.rs | 22 ++++++++++++++++------ vm/src/stdlib/json.rs | 6 ++---- vm/src/sysmodule.rs | 6 +++--- vm/src/vm.rs | 14 +++++++------- wasm/lib/src/convert.rs | 5 +++-- 12 files changed, 78 insertions(+), 59 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index e28fd9da45..c04165f166 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -66,6 +66,8 @@ def __getitem__(self, k): assert x['word'] == 4 assert x.get('word') is None +assert 5 == eval("a + word", LengthDict()) + # An object that hashes to the same value always, and compares equal if any its values match. class Hashable(object): def __init__(self, *args): diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 5aa5ee226d..210d12b49e 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -20,7 +20,7 @@ use crate::obj::objtype::{self, PyClassRef}; use crate::frame::Scope; use crate::function::{Args, OptionalArg, PyFuncArgs}; use crate::pyobject::{ - DictProtocol, IdProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, + IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -804,6 +804,6 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu "__call__", vec![name_arg, bases, namespace.into_object()], )?; - cells.set_item("__class__", class.clone(), vm); + cells.set_item("__class__", class.clone(), vm)?; Ok(class) } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 2f525afdd1..ca3e54d074 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -21,8 +21,8 @@ use crate::obj::objstr; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - DictProtocol, IdProtocol, ItemProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, - TryFromObject, TypeProtocol, + IdProtocol, ItemProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, + TypeProtocol, }; use crate::vm::VirtualMachine; use itertools::Itertools; @@ -133,12 +133,12 @@ pub trait NameProtocol { impl NameProtocol for Scope { fn load_name(&self, vm: &VirtualMachine, name: &str) -> Option { for dict in self.locals.iter() { - if let Some(value) = dict.get_item(name, vm) { + if let Some(value) = dict.get_item_option(name, vm).unwrap() { return Some(value); } } - if let Some(value) = self.globals.get_item(name, vm) { + if let Some(value) = self.globals.get_item_option(name, vm).unwrap() { return Some(value); } @@ -147,7 +147,7 @@ impl NameProtocol for Scope { fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option { for dict in self.locals.iter().skip(1) { - if let Some(value) = dict.get_item(name, vm) { + if let Some(value) = dict.get_item_option(name, vm).unwrap() { return Some(value); } } @@ -155,11 +155,11 @@ impl NameProtocol for Scope { } fn store_name(&self, vm: &VirtualMachine, key: &str, value: PyObjectRef) { - self.get_locals().set_item(key, value, vm) + self.get_locals().set_item(key, value, vm).unwrap(); } fn delete_name(&self, vm: &VirtualMachine, key: &str) { - self.get_locals().del_item(key, vm) + self.get_locals().del_item(key, vm).unwrap(); } } @@ -394,12 +394,12 @@ impl Frame { obj.downcast().expect("Need a dictionary to build a map."); let dict_elements = dict.get_key_value_pairs(); for (key, value) in dict_elements.iter() { - map_obj.set_item(key.clone(), value.clone(), vm); + map_obj.set_item(key.clone(), value.clone(), vm).unwrap(); } } } else { for (key, value) in self.pop_multiple(2 * size).into_iter().tuples() { - map_obj.set_item(key, value, vm) + map_obj.set_item(key, value, vm).unwrap(); } } diff --git a/vm/src/import.rs b/vm/src/import.rs index 713393debf..14c0a75665 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -8,7 +8,7 @@ use std::path::PathBuf; use crate::compile; use crate::frame::Scope; use crate::obj::{objsequence, objstr}; -use crate::pyobject::{DictProtocol, ItemProtocol, PyResult}; +use crate::pyobject::{ItemProtocol, PyResult}; use crate::util; use crate::vm::VirtualMachine; @@ -39,7 +39,7 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s // trace!("Code object: {:?}", code_obj); let attrs = vm.ctx.new_dict(); - attrs.set_item("__name__", vm.new_str(module.to_string()), vm); + attrs.set_item("__name__", vm.new_str(module.to_string()), vm)?; vm.run_code_obj(code_obj, Scope::new(None, attrs.clone()))?; Ok(vm.ctx.new_module(module, attrs)) } diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 0e1a2aedd7..02f8443b5b 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -3,8 +3,7 @@ use std::fmt; use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ - DictProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, - PyResult, PyValue, + IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, PyValue, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -174,8 +173,8 @@ impl PyDictRef { self.entries.borrow().get_items() } - fn setitem(self, key: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) { - self.set_item(key, value, vm) + fn setitem(self, key: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + self.entries.borrow_mut().insert(vm, &key, value) } fn getitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -223,21 +222,26 @@ impl PyDictRef { } } -impl DictProtocol for PyDictRef { - fn get_item(&self, key: T, vm: &VirtualMachine) -> Option { - let key = key.into_pyobject(vm).unwrap(); - self.entries.borrow().get(vm, &key).unwrap() +impl ItemProtocol for PyDictRef { + fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult { + vm.call_method(self.as_object(), "__getitem__", key.into_pyobject(vm)?) } - // Item set/get: - fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine) { - let key = key.into_pyobject(vm).unwrap(); - self.entries.borrow_mut().insert(vm, &key, value).unwrap() + fn set_item( + &self, + key: T, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + vm.call_method( + self.as_object(), + "__setitem__", + vec![key.into_pyobject(vm)?, value], + ) } - fn del_item(&self, key: T, vm: &VirtualMachine) { - let key = key.into_pyobject(vm).unwrap(); - self.entries.borrow_mut().delete(vm, &key).unwrap(); + fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult { + vm.call_method(self.as_object(), "__delitem__", key.into_pyobject(vm)?) } } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index c03ff31a15..a3bc411038 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -6,7 +6,7 @@ use crate::function::PyFuncArgs; use crate::obj::objproperty::PropertyBuilder; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - DictProtocol, IdProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyResult, PyValue, + IdProtocol, ItemProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -77,7 +77,7 @@ fn object_setattr( } if let Some(ref dict) = obj.clone().dict { - dict.set_item(attr_name, value, vm); + dict.set_item(attr_name, value, vm)?; Ok(()) } else { Err(vm.new_attribute_error(format!( @@ -98,7 +98,7 @@ fn object_delattr(obj: PyObjectRef, attr_name: PyStringRef, vm: &VirtualMachine) } if let Some(ref dict) = obj.dict { - dict.del_item(attr_name, vm); + dict.del_item(attr_name, vm)?; Ok(()) } else { Err(vm.new_attribute_error(format!( @@ -208,7 +208,7 @@ fn object_getattribute(obj: PyObjectRef, name_str: PyStringRef, vm: &VirtualMach } } - if let Some(obj_attr) = object_getattr(&obj, &name, &vm) { + if let Some(obj_attr) = object_getattr(&obj, &name, &vm)? { Ok(obj_attr) } else if let Some(attr) = objtype::class_get_attr(&cls, &name) { vm.call_get_descriptor(attr, obj) @@ -219,11 +219,15 @@ fn object_getattribute(obj: PyObjectRef, name_str: PyStringRef, vm: &VirtualMach } } -fn object_getattr(obj: &PyObjectRef, attr_name: &str, vm: &VirtualMachine) -> Option { +fn object_getattr( + obj: &PyObjectRef, + attr_name: &str, + vm: &VirtualMachine, +) -> PyResult> { if let Some(ref dict) = obj.dict { - dict.get_item(attr_name, vm) + dict.get_item_option(attr_name, vm) } else { - None + Ok(None) } } diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 19c7c2ea88..94fe0fc7b7 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -12,7 +12,7 @@ use crate::obj::objfunction::PyMethod; use crate::obj::objstr; use crate::obj::objtype::{PyClass, PyClassRef}; use crate::pyobject::{ - DictProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, + ItemProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -124,7 +124,7 @@ fn super_new( } else { let frame = vm.current_frame().expect("no current frame for super()"); if let Some(first_arg) = frame.code.arg_names.get(0) { - match vm.get_locals().get_item(first_arg, vm) { + match vm.get_locals().get_item_option(first_arg, vm)? { Some(obj) => obj.clone(), _ => { return Err(vm diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index bf5cf3c860..ee765f9875 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -912,12 +912,6 @@ impl TypeProtocol for PyRef { } } -pub trait DictProtocol { - fn get_item(&self, key: T, vm: &VirtualMachine) -> Option; - fn set_item(&self, key: T, value: PyObjectRef, vm: &VirtualMachine); - fn del_item(&self, key: T, vm: &VirtualMachine); -} - pub trait ItemProtocol { fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult; fn set_item( @@ -927,6 +921,22 @@ pub trait ItemProtocol { vm: &VirtualMachine, ) -> PyResult; fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult; + fn get_item_option( + &self, + key: T, + vm: &VirtualMachine, + ) -> PyResult> { + match self.get_item(key, vm) { + Ok(value) => Ok(Some(value)), + Err(exc) => { + if objtype::isinstance(&exc, &vm.ctx.exceptions.key_error) { + Ok(None) + } else { + Err(exc) + } + } + } + } } impl ItemProtocol for PyObjectRef { diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index b7cb704b1c..f4061bc2a0 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -13,9 +13,7 @@ use crate::obj::{ objstr::{self, PyString}, objtype, }; -use crate::pyobject::{ - create_type, DictProtocol, IdProtocol, ItemProtocol, PyObjectRef, PyResult, TypeProtocol, -}; +use crate::pyobject::{create_type, IdProtocol, ItemProtocol, PyObjectRef, PyResult, TypeProtocol}; use crate::VirtualMachine; use num_traits::cast::ToPrimitive; @@ -178,7 +176,7 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { Some(PyString { ref value }) => value.clone(), _ => unimplemented!("map keys must be strings"), }; - dict.set_item(&key, value, self.vm); + dict.set_item(&key, value, self.vm).unwrap(); } Ok(dict.into_object()) } diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 772dfe198e..2f86904487 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -3,7 +3,7 @@ use std::{env, mem}; use crate::frame::FrameRef; use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{DictProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{ItemProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; /* @@ -139,6 +139,6 @@ settrace() -- set the global debug tracing function "modules" => modules.clone(), }); - modules.set_item("sys", module.clone(), vm); - modules.set_item("builtins", builtins.clone(), vm); + modules.set_item("sys", module.clone(), vm).unwrap(); + modules.set_item("builtins", builtins.clone(), vm).unwrap(); } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 0bd2555742..90daaa6687 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -30,7 +30,7 @@ use crate::obj::objtuple::PyTuple; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - DictProtocol, IdProtocol, PyContext, PyObjectRef, PyResult, PyValue, TryFromObject, TryIntoRef, + IdProtocol, ItemProtocol, PyContext, PyObjectRef, PyResult, PyValue, TryFromObject, TryIntoRef, TypeProtocol, }; use crate::stdlib; @@ -423,7 +423,7 @@ impl VirtualMachine { for i in 0..n { let arg_name = &code_object.arg_names[i]; let arg = &args.args[i]; - locals.set_item(arg_name, arg.clone(), self); + locals.set_item(arg_name, arg.clone(), self)?; } // Pack other positional arguments in to *args: @@ -436,7 +436,7 @@ impl VirtualMachine { } let vararg_value = self.ctx.new_tuple(last_args); - locals.set_item(vararg_name, vararg_value, self); + locals.set_item(vararg_name, vararg_value, self)?; } bytecode::Varargs::Unnamed => { // just ignore the rest of the args @@ -456,7 +456,7 @@ impl VirtualMachine { let kwargs = match code_object.varkeywords { bytecode::Varargs::Named(ref kwargs_name) => { let d = self.ctx.new_dict(); - locals.set_item(kwargs_name, d.as_object().clone(), self); + locals.set_item(kwargs_name, d.as_object().clone(), self)?; Some(d) } bytecode::Varargs::Unnamed => Some(self.ctx.new_dict()), @@ -474,9 +474,9 @@ impl VirtualMachine { ); } - locals.set_item(&name, value, self); + locals.set_item(&name, value, self)?; } else if let Some(d) = &kwargs { - d.set_item(&name, value, self); + d.set_item(&name, value, self)?; } else { return Err( self.new_type_error(format!("Got an unexpected keyword argument '{}'", name)) @@ -520,7 +520,7 @@ impl VirtualMachine { for (default_index, i) in (required_args..nexpected_args).enumerate() { let arg_name = &code_object.arg_names[i]; if !locals.contains_key(arg_name, self) { - locals.set_item(arg_name, available_defaults[default_index].clone(), self); + locals.set_item(arg_name, available_defaults[default_index].clone(), self)?; } } }; diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 717c9a9730..25024734c3 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -4,7 +4,7 @@ use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; use rustpython_vm::function::PyFuncArgs; use rustpython_vm::obj::{objbytes, objint, objsequence, objtype}; -use rustpython_vm::pyobject::{DictProtocol, PyObjectRef, PyResult, PyValue}; +use rustpython_vm::pyobject::{ItemProtocol, PyObjectRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; use crate::browser_module; @@ -192,7 +192,8 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { for pair in object_entries(&Object::from(js_val)) { let (key, val) = pair.expect("iteration over object to not fail"); let py_val = js_to_py(vm, val); - dict.set_item(&String::from(js_sys::JsString::from(key)), py_val, vm); + dict.set_item(&String::from(js_sys::JsString::from(key)), py_val, vm) + .unwrap(); } dict.into_object() } From 0fe3f7748cf5a8a01097a70ee2db9292fd033a18 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 6 Apr 2019 09:46:17 +0100 Subject: [PATCH 179/884] Simplify PyDictRef.__repr__. --- vm/src/obj/objdict.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 02f8443b5b..7cefce9af5 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -89,7 +89,7 @@ impl PyDictRef { self.entries.borrow().len() } - fn repr(self, vm: &VirtualMachine) -> PyResult { + fn repr(self, vm: &VirtualMachine) -> PyResult { let s = if let Some(_guard) = ReprGuard::enter(self.as_object()) { let mut str_parts = vec![]; for (key, value) in self.get_key_value_pairs() { @@ -102,7 +102,7 @@ impl PyDictRef { } else { "{...}".to_string() }; - Ok(vm.new_str(s)) + Ok(s) } fn contains(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { From e2a4f22be87cdd43c00eed9f134379c9a3d3f21e Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 6 Apr 2019 09:49:32 +0100 Subject: [PATCH 180/884] Avoid name similarity between ItemProtocol and inner methods. --- vm/src/obj/objdict.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 7cefce9af5..17910db235 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -109,7 +109,7 @@ impl PyDictRef { self.entries.borrow().contains(vm, &key) } - fn delitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + fn inner_delitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self.entries.borrow_mut().delete(vm, &key) } @@ -173,11 +173,16 @@ impl PyDictRef { self.entries.borrow().get_items() } - fn setitem(self, key: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + fn inner_setitem( + self, + key: PyObjectRef, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult<()> { self.entries.borrow_mut().insert(vm, &key, value) } - fn getitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn inner_getitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { match self.entries.borrow().get(vm, &key)? { Some(value) => Ok(value), None => { @@ -250,12 +255,12 @@ pub fn init(context: &PyContext) { "__bool__" => context.new_rustfunc(PyDictRef::bool), "__len__" => context.new_rustfunc(PyDictRef::len), "__contains__" => context.new_rustfunc(PyDictRef::contains), - "__delitem__" => context.new_rustfunc(PyDictRef::delitem), - "__getitem__" => context.new_rustfunc(PyDictRef::getitem), + "__delitem__" => context.new_rustfunc(PyDictRef::inner_delitem), + "__getitem__" => context.new_rustfunc(PyDictRef::inner_getitem), "__iter__" => context.new_rustfunc(PyDictRef::iter), "__new__" => context.new_rustfunc(PyDictRef::new), "__repr__" => context.new_rustfunc(PyDictRef::repr), - "__setitem__" => context.new_rustfunc(PyDictRef::setitem), + "__setitem__" => context.new_rustfunc(PyDictRef::inner_setitem), "__hash__" => context.new_rustfunc(PyDictRef::hash), "clear" => context.new_rustfunc(PyDictRef::clear), "values" => context.new_rustfunc(PyDictRef::values), From b1dd5836af08bd550cf85005d0c4eaccc1e0cdcd Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 6 Apr 2019 09:52:18 +0100 Subject: [PATCH 181/884] Simpler implementation of ItemProtocol for PyDictRef. --- vm/src/obj/objdict.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 17910db235..3b7a336668 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -229,7 +229,7 @@ impl PyDictRef { impl ItemProtocol for PyDictRef { fn get_item(&self, key: T, vm: &VirtualMachine) -> PyResult { - vm.call_method(self.as_object(), "__getitem__", key.into_pyobject(vm)?) + self.as_object().get_item(key, vm) } fn set_item( @@ -238,15 +238,11 @@ impl ItemProtocol for PyDictRef { value: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - vm.call_method( - self.as_object(), - "__setitem__", - vec![key.into_pyobject(vm)?, value], - ) + self.as_object().set_item(key, value, vm) } fn del_item(&self, key: T, vm: &VirtualMachine) -> PyResult { - vm.call_method(self.as_object(), "__delitem__", key.into_pyobject(vm)?) + self.as_object().del_item(key, vm) } } From 584b707356ba6aa614ab3d7d2de02feb280c7f79 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 6 Apr 2019 10:04:18 +0100 Subject: [PATCH 182/884] Add dict.__missing__ support. --- tests/snippets/dict.py | 10 ++++++++++ vm/src/obj/objdict.rs | 14 ++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index c04165f166..69040d6246 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -68,6 +68,16 @@ def __getitem__(self, k): assert 5 == eval("a + word", LengthDict()) + +class Squares(dict): + def __missing__(self, k): + v = k * k + self[k] = v + return v + +x = Squares() +assert x[-5] == 25 + # An object that hashes to the same value always, and compares equal if any its values match. class Hashable(object): def __init__(self, *args): diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 3b7a336668..d54f62bb71 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -183,13 +183,15 @@ impl PyDictRef { } fn inner_getitem(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match self.entries.borrow().get(vm, &key)? { - Some(value) => Ok(value), - None => { - let key_repr = vm.to_pystr(&key)?; - Err(vm.new_key_error(format!("Key not found: {}", key_repr))) - } + if let Some(value) = self.entries.borrow().get(vm, &key)? { + return Ok(value); } + + if let Ok(method) = vm.get_method(self.clone().into_object(), "__missing__") { + return vm.invoke(method, vec![key]); + } + + Err(vm.new_key_error(format!("Key not found: {}", vm.to_pystr(&key)?))) } fn get( From afb28bfccba4e4b07f5c43b6100d330b18fdc430 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 6 Apr 2019 11:53:59 +0200 Subject: [PATCH 183/884] Fix moment when docstring is set. --- tests/snippets/function.py | 14 ++++++++++++++ vm/src/compile.rs | 31 +++++++++++++------------------ 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/tests/snippets/function.py b/tests/snippets/function.py index aafc9b53c0..e4d0ab544a 100644 --- a/tests/snippets/function.py +++ b/tests/snippets/function.py @@ -41,3 +41,17 @@ def f4(): pass assert f4.__doc__ == "test4" + + +def revdocstr(f): + d = f.__doc__ + d = d + 'w00t' + f.__doc__ = d + return f + +@revdocstr +def f5(): + """abc""" + +assert f5.__doc__ == 'abcw00t', f5.__doc__ + diff --git a/vm/src/compile.rs b/vm/src/compile.rs index d7f774b83a..d0ea96d324 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -650,25 +650,13 @@ impl Compiler { // Turn code object into function object: self.emit(Instruction::MakeFunction { flags }); + self.store_docstring(doc_str); self.apply_decorators(decorator_list); self.emit(Instruction::StoreName { name: name.to_string(), }); - if let Some(doc_string) = doc_str { - self.emit(Instruction::LoadConst { - value: bytecode::Constant::String { - value: doc_string.to_string(), - }, - }); - self.emit(Instruction::LoadName { - name: name.to_string(), - }); - self.emit(Instruction::StoreAttr { - name: "__doc__".to_string(), - }); - } self.in_loop = was_in_loop; self.in_function_def = was_in_function_def; Ok(()) @@ -761,26 +749,33 @@ impl Compiler { }); } + self.store_docstring(doc_str); self.apply_decorators(decorator_list); self.emit(Instruction::StoreName { name: name.to_string(), }); + self.in_loop = was_in_loop; + Ok(()) + } + + fn store_docstring(&mut self, doc_str: Option) { if let Some(doc_string) = doc_str { + // Duplicate top of stack (the function or class object) + self.emit(Instruction::Duplicate); + + // Doc string value: self.emit(Instruction::LoadConst { value: bytecode::Constant::String { value: doc_string.to_string(), }, }); - self.emit(Instruction::LoadName { - name: name.to_string(), - }); + + self.emit(Instruction::Rotate { amount: 2 }); self.emit(Instruction::StoreAttr { name: "__doc__".to_string(), }); } - self.in_loop = was_in_loop; - Ok(()) } fn compile_for( From 1f0fd39722e63468884ee4b89947b97c89d40657 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 6 Apr 2019 17:29:40 +0300 Subject: [PATCH 184/884] Change os new funcs to new arg style --- vm/src/obj/objbytes.rs | 2 +- vm/src/stdlib/os.rs | 38 +++++++++++--------------------------- 2 files changed, 12 insertions(+), 28 deletions(-) diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index f24c9273c5..4a8b1ad5eb 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -17,7 +17,7 @@ use super::objtype::PyClassRef; pub struct PyBytes { value: Vec, } -type PyBytesRef = PyRef; +pub type PyBytesRef = PyRef; impl PyBytes { pub fn new(data: Vec) -> Self { diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index cd4c0709bb..ae487598a0 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -6,9 +6,11 @@ use std::io::{ErrorKind, Read, Write}; use num_traits::cast::ToPrimitive; use crate::function::PyFuncArgs; -use crate::obj::objbytes; +use crate::obj::objbytes::PyBytesRef; use crate::obj::objint; +use crate::obj::objint::PyIntRef; use crate::obj::objstr; +use crate::obj::objstr::PyStringRef; use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; @@ -115,15 +117,9 @@ fn os_error(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Err(vm.new_os_error(msg)) } -fn os_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(fd, Some(vm.ctx.int_type())), (n, Some(vm.ctx.int_type()))] - ); - - let mut buffer = vec![0u8; objint::get_value(n).to_usize().unwrap()]; - let mut file = rust_file(objint::get_value(fd).to_i64().unwrap()); +fn os_read(fd: PyIntRef, n: PyIntRef, vm: &VirtualMachine) -> PyResult { + let mut buffer = vec![0u8; n.as_bigint().to_usize().unwrap()]; + let mut file = rust_file(fd.as_bigint().to_i64().unwrap()); match file.read_exact(&mut buffer) { Ok(_) => (), Err(s) => return Err(vm.new_os_error(s.to_string())), @@ -134,18 +130,9 @@ fn os_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bytes(buffer)) } -fn os_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (fd, Some(vm.ctx.int_type())), - (data, Some(vm.ctx.bytes_type())) - ] - ); - - let mut file = rust_file(objint::get_value(fd).to_i64().unwrap()); - let written = match file.write(&objbytes::get_value(&data)) { +fn os_write(fd: PyIntRef, data: PyBytesRef, vm: &VirtualMachine) -> PyResult { + let mut file = rust_file(fd.as_bigint().to_i64().unwrap()); + let written = match file.write(&data) { Ok(written) => written, Err(s) => return Err(vm.new_os_error(s.to_string())), }; @@ -155,11 +142,8 @@ fn os_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_int(written)) } -fn os_remove(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(path, Some(vm.ctx.str_type()))]); - let fname = objstr::get_value(&path); - - match fs::remove_file(fname) { +fn os_remove(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + match fs::remove_file(&path.value) { Ok(_) => (), Err(s) => return Err(vm.new_os_error(s.to_string())), } From 04acfe266523139f793e31e9cf148ab9374d27fe Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 6 Apr 2019 17:29:52 +0300 Subject: [PATCH 185/884] Remove failing line in windows --- tests/snippets/stdlib_os.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 29c8005dc8..ba6033ff8c 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -5,7 +5,6 @@ fd = os.open('README.md', 0) assert fd > 0 -assert_raises(OSError, lambda: os.read(fd + 1, 10)) os.close(fd) assert_raises(OSError, lambda: os.read(fd, 10)) From e67d5f3c627f8701f07dc55e5136f6c4389d7c9e Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 6 Apr 2019 15:27:46 +0100 Subject: [PATCH 186/884] Parameterise dictdatatype::Dict by value type. --- vm/src/dictdatatype.rs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 65ad55110b..6641a1884e 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -9,20 +9,29 @@ use num_traits::ToPrimitive; /// And: http://code.activestate.com/recipes/578375/ use std::collections::HashMap; -#[derive(Default)] -pub struct Dict { +pub struct Dict { size: usize, indices: HashMap, - entries: Vec>, + entries: Vec>>, } -struct DictEntry { +impl Default for Dict { + fn default() -> Self { + Dict { + size: 0, + indices: HashMap::new(), + entries: Vec::new(), + } + } +} + +struct DictEntry { hash: usize, key: PyObjectRef, - value: PyObjectRef, + value: T, } -impl Dict { +impl Dict { pub fn new() -> Self { Dict { size: 0, @@ -32,12 +41,7 @@ impl Dict { } /// Store a key - pub fn insert( - &mut self, - vm: &VirtualMachine, - key: &PyObjectRef, - value: PyObjectRef, - ) -> PyResult<()> { + pub fn insert(&mut self, vm: &VirtualMachine, key: &PyObjectRef, value: T) -> PyResult<()> { match self.lookup(vm, key)? { LookupResult::Existing(index) => { // Update existing key @@ -76,7 +80,7 @@ impl Dict { } /// Retrieve a key - pub fn get(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult> { + pub fn get(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult> { if let LookupResult::Existing(index) = self.lookup(vm, key)? { if let Some(entry) = &self.entries[index] { Ok(Some(entry.value.clone())) @@ -114,7 +118,7 @@ impl Dict { self.len() == 0 } - pub fn get_items(&self) -> Vec<(PyObjectRef, PyObjectRef)> { + pub fn get_items(&self) -> Vec<(PyObjectRef, T)> { self.entries .iter() .filter(|e| e.is_some()) From eb9378fe900db7a859f0d71ab99bf8ce9071e932 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 6 Apr 2019 18:20:18 +0300 Subject: [PATCH 187/884] Add os.mkdir --- vm/src/stdlib/os.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index ae487598a0..312b79549e 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -151,6 +151,15 @@ fn os_remove(path: PyStringRef, vm: &VirtualMachine) -> PyResult { Ok(vm.get_none()) } +fn os_mkdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + match fs::create_dir(&path.value) { + Ok(_) => (), + Err(s) => return Err(vm.new_os_error(s.to_string())), + } + + Ok(vm.get_none()) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -168,6 +177,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "write" => ctx.new_rustfunc(os_write), "remove" => ctx.new_rustfunc(os_remove), "unlink" => ctx.new_rustfunc(os_remove), + "mkdir" => ctx.new_rustfunc(os_mkdir), "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From 2ec6afeb11a4a400584c000b6606dbd0640f09a0 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 6 Apr 2019 18:24:06 +0300 Subject: [PATCH 188/884] Add os.mkdirs --- vm/src/stdlib/os.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 312b79549e..fc7651027e 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -160,6 +160,15 @@ fn os_mkdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { Ok(vm.get_none()) } +fn os_mkdirs(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + match fs::create_dir_all(&path.value) { + Ok(_) => (), + Err(s) => return Err(vm.new_os_error(s.to_string())), + } + + Ok(vm.get_none()) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -178,6 +187,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "remove" => ctx.new_rustfunc(os_remove), "unlink" => ctx.new_rustfunc(os_remove), "mkdir" => ctx.new_rustfunc(os_mkdir), + "mkdirs" => ctx.new_rustfunc(os_mkdirs), "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From bf6b12266a9b3996badd01f2ef36aac1b35ca4b7 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 6 Apr 2019 18:30:40 +0300 Subject: [PATCH 189/884] Add os.rmdir --- vm/src/stdlib/os.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index fc7651027e..6ffb36ca5b 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -169,6 +169,15 @@ fn os_mkdirs(path: PyStringRef, vm: &VirtualMachine) -> PyResult { Ok(vm.get_none()) } +fn os_rmdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + match fs::remove_dir(&path.value) { + Ok(_) => (), + Err(s) => return Err(vm.new_os_error(s.to_string())), + } + + Ok(vm.get_none()) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -188,6 +197,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "unlink" => ctx.new_rustfunc(os_remove), "mkdir" => ctx.new_rustfunc(os_mkdir), "mkdirs" => ctx.new_rustfunc(os_mkdirs), + "rmdir" => ctx.new_rustfunc(os_rmdir), "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From a21aa6eac9ca8016e13c54689c83120eb57b482c Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 6 Apr 2019 19:14:23 +0300 Subject: [PATCH 190/884] Add os.listdir --- vm/src/stdlib/os.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 6ffb36ca5b..f0f5c3dd50 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -178,6 +178,21 @@ fn os_rmdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { Ok(vm.get_none()) } +fn os_listdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + match fs::read_dir(&path.value) { + Ok(iter) => { + let res: PyResult> = iter + .map(|entry| match entry { + Ok(path) => Ok(vm.ctx.new_str(path.file_name().into_string().unwrap())), + Err(s) => Err(vm.new_os_error(s.to_string())), + }) + .collect(); + Ok(vm.ctx.new_list(res?)) + } + Err(s) => Err(vm.new_os_error(s.to_string())), + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -198,6 +213,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "mkdir" => ctx.new_rustfunc(os_mkdir), "mkdirs" => ctx.new_rustfunc(os_mkdirs), "rmdir" => ctx.new_rustfunc(os_rmdir), + "listdir" => ctx.new_rustfunc(os_listdir), "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From 401ca08b659d137200a80caf3bc87f08c3dc2642 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 6 Apr 2019 21:11:58 +0300 Subject: [PATCH 191/884] Use map_err --- vm/src/stdlib/os.rs | 36 ++++++++---------------------------- 1 file changed, 8 insertions(+), 28 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index f0f5c3dd50..25937cd5c1 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -142,40 +142,20 @@ fn os_write(fd: PyIntRef, data: PyBytesRef, vm: &VirtualMachine) -> PyResult { Ok(vm.ctx.new_int(written)) } -fn os_remove(path: PyStringRef, vm: &VirtualMachine) -> PyResult { - match fs::remove_file(&path.value) { - Ok(_) => (), - Err(s) => return Err(vm.new_os_error(s.to_string())), - } - - Ok(vm.get_none()) +fn os_remove(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + fs::remove_file(&path.value).map_err(|s| vm.new_os_error(s.to_string())) } -fn os_mkdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { - match fs::create_dir(&path.value) { - Ok(_) => (), - Err(s) => return Err(vm.new_os_error(s.to_string())), - } - - Ok(vm.get_none()) +fn os_mkdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + fs::create_dir(&path.value).map_err(|s| vm.new_os_error(s.to_string())) } -fn os_mkdirs(path: PyStringRef, vm: &VirtualMachine) -> PyResult { - match fs::create_dir_all(&path.value) { - Ok(_) => (), - Err(s) => return Err(vm.new_os_error(s.to_string())), - } - - Ok(vm.get_none()) +fn os_mkdirs(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + fs::create_dir_all(&path.value).map_err(|s| vm.new_os_error(s.to_string())) } -fn os_rmdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { - match fs::remove_dir(&path.value) { - Ok(_) => (), - Err(s) => return Err(vm.new_os_error(s.to_string())), - } - - Ok(vm.get_none()) +fn os_rmdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + fs::remove_dir(&path.value).map_err(|s| vm.new_os_error(s.to_string())) } fn os_listdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { From 5fd3cf2bcda2858acb5e2ca01163cf7a7a66dee1 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 6 Apr 2019 20:29:25 +1300 Subject: [PATCH 192/884] Implemented keyword only defaults --- tests/snippets/function_args.py | 36 +++++++++++---- vm/src/bytecode.rs | 1 + vm/src/compile.rs | 28 ++++++++++-- vm/src/frame.rs | 26 +++++++++-- vm/src/obj/objfunction.rs | 13 +++++- vm/src/pyobject.rs | 7 +-- vm/src/vm.rs | 79 +++++++++++++++------------------ 7 files changed, 127 insertions(+), 63 deletions(-) diff --git a/tests/snippets/function_args.py b/tests/snippets/function_args.py index 884cb47df1..fd164d1137 100644 --- a/tests/snippets/function_args.py +++ b/tests/snippets/function_args.py @@ -1,21 +1,28 @@ +from testutils import assertRaises + + def sum(x, y): return x+y -# def total(a, b, c, d): -# return sum(sum(a,b), sum(c,d)) -# -# assert total(1,1,1,1) == 4 -# assert total(1,2,3,4) == 10 +def total(a, b, c, d): + return sum(sum(a,b), sum(c,d)) + + +assert total(1,1,1,1) == 4 +assert total(1,2,3,4) == 10 assert sum(1, 1) == 2 assert sum(1, 3) == 4 + def sum2y(x, y): return x+y*2 + assert sum2y(1, 1) == 3 assert sum2y(1, 3) == 7 + def va(a, b=2, *c, d, **e): assert a == 1 assert b == 22 @@ -23,21 +30,34 @@ def va(a, b=2, *c, d, **e): assert d == 1337 assert e['f'] == 42 + va(1, 22, 3, 4, d=1337, f=42) + def va2(*args, **kwargs): assert args == (5, 4) assert len(kwargs) == 0 + va2(5, 4) x = (5, 4) va2(*x) va2(5, *x[1:]) -# def va3(x, *, b=2): -# pass -# va3(1, 2, 3, b=10) + +def va3(x, *, a, b=2, c=9): + return x + b + c + + +assert va3(1, a=1, b=10) == 20 + +with assertRaises(TypeError): + va3(1, 2, 3, a=1, b=10) + +with assertRaises(TypeError): + va3(1, b=10) + x = {'f': 42, 'e': 1337} y = {'d': 1337} diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 2fe485b63b..b248018616 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -31,6 +31,7 @@ pub struct CodeObject { bitflags! { pub struct FunctionOpArg: u8 { const HAS_DEFAULTS = 0x01; + const HAS_KW_ONLY_DEFAULTS = 0x02; const HAS_ANNOTATIONS = 0x04; } } diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 3780595c21..dd045c3bf9 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -422,8 +422,8 @@ impl Compiler { name: &str, args: &ast::Parameters, ) -> Result { - let have_kwargs = !args.defaults.is_empty(); - if have_kwargs { + let have_defaults = !args.defaults.is_empty(); + if have_defaults { // Construct a tuple: let size = args.defaults.len(); for element in &args.defaults { @@ -435,6 +435,25 @@ impl Compiler { }); } + let mut num_kw_only_defaults = 0; + for (kw, default) in args.kwonlyargs.iter().zip(&args.kw_defaults) { + if let Some(default) = default { + self.emit(Instruction::LoadConst { + value: bytecode::Constant::String { + value: kw.arg.clone(), + }, + }); + self.compile_expression(default)?; + num_kw_only_defaults += 1; + } + } + if num_kw_only_defaults > 0 { + self.emit(Instruction::BuildMap { + size: num_kw_only_defaults, + unpack: false, + }); + } + let line_number = self.get_source_line_number(); self.code_object_stack.push(CodeObject::new( args.args.iter().map(|a| a.arg.clone()).collect(), @@ -447,9 +466,12 @@ impl Compiler { )); let mut flags = bytecode::FunctionOpArg::empty(); - if have_kwargs { + if have_defaults { flags |= bytecode::FunctionOpArg::HAS_DEFAULTS; } + if num_kw_only_defaults > 0 { + flags |= bytecode::FunctionOpArg::HAS_KW_ONLY_DEFAULTS; + } Ok(flags) } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index ca3e54d074..2570c09345 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -12,12 +12,13 @@ use crate::function::PyFuncArgs; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode::PyCodeRef; -use crate::obj::objdict::PyDictRef; +use crate::obj::objdict::{PyDict, PyDictRef}; use crate::obj::objint::PyInt; use crate::obj::objiter; use crate::obj::objlist; use crate::obj::objslice::PySlice; use crate::obj::objstr; +use crate::obj::objtuple::PyTuple; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ @@ -578,16 +579,33 @@ impl Frame { vm.ctx.new_dict().into_object() }; + let kw_only_defaults = + if flags.contains(bytecode::FunctionOpArg::HAS_KW_ONLY_DEFAULTS) { + Some( + self.pop_value().downcast::().expect( + "Stack value for keyword only defaults expected to be a dict", + ), + ) + } else { + None + }; + let defaults = if flags.contains(bytecode::FunctionOpArg::HAS_DEFAULTS) { - self.pop_value() + Some( + self.pop_value() + .downcast::() + .expect("Stack value for defaults expected to be a tuple"), + ) } else { - vm.get_none() + None }; // pop argc arguments // argument: name, args, globals let scope = self.scope.clone(); - let obj = vm.ctx.new_function(code_obj, scope, defaults); + let obj = vm + .ctx + .new_function(code_obj, scope, defaults, kw_only_defaults); vm.set_attr(&obj, "__annotations__", annotations)?; diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index 4107946968..e8c1d7df80 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -1,5 +1,7 @@ use crate::frame::Scope; use crate::obj::objcode::PyCodeRef; +use crate::obj::objdict::PyDictRef; +use crate::obj::objtuple::PyTupleRef; use crate::obj::objtype::PyClassRef; use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; @@ -11,15 +13,22 @@ pub struct PyFunction { // TODO: these shouldn't be public pub code: PyCodeRef, pub scope: Scope, - pub defaults: PyObjectRef, + pub defaults: Option, + pub kw_only_defaults: Option, } impl PyFunction { - pub fn new(code: PyCodeRef, scope: Scope, defaults: PyObjectRef) -> Self { + pub fn new( + code: PyCodeRef, + scope: Scope, + defaults: Option, + kw_only_defaults: Option, + ) -> Self { PyFunction { code, scope, defaults, + kw_only_defaults, } } } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index ee765f9875..4d2c8353bc 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -48,7 +48,7 @@ use crate::obj::objslice; use crate::obj::objstaticmethod; use crate::obj::objstr; use crate::obj::objsuper; -use crate::obj::objtuple::{self, PyTuple}; +use crate::obj::objtuple::{self, PyTuple, PyTupleRef}; use crate::obj::objtype::{self, PyClass, PyClassRef}; use crate::obj::objweakref; use crate::obj::objzip; @@ -658,10 +658,11 @@ impl PyContext { &self, code_obj: PyCodeRef, scope: Scope, - defaults: PyObjectRef, + defaults: Option, + kw_only_defaults: Option, ) -> PyObjectRef { PyObject::new( - PyFunction::new(code_obj, scope, defaults), + PyFunction::new(code_obj, scope, defaults, kw_only_defaults), self.function_type(), Some(self.new_dict()), ) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 90daaa6687..d43ee7beab 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -23,10 +23,9 @@ use crate::obj::objdict::PyDictRef; use crate::obj::objfunction::{PyFunction, PyMethod}; use crate::obj::objgenerator::PyGeneratorRef; use crate::obj::objiter; -use crate::obj::objlist::PyList; use crate::obj::objsequence; use crate::obj::objstr::{PyString, PyStringRef}; -use crate::obj::objtuple::PyTuple; +use crate::obj::objtuple::PyTupleRef; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ @@ -332,9 +331,10 @@ impl VirtualMachine { ref code, ref scope, ref defaults, + ref kw_only_defaults, }) = func_ref.payload() { - return self.invoke_python_function(code, scope, defaults, args); + return self.invoke_python_function(code, scope, defaults, kw_only_defaults, args); } if let Some(PyMethod { ref function, @@ -356,11 +356,18 @@ impl VirtualMachine { &self, code: &PyCodeRef, scope: &Scope, - defaults: &PyObjectRef, + defaults: &Option, + kw_only_defaults: &Option, args: PyFuncArgs, ) -> PyResult { let scope = scope.child_scope(&self.ctx); - self.fill_locals_from_args(&code.code, &scope.get_locals(), args, defaults)?; + self.fill_locals_from_args( + &code.code, + &scope.get_locals(), + args, + defaults, + kw_only_defaults, + )?; // Construct frame: let frame = Frame::new(code.clone(), scope).into_ref(self); @@ -379,12 +386,7 @@ impl VirtualMachine { cells: PyDictRef, locals: PyDictRef, ) -> PyResult { - if let Some(PyFunction { - code, - scope, - defaults: _, - }) = &function.payload() - { + if let Some(PyFunction { code, scope, .. }) = &function.payload() { let scope = scope .child_scope_with_locals(cells) .child_scope_with_locals(locals); @@ -402,7 +404,8 @@ impl VirtualMachine { code_object: &bytecode::CodeObject, locals: &PyDictRef, args: PyFuncArgs, - defaults: &PyObjectRef, + defaults: &Option, + kw_only_defaults: &Option, ) -> PyResult<()> { let nargs = args.args.len(); let nexpected_args = code_object.arg_names.len(); @@ -438,10 +441,7 @@ impl VirtualMachine { locals.set_item(vararg_name, vararg_value, self)?; } - bytecode::Varargs::Unnamed => { - // just ignore the rest of the args - } - bytecode::Varargs::None => { + bytecode::Varargs::Unnamed | bytecode::Varargs::None => { // Check the number of positional arguments if nargs > nexpected_args { return Err(self.new_type_error(format!( @@ -487,19 +487,11 @@ impl VirtualMachine { // Add missing positional arguments, if we have fewer positional arguments than the // function definition calls for if nargs < nexpected_args { - let available_defaults = if defaults.is(&self.get_none()) { - vec![] - } else if let Some(list) = defaults.payload::() { - list.elements.borrow().clone() - } else if let Some(tuple) = defaults.payload::() { - tuple.elements.borrow().clone() - } else { - panic!("function defaults not tuple or None"); - }; + let num_defaults_available = defaults.as_ref().map_or(0, |d| d.elements.borrow().len()); // Given the number of defaults available, check all the arguments for which we // _don't_ have defaults; if any are missing, raise an exception - let required_args = nexpected_args - available_defaults.len(); + let required_args = nexpected_args - num_defaults_available; let mut missing = vec![]; for i in 0..required_args { let variable_name = &code_object.arg_names[i]; @@ -514,31 +506,32 @@ impl VirtualMachine { missing ))); } - - // We have sufficient defaults, so iterate over the corresponding names and use - // the default if we don't already have a value - for (default_index, i) in (required_args..nexpected_args).enumerate() { - let arg_name = &code_object.arg_names[i]; - if !locals.contains_key(arg_name, self) { - locals.set_item(arg_name, available_defaults[default_index].clone(), self)?; + if let Some(defaults) = defaults { + let defaults = defaults.elements.borrow(); + // We have sufficient defaults, so iterate over the corresponding names and use + // the default if we don't already have a value + for (default_index, i) in (required_args..nexpected_args).enumerate() { + let arg_name = &code_object.arg_names[i]; + if !locals.contains_key(arg_name, self) { + locals.set_item(arg_name, defaults[default_index].clone(), self)?; + } } } }; // Check if kw only arguments are all present: - let kwdefs: HashMap = HashMap::new(); for arg_name in &code_object.kwonlyarg_names { if !locals.contains_key(arg_name, self) { - if kwdefs.contains_key(arg_name) { - // If not yet specified, take the default value - unimplemented!(); - } else { - // No default value and not specified. - return Err(self.new_type_error(format!( - "Missing required kw only argument: '{}'", - arg_name - ))); + if let Some(kw_only_defaults) = kw_only_defaults { + if let Some(default) = kw_only_defaults.get_item(arg_name, self) { + locals.set_item(arg_name, default, self); + continue; + } } + + // No default value and not specified. + return Err(self + .new_type_error(format!("Missing required kw only argument: '{}'", arg_name))); } } From d5a9d96d62126f0b2080cf2ce3a33bd822b2bf15 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 7 Apr 2019 07:36:07 +1200 Subject: [PATCH 193/884] Expose function.__defaults__ and function.__kwdefaults__(only getters) --- tests/snippets/function_args.py | 10 ++++++++++ vm/src/obj/objfunction.rs | 12 +++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/snippets/function_args.py b/tests/snippets/function_args.py index fd164d1137..b9c5a16264 100644 --- a/tests/snippets/function_args.py +++ b/tests/snippets/function_args.py @@ -33,6 +33,9 @@ def va(a, b=2, *c, d, **e): va(1, 22, 3, 4, d=1337, f=42) +assert va.__defaults__ == (2,) +assert va.__kwdefaults__ is None + def va2(*args, **kwargs): assert args == (5, 4) @@ -59,6 +62,13 @@ def va3(x, *, a, b=2, c=9): va3(1, b=10) +assert va3.__defaults__ is None +kw_defaults = va3.__kwdefaults__ +# assert va3.__kwdefaults__ == {'b': 2, 'c': 9} +assert set(kw_defaults) == {'b', 'c'} +assert kw_defaults['b'] == 2 +assert kw_defaults['c'] == 9 + x = {'f': 42, 'e': 1337} y = {'d': 1337} va(1, 22, 3, 4, **x, **y) diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index e8c1d7df80..01e4b586fd 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -43,6 +43,14 @@ impl PyFunctionRef { fn code(self, _vm: &VirtualMachine) -> PyCodeRef { self.code.clone() } + + fn defaults(self, _vm: &VirtualMachine) -> Option { + self.defaults.clone() + } + + fn kwdefaults(self, _vm: &VirtualMachine) -> Option { + self.kw_only_defaults.clone() + } } #[derive(Debug)] @@ -68,7 +76,9 @@ pub fn init(context: &PyContext) { let function_type = &context.function_type; extend_class!(context, function_type, { "__get__" => context.new_rustfunc(bind_method), - "__code__" => context.new_property(PyFunctionRef::code) + "__code__" => context.new_property(PyFunctionRef::code), + "__defaults__" => context.new_property(PyFunctionRef::defaults), + "__kwdefaults__" => context.new_property(PyFunctionRef::kwdefaults), }); let builtin_function_or_method_type = &context.builtin_function_or_method_type; From 51732feaed7e4f7196ecaac9ab1d28b3cdc23181 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 7 Apr 2019 07:43:29 +1200 Subject: [PATCH 194/884] Use get_item_option from ItemProtocol instead of get_item --- vm/src/vm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index d43ee7beab..7be05023be 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -523,8 +523,8 @@ impl VirtualMachine { for arg_name in &code_object.kwonlyarg_names { if !locals.contains_key(arg_name, self) { if let Some(kw_only_defaults) = kw_only_defaults { - if let Some(default) = kw_only_defaults.get_item(arg_name, self) { - locals.set_item(arg_name, default, self); + if let Some(default) = kw_only_defaults.get_item_option(arg_name, self)? { + locals.set_item(arg_name, default, self)?; continue; } } From 10cbf2ae740c0fe7de2151a2e3daf88576c7a394 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 7 Apr 2019 01:49:16 +0200 Subject: [PATCH 195/884] bytes._new stuff --- vm/src/obj/objbytes.rs | 81 +++++++++++++++++++++++++++++++++++------- 1 file changed, 69 insertions(+), 12 deletions(-) diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 8f43cd05f6..26de250107 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,3 +1,9 @@ +use crate::obj::objint::PyInt; +use crate::obj::objlist; +use crate::obj::objlist::PyList; +use crate::obj::objstr::PyString; +use crate::obj::objtuple::PyTuple; +use crate::obj::objtype; use std::cell::Cell; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -7,7 +13,8 @@ use num_traits::ToPrimitive; use crate::function::OptionalArg; use crate::pyobject::{ - IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + IntoPyObject, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, + TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -87,29 +94,79 @@ extend_class!(context, bytesiterator_type, { "__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), });*/ //} + +fn load_byte( + elements: PyResult>, + vm: &VirtualMachine, +) -> Result, PyObjectRef> { + if let Ok(value) = elements { + let mut data_bytes = vec![]; + for elem in value.iter() { + let v = objint::to_int(vm, &elem, 10)?; + if let Some(i) = v.to_u8() { + data_bytes.push(i); + } else { + return Err(vm.new_value_error("byte must be in range(0, 256)".to_string())); + } + } + Ok(data_bytes) + } else { + Err(vm.new_value_error("byte must be in range(0, 256)".to_string())) + } +} + #[pyimpl(__inside_vm)] impl PyBytesRef { #[pymethod(name = "__new__")] fn bytes_new( cls: PyClassRef, val_option: OptionalArg, + enc_option: OptionalArg, vm: &VirtualMachine, ) -> PyResult { // Create bytes data: - let value = if let OptionalArg::Present(ival) = val_option { - let elements = vm.extract_elements(&ival)?; - let mut data_bytes = vec![]; - for elem in elements.iter() { - let v = objint::to_int(vm, elem, 10)?; - data_bytes.push(v.to_u8().unwrap()); + if let OptionalArg::Present(enc) = enc_option { + if let OptionalArg::Present(eval) = val_option { + if objtype::isinstance(&eval, &vm.ctx.str_type()) + && objtype::isinstance(&enc, &vm.ctx.str_type()) + { + //return Ok(PyBytes::new(vec![1]).into_ref_with_type(vm, cls.clone())); + return Err(vm.new_type_error("Ok les 2 sont sstr".to_string())); + } else { + return Err(vm.new_type_error(format!( + "bytes() argument 2 must be str, not {}", + enc.class().name + ))); + } + } else { + return Err(vm.new_type_error("encoding without a string argument".to_string())); } - data_bytes - // return Err(vm.new_type_error("Cannot construct bytes".to_string())); + } + + let value = if let OptionalArg::Present(ival) = val_option { + println!("{:?}", ival); + match_class!(ival.clone(), + _i @ PyInt => { + let size = objint::get_value(&ival).to_usize().unwrap(); + let mut res: Vec = Vec::with_capacity(size); + for _ in 0..size { + res.push(0) + } + Ok(res)}, + _j @ PyList => load_byte(vm.extract_elements(&ival), vm).or_else(|x| {return Err(x) }), + _k @ PyTuple => load_byte(vm.extract_elements(&ival), vm).or_else(|x| {return Err(x) }), + _l @ PyString=> load_byte(vm.extract_elements(&ival), vm).or_else(|x| {return Err(x) }), + _obj => {return Err(vm.new_type_error(format!( + "int() argument must be a string or a number, not " + )));} + ) } else { - vec![] + Ok(vec![]) }; - - PyBytes::new(value).into_ref_with_type(vm, cls) + match value { + Ok(val) => PyBytes::new(val).into_ref_with_type(vm, cls.clone()), + Err(err) => Err(err), + } } #[pymethod(name = "__repr__")] From be55562457fbd361e3478035cdaeccef6151b8dd Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sat, 6 Apr 2019 12:56:09 -0700 Subject: [PATCH 196/884] Allow first argument to be a shared ref to a python value --- vm/src/function.rs | 27 ++++++++++++++++++++++++--- vm/src/obj/objfloat.rs | 14 ++++++++------ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/vm/src/function.rs b/vm/src/function.rs index aae4eefe89..305c4de5ea 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -2,7 +2,9 @@ use std::collections::HashMap; use std::ops::RangeInclusive; use crate::obj::objtype::{isinstance, PyClassRef}; -use crate::pyobject::{IntoPyObject, PyObjectRef, PyResult, TryFromObject, TypeProtocol}; +use crate::pyobject::{ + IntoPyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, +}; use crate::vm::VirtualMachine; use self::OptionalArg::*; @@ -403,6 +405,7 @@ tuple_from_py_func_args!(A, B); tuple_from_py_func_args!(A, B, C); tuple_from_py_func_args!(A, B, C, D); tuple_from_py_func_args!(A, B, C, D, E); +tuple_from_py_func_args!(A, B, C, D, E, F); /// A built-in Python function. pub type PyNativeFunc = Box PyResult + 'static>; @@ -440,17 +443,19 @@ impl IntoPyNativeFunc for PyNativeFunc { } } +pub struct OwnedParam(std::marker::PhantomData); +pub struct RefParam(std::marker::PhantomData); + // This is the "magic" that allows rust functions of varying signatures to // generate native python functions. // // Note that this could be done without a macro - it is simply to avoid repetition. macro_rules! into_py_native_func_tuple { ($(($n:tt, $T:ident)),*) => { - impl IntoPyNativeFunc<($($T,)*), R> for F + impl IntoPyNativeFunc<($(OwnedParam<$T>,)*), R> for F where F: Fn($($T,)* &VirtualMachine) -> R + 'static, $($T: FromArgs,)* - ($($T,)*): FromArgs, R: IntoPyObject, { fn into_func(self) -> PyNativeFunc { @@ -461,6 +466,22 @@ macro_rules! into_py_native_func_tuple { }) } } + + impl IntoPyNativeFunc<(RefParam, $(OwnedParam<$T>,)*), R> for F + where + F: Fn(&S, $($T,)* &VirtualMachine) -> R + 'static, + S: PyValue, + $($T: FromArgs,)* + R: IntoPyObject, + { + fn into_func(self) -> PyNativeFunc { + Box::new(move |vm, args| { + let (zelf, $($n,)*) = args.bind::<(PyRef, $($T,)*)>(vm)?; + + (self)(&zelf, $($n,)* vm).into_pyobject(vm) + }) + } + } }; } diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index ca6bad9d22..de7c465d32 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -34,6 +34,13 @@ impl From for PyFloat { } } +impl PyFloat { + fn is_integer(&self, _vm: &VirtualMachine) -> bool { + let v = self.value; + (v - v.round()).abs() < std::f64::EPSILON + } +} + pub type PyFloatRef = PyRef; impl PyFloatRef { @@ -311,11 +318,6 @@ impl PyFloatRef { } } - fn is_integer(self, _vm: &VirtualMachine) -> bool { - let v = self.value; - (v - v.round()).abs() < std::f64::EPSILON - } - fn real(self, _vm: &VirtualMachine) -> Self { self } @@ -385,7 +387,7 @@ pub fn init(context: &PyContext) { "__mul__" => context.new_rustfunc(PyFloatRef::mul), "__rmul__" => context.new_rustfunc(PyFloatRef::mul), "real" => context.new_property(PyFloatRef::real), - "is_integer" => context.new_rustfunc(PyFloatRef::is_integer), + "is_integer" => context.new_rustfunc(PyFloat::is_integer), "as_integer_ratio" => context.new_rustfunc(PyFloatRef::as_integer_ratio) }); } From c2716040d2419311837d9ef025e476b68f1310e0 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sat, 6 Apr 2019 19:05:18 -0700 Subject: [PATCH 197/884] float: take self by shared ref --- vm/src/obj/objfloat.rs | 112 ++++++++++++++++++++--------------------- 1 file changed, 55 insertions(+), 57 deletions(-) diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index de7c465d32..796da71ae2 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -35,16 +35,7 @@ impl From for PyFloat { } impl PyFloat { - fn is_integer(&self, _vm: &VirtualMachine) -> bool { - let v = self.value; - (v - v.round()).abs() < std::f64::EPSILON - } -} - -pub type PyFloatRef = PyRef; - -impl PyFloatRef { - fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let value = self.value; let result = if objtype::isinstance(&other, &vm.ctx.float_type()) { let other = get_value(&other); @@ -63,7 +54,7 @@ impl PyFloatRef { vm.ctx.new_bool(result) } - fn lt(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn lt(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&i2, &vm.ctx.float_type()) { vm.ctx.new_bool(v1 < get_value(&i2)) @@ -75,7 +66,7 @@ impl PyFloatRef { } } - fn le(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn le(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&i2, &vm.ctx.float_type()) { vm.ctx.new_bool(v1 <= get_value(&i2)) @@ -87,7 +78,7 @@ impl PyFloatRef { } } - fn gt(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn gt(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&i2, &vm.ctx.float_type()) { vm.ctx.new_bool(v1 > get_value(&i2)) @@ -99,7 +90,7 @@ impl PyFloatRef { } } - fn ge(self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn ge(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&i2, &vm.ctx.float_type()) { vm.ctx.new_bool(v1 >= get_value(&i2)) @@ -111,11 +102,11 @@ impl PyFloatRef { } } - fn abs(self, _vm: &VirtualMachine) -> f64 { + fn abs(&self, _vm: &VirtualMachine) -> f64 { self.value.abs() } - fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&other, &vm.ctx.float_type()) { vm.ctx.new_float(v1 + get_value(&other)) @@ -127,23 +118,23 @@ impl PyFloatRef { } } - fn bool(self, _vm: &VirtualMachine) -> bool { + fn bool(&self, _vm: &VirtualMachine) -> bool { self.value != 0.0 } - fn divmod(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn divmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.float_type()) || objtype::isinstance(&other, &vm.ctx.int_type()) { - let r1 = PyFloatRef::floordiv(self.clone(), other.clone(), vm)?; - let r2 = PyFloatRef::mod_(self, other, vm)?; + let r1 = self.floordiv(other.clone(), vm)?; + let r2 = self.mod_(other, vm)?; Ok(vm.ctx.new_tuple(vec![r1, r2])) } else { Ok(vm.ctx.not_implemented()) } } - fn floordiv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn floordiv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) @@ -202,7 +193,7 @@ impl PyFloatRef { PyFloat { value }.into_ref_with_type(vm, cls) } - fn mod_(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) @@ -221,11 +212,11 @@ impl PyFloatRef { } } - fn neg(self, _vm: &VirtualMachine) -> f64 { + fn neg(&self, _vm: &VirtualMachine) -> f64 { -self.value } - fn pow(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&other, &vm.ctx.float_type()) { vm.ctx.new_float(v1.powf(get_value(&other))) @@ -237,7 +228,7 @@ impl PyFloatRef { } } - fn sub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; if objtype::isinstance(&other, &vm.ctx.float_type()) { Ok(vm.ctx.new_float(v1 - get_value(&other))) @@ -250,7 +241,7 @@ impl PyFloatRef { } } - fn rsub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn rsub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; if objtype::isinstance(&other, &vm.ctx.float_type()) { Ok(vm.ctx.new_float(get_value(&other) - v1)) @@ -263,11 +254,11 @@ impl PyFloatRef { } } - fn repr(self, _vm: &VirtualMachine) -> String { + fn repr(&self, _vm: &VirtualMachine) -> String { self.value.to_string() } - fn truediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn truediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) @@ -286,7 +277,7 @@ impl PyFloatRef { } } - fn rtruediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn rtruediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) @@ -305,7 +296,7 @@ impl PyFloatRef { } } - fn mul(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn mul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; if objtype::isinstance(&other, &vm.ctx.float_type) { Ok(vm.ctx.new_float(v1 * get_value(&other))) @@ -318,11 +309,16 @@ impl PyFloatRef { } } - fn real(self, _vm: &VirtualMachine) -> Self { - self + fn real(zelf: PyRef, _vm: &VirtualMachine) -> PyFloatRef { + zelf } - fn as_integer_ratio(self, vm: &VirtualMachine) -> PyResult { + fn is_integer(&self, _vm: &VirtualMachine) -> bool { + let v = self.value; + (v - v.round()).abs() < std::f64::EPSILON + } + + fn as_integer_ratio(&self, vm: &VirtualMachine) -> PyResult { let value = self.value; if value.is_infinite() { return Err( @@ -340,6 +336,8 @@ impl PyFloatRef { } } +pub type PyFloatRef = PyRef; + // Retrieve inner float value: pub fn get_value(obj: &PyObjectRef) -> f64 { obj.payload::().unwrap().value @@ -363,31 +361,31 @@ pub fn init(context: &PyContext) { let float_doc = "Convert a string or number to a floating point number, if possible."; extend_class!(context, float_type, { - "__eq__" => context.new_rustfunc(PyFloatRef::eq), - "__lt__" => context.new_rustfunc(PyFloatRef::lt), - "__le__" => context.new_rustfunc(PyFloatRef::le), - "__gt__" => context.new_rustfunc(PyFloatRef::gt), - "__ge__" => context.new_rustfunc(PyFloatRef::ge), - "__abs__" => context.new_rustfunc(PyFloatRef::abs), - "__add__" => context.new_rustfunc(PyFloatRef::add), - "__radd__" => context.new_rustfunc(PyFloatRef::add), - "__bool__" => context.new_rustfunc(PyFloatRef::bool), - "__divmod__" => context.new_rustfunc(PyFloatRef::divmod), - "__floordiv__" => context.new_rustfunc(PyFloatRef::floordiv), - "__new__" => context.new_rustfunc(PyFloatRef::new_float), - "__mod__" => context.new_rustfunc(PyFloatRef::mod_), - "__neg__" => context.new_rustfunc(PyFloatRef::neg), - "__pow__" => context.new_rustfunc(PyFloatRef::pow), - "__sub__" => context.new_rustfunc(PyFloatRef::sub), - "__rsub__" => context.new_rustfunc(PyFloatRef::rsub), - "__repr__" => context.new_rustfunc(PyFloatRef::repr), + "__eq__" => context.new_rustfunc(PyFloat::eq), + "__lt__" => context.new_rustfunc(PyFloat::lt), + "__le__" => context.new_rustfunc(PyFloat::le), + "__gt__" => context.new_rustfunc(PyFloat::gt), + "__ge__" => context.new_rustfunc(PyFloat::ge), + "__abs__" => context.new_rustfunc(PyFloat::abs), + "__add__" => context.new_rustfunc(PyFloat::add), + "__radd__" => context.new_rustfunc(PyFloat::add), + "__bool__" => context.new_rustfunc(PyFloat::bool), + "__divmod__" => context.new_rustfunc(PyFloat::divmod), + "__floordiv__" => context.new_rustfunc(PyFloat::floordiv), + "__new__" => context.new_rustfunc(PyFloat::new_float), + "__mod__" => context.new_rustfunc(PyFloat::mod_), + "__neg__" => context.new_rustfunc(PyFloat::neg), + "__pow__" => context.new_rustfunc(PyFloat::pow), + "__sub__" => context.new_rustfunc(PyFloat::sub), + "__rsub__" => context.new_rustfunc(PyFloat::rsub), + "__repr__" => context.new_rustfunc(PyFloat::repr), "__doc__" => context.new_str(float_doc.to_string()), - "__truediv__" => context.new_rustfunc(PyFloatRef::truediv), - "__rtruediv__" => context.new_rustfunc(PyFloatRef::rtruediv), - "__mul__" => context.new_rustfunc(PyFloatRef::mul), - "__rmul__" => context.new_rustfunc(PyFloatRef::mul), - "real" => context.new_property(PyFloatRef::real), + "__truediv__" => context.new_rustfunc(PyFloat::truediv), + "__rtruediv__" => context.new_rustfunc(PyFloat::rtruediv), + "__mul__" => context.new_rustfunc(PyFloat::mul), + "__rmul__" => context.new_rustfunc(PyFloat::mul), + "real" => context.new_property(PyFloat::real), "is_integer" => context.new_rustfunc(PyFloat::is_integer), - "as_integer_ratio" => context.new_rustfunc(PyFloatRef::as_integer_ratio) + "as_integer_ratio" => context.new_rustfunc(PyFloat::as_integer_ratio) }); } From 5afc5585648b8faf8661269a8bc7442a2adf0245 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 6 Apr 2019 23:47:01 -0500 Subject: [PATCH 198/884] Add id() method --- wasm/lib/src/vm_class.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 3f7bdef8ae..c159f60e16 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -37,6 +37,13 @@ impl StoredVirtualMachine { held_objects: RefCell::new(Vec::new()), } } + + pub fn id(&self) -> &str { + self.vm + .wasm_id + .as_ref() + .expect("VirtualMachine inside of WASM crate should have wasm_id set") + } } // It's fine that it's thread local, since WASM doesn't even have threads yet. thread_local! From 7ca3b77fcdeaf0349cab7071cee3ea1ca3f8783d Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 6 Apr 2019 23:55:30 -0500 Subject: [PATCH 199/884] Just a fn --- wasm/lib/src/vm_class.rs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index c159f60e16..5efa7c849b 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -37,13 +37,6 @@ impl StoredVirtualMachine { held_objects: RefCell::new(Vec::new()), } } - - pub fn id(&self) -> &str { - self.vm - .wasm_id - .as_ref() - .expect("VirtualMachine inside of WASM crate should have wasm_id set") - } } // It's fine that it's thread local, since WASM doesn't even have threads yet. thread_local! @@ -53,6 +46,11 @@ thread_local! { static STORED_VMS: RefCell>> = RefCell::default(); } +pub fn get_vm_id(vm: &VirtualMachine) -> &str { + vm.wasm_id + .as_ref() + .expect("VirtualMachine inside of WASM crate should have wasm_id set") +} pub(crate) fn stored_vm_from_wasm(wasm_vm: &WASMVirtualMachine) -> Rc { STORED_VMS.with(|cell| { cell.borrow() @@ -62,10 +60,7 @@ pub(crate) fn stored_vm_from_wasm(wasm_vm: &WASMVirtualMachine) -> Rc Weak { - let id = vm - .wasm_id - .as_ref() - .expect("VirtualMachine inside of WASM crate should have wasm_id set"); + let id = get_vm_id(vm); STORED_VMS .with(|cell| Rc::downgrade(cell.borrow().get(id).expect("VirtualMachine is not valid"))) } From 02a21cd07262ceadcefaa5be814aee5a57bdab03 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 7 Apr 2019 09:46:45 +1200 Subject: [PATCH 200/884] Add builtin warning classes --- vm/src/builtins.rs | 13 +++++++++++++ vm/src/exceptions.rs | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 210d12b49e..27e024aaf2 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -767,6 +767,19 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone(), "KeyError" => ctx.exceptions.key_error.clone(), "OSError" => ctx.exceptions.os_error.clone(), + + // Warnings + "Warning" => ctx.exceptions.warning.clone(), + "BytesWarning" => ctx.exceptions.bytes_warning.clone(), + "UnicodeWarning" => ctx.exceptions.unicode_warning.clone(), + "DeprecationWarning" => ctx.exceptions.deprecation_warning.clone(), + "PendingDeprecationWarning" => ctx.exceptions.pending_deprecation_warning.clone(), + "FutureWarning" => ctx.exceptions.future_warning.clone(), + "ImportWarning" => ctx.exceptions.import_warning.clone(), + "SyntaxWarning" => ctx.exceptions.syntax_warning.clone(), + "ResourceWarning" => ctx.exceptions.resource_warning.clone(), + "RuntimeWarning" => ctx.exceptions.runtime_warning.clone(), + "UserWarning" => ctx.exceptions.user_warning.clone(), }); } diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index cec4b3125e..d81764e250 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -103,6 +103,18 @@ pub struct ExceptionZoo { pub type_error: PyClassRef, pub value_error: PyClassRef, pub zero_division_error: PyClassRef, + + pub warning: PyClassRef, + pub bytes_warning: PyClassRef, + pub unicode_warning: PyClassRef, + pub deprecation_warning: PyClassRef, + pub pending_deprecation_warning: PyClassRef, + pub future_warning: PyClassRef, + pub import_warning: PyClassRef, + pub syntax_warning: PyClassRef, + pub resource_warning: PyClassRef, + pub runtime_warning: PyClassRef, + pub user_warning: PyClassRef, } impl ExceptionZoo { @@ -130,6 +142,19 @@ impl ExceptionZoo { let file_not_found_error = create_type("FileNotFoundError", &type_type, &os_error); let permission_error = create_type("PermissionError", &type_type, &os_error); + let warning = create_type("Warning", &type_type, &exception_type); + let bytes_warning = create_type("BytesWarning", &type_type, &warning); + let unicode_warning = create_type("UnicodeWarning", &type_type, &warning); + let deprecation_warning = create_type("DeprecationWarning", &type_type, &warning); + let pending_deprecation_warning = + create_type("PendingDeprecationWarning", &type_type, &warning); + let future_warning = create_type("FutureWarning", &type_type, &warning); + let import_warning = create_type("ImportWarning", &type_type, &warning); + let syntax_warning = create_type("SyntaxWarning", &type_type, &warning); + let resource_warning = create_type("ResourceWarning", &type_type, &warning); + let runtime_warning = create_type("RuntimeWarning", &type_type, &warning); + let user_warning = create_type("UserWarning", &type_type, &warning); + ExceptionZoo { arithmetic_error, assertion_error, @@ -152,6 +177,17 @@ impl ExceptionZoo { type_error, value_error, zero_division_error, + warning, + bytes_warning, + unicode_warning, + deprecation_warning, + pending_deprecation_warning, + future_warning, + import_warning, + syntax_warning, + resource_warning, + runtime_warning, + user_warning, } } } From 32051c9d961dce2488c79541ba43b5ddd7e61598 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 7 Apr 2019 19:19:39 +1200 Subject: [PATCH 201/884] Implement copy and update dict methods --- tests/snippets/dict.py | 16 ++++++++++++ vm/src/dictdatatype.rs | 9 +++++-- vm/src/obj/objdict.rs | 58 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 69040d6246..f924bb0e7c 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -107,3 +107,19 @@ def __eq__(self, other): x['a'] = 2 x['b'] = 10 assert list(x) == ['a', 'b'] + +y = x.copy() +x['c'] = 12 +assert list(y) == ['a', 'b'] +assert y['a'] == 2 +assert y['b'] == 10 + +y.update({'c': 19, "d": -1, 'b': 12}) +assert list(y) == ['a', 'b', 'c', 'd'] +assert y['a'] == 2 +assert y['b'] == 12 +assert y['c'] == 19 +assert y['d'] == -1 + +y.update(y) +assert list(y) == ['a', 'b', 'c', 'd'] diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 6641a1884e..8a6a8df1be 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -9,6 +9,7 @@ use num_traits::ToPrimitive; /// And: http://code.activestate.com/recipes/578375/ use std::collections::HashMap; +#[derive(Clone)] pub struct Dict { size: usize, indices: HashMap, @@ -25,6 +26,7 @@ impl Default for Dict { } } +#[derive(Clone)] struct DictEntry { hash: usize, key: PyObjectRef, @@ -118,13 +120,16 @@ impl Dict { self.len() == 0 } - pub fn get_items(&self) -> Vec<(PyObjectRef, T)> { + pub fn iter_items(&self) -> impl Iterator + '_ { self.entries .iter() .filter(|e| e.is_some()) .map(|e| e.as_ref().unwrap()) .map(|e| (e.key.clone(), e.value.clone())) - .collect() + } + + pub fn get_items(&self) -> Vec<(PyObjectRef, T)> { + self.iter_items().collect() } /// Lookup the index for the given key. diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index d54f62bb71..e84f7b1786 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -3,7 +3,8 @@ use std::fmt; use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ - IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + IdProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, + PyValue, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -44,13 +45,28 @@ impl PyDictRef { kwargs: KwArgs, vm: &VirtualMachine, ) -> PyResult { - let mut dict = DictContentType::default(); + let dict = DictContentType::default(); + let entries = RefCell::new(dict); + // it's unfortunate that we can't abstract over RefCall, as we should be able to use dict + // directly here, but that would require generic associated types + PyDictRef::merge(&entries, dict_obj, kwargs, vm)?; + + PyDict { entries }.into_ref_with_type(vm, class) + } + + fn merge( + dict: &RefCell, + dict_obj: OptionalArg, + kwargs: KwArgs, + vm: &VirtualMachine, + ) -> PyResult<()> { if let OptionalArg::Present(dict_obj) = dict_obj { let dicted: PyResult = dict_obj.clone().downcast(); if let Ok(dict_obj) = dicted { - for (key, value) in dict_obj.get_key_value_pairs() { - dict.insert(vm, &key, value)?; + let mut dict_borrowed = dict.borrow_mut(); + for (key, value) in dict_obj.entries.borrow().iter_items() { + dict_borrowed.insert(vm, &key, value)?; } } else { let iter = objiter::get_iter(vm, &dict_obj)?; @@ -68,17 +84,15 @@ impl PyDictRef { if objiter::get_next_object(vm, &elem_iter)?.is_some() { return Err(err(vm)); } - dict.insert(vm, &key, value)?; + dict.borrow_mut().insert(vm, &key, value)?; } } } + let mut dict_borrowed = dict.borrow_mut(); for (key, value) in kwargs.into_iter() { - dict.insert(vm, &vm.new_str(key), value)?; - } - PyDict { - entries: RefCell::new(dict), + dict_borrowed.insert(vm, &vm.new_str(key), value)?; } - .into_ref_with_type(vm, class) + Ok(()) } fn bool(self, _vm: &VirtualMachine) -> bool { @@ -209,6 +223,28 @@ impl PyDictRef { } } + fn copy(self, _vm: &VirtualMachine) -> PyDict { + PyDict { + entries: self.entries.clone(), + } + } + + fn update( + self, + mut dict_obj: OptionalArg, + kwargs: KwArgs, + vm: &VirtualMachine, + ) -> PyResult<()> { + if let OptionalArg::Present(ref other) = dict_obj { + if self.is(other) { + // Updating yourself is a noop, and this avoids a borrow error + dict_obj = OptionalArg::Missing; + } + } + + PyDictRef::merge(&self.entries, dict_obj, kwargs, vm) + } + /// Take a python dictionary and convert it to attributes. pub fn to_attributes(self) -> PyAttributes { let mut attrs = PyAttributes::new(); @@ -266,5 +302,7 @@ pub fn init(context: &PyContext) { // TODO: separate type. `keys` should be a live view over the collection, not an iterator. "keys" => context.new_rustfunc(PyDictRef::iter), "get" => context.new_rustfunc(PyDictRef::get), + "copy" => context.new_rustfunc(PyDictRef::copy), + "update" => context.new_rustfunc(PyDictRef::update), }); } From 7e965c7cc8b331c11a339537161aee841b5a6ad0 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 7 Apr 2019 10:12:57 +0200 Subject: [PATCH 202/884] check encoding in new --- vm/src/obj/objbytes.rs | 71 +++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 29 deletions(-) diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 26de250107..1b06a96416 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -21,7 +21,9 @@ use crate::vm::VirtualMachine; use super::objbyteinner::PyByteInner; use super::objint; use super::objiter; +use super::objstr; use super::objtype::PyClassRef; +use std::clone::Clone; /// "bytes(iterable_of_ints) -> bytes\n\ /// bytes(string, encoding[, errors]) -> bytes\n\ @@ -125,13 +127,24 @@ impl PyBytesRef { vm: &VirtualMachine, ) -> PyResult { // Create bytes data: + if let OptionalArg::Present(enc) = enc_option { if let OptionalArg::Present(eval) = val_option { - if objtype::isinstance(&eval, &vm.ctx.str_type()) - && objtype::isinstance(&enc, &vm.ctx.str_type()) - { - //return Ok(PyBytes::new(vec![1]).into_ref_with_type(vm, cls.clone())); - return Err(vm.new_type_error("Ok les 2 sont sstr".to_string())); + if let Ok(input) = eval.downcast::() { + if let Ok(encoding) = enc.downcast::() { + if encoding.value.to_lowercase() == "utf8".to_string() + || encoding.value.to_lowercase() == "utf-8".to_string() + { + return PyBytes::new(input.value.as_bytes().to_vec()) + .into_ref_with_type(vm, cls.clone()); + } else { + return Err( + vm.new_value_error(format!("unknown encoding: {}", encoding.value)), //should be lookup error + ); + } + } else { + return Err(vm.new_type_error("encodin is not string".to_string())); + } } else { return Err(vm.new_type_error(format!( "bytes() argument 2 must be str, not {}", @@ -141,31 +154,31 @@ impl PyBytesRef { } else { return Err(vm.new_type_error("encoding without a string argument".to_string())); } - } - - let value = if let OptionalArg::Present(ival) = val_option { - println!("{:?}", ival); - match_class!(ival.clone(), - _i @ PyInt => { - let size = objint::get_value(&ival).to_usize().unwrap(); - let mut res: Vec = Vec::with_capacity(size); - for _ in 0..size { - res.push(0) - } - Ok(res)}, - _j @ PyList => load_byte(vm.extract_elements(&ival), vm).or_else(|x| {return Err(x) }), - _k @ PyTuple => load_byte(vm.extract_elements(&ival), vm).or_else(|x| {return Err(x) }), - _l @ PyString=> load_byte(vm.extract_elements(&ival), vm).or_else(|x| {return Err(x) }), - _obj => {return Err(vm.new_type_error(format!( - "int() argument must be a string or a number, not " - )));} - ) } else { - Ok(vec![]) - }; - match value { - Ok(val) => PyBytes::new(val).into_ref_with_type(vm, cls.clone()), - Err(err) => Err(err), + let value = if let OptionalArg::Present(ival) = val_option { + println!("{:?}", ival); + match_class!(ival.clone(), + _i @ PyInt => { + let size = objint::get_value(&ival).to_usize().unwrap(); + let mut res: Vec = Vec::with_capacity(size); + for _ in 0..size { + res.push(0) + } + Ok(res)}, + _l @ PyString=> {return Err(vm.new_type_error(format!( + "string argument without an encoding" + )));}, + _j @ PyList => load_byte(vm.extract_elements(&ival), vm).or_else(|x| {return Err(x) }), + _k @ PyTuple => load_byte(vm.extract_elements(&ival), vm).or_else(|x| {return Err(x) }), + _obj => {} + ) + } else { + Ok(vec![]) + }; + match value { + Ok(val) => PyBytes::new(val).into_ref_with_type(vm, cls.clone()), + Err(err) => Err(err), + } } } From f03a164c146d15b3016c2a8e08d14f3df6b16d5f Mon Sep 17 00:00:00 2001 From: andrew Date: Sun, 7 Apr 2019 12:22:10 +0200 Subject: [PATCH 203/884] use a code generator for not implemented tests --- tests/README.md | 6 +- tests/generator/not_impl_footer.txt | 12 + tests/generator/not_impl_header.txt | 2 + tests/not_impl_gen.py | 33 + tests/snippets/whats_left_to_implement.py | 873 ---------------------- 5 files changed, 51 insertions(+), 875 deletions(-) create mode 100644 tests/generator/not_impl_footer.txt create mode 100644 tests/generator/not_impl_header.txt create mode 100644 tests/not_impl_gen.py delete mode 100644 tests/snippets/whats_left_to_implement.py diff --git a/tests/README.md b/tests/README.md index a225ba3b68..ba24ab8bc2 100644 --- a/tests/README.md +++ b/tests/README.md @@ -1,4 +1,3 @@ - # Test snippets This directory contains two sets of test snippets which can be run in @@ -6,6 +5,10 @@ Python. The `snippets/` directory contains functional tests, and the `benchmarks/` directory contains snippets for use in benchmarking RustPython's performance. +## Generates the test for not implemented methods + +run using cpython not_impl_gen.py it automatically generate a +test snippet to check not yet implemented methods ## Running with CPython + RustPython @@ -15,4 +18,3 @@ compilation to bytecode. When this is done, run the bytecode with rustpython. ## Running with RustPython The other option is to run all snippets with RustPython. - diff --git a/tests/generator/not_impl_footer.txt b/tests/generator/not_impl_footer.txt new file mode 100644 index 0000000000..5cd500999a --- /dev/null +++ b/tests/generator/not_impl_footer.txt @@ -0,0 +1,12 @@ +not_implemented = [(name, method) + for name, (val, methods) in expected_methods.items() + for method in methods + if not hasattr(val, method)] + +for r in not_implemented: + print(r[0], ".", r[1], sep="") +if not not_implemented: + print("Not much \\o/") + +if platform.python_implementation() == "CPython": + assert len(not_implemented) == 0, "CPython should have all the methods" diff --git a/tests/generator/not_impl_header.txt b/tests/generator/not_impl_header.txt new file mode 100644 index 0000000000..cd6f8f5a12 --- /dev/null +++ b/tests/generator/not_impl_header.txt @@ -0,0 +1,2 @@ +import platform + diff --git a/tests/not_impl_gen.py b/tests/not_impl_gen.py new file mode 100644 index 0000000000..2e20f56094 --- /dev/null +++ b/tests/not_impl_gen.py @@ -0,0 +1,33 @@ +objects = [ + bool, + bytearray, + bytes, + complex, + dict, + float, + frozenset, + int, + iter, + list, + memoryview, + range, + set, + str, + tuple, + object +] + +header = open("generator/not_impl_header.txt") +footer = open("generator/not_impl_footer.txt") +output = open("snippets/whats_left_to_implement.py", "w") + +output.write(header.read()) +output.write("expected_methods = {\n") + +for obj in objects: + output.write(f" '{obj.__name__}': ({obj.__name__}, [\n") + output.write("\n".join(f" '{attr}'," for attr in dir(obj))) + output.write("\n ])," + ("\n" if objects[-1] == obj else "\n\n")) + +output.write("}\n\n") +output.write(footer.read()) diff --git a/tests/snippets/whats_left_to_implement.py b/tests/snippets/whats_left_to_implement.py deleted file mode 100644 index 6abade9df2..0000000000 --- a/tests/snippets/whats_left_to_implement.py +++ /dev/null @@ -1,873 +0,0 @@ -import platform - -expected_methods = { - 'bool': (bool, [ - '__abs__', - '__add__', - '__and__', - '__bool__', - '__ceil__', - '__class__', - '__delattr__', - '__dir__', - '__divmod__', - '__doc__', - '__eq__', - '__float__', - '__floor__', - '__floordiv__', - '__format__', - '__ge__', - '__getattribute__', - '__getnewargs__', - '__gt__', - '__hash__', - '__index__', - '__init__', - '__init_subclass__', - '__int__', - '__invert__', - '__le__', - '__lshift__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__neg__', - '__new__', - '__or__', - '__pos__', - '__pow__', - '__radd__', - '__rand__', - '__rdivmod__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rfloordiv__', - '__rlshift__', - '__rmod__', - '__rmul__', - '__ror__', - '__round__', - '__rpow__', - '__rrshift__', - '__rshift__', - '__rsub__', - '__rtruediv__', - '__rxor__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__truediv__', - '__trunc__', - '__xor__', - 'bit_length', - 'conjugate', - 'denominator', - 'from_bytes', - 'imag', - 'numerator', - 'real', - 'to_bytes', - ]), - 'bytearray': (bytearray, [ - '__add__', - '__alloc__', - '__class__', - '__contains__', - '__delattr__', - '__delitem__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__gt__', - '__hash__', - '__iadd__', - '__imul__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rmod__', - '__rmul__', - '__setattr__', - '__setitem__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'append', - 'capitalize', - 'center', - 'clear', - 'copy', - 'count', - 'decode', - 'endswith', - 'expandtabs', - 'extend', - 'find', - 'fromhex', - 'hex', - 'index', - 'insert', - 'isalnum', - 'isalpha', - 'isdigit', - 'islower', - 'isspace', - 'istitle', - 'isupper', - 'join', - 'ljust', - 'lower', - 'lstrip', - 'maketrans', - 'partition', - 'pop', - 'remove', - 'replace', - 'reverse', - 'rfind', - 'rindex', - 'rjust', - 'rpartition', - 'rsplit', - 'rstrip', - 'split', - 'splitlines', - 'startswith', - 'strip', - 'swapcase', - 'title', - 'translate', - 'upper', - 'zfill', - ]), - 'bytes': (bytes, [ - '__add__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__getnewargs__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rmod__', - '__rmul__', - '__setattr__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'capitalize', - 'center', - 'count', - 'decode', - 'endswith', - 'expandtabs', - 'find', - 'fromhex', - 'hex', - 'index', - 'isalnum', - 'isalpha', - 'isdigit', - 'islower', - 'isspace', - 'istitle', - 'isupper', - 'join', - 'ljust', - 'lower', - 'lstrip', - 'maketrans', - 'partition', - 'replace', - 'rfind', - 'rindex', - 'rjust', - 'rpartition', - 'rsplit', - 'rstrip', - 'split', - 'splitlines', - 'startswith', - 'strip', - 'swapcase', - 'title', - 'translate', - 'upper', - 'zfill', - ]), - 'complex': (complex, [ - '__abs__', - '__add__', - '__bool__', - '__class__', - '__delattr__', - '__dir__', - '__divmod__', - '__doc__', - '__eq__', - '__float__', - '__floordiv__', - '__format__', - '__ge__', - '__getattribute__', - '__getnewargs__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__int__', - '__le__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__neg__', - '__new__', - '__pos__', - '__pow__', - '__radd__', - '__rdivmod__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rfloordiv__', - '__rmod__', - '__rmul__', - '__rpow__', - '__rsub__', - '__rtruediv__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__truediv__', - 'conjugate', - 'imag', - 'real', - ]), - 'dict': (dict, [ - '__class__', - '__contains__', - '__delattr__', - '__delitem__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__setattr__', - '__setitem__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'clear', - 'copy', - 'fromkeys', - 'get', - 'items', - 'keys', - 'pop', - 'popitem', - 'setdefault', - 'update', - 'values', - ]), - 'float': (float, [ - '__abs__', - '__add__', - '__bool__', - '__class__', - '__delattr__', - '__dir__', - '__divmod__', - '__doc__', - '__eq__', - '__float__', - '__floordiv__', - '__format__', - '__ge__', - '__getattribute__', - '__getformat__', - '__getnewargs__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__int__', - '__le__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__neg__', - '__new__', - '__pos__', - '__pow__', - '__radd__', - '__rdivmod__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rfloordiv__', - '__rmod__', - '__rmul__', - '__round__', - '__rpow__', - '__rsub__', - '__rtruediv__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__truediv__', - '__trunc__', - 'as_integer_ratio', - 'conjugate', - 'fromhex', - 'hex', - 'imag', - 'is_integer', - 'real', - ]), - 'frozenset': (frozenset, [ - '__and__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__ne__', - '__new__', - '__or__', - '__rand__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__ror__', - '__rsub__', - '__rxor__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__xor__', - 'copy', - 'difference', - 'intersection', - 'isdisjoint', - 'issubset', - 'issuperset', - 'symmetric_difference', - 'union', - ]), - 'int': (int, [ - '__abs__', - '__add__', - '__and__', - '__bool__', - '__ceil__', - '__class__', - '__delattr__', - '__dir__', - '__divmod__', - '__doc__', - '__eq__', - '__float__', - '__floor__', - '__floordiv__', - '__format__', - '__ge__', - '__getattribute__', - '__getnewargs__', - '__gt__', - '__hash__', - '__index__', - '__init__', - '__init_subclass__', - '__int__', - '__invert__', - '__le__', - '__lshift__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__neg__', - '__new__', - '__or__', - '__pos__', - '__pow__', - '__radd__', - '__rand__', - '__rdivmod__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rfloordiv__', - '__rlshift__', - '__rmod__', - '__rmul__', - '__ror__', - '__round__', - '__rpow__', - '__rrshift__', - '__rshift__', - '__rsub__', - '__rtruediv__', - '__rxor__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__truediv__', - '__trunc__', - '__xor__', - 'bit_length', - 'conjugate', - 'denominator', - 'from_bytes', - 'imag', - 'numerator', - 'real', - 'to_bytes', - ]), - 'iter': ([].__iter__(), [ - '__class__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__length_hint__', - '__lt__', - '__ne__', - '__new__', - '__next__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__setattr__', - '__setstate__', - '__sizeof__', - '__str__', - '__subclasshook__', - ]), - 'list': (list, [ - '__add__', - '__class__', - '__contains__', - '__delattr__', - '__delitem__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__gt__', - '__hash__', - '__iadd__', - '__imul__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__reversed__', - '__rmul__', - '__setattr__', - '__setitem__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'append', - 'clear', - 'copy', - 'count', - 'extend', - 'index', - 'insert', - 'pop', - 'remove', - 'reverse', - 'sort', - ]), - 'memoryview': (memoryview, [ - '__class__', - '__delattr__', - '__delitem__', - '__dir__', - '__doc__', - '__enter__', - '__eq__', - '__exit__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__le__', - '__len__', - '__lt__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__setattr__', - '__setitem__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'c_contiguous', - 'cast', - 'contiguous', - 'f_contiguous', - 'format', - 'hex', - 'itemsize', - 'nbytes', - 'ndim', - 'obj', - 'readonly', - 'release', - 'shape', - 'strides', - 'suboffsets', - 'tobytes', - 'tolist', - ]), - 'range': (range, [ - '__bool__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__reversed__', - '__setattr__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'count', - 'index', - 'start', - 'step', - 'stop', - ]), - 'set': (set, [ - '__and__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__gt__', - '__hash__', - '__iand__', - '__init__', - '__init_subclass__', - '__ior__', - '__isub__', - '__iter__', - '__ixor__', - '__le__', - '__len__', - '__lt__', - '__ne__', - '__new__', - '__or__', - '__rand__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__ror__', - '__rsub__', - '__rxor__', - '__setattr__', - '__sizeof__', - '__str__', - '__sub__', - '__subclasshook__', - '__xor__', - 'add', - 'clear', - 'copy', - 'difference', - 'difference_update', - 'discard', - 'intersection', - 'intersection_update', - 'isdisjoint', - 'issubset', - 'issuperset', - 'pop', - 'remove', - 'symmetric_difference', - 'symmetric_difference_update', - 'union', - 'update', - ]), - 'str': (str, [ - '__add__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__getnewargs__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mod__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rmod__', - '__rmul__', - '__setattr__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'capitalize', - 'casefold', - 'center', - 'count', - 'encode', - 'endswith', - 'expandtabs', - 'find', - 'format', - 'format_map', - 'index', - 'isalnum', - 'isalpha', - 'isdecimal', - 'isdigit', - 'isidentifier', - 'islower', - 'isnumeric', - 'isprintable', - 'isspace', - 'istitle', - 'isupper', - 'join', - 'ljust', - 'lower', - 'lstrip', - 'maketrans', - 'partition', - 'replace', - 'rfind', - 'rindex', - 'rjust', - 'rpartition', - 'rsplit', - 'rstrip', - 'split', - 'splitlines', - 'startswith', - 'strip', - 'swapcase', - 'title', - 'translate', - 'upper', - 'zfill' - ]), - 'tuple': (tuple, [ - '__add__', - '__class__', - '__contains__', - '__delattr__', - '__dir__', - '__doc__', - '__eq__', - '__format__', - '__ge__', - '__getattribute__', - '__getitem__', - '__getnewargs__', - '__gt__', - '__hash__', - '__init__', - '__init_subclass__', - '__iter__', - '__le__', - '__len__', - '__lt__', - '__mul__', - '__ne__', - '__new__', - '__reduce__', - '__reduce_ex__', - '__repr__', - '__rmul__', - '__setattr__', - '__sizeof__', - '__str__', - '__subclasshook__', - 'count', - 'index', - ]), - 'object': (object, [ - '__repr__', - '__hash__', - '__str__', - '__getattribute__', - '__setattr__', - '__delattr__', - '__lt__', - '__le__', - '__eq__', - '__ne__', - '__gt__', - '__ge__', - '__init__', - '__new__', - '__reduce_ex__', - '__reduce__', - '__subclasshook__', - '__init_subclass__', - '__format__', - '__sizeof__', - '__dir__', - '__class__', - '__doc__' - ]), -} - -not_implemented = [(name, method) - for name, (val, methods) in expected_methods.items() - for method in methods - if not hasattr(val, method)] - -for r in not_implemented: - print(r[0], ".", r[1], sep="") -if not not_implemented: - print("Not much \\o/") - -if platform.python_implementation() == "CPython": - assert len(not_implemented) == 0, "CPython should have all the methods" From 2eb8e7bf2b49ba578f0180588a5e8592d38c5fd0 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 7 Apr 2019 18:55:35 +0200 Subject: [PATCH 204/884] new ok for pybytes --- tests/snippets/bytes.py | 18 ++++++++++++ vm/src/obj/objbytes.rs | 61 ++++++++++++++++++----------------------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 19ebef68ae..1a4b43dfb0 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -1,3 +1,21 @@ +from testutils import assertRaises + +# new +assert bytes([1,2,3]) +assert bytes((1,2,3)) +assert bytes(range(4)) +assert b'bla' +assert bytes(3) +assert bytes("bla", "utf8") +try: + bytes("bla") +except TypeError: + assert True +else: + assert False + +# + assert b'foobar'.__eq__(2) == NotImplemented assert b'foobar'.__ne__(2) == NotImplemented assert b'foobar'.__gt__(2) == NotImplemented diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 1b06a96416..74795088f6 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -97,26 +97,6 @@ extend_class!(context, bytesiterator_type, { });*/ //} -fn load_byte( - elements: PyResult>, - vm: &VirtualMachine, -) -> Result, PyObjectRef> { - if let Ok(value) = elements { - let mut data_bytes = vec![]; - for elem in value.iter() { - let v = objint::to_int(vm, &elem, 10)?; - if let Some(i) = v.to_u8() { - data_bytes.push(i); - } else { - return Err(vm.new_value_error("byte must be in range(0, 256)".to_string())); - } - } - Ok(data_bytes) - } else { - Err(vm.new_value_error("byte must be in range(0, 256)".to_string())) - } -} - #[pyimpl(__inside_vm)] impl PyBytesRef { #[pymethod(name = "__new__")] @@ -126,14 +106,14 @@ impl PyBytesRef { enc_option: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - // Create bytes data: - + // First handle bytes(string, encoding[, errors]) if let OptionalArg::Present(enc) = enc_option { if let OptionalArg::Present(eval) = val_option { if let Ok(input) = eval.downcast::() { - if let Ok(encoding) = enc.downcast::() { + if let Ok(encoding) = enc.clone().downcast::() { if encoding.value.to_lowercase() == "utf8".to_string() || encoding.value.to_lowercase() == "utf-8".to_string() + // TODO: different encoding { return PyBytes::new(input.value.as_bytes().to_vec()) .into_ref_with_type(vm, cls.clone()); @@ -143,23 +123,23 @@ impl PyBytesRef { ); } } else { - return Err(vm.new_type_error("encodin is not string".to_string())); + return Err(vm.new_type_error(format!( + "bytes() argument 2 must be str, not {}", + enc.class().name + ))); } } else { - return Err(vm.new_type_error(format!( - "bytes() argument 2 must be str, not {}", - enc.class().name - ))); + return Err(vm.new_type_error("encoding without a string argument".to_string())); } } else { return Err(vm.new_type_error("encoding without a string argument".to_string())); } + // On ly one argument } else { let value = if let OptionalArg::Present(ival) = val_option { - println!("{:?}", ival); match_class!(ival.clone(), - _i @ PyInt => { - let size = objint::get_value(&ival).to_usize().unwrap(); + i @ PyInt => { + let size = objint::get_value(&i.into_object()).to_usize().unwrap(); let mut res: Vec = Vec::with_capacity(size); for _ in 0..size { res.push(0) @@ -168,9 +148,22 @@ impl PyBytesRef { _l @ PyString=> {return Err(vm.new_type_error(format!( "string argument without an encoding" )));}, - _j @ PyList => load_byte(vm.extract_elements(&ival), vm).or_else(|x| {return Err(x) }), - _k @ PyTuple => load_byte(vm.extract_elements(&ival), vm).or_else(|x| {return Err(x) }), - _obj => {} + obj => { + let elements = vm.extract_elements(&obj).or_else(|_| {return Err(vm.new_type_error(format!( + "cannot convert {} object to bytes", obj.class().name)));}); + + let mut data_bytes = vec![]; + for elem in elements.unwrap(){ + let v = objint::to_int(vm, &elem, 10)?; + if let Some(i) = v.to_u8() { + data_bytes.push(i); + } else { + return Err(vm.new_value_error("bytes must be in range(0, 256)".to_string())); + } + + } + Ok(data_bytes) + } ) } else { Ok(vec![]) From a0aa88d2fb3aba561e0a085a14f618f7008f1f92 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 7 Apr 2019 20:02:27 +0300 Subject: [PATCH 205/884] clear return implicit None --- vm/src/obj/objset.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 6c67312fb1..c3c580bc4b 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -290,9 +290,8 @@ impl PySetInner { perform_action_with_hash(vm, &mut self.elements, &item, &discard) } - fn clear(&mut self, vm: &VirtualMachine) -> PyResult { + fn clear(&mut self) -> () { self.elements.clear(); - Ok(vm.get_none()) } fn pop(&mut self, vm: &VirtualMachine) -> PyResult { @@ -498,8 +497,8 @@ impl PySetRef { self.inner.borrow_mut().discard(&item, vm) } - fn clear(self, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().clear(vm) + fn clear(self, _vm: &VirtualMachine) -> () { + self.inner.borrow_mut().clear() } fn pop(self, vm: &VirtualMachine) -> PyResult { From aec33634ca1323b68fbbc8ae75deda857b0e10b2 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sun, 7 Apr 2019 09:27:03 -0700 Subject: [PATCH 206/884] int: take self by ref --- vm/src/obj/objint.rs | 198 +++++++++++++++++++++++++------------------ 1 file changed, 115 insertions(+), 83 deletions(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 3c0c3b7079..706b4ecfa1 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -95,12 +95,8 @@ impl_try_from_object_int!( (u64, to_u64), ); -impl PyIntRef { - fn pass_value(self, _vm: &VirtualMachine) -> Self { - self - } - - fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { +impl PyInt { + fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value == *get_value(&other)) } else { @@ -108,7 +104,7 @@ impl PyIntRef { } } - fn ne(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn ne(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value != *get_value(&other)) } else { @@ -116,7 +112,7 @@ impl PyIntRef { } } - fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn lt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value < *get_value(&other)) } else { @@ -124,7 +120,7 @@ impl PyIntRef { } } - fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn le(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value <= *get_value(&other)) } else { @@ -132,7 +128,7 @@ impl PyIntRef { } } - fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn gt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value > *get_value(&other)) } else { @@ -140,7 +136,7 @@ impl PyIntRef { } } - fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn ge(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value >= *get_value(&other)) } else { @@ -148,7 +144,7 @@ impl PyIntRef { } } - fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) + get_value(&other)) } else { @@ -156,7 +152,7 @@ impl PyIntRef { } } - fn sub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) - get_value(&other)) } else { @@ -164,7 +160,7 @@ impl PyIntRef { } } - fn rsub(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn rsub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int(get_value(&other) - (&self.value)) } else { @@ -172,7 +168,7 @@ impl PyIntRef { } } - fn mul(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn mul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) * get_value(&other)) } else { @@ -180,7 +176,7 @@ impl PyIntRef { } } - fn truediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn truediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { div_ints(vm, &self.value, &get_value(&other)) } else { @@ -188,7 +184,7 @@ impl PyIntRef { } } - fn rtruediv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn rtruediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { div_ints(vm, &get_value(&other), &self.value) } else { @@ -196,7 +192,7 @@ impl PyIntRef { } } - fn floordiv(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn floordiv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other); if *v2 != BigInt::zero() { @@ -209,7 +205,7 @@ impl PyIntRef { } } - fn lshift(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn lshift(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if !objtype::isinstance(&other, &vm.ctx.int_type()) { return Ok(vm.ctx.not_implemented()); } @@ -228,7 +224,7 @@ impl PyIntRef { } } - fn rshift(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn rshift(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if !objtype::isinstance(&other, &vm.ctx.int_type()) { return Ok(vm.ctx.not_implemented()); } @@ -247,7 +243,7 @@ impl PyIntRef { } } - fn xor(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn xor(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) ^ get_value(&other)) } else { @@ -255,7 +251,7 @@ impl PyIntRef { } } - fn rxor(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn rxor(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int(get_value(&other) ^ (&self.value)) } else { @@ -263,7 +259,7 @@ impl PyIntRef { } } - fn or(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn or(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) | get_value(&other)) } else { @@ -271,7 +267,7 @@ impl PyIntRef { } } - fn and(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn and(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other); vm.ctx.new_int((&self.value) & v2) @@ -280,7 +276,7 @@ impl PyIntRef { } } - fn pow(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other).to_u32().unwrap(); vm.ctx.new_int(self.value.pow(v2)) @@ -292,7 +288,7 @@ impl PyIntRef { } } - fn mod_(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other); if *v2 != BigInt::zero() { @@ -305,7 +301,7 @@ impl PyIntRef { } } - fn divmod(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn divmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other); if *v2 != BigInt::zero() { @@ -321,37 +317,65 @@ impl PyIntRef { } } - fn neg(self, _vm: &VirtualMachine) -> BigInt { + fn neg(&self, _vm: &VirtualMachine) -> BigInt { -(&self.value) } - fn hash(self, _vm: &VirtualMachine) -> u64 { + fn hash(&self, _vm: &VirtualMachine) -> u64 { let mut hasher = std::collections::hash_map::DefaultHasher::new(); self.value.hash(&mut hasher); hasher.finish() } - fn abs(self, _vm: &VirtualMachine) -> BigInt { + fn abs(&self, _vm: &VirtualMachine) -> BigInt { self.value.abs() } - fn round(self, _precision: OptionalArg, _vm: &VirtualMachine) -> Self { - self + fn round( + zelf: PyRef, + _precision: OptionalArg, + _vm: &VirtualMachine, + ) -> PyIntRef { + zelf + } + + fn int(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { + zelf } - fn float(self, _vm: &VirtualMachine) -> f64 { - self.value.to_f64().unwrap() + fn pos(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { + zelf } - fn invert(self, _vm: &VirtualMachine) -> BigInt { + fn float(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { + zelf + } + + fn trunc(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { + zelf + } + + fn floor(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { + zelf + } + + fn ceil(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { + zelf + } + + fn index(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { + zelf + } + + fn invert(&self, _vm: &VirtualMachine) -> BigInt { !(&self.value) } - fn repr(self, _vm: &VirtualMachine) -> String { + fn repr(&self, _vm: &VirtualMachine) -> String { self.value.to_string() } - fn format(self, spec: PyStringRef, vm: &VirtualMachine) -> PyResult { + fn format(&self, spec: PyStringRef, vm: &VirtualMachine) -> PyResult { let format_spec = FormatSpec::parse(&spec.value); match format_spec.format_int(&self.value) { Ok(string) => Ok(string), @@ -359,15 +383,23 @@ impl PyIntRef { } } - fn bool(self, _vm: &VirtualMachine) -> bool { + fn bool(&self, _vm: &VirtualMachine) -> bool { !self.value.is_zero() } - fn bit_length(self, _vm: &VirtualMachine) -> usize { + fn bit_length(&self, _vm: &VirtualMachine) -> usize { self.value.bits() } - fn imag(self, _vm: &VirtualMachine) -> usize { + fn conjugate(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { + zelf + } + + fn real(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { + zelf + } + + fn imag(&self, _vm: &VirtualMachine) -> usize { 0 } } @@ -484,49 +516,49 @@ Base 0 means to interpret the base from the string as an integer literal. let int_type = &context.int_type; extend_class!(context, int_type, { "__doc__" => context.new_str(int_doc.to_string()), - "__eq__" => context.new_rustfunc(PyIntRef::eq), - "__ne__" => context.new_rustfunc(PyIntRef::ne), - "__lt__" => context.new_rustfunc(PyIntRef::lt), - "__le__" => context.new_rustfunc(PyIntRef::le), - "__gt__" => context.new_rustfunc(PyIntRef::gt), - "__ge__" => context.new_rustfunc(PyIntRef::ge), - "__abs__" => context.new_rustfunc(PyIntRef::abs), - "__add__" => context.new_rustfunc(PyIntRef::add), - "__radd__" => context.new_rustfunc(PyIntRef::add), - "__and__" => context.new_rustfunc(PyIntRef::and), - "__divmod__" => context.new_rustfunc(PyIntRef::divmod), - "__float__" => context.new_rustfunc(PyIntRef::float), - "__round__" => context.new_rustfunc(PyIntRef::round), - "__ceil__" => context.new_rustfunc(PyIntRef::pass_value), - "__floor__" => context.new_rustfunc(PyIntRef::pass_value), - "__index__" => context.new_rustfunc(PyIntRef::pass_value), - "__trunc__" => context.new_rustfunc(PyIntRef::pass_value), - "__int__" => context.new_rustfunc(PyIntRef::pass_value), - "__floordiv__" => context.new_rustfunc(PyIntRef::floordiv), - "__hash__" => context.new_rustfunc(PyIntRef::hash), - "__lshift__" => context.new_rustfunc(PyIntRef::lshift), - "__rshift__" => context.new_rustfunc(PyIntRef::rshift), + "__eq__" => context.new_rustfunc(PyInt::eq), + "__ne__" => context.new_rustfunc(PyInt::ne), + "__lt__" => context.new_rustfunc(PyInt::lt), + "__le__" => context.new_rustfunc(PyInt::le), + "__gt__" => context.new_rustfunc(PyInt::gt), + "__ge__" => context.new_rustfunc(PyInt::ge), + "__abs__" => context.new_rustfunc(PyInt::abs), + "__add__" => context.new_rustfunc(PyInt::add), + "__radd__" => context.new_rustfunc(PyInt::add), + "__and__" => context.new_rustfunc(PyInt::and), + "__divmod__" => context.new_rustfunc(PyInt::divmod), + "__float__" => context.new_rustfunc(PyInt::float), + "__round__" => context.new_rustfunc(PyInt::round), + "__ceil__" => context.new_rustfunc(PyInt::ceil), + "__floor__" => context.new_rustfunc(PyInt::floor), + "__index__" => context.new_rustfunc(PyInt::index), + "__trunc__" => context.new_rustfunc(PyInt::trunc), + "__int__" => context.new_rustfunc(PyInt::int), + "__floordiv__" => context.new_rustfunc(PyInt::floordiv), + "__hash__" => context.new_rustfunc(PyInt::hash), + "__lshift__" => context.new_rustfunc(PyInt::lshift), + "__rshift__" => context.new_rustfunc(PyInt::rshift), "__new__" => context.new_rustfunc(int_new), - "__mod__" => context.new_rustfunc(PyIntRef::mod_), - "__mul__" => context.new_rustfunc(PyIntRef::mul), - "__rmul__" => context.new_rustfunc(PyIntRef::mul), - "__or__" => context.new_rustfunc(PyIntRef::or), - "__neg__" => context.new_rustfunc(PyIntRef::neg), - "__pos__" => context.new_rustfunc(PyIntRef::pass_value), - "__pow__" => context.new_rustfunc(PyIntRef::pow), - "__repr__" => context.new_rustfunc(PyIntRef::repr), - "__sub__" => context.new_rustfunc(PyIntRef::sub), - "__rsub__" => context.new_rustfunc(PyIntRef::rsub), - "__format__" => context.new_rustfunc(PyIntRef::format), - "__truediv__" => context.new_rustfunc(PyIntRef::truediv), - "__rtruediv__" => context.new_rustfunc(PyIntRef::rtruediv), - "__xor__" => context.new_rustfunc(PyIntRef::xor), - "__rxor__" => context.new_rustfunc(PyIntRef::rxor), - "__bool__" => context.new_rustfunc(PyIntRef::bool), - "__invert__" => context.new_rustfunc(PyIntRef::invert), - "bit_length" => context.new_rustfunc(PyIntRef::bit_length), - "conjugate" => context.new_rustfunc(PyIntRef::pass_value), - "real" => context.new_property(PyIntRef::pass_value), - "imag" => context.new_property(PyIntRef::imag) + "__mod__" => context.new_rustfunc(PyInt::mod_), + "__mul__" => context.new_rustfunc(PyInt::mul), + "__rmul__" => context.new_rustfunc(PyInt::mul), + "__or__" => context.new_rustfunc(PyInt::or), + "__neg__" => context.new_rustfunc(PyInt::neg), + "__pos__" => context.new_rustfunc(PyInt::pos), + "__pow__" => context.new_rustfunc(PyInt::pow), + "__repr__" => context.new_rustfunc(PyInt::repr), + "__sub__" => context.new_rustfunc(PyInt::sub), + "__rsub__" => context.new_rustfunc(PyInt::rsub), + "__format__" => context.new_rustfunc(PyInt::format), + "__truediv__" => context.new_rustfunc(PyInt::truediv), + "__rtruediv__" => context.new_rustfunc(PyInt::rtruediv), + "__xor__" => context.new_rustfunc(PyInt::xor), + "__rxor__" => context.new_rustfunc(PyInt::rxor), + "__bool__" => context.new_rustfunc(PyInt::bool), + "__invert__" => context.new_rustfunc(PyInt::invert), + "bit_length" => context.new_rustfunc(PyInt::bit_length), + "conjugate" => context.new_rustfunc(PyInt::conjugate), + "real" => context.new_property(PyInt::real), + "imag" => context.new_property(PyInt::imag) }); } From e70f174eb6f2802f5fb935a82a8b387228ffd4ae Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sun, 7 Apr 2019 09:35:39 -0700 Subject: [PATCH 207/884] str: take self by ref --- vm/src/obj/objstr.rs | 117 ++++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 57 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 62cc923b1e..0e16a8b3b8 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -67,7 +67,7 @@ impl TryIntoRef for &str { } #[pyimpl(__inside_vm)] -impl PyStringRef { +impl PyString { // TODO: should with following format // class str(object='') // class str(object=b'', encoding='utf-8', errors='strict') @@ -89,7 +89,7 @@ impl PyStringRef { } } #[pymethod(name = "__add__")] - fn add(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn add(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { Ok(format!("{}{}", self.value, get_value(&rhs))) } else { @@ -98,12 +98,12 @@ impl PyStringRef { } #[pymethod(name = "__bool__")] - fn bool(self, _vm: &VirtualMachine) -> bool { + fn bool(&self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() } #[pymethod(name = "__eq__")] - fn eq(self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { + fn eq(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { self.value == get_value(&rhs) } else { @@ -112,17 +112,17 @@ impl PyStringRef { } #[pymethod(name = "__contains__")] - fn contains(self, needle: PyStringRef, _vm: &VirtualMachine) -> bool { + fn contains(&self, needle: PyStringRef, _vm: &VirtualMachine) -> bool { self.value.contains(&needle.value) } #[pymethod(name = "__getitem__")] - fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { subscript(vm, &self.value, needle) } #[pymethod(name = "__gt__")] - fn gt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn gt(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { Ok(self.value > get_value(&rhs)) } else { @@ -131,7 +131,7 @@ impl PyStringRef { } #[pymethod(name = "__ge__")] - fn ge(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn ge(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { Ok(self.value >= get_value(&rhs)) } else { @@ -140,7 +140,7 @@ impl PyStringRef { } #[pymethod(name = "__lt__")] - fn lt(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn lt(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { Ok(self.value < get_value(&rhs)) } else { @@ -149,7 +149,7 @@ impl PyStringRef { } #[pymethod(name = "__le__")] - fn le(self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn le(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&rhs, &vm.ctx.str_type()) { Ok(self.value <= get_value(&rhs)) } else { @@ -158,19 +158,19 @@ impl PyStringRef { } #[pymethod(name = "__hash__")] - fn hash(self, _vm: &VirtualMachine) -> usize { + fn hash(&self, _vm: &VirtualMachine) -> usize { let mut hasher = std::collections::hash_map::DefaultHasher::new(); self.value.hash(&mut hasher); hasher.finish() as usize } #[pymethod(name = "__len__")] - fn len(self, _vm: &VirtualMachine) -> usize { + fn len(&self, _vm: &VirtualMachine) -> usize { self.value.chars().count() } #[pymethod(name = "__mul__")] - fn mul(self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn mul(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&val, &vm.ctx.int_type()) { let value = &self.value; let multiplier = objint::get_value(&val).to_i32().unwrap(); @@ -185,12 +185,12 @@ impl PyStringRef { } #[pymethod(name = "__str__")] - fn str(self, _vm: &VirtualMachine) -> PyStringRef { - self + fn str(zelf: PyRef, _vm: &VirtualMachine) -> PyStringRef { + zelf } #[pymethod(name = "__repr__")] - fn repr(self, _vm: &VirtualMachine) -> String { + fn repr(&self, _vm: &VirtualMachine) -> String { let value = &self.value; let quote_char = if count_char(value, '\'') > count_char(value, '"') { '"' @@ -221,30 +221,30 @@ impl PyStringRef { } #[pymethod] - fn lower(self, _vm: &VirtualMachine) -> String { + fn lower(&self, _vm: &VirtualMachine) -> String { self.value.to_lowercase() } // casefold is much more aggressive than lower #[pymethod] - fn casefold(self, _vm: &VirtualMachine) -> String { + fn casefold(&self, _vm: &VirtualMachine) -> String { caseless::default_case_fold_str(&self.value) } #[pymethod] - fn upper(self, _vm: &VirtualMachine) -> String { + fn upper(&self, _vm: &VirtualMachine) -> String { self.value.to_uppercase() } #[pymethod] - fn capitalize(self, _vm: &VirtualMachine) -> String { + fn capitalize(&self, _vm: &VirtualMachine) -> String { let (first_part, lower_str) = self.value.split_at(1); format!("{}{}", first_part.to_uppercase(), lower_str) } #[pymethod] fn split( - self, + &self, pattern: OptionalArg, num: OptionalArg, vm: &VirtualMachine, @@ -266,7 +266,7 @@ impl PyStringRef { #[pymethod] fn rsplit( - self, + &self, pattern: OptionalArg, num: OptionalArg, vm: &VirtualMachine, @@ -287,23 +287,23 @@ impl PyStringRef { } #[pymethod] - fn strip(self, _vm: &VirtualMachine) -> String { + fn strip(&self, _vm: &VirtualMachine) -> String { self.value.trim().to_string() } #[pymethod] - fn lstrip(self, _vm: &VirtualMachine) -> String { + fn lstrip(&self, _vm: &VirtualMachine) -> String { self.value.trim_start().to_string() } #[pymethod] - fn rstrip(self, _vm: &VirtualMachine) -> String { + fn rstrip(&self, _vm: &VirtualMachine) -> String { self.value.trim_end().to_string() } #[pymethod] fn endswith( - self, + &self, suffix: PyStringRef, start: OptionalArg, end: OptionalArg, @@ -318,7 +318,7 @@ impl PyStringRef { #[pymethod] fn startswith( - self, + &self, prefix: PyStringRef, start: OptionalArg, end: OptionalArg, @@ -332,17 +332,17 @@ impl PyStringRef { } #[pymethod] - fn isalnum(self, _vm: &VirtualMachine) -> bool { + fn isalnum(&self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) } #[pymethod] - fn isnumeric(self, _vm: &VirtualMachine) -> bool { + fn isnumeric(&self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self.value.chars().all(char::is_numeric) } #[pymethod] - fn isdigit(self, _vm: &VirtualMachine) -> bool { + fn isdigit(&self, _vm: &VirtualMachine) -> bool { // python's isdigit also checks if exponents are digits, these are the unicodes for exponents let valid_unicodes: [u16; 10] = [ 0x2070, 0x00B9, 0x00B2, 0x00B3, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, 0x2079, @@ -359,7 +359,7 @@ impl PyStringRef { } #[pymethod] - fn isdecimal(self, _vm: &VirtualMachine) -> bool { + fn isdecimal(&self, _vm: &VirtualMachine) -> bool { if self.value.is_empty() { false } else { @@ -397,12 +397,12 @@ impl PyStringRef { } #[pymethod] - fn title(self, _vm: &VirtualMachine) -> String { + fn title(&self, _vm: &VirtualMachine) -> String { make_title(&self.value) } #[pymethod] - fn swapcase(self, _vm: &VirtualMachine) -> String { + fn swapcase(&self, _vm: &VirtualMachine) -> String { let mut swapped_str = String::with_capacity(self.value.len()); for c in self.value.chars() { // to_uppercase returns an iterator, to_ascii_uppercase returns the char @@ -418,13 +418,13 @@ impl PyStringRef { } #[pymethod] - fn isalpha(self, _vm: &VirtualMachine) -> bool { + fn isalpha(&self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) } #[pymethod] fn replace( - self, + &self, old: PyStringRef, new: PyStringRef, num: OptionalArg, @@ -439,12 +439,12 @@ impl PyStringRef { // cpython's isspace ignores whitespace, including \t and \n, etc, unless the whole string is empty // which is why isspace is using is_ascii_whitespace. Same for isupper & islower #[pymethod] - fn isspace(self, _vm: &VirtualMachine) -> bool { + fn isspace(&self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self.value.chars().all(|c| c.is_ascii_whitespace()) } #[pymethod] - fn isupper(self, _vm: &VirtualMachine) -> bool { + fn isupper(&self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self .value @@ -454,7 +454,7 @@ impl PyStringRef { } #[pymethod] - fn islower(self, _vm: &VirtualMachine) -> bool { + fn islower(&self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self .value @@ -464,13 +464,13 @@ impl PyStringRef { } #[pymethod] - fn isascii(self, _vm: &VirtualMachine) -> bool { + fn isascii(&self, _vm: &VirtualMachine) -> bool { !self.value.is_empty() && self.value.chars().all(|c| c.is_ascii()) } // doesn't implement keep new line delimiter just yet #[pymethod] - fn splitlines(self, vm: &VirtualMachine) -> PyObjectRef { + fn splitlines(&self, vm: &VirtualMachine) -> PyObjectRef { let elements = self .value .split('\n') @@ -480,7 +480,7 @@ impl PyStringRef { } #[pymethod] - fn join(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + fn join(&self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { let mut joined = String::new(); for (idx, elem) in iterable.iter(vm)?.enumerate() { @@ -496,7 +496,7 @@ impl PyStringRef { #[pymethod] fn find( - self, + &self, sub: PyStringRef, start: OptionalArg, end: OptionalArg, @@ -515,7 +515,7 @@ impl PyStringRef { #[pymethod] fn rfind( - self, + &self, sub: PyStringRef, start: OptionalArg, end: OptionalArg, @@ -534,7 +534,7 @@ impl PyStringRef { #[pymethod] fn index( - self, + &self, sub: PyStringRef, start: OptionalArg, end: OptionalArg, @@ -553,7 +553,7 @@ impl PyStringRef { #[pymethod] fn rindex( - self, + &self, sub: PyStringRef, start: OptionalArg, end: OptionalArg, @@ -571,7 +571,7 @@ impl PyStringRef { } #[pymethod] - fn partition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { + fn partition(&self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { let value = &self.value; let sub = &sub.value; let mut new_tup = Vec::new(); @@ -590,7 +590,7 @@ impl PyStringRef { } #[pymethod] - fn rpartition(self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { + fn rpartition(&self, sub: PyStringRef, vm: &VirtualMachine) -> PyObjectRef { let value = &self.value; let sub = &sub.value; let mut new_tup = Vec::new(); @@ -610,7 +610,7 @@ impl PyStringRef { } #[pymethod] - fn istitle(self, _vm: &VirtualMachine) -> bool { + fn istitle(&self, _vm: &VirtualMachine) -> bool { if self.value.is_empty() { false } else { @@ -620,7 +620,7 @@ impl PyStringRef { #[pymethod] fn count( - self, + &self, sub: PyStringRef, start: OptionalArg, end: OptionalArg, @@ -635,7 +635,7 @@ impl PyStringRef { } #[pymethod] - fn zfill(self, len: usize, _vm: &VirtualMachine) -> String { + fn zfill(&self, len: usize, _vm: &VirtualMachine) -> String { let value = &self.value; if len <= value.len() { value.to_string() @@ -644,7 +644,10 @@ impl PyStringRef { } } - fn get_fill_char<'a>(rep: &'a OptionalArg, vm: &VirtualMachine) -> PyResult<&'a str> { + fn get_fill_char<'a>( + rep: &'a OptionalArg, + vm: &VirtualMachine, + ) -> PyResult<&'a str> { let rep_str = match rep { OptionalArg::Present(ref st) => &st.value, OptionalArg::Missing => " ", @@ -660,7 +663,7 @@ impl PyStringRef { #[pymethod] fn ljust( - self, + &self, len: usize, rep: OptionalArg, vm: &VirtualMachine, @@ -672,7 +675,7 @@ impl PyStringRef { #[pymethod] fn rjust( - self, + &self, len: usize, rep: OptionalArg, vm: &VirtualMachine, @@ -684,7 +687,7 @@ impl PyStringRef { #[pymethod] fn center( - self, + &self, len: usize, rep: OptionalArg, vm: &VirtualMachine, @@ -702,7 +705,7 @@ impl PyStringRef { } #[pymethod] - fn expandtabs(self, tab_stop: OptionalArg, _vm: &VirtualMachine) -> String { + fn expandtabs(&self, tab_stop: OptionalArg, _vm: &VirtualMachine) -> String { let tab_stop = tab_stop.into_option().unwrap_or(8 as usize); let mut expanded_str = String::new(); let mut tab_size = tab_stop; @@ -726,7 +729,7 @@ impl PyStringRef { } #[pymethod] - fn isidentifier(self, _vm: &VirtualMachine) -> bool { + fn isidentifier(&self, _vm: &VirtualMachine) -> bool { let value = &self.value; // a string is not an identifier if it has whitespace or starts with a number if !value.chars().any(|c| c.is_ascii_whitespace()) @@ -769,7 +772,7 @@ impl IntoPyObject for &String { } pub fn init(ctx: &PyContext) { - PyStringRef::extend_class(ctx, &ctx.str_type); + PyString::extend_class(ctx, &ctx.str_type); } pub fn get_value(obj: &PyObjectRef) -> String { From ea61599da46f302079e633ee80ea8acb1016c6b0 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sun, 7 Apr 2019 09:41:52 -0700 Subject: [PATCH 208/884] range: take self by ref --- vm/src/obj/objrange.rs | 56 +++++++++++++++++++++--------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 71adb41189..2820dd9b45 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -101,20 +101,20 @@ pub fn init(context: &PyContext) { When step is given, it specifies the increment (or decrement)."; extend_class!(context, range_type, { - "__bool__" => context.new_rustfunc(PyRangeRef::bool), - "__contains__" => context.new_rustfunc(PyRangeRef::contains), + "__bool__" => context.new_rustfunc(PyRange::bool), + "__contains__" => context.new_rustfunc(PyRange::contains), "__doc__" => context.new_str(range_doc.to_string()), - "__getitem__" => context.new_rustfunc(PyRangeRef::getitem), - "__iter__" => context.new_rustfunc(PyRangeRef::iter), - "__len__" => context.new_rustfunc(PyRangeRef::len), + "__getitem__" => context.new_rustfunc(PyRange::getitem), + "__iter__" => context.new_rustfunc(PyRange::iter), + "__len__" => context.new_rustfunc(PyRange::len), "__new__" => context.new_rustfunc(range_new), - "__repr__" => context.new_rustfunc(PyRangeRef::repr), - "__reversed__" => context.new_rustfunc(PyRangeRef::reversed), - "count" => context.new_rustfunc(PyRangeRef::count), - "index" => context.new_rustfunc(PyRangeRef::index), - "start" => context.new_property(PyRangeRef::start), - "stop" => context.new_property(PyRangeRef::stop), - "step" => context.new_property(PyRangeRef::step), + "__repr__" => context.new_rustfunc(PyRange::repr), + "__reversed__" => context.new_rustfunc(PyRange::reversed), + "count" => context.new_rustfunc(PyRange::count), + "index" => context.new_rustfunc(PyRange::index), + "start" => context.new_property(PyRange::start), + "stop" => context.new_property(PyRange::stop), + "step" => context.new_property(PyRange::step), }); let rangeiterator_type = &context.rangeiterator_type; @@ -126,7 +126,7 @@ pub fn init(context: &PyContext) { type PyRangeRef = PyRef; -impl PyRangeRef { +impl PyRange { fn new(cls: PyClassRef, stop: PyIntRef, vm: &VirtualMachine) -> PyResult { PyRange { start: PyInt::new(BigInt::zero()).into_ref(vm), @@ -153,26 +153,26 @@ impl PyRangeRef { .into_ref_with_type(vm, cls) } - fn start(self, _vm: &VirtualMachine) -> PyIntRef { + fn start(&self, _vm: &VirtualMachine) -> PyIntRef { self.start.clone() } - fn stop(self, _vm: &VirtualMachine) -> PyIntRef { + fn stop(&self, _vm: &VirtualMachine) -> PyIntRef { self.stop.clone() } - fn step(self, _vm: &VirtualMachine) -> PyIntRef { + fn step(&self, _vm: &VirtualMachine) -> PyIntRef { self.step.clone() } - fn iter(self: PyRangeRef, _vm: &VirtualMachine) -> PyRangeIterator { + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRangeIterator { PyRangeIterator { position: Cell::new(0), - range: self, + range: zelf, } } - fn reversed(self: PyRangeRef, vm: &VirtualMachine) -> PyRangeIterator { + fn reversed(&self, vm: &VirtualMachine) -> PyRangeIterator { let start = self.start.as_bigint(); let stop = self.stop.as_bigint(); let step = self.step.as_bigint(); @@ -204,7 +204,7 @@ impl PyRangeRef { } } - fn len(self, _vm: &VirtualMachine) -> PyInt { + fn len(&self, _vm: &VirtualMachine) -> PyInt { let start = self.start.as_bigint(); let stop = self.stop.as_bigint(); let step = self.step.as_bigint(); @@ -217,7 +217,7 @@ impl PyRangeRef { } } - fn repr(self, _vm: &VirtualMachine) -> String { + fn repr(&self, _vm: &VirtualMachine) -> String { if self.step.as_bigint().is_one() { format!("range({}, {})", self.start, self.stop) } else { @@ -225,11 +225,11 @@ impl PyRangeRef { } } - fn bool(self, _vm: &VirtualMachine) -> bool { + fn bool(&self, _vm: &VirtualMachine) -> bool { !self.is_empty() } - fn contains(self, needle: PyObjectRef, _vm: &VirtualMachine) -> bool { + fn contains(&self, needle: PyObjectRef, _vm: &VirtualMachine) -> bool { if let Ok(int) = needle.downcast::() { match self.offset(int.as_bigint()) { Some(ref offset) => offset.is_multiple_of(self.step.as_bigint()), @@ -240,7 +240,7 @@ impl PyRangeRef { } } - fn index(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn index(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { if let Ok(int) = needle.downcast::() { match self.index_of(int.as_bigint()) { Some(idx) => Ok(PyInt::new(idx)), @@ -251,7 +251,7 @@ impl PyRangeRef { } } - fn count(self, item: PyObjectRef, _vm: &VirtualMachine) -> PyInt { + fn count(&self, item: PyObjectRef, _vm: &VirtualMachine) -> PyInt { if let Ok(int) = item.downcast::() { if self.index_of(int.as_bigint()).is_some() { PyInt::new(1) @@ -263,7 +263,7 @@ impl PyRangeRef { } } - fn getitem(self, subscript: RangeIndex, vm: &VirtualMachine) -> PyResult { + fn getitem(&self, subscript: RangeIndex, vm: &VirtualMachine) -> PyResult { match subscript { RangeIndex::Int(index) => { if let Some(value) = self.get(index.as_bigint()) { @@ -314,10 +314,10 @@ impl PyRangeRef { fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let range = if args.args.len() <= 2 { let (cls, stop) = args.bind(vm)?; - PyRangeRef::new(cls, stop, vm) + PyRange::new(cls, stop, vm) } else { let (cls, start, stop, step) = args.bind(vm)?; - PyRangeRef::new_from(cls, start, stop, step, vm) + PyRange::new_from(cls, start, stop, step, vm) }?; Ok(range.into_object()) From 3bc1e3598c9999a4f1aca5ea72a3d4f749702317 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 7 Apr 2019 21:26:20 +0300 Subject: [PATCH 209/884] Use PyIterable --- tests/snippets/set.py | 2 ++ vm/src/obj/objset.rs | 48 +++++++++++++++++++------------------------ 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index 0117163350..11d62944a3 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -49,6 +49,7 @@ def __hash__(self): assert set([1,2,3]).union(set([4,5])) == set([1,2,3,4,5]) assert set([1,2,3]).union(set([1,2,3,4,5])) == set([1,2,3,4,5]) +assert set([1,2,3]).union([1,2,3,4,5]) == set([1,2,3,4,5]) assert set([1,2,3]) | set([4,5]) == set([1,2,3,4,5]) assert set([1,2,3]) | set([1,2,3,4,5]) == set([1,2,3,4,5]) @@ -181,6 +182,7 @@ def __hash__(self): assert frozenset([1,2,3]).union(frozenset([4,5])) == frozenset([1,2,3,4,5]) assert frozenset([1,2,3]).union(frozenset([1,2,3,4,5])) == frozenset([1,2,3,4,5]) +assert frozenset([1,2,3]).union([1,2,3,4,5]) == frozenset([1,2,3,4,5]) assert frozenset([1,2,3]) | frozenset([4,5]) == frozenset([1,2,3,4,5]) assert frozenset([1,2,3]) | frozenset([1,2,3,4,5]) == frozenset([1,2,3,4,5]) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index c3c580bc4b..46ba1db664 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -8,7 +8,9 @@ use std::fmt; use std::hash::{Hash, Hasher}; use crate::function::OptionalArg; -use crate::pyobject::{PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{ + PyContext, PyIterable, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, +}; use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; @@ -61,14 +63,13 @@ struct PySetInner { } impl PySetInner { - fn new(iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { + fn new(iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { let elements: HashMap = match iterable { OptionalArg::Missing => HashMap::new(), OptionalArg::Present(iterable) => { let mut elements = HashMap::new(); - let iterator = objiter::get_iter(vm, &iterable)?; - while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { - insert_into_set(vm, &mut elements, &v)?; + for item in iterable.iter(vm)? { + insert_into_set(vm, &mut elements, &item?)?; } elements } @@ -175,9 +176,11 @@ impl PySetInner { ) } - fn union(&self, other: &PySetInner, _vm: &VirtualMachine) -> PyResult { + fn union(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { let mut elements = self.elements.clone(); - elements.extend(other.elements.clone()); + for item in other.iter(vm)? { + insert_into_set(vm, &mut elements, &item?)?; + } Ok(PySetInner { elements }) } @@ -302,10 +305,9 @@ impl PySetInner { } } - fn update(&mut self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let iterator = objiter::get_iter(vm, &iterable)?; - while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { - insert_into_set(vm, &mut self.elements, &v)?; + fn update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + for item in iterable.iter(vm)? { + insert_into_set(vm, &mut self.elements, &item?)?; } Ok(vm.get_none()) } @@ -350,7 +352,7 @@ impl PySetInner { impl PySetRef { fn new( cls: PyClassRef, - iterable: OptionalArg, + iterable: OptionalArg, vm: &VirtualMachine, ) -> PyResult { PySet { @@ -413,14 +415,10 @@ impl PySetRef { ) } - fn union(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn union(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { - inner: RefCell::new(match_class!(other, - set @ PySet => self.inner.borrow().union(&set.inner.borrow(), vm)?, - frozen @ PyFrozenSet => self.inner.borrow().union(&frozen.inner, vm)?, - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - )), + inner: RefCell::new(self.inner.borrow().union(other, vm)?), }, PySet::class(vm), None, @@ -505,12 +503,12 @@ impl PySetRef { self.inner.borrow_mut().pop(vm) } - fn ior(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn ior(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner.borrow_mut().update(iterable, vm)?; Ok(self.as_object().clone()) } - fn update(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn update(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner.borrow_mut().update(iterable, vm)?; Ok(vm.get_none()) } @@ -565,7 +563,7 @@ impl PySetRef { impl PyFrozenSetRef { fn new( cls: PyClassRef, - iterable: OptionalArg, + iterable: OptionalArg, vm: &VirtualMachine, ) -> PyResult { PyFrozenSet { @@ -628,14 +626,10 @@ impl PyFrozenSetRef { ) } - fn union(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn union(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { - inner: match_class!(other, - set @ PySet => self.inner.union(&set.inner.borrow(), vm)?, - frozen @ PyFrozenSet => self.inner.union(&frozen.inner, vm)?, - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ), + inner: self.inner.union(other, vm)?, }, PyFrozenSet::class(vm), None, From f538a92007f375c07dd018f295fd34b101be4d4b Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 7 Apr 2019 20:34:27 +0200 Subject: [PATCH 210/884] pybytes.__new__ ok --- vm/src/obj/objbyteinner.rs | 91 +++++++++++++++++++++++++++++++++++++- vm/src/obj/objbytes.rs | 69 ++--------------------------- 2 files changed, 92 insertions(+), 68 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 0607621f1a..fd08e4b939 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -1,9 +1,96 @@ +use crate::obj::objtype::PyClassRef; +use crate::pyobject::PyObjectRef; + +use crate::function::OptionalArg; + +use crate::vm::VirtualMachine; + +use crate::pyobject::{PyResult, TypeProtocol}; + +use crate::obj::objstr::PyString; + +use super::objint; +use crate::obj::objint::PyInt; +use num_traits::ToPrimitive; + #[derive(Debug, Default, Clone)] pub struct PyByteInner { pub elements: Vec, } + impl PyByteInner { - pub fn new(data: Vec) -> Self { - PyByteInner { elements: data } + pub fn new( + val_option: OptionalArg, + enc_option: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + // First handle bytes(string, encoding[, errors]) + if let OptionalArg::Present(enc) = enc_option { + if let OptionalArg::Present(eval) = val_option { + if let Ok(input) = eval.downcast::() { + if let Ok(encoding) = enc.clone().downcast::() { + if encoding.value.to_lowercase() == "utf8".to_string() + || encoding.value.to_lowercase() == "utf-8".to_string() + // TODO: different encoding + { + return Ok(PyByteInner { + elements: input.value.as_bytes().to_vec(), + }); + } else { + return Err( + vm.new_value_error(format!("unknown encoding: {}", encoding.value)), //should be lookup error + ); + } + } else { + return Err(vm.new_type_error(format!( + "bytes() argument 2 must be str, not {}", + enc.class().name + ))); + } + } else { + return Err(vm.new_type_error("encoding without a string argument".to_string())); + } + } else { + return Err(vm.new_type_error("encoding without a string argument".to_string())); + } + // On ly one argument + } else { + let value = if let OptionalArg::Present(ival) = val_option { + match_class!(ival.clone(), + i @ PyInt => { + let size = objint::get_value(&i.into_object()).to_usize().unwrap(); + let mut res: Vec = Vec::with_capacity(size); + for _ in 0..size { + res.push(0) + } + Ok(res)}, + _l @ PyString=> {return Err(vm.new_type_error(format!( + "string argument without an encoding" + )));}, + obj => { + let elements = vm.extract_elements(&obj).or_else(|_| {return Err(vm.new_type_error(format!( + "cannot convert {} object to bytes", obj.class().name)));}); + + let mut data_bytes = vec![]; + for elem in elements.unwrap(){ + let v = objint::to_int(vm, &elem, 10)?; + if let Some(i) = v.to_u8() { + data_bytes.push(i); + } else { + return Err(vm.new_value_error("bytes must be in range(0, 256)".to_string())); + } + + } + Ok(data_bytes) + } + ) + } else { + Ok(vec![]) + }; + match value { + Ok(val) => Ok(PyByteInner { elements: val }), + Err(err) => Err(err), + } + } } } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 74795088f6..07e71e23cf 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -106,73 +106,10 @@ impl PyBytesRef { enc_option: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - // First handle bytes(string, encoding[, errors]) - if let OptionalArg::Present(enc) = enc_option { - if let OptionalArg::Present(eval) = val_option { - if let Ok(input) = eval.downcast::() { - if let Ok(encoding) = enc.clone().downcast::() { - if encoding.value.to_lowercase() == "utf8".to_string() - || encoding.value.to_lowercase() == "utf-8".to_string() - // TODO: different encoding - { - return PyBytes::new(input.value.as_bytes().to_vec()) - .into_ref_with_type(vm, cls.clone()); - } else { - return Err( - vm.new_value_error(format!("unknown encoding: {}", encoding.value)), //should be lookup error - ); - } - } else { - return Err(vm.new_type_error(format!( - "bytes() argument 2 must be str, not {}", - enc.class().name - ))); - } - } else { - return Err(vm.new_type_error("encoding without a string argument".to_string())); - } - } else { - return Err(vm.new_type_error("encoding without a string argument".to_string())); - } - // On ly one argument - } else { - let value = if let OptionalArg::Present(ival) = val_option { - match_class!(ival.clone(), - i @ PyInt => { - let size = objint::get_value(&i.into_object()).to_usize().unwrap(); - let mut res: Vec = Vec::with_capacity(size); - for _ in 0..size { - res.push(0) - } - Ok(res)}, - _l @ PyString=> {return Err(vm.new_type_error(format!( - "string argument without an encoding" - )));}, - obj => { - let elements = vm.extract_elements(&obj).or_else(|_| {return Err(vm.new_type_error(format!( - "cannot convert {} object to bytes", obj.class().name)));}); - - let mut data_bytes = vec![]; - for elem in elements.unwrap(){ - let v = objint::to_int(vm, &elem, 10)?; - if let Some(i) = v.to_u8() { - data_bytes.push(i); - } else { - return Err(vm.new_value_error("bytes must be in range(0, 256)".to_string())); - } - - } - Ok(data_bytes) - } - ) - } else { - Ok(vec![]) - }; - match value { - Ok(val) => PyBytes::new(val).into_ref_with_type(vm, cls.clone()), - Err(err) => Err(err), - } + PyBytes { + inner: PyByteInner::new(val_option, enc_option, vm)?, } + .into_ref_with_type(vm, cls) } #[pymethod(name = "__repr__")] From c2e03ed34287ab9b9cd827b894b3d8fffa82efd1 Mon Sep 17 00:00:00 2001 From: Joey Hain Date: Sun, 7 Apr 2019 11:55:13 -0700 Subject: [PATCH 211/884] fix int.__float__ --- vm/src/obj/objint.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 706b4ecfa1..de211668f3 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -347,8 +347,11 @@ impl PyInt { zelf } - fn float(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { - zelf + fn float(&self, vm: &VirtualMachine) -> PyResult { + self.value + .to_f64() + .map(PyFloat::from) + .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string())) } fn trunc(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { From fa369ff77964b53d34a40a3a481bc728c2e0be47 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 7 Apr 2019 21:58:37 +0300 Subject: [PATCH 212/884] Limit non-operator versions to set and frozenset --- tests/snippets/set.py | 2 ++ vm/src/obj/objset.rs | 37 ++++++++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index 11d62944a3..174426aa2b 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -53,6 +53,7 @@ def __hash__(self): assert set([1,2,3]) | set([4,5]) == set([1,2,3,4,5]) assert set([1,2,3]) | set([1,2,3,4,5]) == set([1,2,3,4,5]) +assert_raises(TypeError, lambda: set([1,2,3]) | [1,2,3,4,5]) assert set([1,2,3]).intersection(set([1,2])) == set([1,2]) assert set([1,2,3]).intersection(set([5,6])) == set([]) @@ -186,6 +187,7 @@ def __hash__(self): assert frozenset([1,2,3]) | frozenset([4,5]) == frozenset([1,2,3,4,5]) assert frozenset([1,2,3]) | frozenset([1,2,3,4,5]) == frozenset([1,2,3,4,5]) +assert_raises(TypeError, lambda: frozenset([1,2,3]) | [1,2,3,4,5]) assert frozenset([1,2,3]).intersection(frozenset([1,2])) == frozenset([1,2]) assert frozenset([1,2,3]).intersection(frozenset([5,6])) == frozenset([]) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 46ba1db664..2aa00431bf 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -9,7 +9,8 @@ use std::hash::{Hash, Hasher}; use crate::function::OptionalArg; use crate::pyobject::{ - PyContext, PyIterable, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + PyContext, PyIterable, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, + TypeProtocol, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -17,6 +18,7 @@ use super::objbool; use super::objint; use super::objiter; use super::objlist::PyListIterator; +use super::objtype; use super::objtype::PyClassRef; #[derive(Default)] @@ -467,6 +469,10 @@ impl PySetRef { )) } + fn or(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.union(other.iterable, vm) + } + fn iter(self, vm: &VirtualMachine) -> PyListIterator { self.inner.borrow().iter(vm) } @@ -636,6 +642,10 @@ impl PyFrozenSetRef { )) } + fn or(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.union(other.iterable, vm) + } + fn intersection(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { @@ -732,6 +742,27 @@ enum SetCombineOperation { Difference, } +struct SetIterable { + iterable: PyIterable, +} + +impl TryFromObject for SetIterable { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + if objtype::issubclass(&obj.class(), &vm.ctx.set_type()) + || objtype::issubclass(&obj.class(), &vm.ctx.frozenset_type()) + { + Ok(SetIterable { + iterable: PyIterable::try_from_object(vm, obj)?, + }) + } else { + Err(vm.new_type_error(format!( + "{} is not a subtype of set or frozenset", + obj.class() + ))) + } + } +} + fn set_hash(_zelf: PySetRef, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error("unhashable type".to_string())) } @@ -757,7 +788,7 @@ pub fn init(context: &PyContext) { "issubset" => context.new_rustfunc(PySetRef::le), "issuperset" => context.new_rustfunc(PySetRef::ge), "union" => context.new_rustfunc(PySetRef::union), - "__or__" => context.new_rustfunc(PySetRef::union), + "__or__" => context.new_rustfunc(PySetRef::or), "intersection" => context.new_rustfunc(PySetRef::intersection), "__and__" => context.new_rustfunc(PySetRef::intersection), "difference" => context.new_rustfunc(PySetRef::difference), @@ -798,7 +829,7 @@ pub fn init(context: &PyContext) { "issubset" => context.new_rustfunc(PyFrozenSetRef::le), "issuperset" => context.new_rustfunc(PyFrozenSetRef::ge), "union" => context.new_rustfunc(PyFrozenSetRef::union), - "__or__" => context.new_rustfunc(PyFrozenSetRef::union), + "__or__" => context.new_rustfunc(PyFrozenSetRef::or), "intersection" => context.new_rustfunc(PyFrozenSetRef::intersection), "__and__" => context.new_rustfunc(PyFrozenSetRef::intersection), "difference" => context.new_rustfunc(PyFrozenSetRef::difference), From e011e3f32721fa1e7b3cd749152956cde336a5ee Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 7 Apr 2019 22:36:56 +0300 Subject: [PATCH 213/884] intersection and difference support iterable --- tests/snippets/set.py | 8 +++ vm/src/obj/objset.rs | 142 +++++++++++++++++++++--------------------- 2 files changed, 80 insertions(+), 70 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index 174426aa2b..a7977f35de 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -57,15 +57,19 @@ def __hash__(self): assert set([1,2,3]).intersection(set([1,2])) == set([1,2]) assert set([1,2,3]).intersection(set([5,6])) == set([]) +assert set([1,2,3]).intersection([1,2]) == set([1,2]) assert set([1,2,3]) & set([4,5]) == set([]) assert set([1,2,3]) & set([1,2,3,4,5]) == set([1,2,3]) +assert_raises(TypeError, lambda: set([1,2,3]) & [1,2,3,4,5]) assert set([1,2,3]).difference(set([1,2])) == set([3]) assert set([1,2,3]).difference(set([5,6])) == set([1,2,3]) +assert set([1,2,3]).difference([1,2]) == set([3]) assert set([1,2,3]) - set([4,5]) == set([1,2,3]) assert set([1,2,3]) - set([1,2,3,4,5]) == set([]) +assert_raises(TypeError, lambda: set([1,2,3]) - [1,2,3,4,5]) assert set([1,2,3]).symmetric_difference(set([1,2])) == set([3]) assert set([1,2,3]).symmetric_difference(set([5,6])) == set([1,2,3,5,6]) @@ -191,15 +195,19 @@ def __hash__(self): assert frozenset([1,2,3]).intersection(frozenset([1,2])) == frozenset([1,2]) assert frozenset([1,2,3]).intersection(frozenset([5,6])) == frozenset([]) +assert frozenset([1,2,3]).intersection([1,2]) == frozenset([1,2]) assert frozenset([1,2,3]) & frozenset([4,5]) == frozenset([]) assert frozenset([1,2,3]) & frozenset([1,2,3,4,5]) == frozenset([1,2,3]) +assert_raises(TypeError, lambda: frozenset([1,2,3]) & [1,2,3,4,5]) assert frozenset([1,2,3]).difference(frozenset([1,2])) == frozenset([3]) assert frozenset([1,2,3]).difference(frozenset([5,6])) == frozenset([1,2,3]) +assert frozenset([1,2,3]).difference([1,2]) == frozenset([3]) assert frozenset([1,2,3]) - frozenset([4,5]) == frozenset([1,2,3]) assert frozenset([1,2,3]) - frozenset([1,2,3,4,5]) == frozenset([]) +assert_raises(TypeError, lambda: frozenset([1,2,3]) - [1,2,3,4,5]) assert frozenset([1,2,3]).symmetric_difference(frozenset([1,2])) == frozenset([3]) assert frozenset([1,2,3]).symmetric_difference(frozenset([5,6])) == frozenset([1,2,3,5,6]) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 2aa00431bf..508400694f 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -187,33 +187,27 @@ impl PySetInner { Ok(PySetInner { elements }) } - fn intersection(&self, other: &PySetInner, vm: &VirtualMachine) -> PyResult { - self._combine_inner(other, vm, SetCombineOperation::Intersection) - } - - fn difference(&self, other: &PySetInner, vm: &VirtualMachine) -> PyResult { - self._combine_inner(other, vm, SetCombineOperation::Difference) - } - - fn _combine_inner( - &self, - other: &PySetInner, - vm: &VirtualMachine, - op: SetCombineOperation, - ) -> PyResult { + fn intersection(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { let mut elements = HashMap::new(); - - for element in self.elements.iter() { - let value = other.contains(element.1.clone(), vm)?; - let should_add = match op { - SetCombineOperation::Intersection => objbool::get_value(&value), - SetCombineOperation::Difference => !objbool::get_value(&value), - }; - if should_add { - elements.insert(element.0.clone(), element.1.clone()); + for item in other.iter(vm)? { + if let Ok(obj) = item { + if objbool::get_value(&self.contains(obj.clone(), vm)?) { + insert_into_set(vm, &mut elements, &obj)?; + } } } + Ok(PySetInner { elements }) + } + fn difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + let mut elements = self.elements.clone(); + for item in other.iter(vm)? { + if let Ok(obj) = item { + if objbool::get_value(&self.contains(obj.clone(), vm)?) { + remove_from_set(vm, &mut elements, &obj)?; + } + } + } Ok(PySetInner { elements }) } @@ -265,21 +259,7 @@ impl PySetInner { } fn remove(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult { - fn remove( - vm: &VirtualMachine, - elements: &mut HashMap, - key: u64, - value: &PyObjectRef, - ) -> PyResult { - match elements.remove(&key) { - None => { - let item_str = format!("{:?}", value); - Err(vm.new_key_error(item_str)) - } - Some(_) => Ok(vm.get_none()), - } - } - perform_action_with_hash(vm, &mut self.elements, &item, &remove) + remove_from_set(vm, &mut self.elements, &item) } fn discard(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -427,28 +407,20 @@ impl PySetRef { )) } - fn intersection(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn intersection(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { - inner: RefCell::new(match_class!(other, - set @ PySet => self.inner.borrow().intersection(&set.inner.borrow(), vm)?, - frozen @ PyFrozenSet => self.inner.borrow().intersection(&frozen.inner, vm)?, - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - )), + inner: RefCell::new(self.inner.borrow().intersection(other, vm)?), }, PySet::class(vm), None, )) } - fn difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn difference(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { - inner: RefCell::new(match_class!(other, - set @ PySet => self.inner.borrow().difference(&set.inner.borrow(), vm)?, - frozen @ PyFrozenSet => self.inner.borrow().difference(&frozen.inner, vm)?, - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - )), + inner: RefCell::new(self.inner.borrow().difference(other, vm)?), }, PySet::class(vm), None, @@ -473,6 +445,14 @@ impl PySetRef { self.union(other.iterable, vm) } + fn and(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.intersection(other.iterable, vm) + } + + fn sub(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.difference(other.iterable, vm) + } + fn iter(self, vm: &VirtualMachine) -> PyListIterator { self.inner.borrow().iter(vm) } @@ -642,32 +622,20 @@ impl PyFrozenSetRef { )) } - fn or(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { - self.union(other.iterable, vm) - } - - fn intersection(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn intersection(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { - inner: match_class!(other, - set @ PySet => self.inner.intersection(&set.inner.borrow(), vm)?, - frozen @ PyFrozenSet => self.inner.intersection(&frozen.inner, vm)?, - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ), + inner: self.inner.intersection(other, vm)?, }, PyFrozenSet::class(vm), None, )) } - fn difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn difference(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { - inner: match_class!(other, - set @ PySet => self.inner.difference(&set.inner.borrow(), vm)?, - frozen @ PyFrozenSet => self.inner.difference(&frozen.inner, vm)?, - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ), + inner: self.inner.difference(other, vm)?, }, PyFrozenSet::class(vm), None, @@ -688,6 +656,18 @@ impl PyFrozenSetRef { )) } + fn or(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.union(other.iterable, vm) + } + + fn and(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.intersection(other.iterable, vm) + } + + fn sub(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.difference(other.iterable, vm) + } + fn iter(self, vm: &VirtualMachine) -> PyListIterator { self.inner.iter(vm) } @@ -737,6 +717,28 @@ fn insert_into_set( perform_action_with_hash(vm, elements, item, &insert) } +fn remove_from_set( + vm: &VirtualMachine, + elements: &mut HashMap, + item: &PyObjectRef, +) -> PyResult { + fn remove( + vm: &VirtualMachine, + elements: &mut HashMap, + key: u64, + value: &PyObjectRef, + ) -> PyResult { + match elements.remove(&key) { + None => { + let item_str = format!("{:?}", value); + Err(vm.new_key_error(item_str)) + } + Some(_) => Ok(vm.get_none()), + } + } + perform_action_with_hash(vm, elements, item, &remove) +} + enum SetCombineOperation { Intersection, Difference, @@ -790,9 +792,9 @@ pub fn init(context: &PyContext) { "union" => context.new_rustfunc(PySetRef::union), "__or__" => context.new_rustfunc(PySetRef::or), "intersection" => context.new_rustfunc(PySetRef::intersection), - "__and__" => context.new_rustfunc(PySetRef::intersection), + "__and__" => context.new_rustfunc(PySetRef::and), "difference" => context.new_rustfunc(PySetRef::difference), - "__sub__" => context.new_rustfunc(PySetRef::difference), + "__sub__" => context.new_rustfunc(PySetRef::sub), "symmetric_difference" => context.new_rustfunc(PySetRef::symmetric_difference), "__xor__" => context.new_rustfunc(PySetRef::symmetric_difference), "__doc__" => context.new_str(set_doc.to_string()), @@ -831,9 +833,9 @@ pub fn init(context: &PyContext) { "union" => context.new_rustfunc(PyFrozenSetRef::union), "__or__" => context.new_rustfunc(PyFrozenSetRef::or), "intersection" => context.new_rustfunc(PyFrozenSetRef::intersection), - "__and__" => context.new_rustfunc(PyFrozenSetRef::intersection), + "__and__" => context.new_rustfunc(PyFrozenSetRef::and), "difference" => context.new_rustfunc(PyFrozenSetRef::difference), - "__sub__" => context.new_rustfunc(PyFrozenSetRef::difference), + "__sub__" => context.new_rustfunc(PyFrozenSetRef::sub), "symmetric_difference" => context.new_rustfunc(PyFrozenSetRef::symmetric_difference), "__xor__" => context.new_rustfunc(PyFrozenSetRef::symmetric_difference), "__contains__" => context.new_rustfunc(PyFrozenSetRef::contains), From 3ed8727ee5b8252941d19e6c7e318510826ae903 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 7 Apr 2019 23:01:49 +0300 Subject: [PATCH 214/884] symmetric_difference support iterable --- tests/snippets/set.py | 4 +++ vm/src/obj/objset.rs | 69 ++++++++++++++++++------------------------- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index a7977f35de..42237dd2e1 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -73,9 +73,11 @@ def __hash__(self): assert set([1,2,3]).symmetric_difference(set([1,2])) == set([3]) assert set([1,2,3]).symmetric_difference(set([5,6])) == set([1,2,3,5,6]) +assert set([1,2,3]).symmetric_difference([1,2]) == set([3]) assert set([1,2,3]) ^ set([4,5]) == set([1,2,3,4,5]) assert set([1,2,3]) ^ set([1,2,3,4,5]) == set([4,5]) +assert_raises(TypeError, lambda: set([1,2,3]) ^ [1,2,3,4,5]) assert_raises(TypeError, lambda: set([[]])) assert_raises(TypeError, lambda: set().add([])) @@ -211,9 +213,11 @@ def __hash__(self): assert frozenset([1,2,3]).symmetric_difference(frozenset([1,2])) == frozenset([3]) assert frozenset([1,2,3]).symmetric_difference(frozenset([5,6])) == frozenset([1,2,3,5,6]) +assert frozenset([1,2,3]).symmetric_difference([1,2]) == frozenset([3]) assert frozenset([1,2,3]) ^ frozenset([4,5]) == frozenset([1,2,3,4,5]) assert frozenset([1,2,3]) ^ frozenset([1,2,3,4,5]) == frozenset([4,5]) +assert_raises(TypeError, lambda: frozenset([1,2,3]) ^ [1,2,3,4,5]) assert_raises(TypeError, lambda: frozenset([[]])) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 508400694f..f8c37fdd65 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -190,10 +190,9 @@ impl PySetInner { fn intersection(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { let mut elements = HashMap::new(); for item in other.iter(vm)? { - if let Ok(obj) = item { - if objbool::get_value(&self.contains(obj.clone(), vm)?) { - insert_into_set(vm, &mut elements, &obj)?; - } + let obj = item?; + if objbool::get_value(&self.contains(obj.clone(), vm)?) { + insert_into_set(vm, &mut elements, &obj)?; } } Ok(PySetInner { elements }) @@ -202,37 +201,27 @@ impl PySetInner { fn difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { let mut elements = self.elements.clone(); for item in other.iter(vm)? { - if let Ok(obj) = item { - if objbool::get_value(&self.contains(obj.clone(), vm)?) { - remove_from_set(vm, &mut elements, &obj)?; - } + let obj = item?; + if objbool::get_value(&self.contains(obj.clone(), vm)?) { + remove_from_set(vm, &mut elements, &obj)?; } } Ok(PySetInner { elements }) } - fn symmetric_difference( - &self, - other: &PySetInner, - vm: &VirtualMachine, - ) -> PyResult { - let mut elements = HashMap::new(); + fn symmetric_difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + let mut new_inner = self.clone(); - for element in self.elements.iter() { - let value = other.contains(element.1.clone(), vm)?; - if !objbool::get_value(&value) { - elements.insert(element.0.clone(), element.1.clone()); - } - } - - for element in other.elements.iter() { - let value = self.contains(element.1.clone(), vm)?; - if !objbool::get_value(&value) { - elements.insert(element.0.clone(), element.1.clone()); + for item in other.iter(vm)? { + let obj = item?; + if !objbool::get_value(&self.contains(obj.clone(), vm)?) { + new_inner.add(&obj, vm)?; + } else { + new_inner.remove(&obj, vm)?; } } - Ok(PySetInner { elements }) + Ok(new_inner) } fn iter(&self, vm: &VirtualMachine) -> PyListIterator { @@ -427,14 +416,10 @@ impl PySetRef { )) } - fn symmetric_difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn symmetric_difference(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { - inner: RefCell::new(match_class!(other, - set @ PySet => self.inner.borrow().symmetric_difference(&set.inner.borrow(), vm)?, - frozen @ PyFrozenSet => self.inner.borrow().symmetric_difference(&frozen.inner, vm)?, - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - )), + inner: RefCell::new(self.inner.borrow().symmetric_difference(other, vm)?), }, PySet::class(vm), None, @@ -453,6 +438,10 @@ impl PySetRef { self.difference(other.iterable, vm) } + fn xor(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.symmetric_difference(other.iterable, vm) + } + fn iter(self, vm: &VirtualMachine) -> PyListIterator { self.inner.borrow().iter(vm) } @@ -642,14 +631,10 @@ impl PyFrozenSetRef { )) } - fn symmetric_difference(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn symmetric_difference(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { - inner: match_class!(other, - set @ PySet => self.inner.symmetric_difference(&set.inner.borrow(), vm)?, - frozen @ PyFrozenSet => self.inner.symmetric_difference(&frozen.inner, vm)?, - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ), + inner: self.inner.symmetric_difference(other, vm)?, }, PyFrozenSet::class(vm), None, @@ -668,6 +653,10 @@ impl PyFrozenSetRef { self.difference(other.iterable, vm) } + fn xor(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.symmetric_difference(other.iterable, vm) + } + fn iter(self, vm: &VirtualMachine) -> PyListIterator { self.inner.iter(vm) } @@ -796,7 +785,7 @@ pub fn init(context: &PyContext) { "difference" => context.new_rustfunc(PySetRef::difference), "__sub__" => context.new_rustfunc(PySetRef::sub), "symmetric_difference" => context.new_rustfunc(PySetRef::symmetric_difference), - "__xor__" => context.new_rustfunc(PySetRef::symmetric_difference), + "__xor__" => context.new_rustfunc(PySetRef::xor), "__doc__" => context.new_str(set_doc.to_string()), "add" => context.new_rustfunc(PySetRef::add), "remove" => context.new_rustfunc(PySetRef::remove), @@ -837,7 +826,7 @@ pub fn init(context: &PyContext) { "difference" => context.new_rustfunc(PyFrozenSetRef::difference), "__sub__" => context.new_rustfunc(PyFrozenSetRef::sub), "symmetric_difference" => context.new_rustfunc(PyFrozenSetRef::symmetric_difference), - "__xor__" => context.new_rustfunc(PyFrozenSetRef::symmetric_difference), + "__xor__" => context.new_rustfunc(PyFrozenSetRef::xor), "__contains__" => context.new_rustfunc(PyFrozenSetRef::contains), "__len__" => context.new_rustfunc(PyFrozenSetRef::len), "__doc__" => context.new_str(frozenset_doc.to_string()), From 848350d3347bac1bc404816a1dfefe0c908b7b3c Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 7 Apr 2019 23:06:42 +0300 Subject: [PATCH 215/884] ior support only set and frozenset --- tests/snippets/set.py | 2 ++ vm/src/obj/objset.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index 42237dd2e1..a44847f9b8 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -119,6 +119,8 @@ def __hash__(self): assert a == set([1,2,3,4,5]) with assertRaises(TypeError): a |= 1 +with assertRaises(TypeError): + a |= [1,2,3] a = set([1,2,3]) a.intersection_update([2,3,4,5]) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index f8c37fdd65..888ffae530 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -478,8 +478,8 @@ impl PySetRef { self.inner.borrow_mut().pop(vm) } - fn ior(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().update(iterable, vm)?; + fn ior(self, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().update(iterable.iterable, vm)?; Ok(self.as_object().clone()) } From 5ae921dc571273343d981a20f469ce2b64bc4303 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 7 Apr 2019 23:26:59 +0300 Subject: [PATCH 216/884] update function support iterable --- tests/snippets/set.py | 6 +++ vm/src/obj/objset.rs | 106 +++++++++++++++++++----------------------- 2 files changed, 53 insertions(+), 59 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index a44847f9b8..393db07549 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -132,6 +132,8 @@ def __hash__(self): assert a == set([2,3]) with assertRaises(TypeError): a &= 1 +with assertRaises(TypeError): + a &= [1,2,3] a = set([1,2,3]) a.difference_update([3,4,5]) @@ -143,6 +145,8 @@ def __hash__(self): assert a == set([1,2]) with assertRaises(TypeError): a -= 1 +with assertRaises(TypeError): + a -= [1,2,3] a = set([1,2,3]) a.symmetric_difference_update([3,4,5]) @@ -154,6 +158,8 @@ def __hash__(self): assert a == set([1,2,4,5]) with assertRaises(TypeError): a ^= 1 +with assertRaises(TypeError): + a ^= [1,2,3] # frozen set diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 888ffae530..4b60b5b5cf 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -16,7 +16,6 @@ use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objint; -use super::objiter; use super::objlist::PyListIterator; use super::objtype; use super::objtype::PyClassRef; @@ -283,39 +282,41 @@ impl PySetInner { Ok(vm.get_none()) } - fn combine_update_inner( - &mut self, - iterable: &PyObjectRef, - vm: &VirtualMachine, - op: SetCombineOperation, - ) -> PyResult { - let elements = &mut self.elements; - for element in elements.clone().iter() { - let value = vm.call_method(iterable, "__contains__", vec![element.1.clone()])?; - let should_remove = match op { - SetCombineOperation::Intersection => !objbool::get_value(&value), - SetCombineOperation::Difference => objbool::get_value(&value), - }; - if should_remove { - elements.remove(&element.0.clone()); + fn intersection_update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + let temp_inner = self.copy(); + self.clear(); + for item in iterable.iter(vm)? { + let obj = item?; + if objbool::get_value(&temp_inner.contains(obj.clone(), vm)?) { + self.add(&obj, vm)?; } } Ok(vm.get_none()) } - fn ixor(&mut self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let elements_original = self.elements.clone(); - let iterator = objiter::get_iter(vm, &iterable)?; - while let Ok(v) = vm.call_method(&iterator, "__next__", vec![]) { - insert_into_set(vm, &mut self.elements, &v)?; - } - for element in elements_original.iter() { - let value = vm.call_method(&iterable, "__contains__", vec![element.1.clone()])?; - if objbool::get_value(&value) { - self.elements.remove(&element.0.clone()); + fn difference_update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + for item in iterable.iter(vm)? { + let obj = item?; + if objbool::get_value(&self.contains(obj.clone(), vm)?) { + self.remove(&obj, vm)?; } } + Ok(vm.get_none()) + } + fn symmetric_difference_update( + &mut self, + iterable: PyIterable, + vm: &VirtualMachine, + ) -> PyResult { + for item in iterable.iter(vm)? { + let obj = item?; + if !objbool::get_value(&self.contains(obj.clone(), vm)?) { + self.add(&obj, vm)?; + } else { + self.remove(&obj, vm)?; + } + } Ok(vm.get_none()) } } @@ -488,49 +489,41 @@ impl PySetRef { Ok(vm.get_none()) } - fn intersection_update(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().combine_update_inner( - &iterable, - vm, - SetCombineOperation::Intersection, - )?; + fn intersection_update(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().intersection_update(iterable, vm)?; Ok(vm.get_none()) } - fn iand(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().combine_update_inner( - &iterable, - vm, - SetCombineOperation::Intersection, - )?; + fn iand(self, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { + self.inner + .borrow_mut() + .intersection_update(iterable.iterable, vm)?; Ok(self.as_object().clone()) } - fn difference_update(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().combine_update_inner( - &iterable, - vm, - SetCombineOperation::Difference, - )?; + fn difference_update(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner.borrow_mut().difference_update(iterable, vm)?; Ok(vm.get_none()) } - fn isub(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().combine_update_inner( - &iterable, - vm, - SetCombineOperation::Difference, - )?; + fn isub(self, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { + self.inner + .borrow_mut() + .difference_update(iterable.iterable, vm)?; Ok(self.as_object().clone()) } - fn symmetric_difference_update(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().ixor(iterable, vm)?; + fn symmetric_difference_update(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner + .borrow_mut() + .symmetric_difference_update(iterable, vm)?; Ok(vm.get_none()) } - fn ixor(self, iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().ixor(iterable, vm)?; + fn ixor(self, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { + self.inner + .borrow_mut() + .symmetric_difference_update(iterable.iterable, vm)?; Ok(self.as_object().clone()) } } @@ -728,11 +721,6 @@ fn remove_from_set( perform_action_with_hash(vm, elements, item, &remove) } -enum SetCombineOperation { - Intersection, - Difference, -} - struct SetIterable { iterable: PyIterable, } From ec98b4d6bf509abffe66d47ef43ef60f3bdc7248 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 7 Apr 2019 22:36:13 +0200 Subject: [PATCH 217/884] repr len eq --- tests/snippets/bytes.py | 23 ++++++++++++++++++++++- vm/src/obj/objbyteinner.rs | 30 +++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 1a4b43dfb0..85469b756e 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -14,7 +14,28 @@ else: assert False -# +a = b"abcd" +b = b"ab" +c = b"abcd" + +# +# repr +assert repr(bytes([0, 1, 2])) == repr(b'\x00\x01\x02') +assert ( +repr(bytes([0, 1, 9, 10, 11, 13, 31, 32, 33, 89, 120, 255]) +== "b'\\x00\\x01\\t\\n\\x0b\\r\\x1f !Yx\\xff'") +) +assert repr(b"abcd") == "b'abcd'" + +#len +assert len(bytes("abcdé", "utf8")) == 6 + +# +assert a == b"abcd" +# assert a > b +# assert a >= b +# assert b < a +# assert b <= a assert b'foobar'.__eq__(2) == NotImplemented assert b'foobar'.__ne__(2) == NotImplemented diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index fd08e4b939..8d32aeabf9 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -1,4 +1,3 @@ -use crate::obj::objtype::PyClassRef; use crate::pyobject::PyObjectRef; use crate::function::OptionalArg; @@ -93,4 +92,33 @@ impl PyByteInner { } } } + + pub fn repr(&self) -> PyResult { + let mut res = String::with_capacity(self.elements.len()); + for i in self.elements.iter() { + match i { + 0..=8 => res.push_str(&format!("\\x0{}", i)), + 9 => res.push_str("\\t"), + 10 => res.push_str("\\n"), + 13 => res.push_str("\\r"), + 32..=126 => res.push(*(i) as char), + _ => res.push_str(&format!("\\x{:x}", i)), + } + } + Ok(res) + } + + pub fn len(&self) -> usize { + self.elements.len() + } + + pub fn eq(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { + if self.elements == other.elements { + Ok(vm.new_bool(true)) + } else { + Ok(vm.new_bool(false)) + } + } } +// TODO +// fix b"é" not allowed should be bytes("é", "utf8") From dddf9fee3906348063b9298b4621ae32adfd0e72 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 7 Apr 2019 22:42:47 +0200 Subject: [PATCH 218/884] gt lt ge le --- tests/snippets/bytes.py | 8 +- vm/src/obj/objbyteinner.rs | 32 +++++++ vm/src/obj/objbytes.rs | 181 +++++++++++++++---------------------- 3 files changed, 110 insertions(+), 111 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 85469b756e..a0371b7898 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -32,10 +32,10 @@ # assert a == b"abcd" -# assert a > b -# assert a >= b -# assert b < a -# assert b <= a +assert a > b +assert a >= b +assert b < a +assert b <= a assert b'foobar'.__eq__(2) == NotImplemented assert b'foobar'.__ne__(2) == NotImplemented diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 8d32aeabf9..81c70d4407 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -119,6 +119,38 @@ impl PyByteInner { Ok(vm.new_bool(false)) } } + + pub fn ge(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { + if self.elements >= other.elements { + Ok(vm.new_bool(true)) + } else { + Ok(vm.new_bool(false)) + } + } + + pub fn le(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { + if self.elements <= other.elements { + Ok(vm.new_bool(true)) + } else { + Ok(vm.new_bool(false)) + } + } + + pub fn gt(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { + if self.elements > other.elements { + Ok(vm.new_bool(true)) + } else { + Ok(vm.new_bool(false)) + } + } + + pub fn lt(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { + if self.elements < other.elements { + Ok(vm.new_bool(true)) + } else { + Ok(vm.new_bool(false)) + } + } } // TODO // fix b"é" not allowed should be bytes("é", "utf8") diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 07e71e23cf..54427fc57b 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,30 +1,11 @@ -use crate::obj::objint::PyInt; -use crate::obj::objlist; -use crate::obj::objlist::PyList; -use crate::obj::objstr::PyString; -use crate::obj::objtuple::PyTuple; -use crate::obj::objtype; -use std::cell::Cell; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; +use crate::vm::VirtualMachine; use std::ops::Deref; -use num_traits::ToPrimitive; - use crate::function::OptionalArg; -use crate::pyobject::{ - IntoPyObject, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, - TryFromObject, TypeProtocol, -}; -use crate::vm::VirtualMachine; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use super::objbyteinner::PyByteInner; -use super::objint; -use super::objiter; -use super::objstr; use super::objtype::PyClassRef; -use std::clone::Clone; - /// "bytes(iterable_of_ints) -> bytes\n\ /// bytes(string, encoding[, errors]) -> bytes\n\ /// bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\ @@ -35,7 +16,7 @@ use std::clone::Clone; /// - any object implementing the buffer API.\n \ /// - an integer"; #[pyclass(name = "bytes", __inside_vm)] -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct PyBytes { inner: PyByteInner, } @@ -113,102 +94,88 @@ impl PyBytesRef { } #[pymethod(name = "__repr__")] - fn repr(self, _vm: &VirtualMachine) -> String { - // TODO: don't just unwrap - let data = self.inner.elements.clone(); - format!("b'{:?}'", data) + fn repr(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_str(format!("b'{}'", self.inner.repr()?))) } #[pymethod(name = "__len__")] fn len(self, _vm: &VirtualMachine) -> usize { - self.inner.elements.len() - } -} -/* - fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - if let Ok(other) = other.downcast::() { - vm.ctx.new_bool(self.value == other.value) - } else { - vm.ctx.not_implemented() - } - } - - fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - if let Ok(other) = other.downcast::() { - vm.ctx.new_bool(self.value >= other.value) - } else { - vm.ctx.not_implemented() - } - } - - fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - if let Ok(other) = other.downcast::() { - vm.ctx.new_bool(self.value > other.value) - } else { - vm.ctx.not_implemented() - } + self.inner.len() } - fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - if let Ok(other) = other.downcast::() { - vm.ctx.new_bool(self.value <= other.value) - } else { - vm.ctx.not_implemented() - } + #[pymethod(name = "__eq__")] + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match_class!(other, + bytes @ PyBytes => self.inner.eq(&bytes.inner, vm), + _ => Ok(vm.ctx.not_implemented())) } - fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - if let Ok(other) = other.downcast::() { - vm.ctx.new_bool(self.value < other.value) - } else { - vm.ctx.not_implemented() - } + #[pymethod(name = "__ge__")] + fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match_class!(other, + bytes @ PyBytes => self.inner.ge(&bytes.inner, vm), + _ => Ok(vm.ctx.not_implemented())) } - - - fn hash(self, _vm: &VirtualMachine) -> u64 { - let mut hasher = DefaultHasher::new(); - self.value.hash(&mut hasher); - hasher.finish() + #[pymethod(name = "__le__")] + fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match_class!(other, + bytes @ PyBytes => self.inner.le(&bytes.inner, vm), + _ => Ok(vm.ctx.not_implemented())) } - - - fn iter(self, _vm: &VirtualMachine) -> PyBytesIterator { - PyBytesIterator { - position: Cell::new(0), - bytes: self, - } + #[pymethod(name = "__gt__")] + fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match_class!(other, + bytes @ PyBytes => self.inner.gt(&bytes.inner, vm), + _ => Ok(vm.ctx.not_implemented())) } -} - - - -#[derive(Debug)] -pub struct PyBytesIterator { - position: Cell, - bytes: PyBytesRef, -} - -impl PyValue for PyBytesIterator { - fn class(vm: &VirtualMachine) -> PyClassRef { - vm.ctx.bytesiterator_type() + #[pymethod(name = "__lt__")] + fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match_class!(other, + bytes @ PyBytes => self.inner.lt(&bytes.inner, vm), + _ => Ok(vm.ctx.not_implemented())) } } -type PyBytesIteratorRef = PyRef; - -impl PyBytesIteratorRef { - fn next(self, vm: &VirtualMachine) -> PyResult { - if self.position.get() < self.bytes.value.len() { - let ret = self.bytes[self.position.get()]; - self.position.set(self.position.get() + 1); - Ok(ret) - } else { - Err(objiter::new_stop_iteration(vm)) - } - } - - fn iter(self, _vm: &VirtualMachine) -> Self { - self - } -}*/ +// fn hash(self, _vm: &VirtualMachine) -> u64 { +// let mut hasher = DefaultHasher::new(); +// self.value.hash(&mut hasher); +// hasher.finish() +// } + +// fn iter(self, _vm: &VirtualMachine) -> PyBytesIterator { +// PyBytesIterator { +// position: Cell::new(0), +// bytes: self, +// } +// } +// } + +// #[derive(Debug)] +// pub struct PyBytesIterator { +// position: Cell, +// bytes: PyBytesRef, +// } + +// impl PyValue for PyBytesIterator { +// fn class(vm: &VirtualMachine) -> PyClassRef { +// vm.ctx.bytesiterator_type() +// } +// } + +// type PyBytesIteratorRef = PyRef; + +// impl PyBytesIteratorRef { +// fn next(self, vm: &VirtualMachine) -> PyResult { +// if self.position.get() < self.bytes.value.len() { +// let ret = self.bytes[self.position.get()]; +// self.position.set(self.position.get() + 1); +// Ok(ret) +// } else { +// Err(objiter::new_stop_iteration(vm)) +// } +// } + +// fn iter(self, _vm: &VirtualMachine) -> Self { +// self +// } +// } From abc72e999217e0de9318a04904562182a79ff62b Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 7 Apr 2019 23:54:42 +0300 Subject: [PATCH 219/884] contains return bool --- vm/src/obj/objset.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 4b60b5b5cf..5ec4fca46e 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -88,14 +88,14 @@ impl PySetInner { } } - fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { for element in self.elements.iter() { let value = vm._eq(needle.clone(), element.1.clone())?; if objbool::get_value(&value) { - return Ok(vm.new_bool(true)); + return Ok(true); } } - Ok(vm.new_bool(false)) + Ok(false) } fn _compare_inner( @@ -124,8 +124,7 @@ impl PySetInner { return Ok(vm.new_bool(false)); } for element in get_other(swap).elements.iter() { - let value = get_zelf(swap).contains(element.1.clone(), vm)?; - if !objbool::get_value(&value) { + if !get_zelf(swap).contains(element.1.clone(), vm)? { return Ok(vm.new_bool(false)); } } @@ -190,7 +189,7 @@ impl PySetInner { let mut elements = HashMap::new(); for item in other.iter(vm)? { let obj = item?; - if objbool::get_value(&self.contains(obj.clone(), vm)?) { + if self.contains(obj.clone(), vm)? { insert_into_set(vm, &mut elements, &obj)?; } } @@ -201,7 +200,7 @@ impl PySetInner { let mut elements = self.elements.clone(); for item in other.iter(vm)? { let obj = item?; - if objbool::get_value(&self.contains(obj.clone(), vm)?) { + if self.contains(obj.clone(), vm)? { remove_from_set(vm, &mut elements, &obj)?; } } @@ -213,7 +212,7 @@ impl PySetInner { for item in other.iter(vm)? { let obj = item?; - if !objbool::get_value(&self.contains(obj.clone(), vm)?) { + if !self.contains(obj.clone(), vm)? { new_inner.add(&obj, vm)?; } else { new_inner.remove(&obj, vm)?; @@ -287,7 +286,7 @@ impl PySetInner { self.clear(); for item in iterable.iter(vm)? { let obj = item?; - if objbool::get_value(&temp_inner.contains(obj.clone(), vm)?) { + if temp_inner.contains(obj.clone(), vm)? { self.add(&obj, vm)?; } } @@ -297,7 +296,7 @@ impl PySetInner { fn difference_update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { for item in iterable.iter(vm)? { let obj = item?; - if objbool::get_value(&self.contains(obj.clone(), vm)?) { + if self.contains(obj.clone(), vm)? { self.remove(&obj, vm)?; } } @@ -311,7 +310,7 @@ impl PySetInner { ) -> PyResult { for item in iterable.iter(vm)? { let obj = item?; - if !objbool::get_value(&self.contains(obj.clone(), vm)?) { + if !self.contains(obj.clone(), vm)? { self.add(&obj, vm)?; } else { self.remove(&obj, vm)?; @@ -343,7 +342,7 @@ impl PySetRef { } } - fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.inner.borrow().contains(needle, vm) } @@ -550,7 +549,7 @@ impl PyFrozenSetRef { } } - fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.inner.contains(needle, vm) } From 3c736c1f940f65e95275512e87d3ea28f46238e7 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Mon, 8 Apr 2019 00:11:20 +0200 Subject: [PATCH 220/884] iter --- tests/snippets/bytes.py | 8 ++- vm/src/obj/objbyteinner.rs | 9 +++ vm/src/obj/objbytes.rs | 122 ++++++++++++++++--------------------- 3 files changed, 67 insertions(+), 72 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index a0371b7898..bb72847b65 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -30,7 +30,7 @@ #len assert len(bytes("abcdé", "utf8")) == 6 -# +#comp assert a == b"abcd" assert a > b assert a >= b @@ -43,3 +43,9 @@ assert b'foobar'.__ge__(2) == NotImplemented assert b'foobar'.__lt__(2) == NotImplemented assert b'foobar'.__le__(2) == NotImplemented + +#hash +hash(a) == hash(b"abcd") + +#iter +[i for i in b"abcd"] == ["a", "b", "c", "d"] diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 81c70d4407..93293e6271 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -7,6 +7,8 @@ use crate::vm::VirtualMachine; use crate::pyobject::{PyResult, TypeProtocol}; use crate::obj::objstr::PyString; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; use super::objint; use crate::obj::objint::PyInt; @@ -151,6 +153,13 @@ impl PyByteInner { Ok(vm.new_bool(false)) } } + + pub fn hash(&self) -> usize { + let mut hasher = DefaultHasher::new(); + self.elements.hash(&mut hasher); + hasher.finish() as usize + } } + // TODO // fix b"é" not allowed should be bytes("é", "utf8") diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 54427fc57b..8c64944ca1 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,11 +1,14 @@ use crate::vm::VirtualMachine; +use core::cell::Cell; use std::ops::Deref; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use super::objbyteinner::PyByteInner; +use super::objiter; use super::objtype::PyClassRef; + /// "bytes(iterable_of_ints) -> bytes\n\ /// bytes(string, encoding[, errors]) -> bytes\n\ /// bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\ @@ -44,39 +47,18 @@ impl PyValue for PyBytes { } } -// Binary data support - -// Fill bytes class methods: - pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { &obj.payload::().unwrap().inner.elements } -// pub fn init(context: &PyContext) { -// let bytes_doc = -pub fn init(ctx: &PyContext) { - PyBytesRef::extend_class(ctx, &ctx.bytes_type); +pub fn init(context: &PyContext) { + PyBytesRef::extend_class(context, &context.bytes_type); + let bytesiterator_type = &context.bytesiterator_type; + extend_class!(context, bytesiterator_type, { + "__next__" => context.new_rustfunc(PyBytesIteratorRef::next), + "__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), + }); } -//extend_class!(context, &context.bytes_type, { -//"__new__" => context.new_rustfunc(bytes_new), -/* "__eq__" => context.new_rustfunc(PyBytesRef::eq), -"__lt__" => context.new_rustfunc(PyBytesRef::lt), -"__le__" => context.new_rustfunc(PyBytesRef::le), -"__gt__" => context.new_rustfunc(PyBytesRef::gt), -"__ge__" => context.new_rustfunc(PyBytesRef::ge), -"__hash__" => context.new_rustfunc(PyBytesRef::hash), -"__repr__" => context.new_rustfunc(PyBytesRef::repr), -"__len__" => context.new_rustfunc(PyBytesRef::len), -"__iter__" => context.new_rustfunc(PyBytesRef::iter), -"__doc__" => context.new_str(bytes_doc.to_string())*/ -// }); - -/* let bytesiterator_type = &context.bytesiterator_type; -extend_class!(context, bytesiterator_type, { - "__next__" => context.new_rustfunc(PyBytesIteratorRef::next), - "__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), -});*/ -//} #[pyimpl(__inside_vm)] impl PyBytesRef { @@ -134,48 +116,46 @@ impl PyBytesRef { bytes @ PyBytes => self.inner.lt(&bytes.inner, vm), _ => Ok(vm.ctx.not_implemented())) } + #[pymethod(name = "__hash__")] + fn hash(self, _vm: &VirtualMachine) -> usize { + self.inner.hash() + } + + #[pymethod(name = "__iter__")] + fn iter(self, _vm: &VirtualMachine) -> PyBytesIterator { + PyBytesIterator { + position: Cell::new(0), + bytes: self, + } + } } -// fn hash(self, _vm: &VirtualMachine) -> u64 { -// let mut hasher = DefaultHasher::new(); -// self.value.hash(&mut hasher); -// hasher.finish() -// } - -// fn iter(self, _vm: &VirtualMachine) -> PyBytesIterator { -// PyBytesIterator { -// position: Cell::new(0), -// bytes: self, -// } -// } -// } - -// #[derive(Debug)] -// pub struct PyBytesIterator { -// position: Cell, -// bytes: PyBytesRef, -// } - -// impl PyValue for PyBytesIterator { -// fn class(vm: &VirtualMachine) -> PyClassRef { -// vm.ctx.bytesiterator_type() -// } -// } - -// type PyBytesIteratorRef = PyRef; - -// impl PyBytesIteratorRef { -// fn next(self, vm: &VirtualMachine) -> PyResult { -// if self.position.get() < self.bytes.value.len() { -// let ret = self.bytes[self.position.get()]; -// self.position.set(self.position.get() + 1); -// Ok(ret) -// } else { -// Err(objiter::new_stop_iteration(vm)) -// } -// } - -// fn iter(self, _vm: &VirtualMachine) -> Self { -// self -// } -// } +#[derive(Debug)] +pub struct PyBytesIterator { + position: Cell, + bytes: PyBytesRef, +} + +impl PyValue for PyBytesIterator { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.bytesiterator_type() + } +} + +type PyBytesIteratorRef = PyRef; + +impl PyBytesIteratorRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + if self.position.get() < self.bytes.inner.len() { + let ret = self.bytes[self.position.get()]; + self.position.set(self.position.get() + 1); + Ok(ret) + } else { + Err(objiter::new_stop_iteration(vm)) + } + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } +} From ec65b8480fe35ece7182544eaf75d0582a257266 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Mon, 8 Apr 2019 00:45:53 +0200 Subject: [PATCH 221/884] add contains --- tests/snippets/bytes.py | 14 ++++++++++++++ vm/src/obj/objbyteinner.rs | 26 ++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 27 +++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index bb72847b65..2fd32153d3 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -49,3 +49,17 @@ #iter [i for i in b"abcd"] == ["a", "b", "c", "d"] + +#add +assert a + b == b"abcdab" + +#contains +# contains +assert b"ab" in b"abcd" +assert b"cd" in b"abcd" +assert b"abcd" in b"abcd" +assert b"a" in b"abcd" +assert b"d" in b"abcd" +assert b"dc" not in b"abcd" +# assert 97 in b"abcd" +# assert 150 not in b"abcd" \ No newline at end of file diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 93293e6271..03876095cd 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -159,6 +159,32 @@ impl PyByteInner { self.elements.hash(&mut hasher); hasher.finish() as usize } + + pub fn add(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { + let elements: Vec = self + .elements + .iter() + .chain(other.elements.iter()) + .cloned() + .collect(); + Ok(vm.ctx.new_bytes(elements)) + } + + pub fn contains_bytes(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { + for (n, i) in self.elements.iter().enumerate() { + if n + other.len() <= self.len() && *i == other.elements[0] { + if &self.elements[n..n + other.len()] == other.elements.as_slice() { + return Ok(vm.new_bool(true)); + } + } + } + Ok(vm.new_bool(false)) + } + + pub fn contains_int(&self, int: &PyInt, vm: &VirtualMachine) -> PyResult { + self.elements.contains(int); + Ok(vm.new_bool(false)) + } } // TODO diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 8c64944ca1..e052654bf5 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -128,6 +128,33 @@ impl PyBytesRef { bytes: self, } } + + #[pymethod(name = "__add__")] + fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match_class!(other, + bytes @ PyBytes => self.inner.add(&bytes.inner, vm), + _ => Ok(vm.ctx.not_implemented())) + } + + #[pymethod(name = "__contains__")] + fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // no new style since objint is not. + match_class!(needle, + bytes @ PyBytes => self.inner.contains_bytes(&bytes.inner, vm), + int @ PyInt => self.inner.contains_int(&int, vm), + _ => Ok(vm.ctx.not_implemented())) + // if objtype::isinstance(&needle, &vm.ctx.bytes_type()) { + // let result = vec_contains(&self.value, &get_value(&needle)); + // vm.ctx.new_bool(result) + // } else if objtype::isinstance(&needle, &vm.ctx.int_type()) { + // let result = self + // .value + // .contains(&objint::get_value(&needle).to_u8().unwrap()); + // vm.ctx.new_bool(result) + // } else { + // vm.new_type_error(format!("Cannot add {:?} and {:?}", self, needle)) + // } + } } #[derive(Debug)] From 4d8a56aea1d86d861c488e1eb4fd8ee7669090b4 Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Sun, 7 Apr 2019 19:09:44 -0600 Subject: [PATCH 222/884] `list.__setitem__` with slice and int indexing optimisation is uncertain correctness is uncertain but it does appear to work --- vm/src/obj/objlist.rs | 173 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 159 insertions(+), 14 deletions(-) diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 60c14b1074..3a4035409a 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -11,11 +11,11 @@ use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyVal use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; -use super::objint; +//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, SequenceIndex, + SequenceIndex, }; use super::objslice::PySliceRef; use super::objtype; @@ -181,22 +181,167 @@ impl PyListRef { } } - fn setitem(self, key: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let mut elements = self.elements.borrow_mut(); + fn setitem( + self, + subscript: SequenceIndex, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + match subscript { + SequenceIndex::Int(index) => self.setindex(index, value, vm), + SequenceIndex::Slice(slice) => { + // TODO check special case of if a == b, may need copy of b first? + self.setslice(slice, value, vm) + } + } + } - if objtype::isinstance(&key, &vm.ctx.int_type()) { - let idx = objint::get_value(&key).to_i32().unwrap(); - if let Some(pos_index) = elements.get_pos(idx) { - elements[pos_index] = value; - Ok(vm.get_none()) + fn setindex(self, index: i32, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(pos_index) = self.get_pos(index) { + self.elements.borrow_mut()[pos_index] = value; + Ok(vm.get_none()) + } else { + Err(vm.new_index_error("list assignment index out of range".to_string())) + } + } + + fn setslice(self, slice: PySliceRef, sec: PyObjectRef, vm: &VirtualMachine) -> PyResult { + 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(&slice.start, &slice.stop); + if range.start < range.end { + match step.to_i32() { + Some(1) => self._set_slice(range, sec, vm), + Some(num) => { + // assign to extended slice + self._set_stepped_slice(range, num as usize, sec, vm) + } + None => { + // not sure how this is reached, step too big for i32? + // then step is bigger than the than len of the list, no question + #[allow(clippy::range_plus_one)] + self._set_stepped_slice(range.start..(range.start + 1), 1, sec, vm) + } + } + } else { + // this functions as an insert of sec before range.start + self._set_slice(range.start..range.start, sec, vm) + // TODO this will take some special processing + } + } else { + // calculate the range for the reverse slice, first the bounds needs to be made + // exclusive around stop, the lower number + let start = &slice.start.as_ref().map(|x| x + 1); + let stop = &slice.stop.as_ref().map(|x| x + 1); + let range = self.get_slice_range(&stop, &start); + match (-step).to_i32() { + Some(num) => self._set_stepped_slice_reverse(range, num as usize, sec, vm), + None => { + // not sure how this is reached, step too big for i32? + // then step is bigger than the than len of the list no question + self._set_stepped_slice_reverse(range.end - 1..range.end, 1, sec, vm) + } + } + } + } + + fn _set_slice(self, range: Range, sec: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // consume the iter, we don't need it's size + // but if it's going to fail we want that to happen *before* we start modifing + if let Ok(items) = vm.extract_elements(&sec) { + // replace the range of elements with the full sequence + self.elements.borrow_mut().splice(range, items); + + Ok(vm.get_none()) + } else { + Err(vm.new_type_error("can only assign an iterable to a slice".to_string())) + } + } + + fn _set_stepped_slice( + self, + range: Range, + step: usize, + sec: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + // consume the iter, we need it's size + // and if it's going to fail we want that to happen *before* we start modifing + let slicelen = ((range.end - range.start - 1) / step) + 1; + if let Ok(items) = vm.extract_elements(&sec) { + let n = items.len(); + + if range.start < range.end { + if n == slicelen { + let indexes = range.step_by(step); + self._replace_indexes(indexes, &items); + Ok(vm.get_none()) + } else { + Err(vm.new_value_error(format!( + "attempt to assign sequence of size {} to extended slice of size {}", + n, slicelen + ))) + } } else { - Err(vm.new_index_error("list index out of range".to_string())) + // empty slice but this is an error because stepped slice + Err(vm.new_value_error(format!( + "attempt to assign sequence of size {} to extended slice of size 0", + n + ))) } } else { - panic!( - "TypeError: indexing type {:?} with index {:?} is not supported (yet?)", - elements, key - ) + Err(vm.new_type_error("can only assign an iterable to a slice".to_string())) + } + } + + fn _set_stepped_slice_reverse( + self, + range: Range, + step: usize, + sec: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + // consume the iter, we need it's size + // and if it's going to fail we want that to happen *before* we start modifing + let slicelen = ((range.end - range.start - 1) / step) + 1; + if let Ok(items) = vm.extract_elements(&sec) { + let n = items.len(); + + if range.start < range.end { + if n == slicelen { + let indexes = range.rev().step_by(step); + self._replace_indexes(indexes, &items); + Ok(vm.get_none()) + } else { + Err(vm.new_value_error(format!( + "attempt to assign sequence of size {} to extended slice of size {}", + n, slicelen + ))) + } + } else { + // empty slice but this is an error because stepped slice + Err(vm.new_value_error(format!( + "attempt to assign sequence of size {} to extended slice of size 0", + n + ))) + } + } else { + Err(vm.new_type_error("can only assign an iterable to a slice".to_string())) + } + } + + fn _replace_indexes(self, indexes: I, items: &[PyObjectRef]) + where + I: Iterator, + { + let mut elements = self.elements.borrow_mut(); + + for (i, value) in indexes.zip(items) { + // clone for refrence count + elements[i] = value.clone(); } } From 118c98ccc76977945e78495b28124b42290162f6 Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Sun, 7 Apr 2019 19:12:03 -0600 Subject: [PATCH 223/884] exhaustive (hopefully) test snippets I attempted to cover every case with the snippets but I easily could of missed some. --- tests/snippets/list.py | 198 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 3d9743c0b4..71f7dea067 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -201,3 +201,201 @@ def bad_del_1(): def bad_del_2(): del ['a', 'b'][2] assert_raises(IndexError, bad_del_2) + +# __setitem__ + +# simple index +x = [1, 2, 3, 4, 5] +x[0] = 'a' +assert x == ['a', 2, 3, 4, 5] +x[-1] = 'b' +assert x == ['a', 2, 3, 4, 'b'] +# make sure refrences are assigned correctly +y = [] +x[1] = y +y.append(100) +assert x[1] == y +assert x[1] == [100] + +#index bounds +def set_index_out_of_bounds_high(): + x = [0, 1, 2, 3, 4] + x[5] = 'a' + +def set_index_out_of_bounds_low(): + x = [0, 1, 2, 3, 4] + x[-6] = 'a' + +assert_raises(IndexError, set_index_out_of_bounds_high) +assert_raises(IndexError, set_index_out_of_bounds_low) + +# non stepped slice index +a = list(range(10)) +x = a[:] +y = a[:] +assert x == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +# replace whole list +x[:] = ['a', 'b', 'c'] +y[::1] = ['a', 'b', 'c'] +assert x == ['a', 'b', 'c'] +assert x == y +# splice list start +x = a[:] +y = a[:] +z = a[:] +zz = a[:] +x[:1] = ['a', 'b', 'c'] +y[0:1] = ['a', 'b', 'c'] +z[:1:1] = ['a', 'b', 'c'] +zz[0:1:1] = ['a', 'b', 'c'] +assert x == ['a', 'b', 'c', 1, 2, 3, 4, 5, 6, 7, 8, 9] +assert x == y +assert x == z +assert x == zz +# splice list end +x = a[:] +y = a[:] +z = a[:] +zz = a[:] +x[5:] = ['a', 'b', 'c'] +y[5::1] = ['a', 'b', 'c'] +z[5:10] = ['a', 'b', 'c'] +zz[5:10:1] = ['a', 'b', 'c'] +assert x == [0, 1, 2, 3, 4, 'a', 'b', 'c'] +assert x == y +assert x == z +assert x == zz +# insert sec +x = a[:] +y = a[:] +z = a[:] +zz = a[:] +x[1:1] = ['a', 'b', 'c'] +y[1:0] = ['a', 'b', 'c'] +z[1:1:1] = ['a', 'b', 'c'] +zz[1:0:1] = ['a', 'b', 'c'] +assert x == [0, 'a', 'b', 'c', 1, 2, 3, 4, 5, 6, 7, 8, 9] +assert x == y +assert x == z +assert x == zz +# same but negative indexes? +x = a[:] +y = a[:] +z = a[:] +zz = a[:] +x[-1:-1] = ['a', 'b', 'c'] +y[-1:9] = ['a', 'b', 'c'] +z[-1:-1:1] = ['a', 'b', 'c'] +zz[-1:9:1] = ['a', 'b', 'c'] +assert x == [0, 1, 2, 3, 4, 5, 6, 7, 8, 'a', 'b', 'c', 9] +assert x == y +assert x == z +assert x == zz +# splice mid +x = a[:] +y = a[:] +x[3:5] = ['a', 'b', 'c', 'd', 'e'] +y[3:5:1] = ['a', 'b', 'c', 'd', 'e'] +assert x == [0, 1, 2, 'a', 'b', 'c', 'd', 'e', 5, 6, 7, 8, 9] +assert x == y +x = a[:] +x[3:5] = ['a'] +assert x == [0, 1, 2, 'a', 5, 6, 7, 8, 9] +# assign empty to non stepped empty slice does nothing +x = a[:] +y = a[:] +x[5:2] = [] +y[5:2:1] = [] +assert x == a +assert y == a +# assign empty to non stepped slice removes elems +x = a[:] +y = a[:] +x[2:8] = [] +y[2:8:1] = [] +assert x == [0, 1, 8, 9] +assert x == y +# make sure refrences are assigned correctly +yy = [] +x = a[:] +y = a[:] +x[3:5] = ['a', 'b', 'c', 'd', yy] +y[3:5:1] = ['a', 'b', 'c', 'd', yy] +assert x == [0, 1, 2, 'a', 'b', 'c', 'd', [], 5, 6, 7, 8, 9] +assert x == y +yy.append(100) +assert x == [0, 1, 2, 'a', 'b', 'c', 'd', [100], 5, 6, 7, 8, 9] +assert x == y +assert x[7] == yy +assert x[7] == [100] +assert y[7] == yy +assert y[7] == [100] + +# no zero step +def no_zero_step_set(): + x = [1, 2, 3, 4, 5] + x[0:4:0] = [11, 12, 13, 14, 15] +assert_raises(ValueError, no_zero_step_set) + +# stepped slice index +# forward slice +x = a[:] +x[2:8:2] = ['a', 'b', 'c'] +assert x == [0, 1, 'a', 3, 'b', 5, 'c', 7, 8, 9] +x = a[:] +y = a[:] +z = a[:] +zz = a[:] +c = ['a', 'b', 'c', 'd', 'e'] +x[::2] = c +y[-10::2] = c +z[0:10:2] = c +zz[-13:13:2] = c # slice indexes will be truncated to bounds +assert x == ['a', 1, 'b', 3, 'c', 5, 'd', 7, 'e', 9] +assert x == y +assert x == z +assert x == zz +# backward slice +x = a[:] +x[8:2:-2] = ['a', 'b', 'c'] +assert x == [0, 1, 2, 3, 'c', 5, 'b', 7, 'a', 9] +x = a[:] +y = a[:] +z = a[:] +zz = a[:] +c = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'] +x[::-1] = c +y[9:-11:-1] = c +z[9::-1] = c +zz[11:-13:-1] = c # slice indexes will be truncated to bounds +assert x == ['j', 'i', 'h', 'g', 'f', 'e', 'd', 'c', 'b', 'a'] +assert x == y +assert x == z +assert x == zz +# step size bigger than len +x = a[:] +x[::200] = ['a'] +assert x == ['a', 1, 2, 3, 4, 5, 6, 7, 8, 9] +x = a[:] +x[5::200] = ['a'] +assert x == [0, 1, 2, 3, 4, 'a', 6, 7, 8, 9] + +# bad stepped slices +def stepped_slice_assign_too_big(): + x = [0, 1, 2, 3, 4] + x[::2] = ['a', 'b', 'c', 'd'] + +assert_raises(ValueError, stepped_slice_assign_too_big) + +def stepped_slice_assign_too_small(): + x = [0, 1, 2, 3, 4] + x[::2] = ['a', 'b'] + +assert_raises(ValueError, stepped_slice_assign_too_small) + +# must assign iter t0 slice +def must_assign_iter_to_slice(): + x = [0, 1, 2, 3, 4] + x[::2] = 42 + +assert_raises(TypeError, must_assign_iter_to_slice) From a23b5bc470f0196079529b11351169cc543a362f Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Sun, 7 Apr 2019 19:32:11 -0600 Subject: [PATCH 224/884] cleanup and cargo fmt --- vm/src/obj/objlist.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 3a4035409a..1ef01ca3e2 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -189,10 +189,7 @@ impl PyListRef { ) -> PyResult { match subscript { SequenceIndex::Int(index) => self.setindex(index, value, vm), - SequenceIndex::Slice(slice) => { - // TODO check special case of if a == b, may need copy of b first? - self.setslice(slice, value, vm) - } + SequenceIndex::Slice(slice) => self.setslice(slice, value, vm), } } @@ -229,7 +226,6 @@ impl PyListRef { } else { // this functions as an insert of sec before range.start self._set_slice(range.start..range.start, sec, vm) - // TODO this will take some special processing } } else { // calculate the range for the reverse slice, first the bounds needs to be made From 0e264426c9b876c4706042eff8cfd50415d81849 Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Mon, 8 Apr 2019 09:12:22 +0200 Subject: [PATCH 225/884] fix import --- tests/snippets/builtin_slice.py | 16 ++++++++-------- tests/test_snippets.py | 22 +++++----------------- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/tests/snippets/builtin_slice.py b/tests/snippets/builtin_slice.py index d616980341..b12ad8021a 100644 --- a/tests/snippets/builtin_slice.py +++ b/tests/snippets/builtin_slice.py @@ -79,13 +79,13 @@ def test_all_slices(): test all possible slices except big number """ - import resources - from resources.cpython_generated_slices import SLICES_RES, START, END, STEP, LL + mod = __import__('cpython_generated_slices') - ll = LL - start = START - end = END - step = STEP + ll = mod.LL + start = mod.START + end = mod.END + step = mod.STEP + slices_res = mod.SLICES_RES count = 0 failures = [] @@ -94,11 +94,11 @@ def test_all_slices(): for t in step: lhs = ll[s:e:t] try: - assert lhs == SLICES_RES[count] + assert lhs == slices_res[count] except AssertionError: failures.append( "start: {} ,stop: {}, step {}. Expected: {}, found: {}".format( - s, e, t, lhs, SLICES_RES[count] + s, e, t, lhs, slices_res[count] ) ) count += 1 diff --git a/tests/test_snippets.py b/tests/test_snippets.py index 7a888f0935..9322485cef 100644 --- a/tests/test_snippets.py +++ b/tests/test_snippets.py @@ -26,10 +26,6 @@ class _TestType(enum.Enum): TEST_DIRS = {_TestType.functional: os.path.join(TEST_ROOT, "snippets")} CPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR, "py_code_object")) RUSTPYTHON_RUNNER_DIR = os.path.abspath(os.path.join(ROOT_DIR)) -RESOURCES_DIR = os.path.abspath( - os.path.join(TEST_DIRS[_TestType.functional], "resources") -) - @contextlib.contextmanager def pushd(path): @@ -149,17 +145,11 @@ def generate_slices(path): class SampleTestCase(unittest.TestCase): @classmethod def setUpClass(cls): - # setup resource dir - cls.resources = Path(RESOURCES_DIR) - if cls.resources.exists(): - shutil.rmtree( - RESOURCES_DIR, ignore_errors=True - ) # we don't care if dir doesnt exist - cls.resources.mkdir() - (cls.resources / "__init__.py").touch() - # Here add resource files - cls.slices_resource_path = Path(RESOURCES_DIR) / "cpython_generated_slices.py" + cls.slices_resource_path = Path(TEST_DIRS[_TestType.functional]) / "cpython_generated_slices.py" + if cls.slices_resource_path.exists(): + cls.slices_resource_path.unlink() + generate_slices(cls.slices_resource_path) # cargo stuff @@ -168,6 +158,4 @@ def setUpClass(cls): @classmethod def tearDownClass(cls): - shutil.rmtree( - RESOURCES_DIR, ignore_errors=True - ) # we don't care if dir doesnt exist + cls.slices_resource_path.unlink() From 86928114fa1505ea6dadfc39cb096e73249ac0ff Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Mon, 8 Apr 2019 10:33:56 +0200 Subject: [PATCH 226/884] resolve conflict --- vm/src/obj/objsequence.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index 1e389bafd5..32c723a26f 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -2,9 +2,6 @@ use std::cell::RefCell; use std::marker::Sized; use std::ops::{Deref, DerefMut, Range}; -use num_bigint::BigInt; -use num_traits::{One, Signed, ToPrimitive, Zero}; - use crate::pyobject::{IdProtocol, PyObject, PyObjectRef, PyResult, TryFromObject, TypeProtocol}; use crate::vm::VirtualMachine; From 9c32de954bf586cd2425bc27014ca6f85e6bab6b Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Sat, 6 Apr 2019 17:09:41 +0100 Subject: [PATCH 227/884] Add iterator for dictionary keys. --- vm/src/dictdatatype.rs | 12 +++++++++++ vm/src/obj/objdict.rs | 49 +++++++++++++++++++++++++++++++----------- vm/src/pyobject.rs | 3 +++ 3 files changed, 52 insertions(+), 12 deletions(-) diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 8a6a8df1be..dc25e6eebe 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -120,6 +120,18 @@ impl Dict { self.len() == 0 } + pub fn next_entry(&self, position: usize) -> Option<(usize, &PyObjectRef, &T)> { + let mut new_position = position; + while position < self.entries.len() { + if let Some(DictEntry { key, value, .. }) = &self.entries[position] { + return Some((new_position + 1, key, value)); + } else { + new_position += 1; + } + } + None + } + pub fn iter_items(&self) -> impl Iterator + '_ { self.entries .iter() diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index e84f7b1786..4a1b6bbb15 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -132,20 +132,11 @@ impl PyDictRef { } /// When iterating over a dictionary, we iterate over the keys of it. - fn iter(self, vm: &VirtualMachine) -> PyListIterator { + fn iter(self, _vm: &VirtualMachine) -> PyDictIterator { // TODO: separate type, not a list iterator - let keys = self - .entries - .borrow() - .get_items() - .iter() - .map(|(k, _v)| k.clone()) - .collect(); - let key_list = vm.ctx.new_list(keys); - - PyListIterator { + PyDictIterator { position: Cell::new(0), - list: key_list.downcast().unwrap(), + dict: self, } } @@ -284,6 +275,35 @@ impl ItemProtocol for PyDictRef { } } +#[derive(Debug)] +struct PyDictIterator { + pub dict: PyDictRef, + pub position: Cell, +} +type PyDictIteratorRef = PyRef; + +impl PyDictIteratorRef { + fn next(self: PyDictIteratorRef, vm: &VirtualMachine) -> PyResult { + match self.dict.entries.borrow().next_entry(self.position.get()) { + Some((new_position, key, _value)) => { + self.position.set(new_position); + Ok(key.clone()) + } + None => Err(objiter::new_stop_iteration(vm)), + } + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } +} + +impl PyValue for PyDictIterator { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.dictiterator_type.clone() + } +} + pub fn init(context: &PyContext) { extend_class!(context, &context.dict_type, { "__bool__" => context.new_rustfunc(PyDictRef::bool), @@ -305,4 +325,9 @@ pub fn init(context: &PyContext) { "copy" => context.new_rustfunc(PyDictRef::copy), "update" => context.new_rustfunc(PyDictRef::update), }); + + extend_class!(context, &context.dictiterator_type, { + "__next__" => context.new_rustfunc(PyDictIteratorRef::next), + "__iter__" => context.new_rustfunc(PyDictIteratorRef::iter), + }); } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 4d2c8353bc..2c2371f21d 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -129,6 +129,7 @@ pub struct PyContext { pub false_value: PyIntRef, pub list_type: PyClassRef, pub listiterator_type: PyClassRef, + pub dictiterator_type: PyClassRef, pub map_type: PyClassRef, pub memoryview_type: PyClassRef, pub none: PyNoneRef, @@ -254,6 +255,7 @@ impl PyContext { let str_type = create_type("str", &type_type, &object_type); let list_type = create_type("list", &type_type, &object_type); let listiterator_type = create_type("list_iterator", &type_type, &object_type); + let dictiterator_type = create_type("dict_iterator", &type_type, &object_type); let set_type = create_type("set", &type_type, &object_type); let frozenset_type = create_type("frozenset", &type_type, &object_type); let int_type = create_type("int", &type_type, &object_type); @@ -313,6 +315,7 @@ impl PyContext { staticmethod_type, list_type, listiterator_type, + dictiterator_type, set_type, frozenset_type, true_value, From 5c4755ffd77f66371e102efee16528086da8c883 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 8 Apr 2019 11:08:45 +0100 Subject: [PATCH 228/884] Separate iterators for items/keys/values. --- tests/snippets/dict.py | 9 ++- vm/src/dictdatatype.rs | 7 +- vm/src/obj/objdict.rs | 155 ++++++++++++++++++++++++++++------------- vm/src/pyobject.rs | 12 +++- 4 files changed, 128 insertions(+), 55 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index f924bb0e7c..f57fc37b66 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -37,11 +37,18 @@ def dict_eq(d1, d2): res.add(key) assert res == set(['a','b']) +x = {'a': 1, 'b': 2, 'c': 3, 'd': 3} +del x['c'] +it = iter(x.items()) +assert ('a', 1) == next(it) +assert ('b', 2) == next(it) +assert ('d', 3) == next(it) + + x = {} x[1] = 1 assert x[1] == 1 - x[7] = 7 x[2] = 2 x[(5, 6)] = 5 diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index dc25e6eebe..0612bc8a35 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -120,13 +120,12 @@ impl Dict { self.len() == 0 } - pub fn next_entry(&self, position: usize) -> Option<(usize, &PyObjectRef, &T)> { - let mut new_position = position; + pub fn next_entry(&self, mut position: usize) -> Option<(usize, &PyObjectRef, &T)> { while position < self.entries.len() { if let Some(DictEntry { key, value, .. }) = &self.entries[position] { - return Some((new_position + 1, key, value)); + return Some((position + 1, key, value)); } else { - new_position += 1; + position += 1; } } None diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 4a1b6bbb15..a0d9210b53 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -11,7 +11,6 @@ use crate::vm::{ReprGuard, VirtualMachine}; use crate::dictdatatype; use super::objiter; -use super::objlist::PyListIterator; use super::objstr; use crate::obj::objtype::PyClassRef; @@ -131,47 +130,16 @@ impl PyDictRef { self.entries.borrow_mut().clear() } - /// When iterating over a dictionary, we iterate over the keys of it. - fn iter(self, _vm: &VirtualMachine) -> PyDictIterator { - // TODO: separate type, not a list iterator - PyDictIterator { - position: Cell::new(0), - dict: self, - } + fn iter(self, vm: &VirtualMachine) -> PyDictKeysIteratorRef { + PyDictKeysIteratorRef::new(self, vm) } - fn values(self, vm: &VirtualMachine) -> PyListIterator { - // TODO: separate type. `values` should be a live view over the collection, not an iterator. - let values = self - .entries - .borrow() - .get_items() - .iter() - .map(|(_k, v)| v.clone()) - .collect(); - let values_list = vm.ctx.new_list(values); - - PyListIterator { - position: Cell::new(0), - list: values_list.downcast().unwrap(), - } + fn values(self, vm: &VirtualMachine) -> PyDictValuesIteratorRef { + PyDictValuesIteratorRef::new(self, vm) } - fn items(self, vm: &VirtualMachine) -> PyListIterator { - // TODO: separate type. `items` should be a live view over the collection, not an iterator. - let items = self - .entries - .borrow() - .get_items() - .iter() - .map(|(k, v)| vm.ctx.new_tuple(vec![k.clone(), v.clone()])) - .collect(); - let items_list = vm.ctx.new_list(items); - - PyListIterator { - position: Cell::new(0), - list: items_list.downcast().unwrap(), - } + fn items(self, vm: &VirtualMachine) -> PyDictItemsIteratorRef { + PyDictItemsIteratorRef::new(self, vm) } pub fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { @@ -276,14 +244,22 @@ impl ItemProtocol for PyDictRef { } #[derive(Debug)] -struct PyDictIterator { +struct PyDictKeysIterator { pub dict: PyDictRef, pub position: Cell, } -type PyDictIteratorRef = PyRef; +type PyDictKeysIteratorRef = PyRef; -impl PyDictIteratorRef { - fn next(self: PyDictIteratorRef, vm: &VirtualMachine) -> PyResult { +impl PyDictKeysIteratorRef { + fn new(dict: PyDictRef, vm: &VirtualMachine) -> PyDictKeysIteratorRef { + PyDictKeysIterator { + position: Cell::new(0), + dict, + } + .into_ref(vm) + } + + fn next(self, vm: &VirtualMachine) -> PyResult { match self.dict.entries.borrow().next_entry(self.position.get()) { Some((new_position, key, _value)) => { self.position.set(new_position); @@ -298,9 +274,84 @@ impl PyDictIteratorRef { } } -impl PyValue for PyDictIterator { +impl PyValue for PyDictKeysIterator { fn class(vm: &VirtualMachine) -> PyClassRef { - vm.ctx.dictiterator_type.clone() + vm.ctx.dictkeysiterator_type.clone() + } +} + +#[derive(Debug)] +struct PyDictValuesIterator { + pub dict: PyDictRef, + pub position: Cell, +} +type PyDictValuesIteratorRef = PyRef; + +impl PyDictValuesIteratorRef { + fn new(dict: PyDictRef, vm: &VirtualMachine) -> PyDictValuesIteratorRef { + PyDictValuesIterator { + position: Cell::new(0), + dict, + } + .into_ref(vm) + } + + fn next(self, vm: &VirtualMachine) -> PyResult { + match self.dict.entries.borrow().next_entry(self.position.get()) { + Some((new_position, _key, value)) => { + self.position.set(new_position); + Ok(value.clone()) + } + None => Err(objiter::new_stop_iteration(vm)), + } + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } +} + +impl PyValue for PyDictValuesIterator { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.dictvaluesiterator_type.clone() + } +} + +#[derive(Debug)] +struct PyDictItemsIterator { + pub dict: PyDictRef, + pub position: Cell, +} + +type PyDictItemsIteratorRef = PyRef; + +impl PyDictItemsIteratorRef { + fn new(dict: PyDictRef, vm: &VirtualMachine) -> PyDictItemsIteratorRef { + PyDictItemsIterator { + position: Cell::new(0), + dict, + } + .into_ref(vm) + } + + fn next(self: PyDictItemsIteratorRef, vm: &VirtualMachine) -> PyResult { + match self.dict.entries.borrow().next_entry(self.position.get()) { + Some((new_position, key, value)) => { + self.position.set(new_position); + Ok(vm.ctx.new_tuple(vec![key.clone(), value.clone()])) + } + None => Err(objiter::new_stop_iteration(vm)), + } + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } +} + +impl PyValue for PyDictItemsIterator { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.dictitemsiterator_type.clone() } } @@ -326,8 +377,18 @@ pub fn init(context: &PyContext) { "update" => context.new_rustfunc(PyDictRef::update), }); - extend_class!(context, &context.dictiterator_type, { - "__next__" => context.new_rustfunc(PyDictIteratorRef::next), - "__iter__" => context.new_rustfunc(PyDictIteratorRef::iter), + extend_class!(context, &context.dictkeysiterator_type, { + "__next__" => context.new_rustfunc(PyDictKeysIteratorRef::next), + "__iter__" => context.new_rustfunc(PyDictKeysIteratorRef::iter), + }); + + extend_class!(context, &context.dictvaluesiterator_type, { + "__next__" => context.new_rustfunc(PyDictValuesIteratorRef::next), + "__iter__" => context.new_rustfunc(PyDictValuesIteratorRef::iter), + }); + + extend_class!(context, &context.dictitemsiterator_type, { + "__next__" => context.new_rustfunc(PyDictItemsIteratorRef::next), + "__iter__" => context.new_rustfunc(PyDictItemsIteratorRef::iter), }); } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 2c2371f21d..8c405c5b1c 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -129,7 +129,9 @@ pub struct PyContext { pub false_value: PyIntRef, pub list_type: PyClassRef, pub listiterator_type: PyClassRef, - pub dictiterator_type: PyClassRef, + pub dictkeysiterator_type: PyClassRef, + pub dictvaluesiterator_type: PyClassRef, + pub dictitemsiterator_type: PyClassRef, pub map_type: PyClassRef, pub memoryview_type: PyClassRef, pub none: PyNoneRef, @@ -255,7 +257,9 @@ impl PyContext { let str_type = create_type("str", &type_type, &object_type); let list_type = create_type("list", &type_type, &object_type); let listiterator_type = create_type("list_iterator", &type_type, &object_type); - let dictiterator_type = create_type("dict_iterator", &type_type, &object_type); + let dictkeysiterator_type = create_type("dict_keys", &type_type, &object_type); + let dictvaluesiterator_type = create_type("dict_values", &type_type, &object_type); + let dictitemsiterator_type = create_type("dict_items", &type_type, &object_type); let set_type = create_type("set", &type_type, &object_type); let frozenset_type = create_type("frozenset", &type_type, &object_type); let int_type = create_type("int", &type_type, &object_type); @@ -315,7 +319,9 @@ impl PyContext { staticmethod_type, list_type, listiterator_type, - dictiterator_type, + dictkeysiterator_type, + dictvaluesiterator_type, + dictitemsiterator_type, set_type, frozenset_type, true_value, From 2d127b983422e68a9d9fe26b509107fbaaf2bfce Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 8 Apr 2019 11:58:11 +0100 Subject: [PATCH 229/884] Use macro to generate full set of views and iterators. --- vm/src/obj/objdict.rs | 203 ++++++++++++++++++++---------------------- vm/src/pyobject.rs | 27 ++++-- 2 files changed, 114 insertions(+), 116 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index a0d9210b53..e80fa7d8a3 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -130,16 +130,20 @@ impl PyDictRef { self.entries.borrow_mut().clear() } - fn iter(self, vm: &VirtualMachine) -> PyDictKeysIteratorRef { - PyDictKeysIteratorRef::new(self, vm) + fn iter(self, _vm: &VirtualMachine) -> PyDictKeyIterator { + PyDictKeyIterator::new(self) } - fn values(self, vm: &VirtualMachine) -> PyDictValuesIteratorRef { - PyDictValuesIteratorRef::new(self, vm) + fn keys(self, _vm: &VirtualMachine) -> PyDictKeys { + PyDictKeys::new(self) } - fn items(self, vm: &VirtualMachine) -> PyDictItemsIteratorRef { - PyDictItemsIteratorRef::new(self, vm) + fn values(self, _vm: &VirtualMachine) -> PyDictValues { + PyDictValues::new(self) + } + + fn items(self, _vm: &VirtualMachine) -> PyDictItems { + PyDictItems::new(self) } pub fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { @@ -243,116 +247,89 @@ impl ItemProtocol for PyDictRef { } } -#[derive(Debug)] -struct PyDictKeysIterator { - pub dict: PyDictRef, - pub position: Cell, -} -type PyDictKeysIteratorRef = PyRef; - -impl PyDictKeysIteratorRef { - fn new(dict: PyDictRef, vm: &VirtualMachine) -> PyDictKeysIteratorRef { - PyDictKeysIterator { - position: Cell::new(0), - dict, +macro_rules! dict_iterator { + ( $name: ident, $iter_name: ident, $class: ident, $iter_class: ident, $result_fn: expr) => { + #[derive(Debug)] + struct $name { + pub dict: PyDictRef, } - .into_ref(vm) - } - fn next(self, vm: &VirtualMachine) -> PyResult { - match self.dict.entries.borrow().next_entry(self.position.get()) { - Some((new_position, key, _value)) => { - self.position.set(new_position); - Ok(key.clone()) + impl $name { + fn new(dict: PyDictRef) -> Self { + $name { dict: dict } } - None => Err(objiter::new_stop_iteration(vm)), - } - } - - fn iter(self, _vm: &VirtualMachine) -> Self { - self - } -} - -impl PyValue for PyDictKeysIterator { - fn class(vm: &VirtualMachine) -> PyClassRef { - vm.ctx.dictkeysiterator_type.clone() - } -} - -#[derive(Debug)] -struct PyDictValuesIterator { - pub dict: PyDictRef, - pub position: Cell, -} -type PyDictValuesIteratorRef = PyRef; -impl PyDictValuesIteratorRef { - fn new(dict: PyDictRef, vm: &VirtualMachine) -> PyDictValuesIteratorRef { - PyDictValuesIterator { - position: Cell::new(0), - dict, + fn iter(&self, _vm: &VirtualMachine) -> $iter_name { + $iter_name::new(self.dict.clone()) + } } - .into_ref(vm) - } - fn next(self, vm: &VirtualMachine) -> PyResult { - match self.dict.entries.borrow().next_entry(self.position.get()) { - Some((new_position, _key, value)) => { - self.position.set(new_position); - Ok(value.clone()) + impl PyValue for $name { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.$class.clone() } - None => Err(objiter::new_stop_iteration(vm)), } - } - - fn iter(self, _vm: &VirtualMachine) -> Self { - self - } -} -impl PyValue for PyDictValuesIterator { - fn class(vm: &VirtualMachine) -> PyClassRef { - vm.ctx.dictvaluesiterator_type.clone() - } -} + #[derive(Debug)] + struct $iter_name { + pub dict: PyDictRef, + pub position: Cell, + } -#[derive(Debug)] -struct PyDictItemsIterator { - pub dict: PyDictRef, - pub position: Cell, -} + impl $iter_name { + fn new(dict: PyDictRef) -> Self { + $iter_name { + position: Cell::new(0), + dict, + } + } -type PyDictItemsIteratorRef = PyRef; + fn next(&self, vm: &VirtualMachine) -> PyResult { + match self.dict.entries.borrow().next_entry(self.position.get()) { + Some((new_position, key, value)) => { + self.position.set(new_position); + Ok($result_fn(vm, key, value)) + } + None => Err(objiter::new_stop_iteration(vm)), + } + } -impl PyDictItemsIteratorRef { - fn new(dict: PyDictRef, vm: &VirtualMachine) -> PyDictItemsIteratorRef { - PyDictItemsIterator { - position: Cell::new(0), - dict, + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf + } } - .into_ref(vm) - } - fn next(self: PyDictItemsIteratorRef, vm: &VirtualMachine) -> PyResult { - match self.dict.entries.borrow().next_entry(self.position.get()) { - Some((new_position, key, value)) => { - self.position.set(new_position); - Ok(vm.ctx.new_tuple(vec![key.clone(), value.clone()])) + impl PyValue for $iter_name { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.$iter_class.clone() } - None => Err(objiter::new_stop_iteration(vm)), } - } + }; +} - fn iter(self, _vm: &VirtualMachine) -> Self { - self - } +dict_iterator! { + PyDictKeys, + PyDictKeyIterator, + dictkeys_type, + dictkeyiterator_type, + |_vm: &VirtualMachine, key: &PyObjectRef, _value: &PyObjectRef| key.clone() } -impl PyValue for PyDictItemsIterator { - fn class(vm: &VirtualMachine) -> PyClassRef { - vm.ctx.dictitemsiterator_type.clone() - } +dict_iterator! { + PyDictValues, + PyDictValueIterator, + dictvalues_type, + dictvalueiterator_type, + |_vm: &VirtualMachine, _key: &PyObjectRef, value: &PyObjectRef| value.clone() +} + +dict_iterator! { + PyDictItems, + PyDictItemIterator, + dictitems_type, + dictitemiterator_type, + |vm: &VirtualMachine, key: &PyObjectRef, value: &PyObjectRef| + vm.ctx.new_tuple(vec![key.clone(), value.clone()]) } pub fn init(context: &PyContext) { @@ -371,24 +348,36 @@ pub fn init(context: &PyContext) { "values" => context.new_rustfunc(PyDictRef::values), "items" => context.new_rustfunc(PyDictRef::items), // TODO: separate type. `keys` should be a live view over the collection, not an iterator. - "keys" => context.new_rustfunc(PyDictRef::iter), + "keys" => context.new_rustfunc(PyDictRef::keys), "get" => context.new_rustfunc(PyDictRef::get), "copy" => context.new_rustfunc(PyDictRef::copy), "update" => context.new_rustfunc(PyDictRef::update), }); - extend_class!(context, &context.dictkeysiterator_type, { - "__next__" => context.new_rustfunc(PyDictKeysIteratorRef::next), - "__iter__" => context.new_rustfunc(PyDictKeysIteratorRef::iter), + extend_class!(context, &context.dictkeys_type, { + "__iter__" => context.new_rustfunc(PyDictKeys::iter), + }); + + extend_class!(context, &context.dictkeyiterator_type, { + "__next__" => context.new_rustfunc(PyDictKeyIterator::next), + "__iter__" => context.new_rustfunc(PyDictKeyIterator::iter), + }); + + extend_class!(context, &context.dictvalues_type, { + "__iter__" => context.new_rustfunc(PyDictValues::iter), + }); + + extend_class!(context, &context.dictvalueiterator_type, { + "__next__" => context.new_rustfunc(PyDictValueIterator::next), + "__iter__" => context.new_rustfunc(PyDictValueIterator::iter), }); - extend_class!(context, &context.dictvaluesiterator_type, { - "__next__" => context.new_rustfunc(PyDictValuesIteratorRef::next), - "__iter__" => context.new_rustfunc(PyDictValuesIteratorRef::iter), + extend_class!(context, &context.dictitems_type, { + "__iter__" => context.new_rustfunc(PyDictItems::iter), }); - extend_class!(context, &context.dictitemsiterator_type, { - "__next__" => context.new_rustfunc(PyDictItemsIteratorRef::next), - "__iter__" => context.new_rustfunc(PyDictItemsIteratorRef::iter), + extend_class!(context, &context.dictitemiterator_type, { + "__next__" => context.new_rustfunc(PyDictItemIterator::next), + "__iter__" => context.new_rustfunc(PyDictItemIterator::iter), }); } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 8c405c5b1c..434693ea63 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -129,9 +129,12 @@ pub struct PyContext { pub false_value: PyIntRef, pub list_type: PyClassRef, pub listiterator_type: PyClassRef, - pub dictkeysiterator_type: PyClassRef, - pub dictvaluesiterator_type: PyClassRef, - pub dictitemsiterator_type: PyClassRef, + pub dictkeyiterator_type: PyClassRef, + pub dictvalueiterator_type: PyClassRef, + pub dictitemiterator_type: PyClassRef, + pub dictkeys_type: PyClassRef, + pub dictvalues_type: PyClassRef, + pub dictitems_type: PyClassRef, pub map_type: PyClassRef, pub memoryview_type: PyClassRef, pub none: PyNoneRef, @@ -257,9 +260,12 @@ impl PyContext { let str_type = create_type("str", &type_type, &object_type); let list_type = create_type("list", &type_type, &object_type); let listiterator_type = create_type("list_iterator", &type_type, &object_type); - let dictkeysiterator_type = create_type("dict_keys", &type_type, &object_type); - let dictvaluesiterator_type = create_type("dict_values", &type_type, &object_type); - let dictitemsiterator_type = create_type("dict_items", &type_type, &object_type); + let dictkeys_type = create_type("dict_keys", &type_type, &object_type); + let dictvalues_type = create_type("dict_values", &type_type, &object_type); + let dictitems_type = create_type("dict_items", &type_type, &object_type); + let dictkeyiterator_type = create_type("dict_keyiterator", &type_type, &object_type); + let dictvalueiterator_type = create_type("dict_valueiterator", &type_type, &object_type); + let dictitemiterator_type = create_type("dict_itemiterator", &type_type, &object_type); let set_type = create_type("set", &type_type, &object_type); let frozenset_type = create_type("frozenset", &type_type, &object_type); let int_type = create_type("int", &type_type, &object_type); @@ -319,9 +325,12 @@ impl PyContext { staticmethod_type, list_type, listiterator_type, - dictkeysiterator_type, - dictvaluesiterator_type, - dictitemsiterator_type, + dictkeys_type, + dictvalues_type, + dictitems_type, + dictkeyiterator_type, + dictvalueiterator_type, + dictitemiterator_type, set_type, frozenset_type, true_value, From 29e9753e51fa127aec3efea7a68503cf02145b7b Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 8 Apr 2019 15:02:04 +0100 Subject: [PATCH 230/884] Even newer style class definitions. --- vm/src/obj/objdict.rs | 51 +++++++++++++++++++------------------------ 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index e80fa7d8a3..612fa2cb7e 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -8,11 +8,11 @@ use crate::pyobject::{ }; use crate::vm::{ReprGuard, VirtualMachine}; -use crate::dictdatatype; - use super::objiter; use super::objstr; +use crate::dictdatatype; use crate::obj::objtype::PyClassRef; +use crate::pyobject::PyClassImpl; pub type DictContentType = dictdatatype::Dict; @@ -248,17 +248,20 @@ impl ItemProtocol for PyDictRef { } macro_rules! dict_iterator { - ( $name: ident, $iter_name: ident, $class: ident, $iter_class: ident, $result_fn: expr) => { + ( $name: ident, $iter_name: ident, $class: ident, $iter_class: ident, $class_name: literal, $iter_class_name: literal, $result_fn: expr) => { + #[pyclass(name = $class_name, __inside_vm)] #[derive(Debug)] struct $name { pub dict: PyDictRef, } + #[pyimpl(__inside_vm)] impl $name { fn new(dict: PyDictRef) -> Self { $name { dict: dict } } + #[pymethod(name = "__iter__")] fn iter(&self, _vm: &VirtualMachine) -> $iter_name { $iter_name::new(self.dict.clone()) } @@ -270,12 +273,14 @@ macro_rules! dict_iterator { } } + #[pyclass(name = $iter_class_name, __inside_vm)] #[derive(Debug)] struct $iter_name { pub dict: PyDictRef, pub position: Cell, } + #[pyimpl(__inside_vm)] impl $iter_name { fn new(dict: PyDictRef) -> Self { $iter_name { @@ -284,6 +289,7 @@ macro_rules! dict_iterator { } } + #[pymethod(name = "__next__")] fn next(&self, vm: &VirtualMachine) -> PyResult { match self.dict.entries.borrow().next_entry(self.position.get()) { Some((new_position, key, value)) => { @@ -294,6 +300,7 @@ macro_rules! dict_iterator { } } + #[pymethod(name = "__iter__")] fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { zelf } @@ -312,6 +319,8 @@ dict_iterator! { PyDictKeyIterator, dictkeys_type, dictkeyiterator_type, + "dictkeys", + "dictkeyiterator", |_vm: &VirtualMachine, key: &PyObjectRef, _value: &PyObjectRef| key.clone() } @@ -320,6 +329,8 @@ dict_iterator! { PyDictValueIterator, dictvalues_type, dictvalueiterator_type, + "dictvalues", + "dictvalueiterator", |_vm: &VirtualMachine, _key: &PyObjectRef, value: &PyObjectRef| value.clone() } @@ -328,6 +339,8 @@ dict_iterator! { PyDictItemIterator, dictitems_type, dictitemiterator_type, + "dictitems", + "dictitemiterator", |vm: &VirtualMachine, key: &PyObjectRef, value: &PyObjectRef| vm.ctx.new_tuple(vec![key.clone(), value.clone()]) } @@ -354,30 +367,10 @@ pub fn init(context: &PyContext) { "update" => context.new_rustfunc(PyDictRef::update), }); - extend_class!(context, &context.dictkeys_type, { - "__iter__" => context.new_rustfunc(PyDictKeys::iter), - }); - - extend_class!(context, &context.dictkeyiterator_type, { - "__next__" => context.new_rustfunc(PyDictKeyIterator::next), - "__iter__" => context.new_rustfunc(PyDictKeyIterator::iter), - }); - - extend_class!(context, &context.dictvalues_type, { - "__iter__" => context.new_rustfunc(PyDictValues::iter), - }); - - extend_class!(context, &context.dictvalueiterator_type, { - "__next__" => context.new_rustfunc(PyDictValueIterator::next), - "__iter__" => context.new_rustfunc(PyDictValueIterator::iter), - }); - - extend_class!(context, &context.dictitems_type, { - "__iter__" => context.new_rustfunc(PyDictItems::iter), - }); - - extend_class!(context, &context.dictitemiterator_type, { - "__next__" => context.new_rustfunc(PyDictItemIterator::next), - "__iter__" => context.new_rustfunc(PyDictItemIterator::iter), - }); + PyDictKeys::extend_class(context, &context.dictkeys_type); + PyDictKeyIterator::extend_class(context, &context.dictkeyiterator_type); + PyDictValues::extend_class(context, &context.dictvalues_type); + PyDictValueIterator::extend_class(context, &context.dictvalueiterator_type); + PyDictItems::extend_class(context, &context.dictitems_type); + PyDictItemIterator::extend_class(context, &context.dictitemiterator_type); } From 2811f27bb546a811ea54628df5b73cf6e61a8d48 Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 8 Apr 2019 16:03:24 +0200 Subject: [PATCH 231/884] add a warning --- tests/generator/not_impl_header.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/generator/not_impl_header.txt b/tests/generator/not_impl_header.txt index cd6f8f5a12..4ceefff4d6 100644 --- a/tests/generator/not_impl_header.txt +++ b/tests/generator/not_impl_header.txt @@ -1,2 +1,7 @@ +# WARNING: THIS IS AN AUTOMATICALLY GENERATED FILE +# EDIT tests/not_impl_gen.py, NOT THIS FILE. +# RESULTS OF THIS TEST DEPEND ON THE CPYTHON +# VERSION USED TO RUN not_impl_gen.py + import platform From 3aa110a2960a7572852f98b8f97a544dcb379d1c Mon Sep 17 00:00:00 2001 From: andrew Date: Mon, 8 Apr 2019 16:03:35 +0200 Subject: [PATCH 232/884] add .gitignore --- tests/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/.gitignore diff --git a/tests/.gitignore b/tests/.gitignore new file mode 100644 index 0000000000..05ba24c43b --- /dev/null +++ b/tests/.gitignore @@ -0,0 +1 @@ +snippets/whats_left_to_implement.py From b1a95f669f9a5fcb5a439ce240b7c767d36cd04b Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Mon, 8 Apr 2019 17:02:27 +0200 Subject: [PATCH 233/884] Initial version of symbol table builder. --- vm/src/compile.rs | 11 +- vm/src/lib.rs | 1 + vm/src/symboltable.rs | 272 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 vm/src/symboltable.rs diff --git a/vm/src/compile.rs b/vm/src/compile.rs index dd045c3bf9..cc52661a4f 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -10,6 +10,7 @@ use crate::error::CompileError; use crate::obj::objcode; use crate::obj::objcode::PyCodeRef; use crate::pyobject::PyValue; +use crate::symboltable::SymbolTable; use crate::VirtualMachine; use num_complex::Complex64; use rustpython_parser::{ast, parser}; @@ -37,6 +38,8 @@ pub fn compile( match mode { Mode::Exec => { let ast = parser::parse_program(source).map_err(CompileError::Parse)?; + let mut symbol_table = SymbolTable::new(); + symbol_table.scan_program(&ast); compiler.compile_program(&ast) } Mode::Eval => { @@ -194,11 +197,8 @@ impl Compiler { // Pop result of stack, since we not use it: self.emit(Instruction::Pop); } - ast::Statement::Global { names } => { - unimplemented!("global {:?}", names); - } - ast::Statement::Nonlocal { names } => { - unimplemented!("nonlocal {:?}", names); + ast::Statement::Global { .. } | ast::Statement::Nonlocal { .. } => { + // Handled during symbol table construction. } ast::Statement::If { test, body, orelse } => { let end_label = self.new_label(); @@ -1624,6 +1624,7 @@ fn get_doc(body: &[ast::LocatedStatement]) -> (&[ast::LocatedStatement], Option< (body, None) } + #[cfg(test)] mod tests { use super::Compiler; diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 89b4dd8720..578f1d99a3 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -55,6 +55,7 @@ pub mod import; pub mod obj; pub mod pyobject; pub mod stdlib; +mod symboltable; mod sysmodule; mod traceback; pub mod util; diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs new file mode 100644 index 0000000000..6525e71e14 --- /dev/null +++ b/vm/src/symboltable.rs @@ -0,0 +1,272 @@ +/* Python code is pre-scanned for symbols in the ast. + +This ensures that global and nonlocal keywords are picked up. +Then the compiler can use the symbol table to generate proper +load and store instructions for names. +*/ + +use rustpython_parser::ast; +use std::collections::HashMap; + +pub enum SymbolRole { + Global, + Nonlocal, + Used, + Assigned, +} + +pub struct SymbolTable { + // TODO: split-up into nested scopes. + symbols: HashMap, +} + +impl SymbolTable { + pub fn new() -> Self { + SymbolTable { + symbols: HashMap::new(), + } + } + + pub fn lookup(&self, name: &str) -> Option<&SymbolRole> { + return self.symbols.get(name); + } + + pub fn scan_program(&mut self, program: &ast::Program) { + self.scan_statements(&program.statements); + } + + pub fn scan_statements(&mut self, statements: &[ast::LocatedStatement]) { + for statement in statements { + self.scan_statement(statement) + } + } + + fn scan_statement(&mut self, statement: &ast::LocatedStatement) { + match &statement.node { + ast::Statement::Global { names } => { + for name in names { + self.register_name(name, SymbolRole::Global); + } + } + ast::Statement::Nonlocal { names } => { + for name in names { + self.register_name(name, SymbolRole::Nonlocal); + } + } + ast::Statement::FunctionDef { + name, + body, + args, + decorator_list, + returns, + } => { + self.scan_expressions(decorator_list); + self.register_name(name, SymbolRole::Assigned); + for parameter in &args.args {} + for parameter in &args.kwonlyargs {} + + self.scan_expressions(&args.defaults); + for kw_default in &args.kw_defaults { + if let Some(expression) = kw_default { + self.scan_expression(&expression); + } + } + + self.scan_statements(body); + if let Some(expression) = returns { + self.scan_expression(expression); + } + } + ast::Statement::ClassDef { + name, + body, + bases, + keywords, + decorator_list, + } => { + self.register_name(name, SymbolRole::Assigned); + self.scan_statements(body); + self.scan_expressions(bases); + for keyword in keywords { + self.scan_expression(&keyword.value); + } + self.scan_expressions(decorator_list); + } + ast::Statement::Expression { expression } => self.scan_expression(expression), + ast::Statement::If { test, body, orelse } => { + self.scan_expression(test); + self.scan_statements(body); + if let Some(code) = orelse { + self.scan_statements(code); + } + } + ast::Statement::For { + target, + iter, + body, + orelse, + } => { + self.scan_expression(target); + self.scan_expression(iter); + self.scan_statements(body); + if let Some(code) = orelse { + self.scan_statements(code); + } + } + ast::Statement::While { test, body, orelse } => { + self.scan_expression(test); + self.scan_statements(body); + if let Some(code) = orelse { + self.scan_statements(code); + } + } + ast::Statement::Break | ast::Statement::Continue | ast::Statement::Pass => { + // No symbols here. + } + ast::Statement::Import { import_parts } => for part in import_parts {}, + ast::Statement::Return { value } => { + if let Some(expression) = value { + self.scan_expression(expression); + } + } + ast::Statement::Assert { test, msg } => { + self.scan_expression(test); + if let Some(expression) = msg { + self.scan_expression(expression); + } + } + ast::Statement::Delete { targets } => { + self.scan_expressions(targets); + } + ast::Statement::Assign { targets, value } => { + self.scan_expressions(targets); + self.scan_expression(value); + } + ast::Statement::AugAssign { + target, + op: _, + value, + } => { + self.scan_expression(target); + self.scan_expression(value); + } + ast::Statement::With { items, body } => { + for item in items { + self.scan_expression(&item.context_expr); + if let Some(expression) = &item.optional_vars { + self.scan_expression(expression); + } + } + self.scan_statements(body); + } + ast::Statement::Try { + body, + handlers, + orelse, + finalbody, + } => { + self.scan_statements(body); + if let Some(code) = orelse { + self.scan_statements(code); + } + if let Some(code) = finalbody { + self.scan_statements(code); + } + } + ast::Statement::Raise { exception, cause } => { + if let Some(expression) = exception { + self.scan_expression(expression); + } + if let Some(expression) = cause { + self.scan_expression(expression); + } + } + } + } + + fn scan_expressions(&mut self, expressions: &[ast::Expression]) { + for expression in expressions { + self.scan_expression(expression); + } + } + + fn scan_expression(&mut self, expression: &ast::Expression) { + match expression { + ast::Expression::Binop { a, op: _, b } => { + self.scan_expression(a); + self.scan_expression(b); + } + ast::Expression::BoolOp { a, op: _, b } => { + self.scan_expression(a); + self.scan_expression(b); + } + ast::Expression::Compare { vals, ops: _ } => { + self.scan_expressions(vals); + } + ast::Expression::Subscript { a, b } => { + self.scan_expression(a); + self.scan_expression(b); + } + ast::Expression::Attribute { value, name: _ } => { + self.scan_expression(value); + } + ast::Expression::Dict { elements } => { + for (key, value) in elements { + self.scan_expression(key); + self.scan_expression(value); + } + } + ast::Expression::Yield { value } => { + if let Some(expression) = value { + self.scan_expression(expression); + } + } + ast::Expression::YieldFrom { value } => { + self.scan_expression(value); + } + ast::Expression::Unop { op: _, a } => { + self.scan_expression(a); + } + ast::Expression::True + | ast::Expression::False + | ast::Expression::None + | ast::Expression::Ellipsis => {} + ast::Expression::Number { .. } => {} + ast::Expression::Starred { value } => { + self.scan_expression(value); + } + ast::Expression::Bytes { .. } => {} + ast::Expression::Tuple { elements } + | ast::Expression::Set { elements } + | ast::Expression::List { elements } + | ast::Expression::Slice { elements } => { + self.scan_expressions(elements); + } + ast::Expression::Comprehension { kind, generators } => {} + ast::Expression::Call { + function, + args, + keywords, + } => { + self.scan_expression(function); + self.scan_expressions(args); + } + ast::Expression::String { value } => {} + ast::Expression::Identifier { name } => { + self.register_name(name, SymbolRole::Used); + } + ast::Expression::Lambda { args, body } => { + self.scan_expression(body); + } + ast::Expression::IfExpression { test, body, orelse } => { + self.scan_expression(test); + self.scan_expression(body); + self.scan_expression(orelse); + } + } + } + + fn register_name(&mut self, name: &String, role: SymbolRole) { + self.symbols.insert(name.clone(), role); + } +} From e7e126e31ded0b1da77d945f0f92d631b0055d57 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 8 Apr 2019 12:30:53 -0500 Subject: [PATCH 234/884] Fix whats_left.sh --- whats_left.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/whats_left.sh b/whats_left.sh index c6f191bd58..e05051d283 100755 --- a/whats_left.sh +++ b/whats_left.sh @@ -1,5 +1,11 @@ #!/bin/sh +set -e -cd "$(dirname "$0")" || exit +cd "$(dirname "$0")" +cd tests + +python3 not_impl_gen.py + +cd .. cargo run -- tests/snippets/whats_left_to_implement.py From 4e277cfcc2d6eac7aa15cc493dacaa46fa8971d8 Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Mon, 8 Apr 2019 12:41:28 -0600 Subject: [PATCH 235/884] move to useing PyIterable --- vm/src/obj/objlist.rs | 108 ++++++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 1ef01ca3e2..4c3c163832 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -7,7 +7,10 @@ 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}; +use crate::pyobject::{ + IdProtocol, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, + TypeProtocol, +}; use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; @@ -189,7 +192,12 @@ impl PyListRef { ) -> PyResult { match subscript { SequenceIndex::Int(index) => self.setindex(index, value, vm), - SequenceIndex::Slice(slice) => self.setslice(slice, value, vm), + SequenceIndex::Slice(slice) => { + if let Ok(sec) = PyIterable::try_from_object(vm, value) { + return self.setslice(slice, sec, vm); + } + Err(vm.new_type_error("can only assign an iterable to a slice".to_string())) + } } } @@ -202,7 +210,7 @@ impl PyListRef { } } - fn setslice(self, slice: PySliceRef, sec: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn setslice(self, slice: PySliceRef, sec: PyIterable, vm: &VirtualMachine) -> PyResult { let step = slice.step.clone().unwrap_or_else(BigInt::one); if step.is_zero() { @@ -244,52 +252,50 @@ impl PyListRef { } } - fn _set_slice(self, range: Range, sec: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // consume the iter, we don't need it's size - // but if it's going to fail we want that to happen *before* we start modifing - if let Ok(items) = vm.extract_elements(&sec) { - // replace the range of elements with the full sequence - self.elements.borrow_mut().splice(range, items); + fn _set_slice(self, range: Range, sec: PyIterable, vm: &VirtualMachine) -> PyResult { + // consume the iter, we need it's size + // and if it's going to fail we want that to happen *before* we start modifing + let items: Result, _> = sec.iter(vm)?.collect(); + let items = items?; - Ok(vm.get_none()) - } else { - Err(vm.new_type_error("can only assign an iterable to a slice".to_string())) - } + // replace the range of elements with the full sequence + self.elements.borrow_mut().splice(range, items); + + Ok(vm.get_none()) } fn _set_stepped_slice( self, range: Range, step: usize, - sec: PyObjectRef, + sec: PyIterable, vm: &VirtualMachine, ) -> PyResult { + let slicelen = ((range.end - range.start - 1) / step) + 1; // consume the iter, we need it's size // and if it's going to fail we want that to happen *before* we start modifing - let slicelen = ((range.end - range.start - 1) / step) + 1; - if let Ok(items) = vm.extract_elements(&sec) { - let n = items.len(); + let items: Result, _> = sec.iter(vm)?.collect(); + let items = items?; - if range.start < range.end { - if n == slicelen { - let indexes = range.step_by(step); - self._replace_indexes(indexes, &items); - Ok(vm.get_none()) - } else { - Err(vm.new_value_error(format!( - "attempt to assign sequence of size {} to extended slice of size {}", - n, slicelen - ))) - } + let n = items.len(); + + if range.start < range.end { + if n == slicelen { + let indexes = range.step_by(step); + self._replace_indexes(indexes, &items); + Ok(vm.get_none()) } else { - // empty slice but this is an error because stepped slice Err(vm.new_value_error(format!( - "attempt to assign sequence of size {} to extended slice of size 0", - n + "attempt to assign sequence of size {} to extended slice of size {}", + n, slicelen ))) } } else { - Err(vm.new_type_error("can only assign an iterable to a slice".to_string())) + // empty slice but this is an error because stepped slice + Err(vm.new_value_error(format!( + "attempt to assign sequence of size {} to extended slice of size 0", + n + ))) } } @@ -297,35 +303,35 @@ impl PyListRef { self, range: Range, step: usize, - sec: PyObjectRef, + sec: PyIterable, vm: &VirtualMachine, ) -> PyResult { + let slicelen = ((range.end - range.start - 1) / step) + 1; + // consume the iter, we need it's size // and if it's going to fail we want that to happen *before* we start modifing - let slicelen = ((range.end - range.start - 1) / step) + 1; - if let Ok(items) = vm.extract_elements(&sec) { - let n = items.len(); + let items: Result, _> = sec.iter(vm)?.collect(); + let items = items?; - if range.start < range.end { - if n == slicelen { - let indexes = range.rev().step_by(step); - self._replace_indexes(indexes, &items); - Ok(vm.get_none()) - } else { - Err(vm.new_value_error(format!( - "attempt to assign sequence of size {} to extended slice of size {}", - n, slicelen - ))) - } + let n = items.len(); + + if range.start < range.end { + if n == slicelen { + let indexes = range.rev().step_by(step); + self._replace_indexes(indexes, &items); + Ok(vm.get_none()) } else { - // empty slice but this is an error because stepped slice Err(vm.new_value_error(format!( - "attempt to assign sequence of size {} to extended slice of size 0", - n + "attempt to assign sequence of size {} to extended slice of size {}", + n, slicelen ))) } } else { - Err(vm.new_type_error("can only assign an iterable to a slice".to_string())) + // empty slice but this is an error because stepped slice + Err(vm.new_value_error(format!( + "attempt to assign sequence of size {} to extended slice of size 0", + n + ))) } } From d0e6d2fa323992ce1bce5ad998a55c1d04c7883c Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Mon, 8 Apr 2019 13:18:37 -0600 Subject: [PATCH 236/884] add tests for custom iters --- tests/snippets/list.py | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 71f7dea067..55c84fdf92 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -399,3 +399,55 @@ def must_assign_iter_to_slice(): x[::2] = 42 assert_raises(TypeError, must_assign_iter_to_slice) + +# other iterables? +a = list(range(10)) + +# string +x = a[:] +x[3:8] = "abcdefghi" +assert x == [0, 1, 2, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 8, 9] + +# tuple +x = a[:] +x[3:8] = (11, 12, 13, 14, 15) +assert x == [0, 1, 2, 11, 12, 13, 14, 15, 8, 9] + +# class +class CIterNext: + def __init__(self, sec=(1, 2, 3)): + self.sec = sec + self.index = 0 + def __iter__(self): + return self + def __next__(self): + if self.index >= len(self.sec): + raise StopIteration + v = self.sec[self.index] + self.index += 1 + return v + +x = list(range(10)) +x[3:8] = CIterNext() +assert x == [0, 1, 2, 1, 2, 3, 8, 9] + +class CIter: + def __init__(self, sec=(1, 2, 3)): + self.sec = sec + def __iter__(self): + for n in self.sec: + yield n + +x = list(range(10)) +x[3:8] = CIter() +assert x == [0, 1, 2, 1, 2, 3, 8, 9] + +class CGetItem: + def __init__(self, sec=(1, 2, 3)): + self.sec = sec + def __getitem__(self, sub): + return self.sec[sub] + +x = list(range(10)) +x[3:8] = CGetItem() +assert x == [0, 1, 2, 1, 2, 3, 8, 9] From 8232a4d28512323125f853e3d448cc190c2c3a6f Mon Sep 17 00:00:00 2001 From: jgirardet Date: Mon, 8 Apr 2019 21:27:44 +0200 Subject: [PATCH 237/884] finish contains --- tests/snippets/bytes.py | 45 ++++++++++++++++++++------------------ vm/src/obj/objbyteinner.rs | 14 ++++++++---- vm/src/obj/objbytes.rs | 12 +--------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 2fd32153d3..b03eb38085 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -1,10 +1,10 @@ from testutils import assertRaises # new -assert bytes([1,2,3]) -assert bytes((1,2,3)) +assert bytes([1, 2, 3]) +assert bytes((1, 2, 3)) assert bytes(range(4)) -assert b'bla' +assert b"bla" assert bytes(3) assert bytes("bla", "utf8") try: @@ -20,40 +20,39 @@ # # repr -assert repr(bytes([0, 1, 2])) == repr(b'\x00\x01\x02') -assert ( -repr(bytes([0, 1, 9, 10, 11, 13, 31, 32, 33, 89, 120, 255]) -== "b'\\x00\\x01\\t\\n\\x0b\\r\\x1f !Yx\\xff'") +assert repr(bytes([0, 1, 2])) == repr(b"\x00\x01\x02") +assert repr( + bytes([0, 1, 9, 10, 11, 13, 31, 32, 33, 89, 120, 255]) + == "b'\\x00\\x01\\t\\n\\x0b\\r\\x1f !Yx\\xff'" ) assert repr(b"abcd") == "b'abcd'" -#len +# len assert len(bytes("abcdé", "utf8")) == 6 -#comp +# comp assert a == b"abcd" assert a > b assert a >= b assert b < a assert b <= a -assert b'foobar'.__eq__(2) == NotImplemented -assert b'foobar'.__ne__(2) == NotImplemented -assert b'foobar'.__gt__(2) == NotImplemented -assert b'foobar'.__ge__(2) == NotImplemented -assert b'foobar'.__lt__(2) == NotImplemented -assert b'foobar'.__le__(2) == NotImplemented +assert b"foobar".__eq__(2) == NotImplemented +assert b"foobar".__ne__(2) == NotImplemented +assert b"foobar".__gt__(2) == NotImplemented +assert b"foobar".__ge__(2) == NotImplemented +assert b"foobar".__lt__(2) == NotImplemented +assert b"foobar".__le__(2) == NotImplemented -#hash +# hash hash(a) == hash(b"abcd") -#iter +# iter [i for i in b"abcd"] == ["a", "b", "c", "d"] -#add +# add assert a + b == b"abcdab" -#contains # contains assert b"ab" in b"abcd" assert b"cd" in b"abcd" @@ -61,5 +60,9 @@ assert b"a" in b"abcd" assert b"d" in b"abcd" assert b"dc" not in b"abcd" -# assert 97 in b"abcd" -# assert 150 not in b"abcd" \ No newline at end of file +assert 97 in b"abcd" +assert 150 not in b"abcd" +try: + 350 in b"abcd" +except ValueError: + pass diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 03876095cd..95df844214 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -181,11 +181,17 @@ impl PyByteInner { Ok(vm.new_bool(false)) } - pub fn contains_int(&self, int: &PyInt, vm: &VirtualMachine) -> PyResult { - self.elements.contains(int); - Ok(vm.new_bool(false)) + pub fn contains_int(&self, int: &PyInt, vm: &VirtualMachine) -> PyResult { + if let Some(int) = int.as_bigint().to_u8() { + if self.elements.contains(&int) { + Ok(vm.new_bool(true)) + } else { + Ok(vm.new_bool(false)) + } + } else { + Err(vm.new_value_error("byte must be in range(0, 256)".to_string())) + } } } - // TODO // fix b"é" not allowed should be bytes("é", "utf8") diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index e052654bf5..f376dbc67a 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,3 +1,4 @@ +use crate::obj::objint::PyInt; use crate::vm::VirtualMachine; use core::cell::Cell; use std::ops::Deref; @@ -143,17 +144,6 @@ impl PyBytesRef { bytes @ PyBytes => self.inner.contains_bytes(&bytes.inner, vm), int @ PyInt => self.inner.contains_int(&int, vm), _ => Ok(vm.ctx.not_implemented())) - // if objtype::isinstance(&needle, &vm.ctx.bytes_type()) { - // let result = vec_contains(&self.value, &get_value(&needle)); - // vm.ctx.new_bool(result) - // } else if objtype::isinstance(&needle, &vm.ctx.int_type()) { - // let result = self - // .value - // .contains(&objint::get_value(&needle).to_u8().unwrap()); - // vm.ctx.new_bool(result) - // } else { - // vm.new_type_error(format!("Cannot add {:?} and {:?}", self, needle)) - // } } } From 4c50defa37d5a38e75247d6ac32fd3c680f9e7fc Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Mon, 8 Apr 2019 13:46:23 -0600 Subject: [PATCH 238/884] extend PyIterable::try_from_object to work with sequences --- vm/src/pyobject.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index ee765f9875..be51128ef8 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1,4 +1,5 @@ use std::any::Any; +use std::cell::Cell; use std::cell::RefCell; use std::collections::HashMap; use std::fmt; @@ -1043,10 +1044,24 @@ where T: TryFromObject, { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - Ok(PyIterable { - method: vm.get_method(obj, "__iter__")?, - _item: std::marker::PhantomData, - }) + if let Ok(method) = vm.get_method(obj.clone(), "__iter__") { + Ok(PyIterable { + method: method, + _item: std::marker::PhantomData, + }) + } else if vm.get_method(obj.clone(), "__getitem__").is_ok() { + Self::try_from_object( + vm, + objiter::PySequenceIterator { + position: Cell::new(0), + obj: obj.clone(), + } + .into_ref(vm) + .into_object(), + ) + } else { + Err(vm.new_type_error(format!("'{}' object is not iterable", obj.class().name))) + } } } From 4d53ddedb020c100eeb6e45675aebd572909f9dd Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Mon, 8 Apr 2019 14:45:34 -0600 Subject: [PATCH 239/884] test for if iter raises error --- tests/snippets/list.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 55c84fdf92..217e790895 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -414,6 +414,7 @@ def must_assign_iter_to_slice(): assert x == [0, 1, 2, 11, 12, 13, 14, 15, 8, 9] # class +# __next__ class CIterNext: def __init__(self, sec=(1, 2, 3)): self.sec = sec @@ -431,6 +432,7 @@ def __next__(self): x[3:8] = CIterNext() assert x == [0, 1, 2, 1, 2, 3, 8, 9] +# __iter__ yield class CIter: def __init__(self, sec=(1, 2, 3)): self.sec = sec @@ -442,6 +444,7 @@ def __iter__(self): x[3:8] = CIter() assert x == [0, 1, 2, 1, 2, 3, 8, 9] +# __getitem but no __iter__ sequence class CGetItem: def __init__(self, sec=(1, 2, 3)): self.sec = sec @@ -451,3 +454,17 @@ def __getitem__(self, sub): x = list(range(10)) x[3:8] = CGetItem() assert x == [0, 1, 2, 1, 2, 3, 8, 9] + +# iter raises error +class CIterError: + def __iter__(self): + for i in range(10): + if i > 5: + raise RuntimeError + yield i + +def bad_iter_assign(): + x = list(range(10)) + x[3:8] = CIterError() + +assert_raises(RuntimeError, bad_iter_assign) From b53595831005023bdfeb6e5c93eb40b3286775f9 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Mon, 8 Apr 2019 23:12:40 +0200 Subject: [PATCH 240/884] add getitem, fix contain error_message --- tests/snippets/bytes.py | 11 +++++++++++ vm/src/obj/objbyteinner.rs | 18 +++++++++++++++++- vm/src/obj/objbytes.rs | 12 ++++++++++-- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index b03eb38085..770f4f373b 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -66,3 +66,14 @@ 350 in b"abcd" except ValueError: pass + + +# getitem +d = b"abcdefghij" + +assert d[1] == 98 +assert d[-1] == 106 +assert d[2:6] == b"cdef" +assert d[-6:] == b"efghij" +assert d[1:8:2] == b"bdfh" +assert d[8:1:-2] == b"igec" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 95df844214..bb9966ebfa 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -11,6 +11,7 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use super::objint; +use super::objsequence::PySliceableSequence; use crate::obj::objint::PyInt; use num_traits::ToPrimitive; @@ -189,9 +190,24 @@ impl PyByteInner { Ok(vm.new_bool(false)) } } else { - Err(vm.new_value_error("byte must be in range(0, 256)".to_string())) + Err(vm.new_value_error("byte mu st be in range(0, 256)".to_string())) } } + + pub fn getitem_int(&self, int: &PyInt, vm: &VirtualMachine) -> PyResult { + if let Some(idx) = self.elements.get_pos(int.as_bigint().to_i32().unwrap()) { + Ok(vm.new_int(self.elements[idx])) + } else { + Err(vm.new_index_error("index out of range".to_string())) + } + } + + pub fn getitem_slice(&self, slice: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(vm + .ctx + .new_bytes(self.elements.get_slice_items(vm, slice).unwrap())) + } } + // TODO // fix b"é" not allowed should be bytes("é", "utf8") diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index f376dbc67a..2c15e60cda 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -8,6 +8,7 @@ use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyVa use super::objbyteinner::PyByteInner; use super::objiter; +use super::objslice::PySlice; use super::objtype::PyClassRef; /// "bytes(iterable_of_ints) -> bytes\n\ @@ -139,11 +140,18 @@ impl PyBytesRef { #[pymethod(name = "__contains__")] fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // no new style since objint is not. match_class!(needle, bytes @ PyBytes => self.inner.contains_bytes(&bytes.inner, vm), int @ PyInt => self.inner.contains_int(&int, vm), - _ => Ok(vm.ctx.not_implemented())) + obj => Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)))) + } + + #[pymethod(name = "__getitem__")] + fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match_class!(needle, + int @ PyInt => self.inner.getitem_int(&int, vm), + slice @ PySlice => self.inner.getitem_slice(slice.as_object(), vm), + obj => Err(vm.new_type_error(format!("byte indices must be integers or slices, not {}", obj)))) } } From b2a1f6580b6dcf3eec299ec1423513c682aa18eb Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Mon, 8 Apr 2019 15:56:06 -0600 Subject: [PATCH 241/884] fix error when start or stop of slice is -1 and step != 1 --- tests/snippets/list.py | 10 ++++++++++ vm/src/obj/objlist.rs | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 217e790895..dc0582d4c5 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -468,3 +468,13 @@ def bad_iter_assign(): x[3:8] = CIterError() assert_raises(RuntimeError, bad_iter_assign) + +# slice assign when step or stop is -1 + +a = list(range(10)) +x = a[:] +x[-1:-5:-1] = ['a', 'b', 'c', 'd'] +assert x == [0, 1, 2, 3, 4, 5, 'd', 'c', 'b', 'a'] +x = a[:] +x[-5:-1:-1] = [] +assert x == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 4c3c163832..6de08dfef3 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -3,7 +3,7 @@ use std::fmt; use std::ops::Range; -use num_bigint::BigInt; +use num_bigint::{BigInt, ToBigInt}; use num_traits::{One, Signed, ToPrimitive, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; @@ -238,8 +238,20 @@ impl PyListRef { } else { // calculate the range for the reverse slice, first the bounds needs to be made // exclusive around stop, the lower number - let start = &slice.start.as_ref().map(|x| x + 1); - let stop = &slice.stop.as_ref().map(|x| x + 1); + let start = &slice.start.as_ref().map(|x| { + if *x == (-1).to_bigint().unwrap() { + self.get_len() + BigInt::one() //.to_bigint().unwrap() + } else { + x + 1 + } + }); + let stop = &slice.stop.as_ref().map(|x| { + if *x == (-1).to_bigint().unwrap() { + self.get_len().to_bigint().unwrap() + } else { + x + 1 + } + }); let range = self.get_slice_range(&stop, &start); match (-step).to_i32() { Some(num) => self._set_stepped_slice_reverse(range, num as usize, sec, vm), @@ -271,7 +283,11 @@ impl PyListRef { sec: PyIterable, vm: &VirtualMachine, ) -> PyResult { - let slicelen = ((range.end - range.start - 1) / step) + 1; + let slicelen = if range.end > range.start { + ((range.end - range.start - 1) / step) + 1 + } else { + 0 + }; // consume the iter, we need it's size // and if it's going to fail we want that to happen *before* we start modifing let items: Result, _> = sec.iter(vm)?.collect(); @@ -290,6 +306,9 @@ impl PyListRef { n, slicelen ))) } + } else if n == 0 { + // slice is empty but so is sequence + Ok(vm.get_none()) } else { // empty slice but this is an error because stepped slice Err(vm.new_value_error(format!( @@ -306,7 +325,11 @@ impl PyListRef { sec: PyIterable, vm: &VirtualMachine, ) -> PyResult { - let slicelen = ((range.end - range.start - 1) / step) + 1; + let slicelen = if range.end > range.start { + ((range.end - range.start - 1) / step) + 1 + } else { + 0 + }; // consume the iter, we need it's size // and if it's going to fail we want that to happen *before* we start modifing @@ -326,6 +349,9 @@ impl PyListRef { n, slicelen ))) } + } else if n == 0 { + // slice is empty but so is sequence + Ok(vm.get_none()) } else { // empty slice but this is an error because stepped slice Err(vm.new_value_error(format!( From 1101b6571ccded8b05e252b47658448d04382783 Mon Sep 17 00:00:00 2001 From: Rachel Powers Date: Mon, 8 Apr 2019 16:17:57 -0600 Subject: [PATCH 242/884] fix error related to #746 but for list.__delitem__ --- tests/snippets/list.py | 8 ++++++++ vm/src/obj/objlist.rs | 18 +++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 3d9743c0b4..41de1b5fdc 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -201,3 +201,11 @@ def bad_del_1(): def bad_del_2(): del ['a', 'b'][2] assert_raises(IndexError, bad_del_2) + +# is step != 1 and start or stop of slice == -1 +x = list(range(10)) +del x[-1:-5:-1] +assert x == [0, 1, 2, 3, 4, 5] +x = list(range(10)) +del x[-5:-1:-1] +assert x == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 60c14b1074..c5278e1fbd 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -3,7 +3,7 @@ use std::fmt; use std::ops::Range; -use num_bigint::BigInt; +use num_bigint::{BigInt, ToBigInt}; use num_traits::{One, Signed, ToPrimitive, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; @@ -411,8 +411,20 @@ impl PyListRef { } 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 start = start.as_ref().map(|x| { + if *x == (-1).to_bigint().unwrap() { + self.get_len() + BigInt::one() //.to_bigint().unwrap() + } else { + x + 1 + } + }); + let stop = stop.as_ref().map(|x| { + if *x == (-1).to_bigint().unwrap() { + self.get_len().to_bigint().unwrap() + } else { + x + 1 + } + }); let range = self.get_slice_range(&stop, &start); if range.start < range.end { match (-step).to_i32() { From 422ab69b99af4611cd8d3f416cfe24e94894782c Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 8 Apr 2019 21:50:38 -0500 Subject: [PATCH 243/884] Use impl style classes --- wasm/lib/src/browser_module.rs | 54 ++++++++++++++++------------------ 1 file changed, 25 insertions(+), 29 deletions(-) diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 607cfc886a..d302ec99ac 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -10,7 +10,7 @@ use rustpython_vm::obj::{ objdict::PyDictRef, objfunction::PyFunctionRef, objint::PyIntRef, objstr::PyStringRef, objtype::PyClassRef, }; -use rustpython_vm::pyobject::{PyObject, PyObjectRef, PyRef, PyResult, PyValue}; +use rustpython_vm::pyobject::{PyClassImpl, PyObject, PyObjectRef, PyRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; use crate::{convert, vm_class::weak_vm, wasm_builtins::window}; @@ -161,6 +161,7 @@ fn browser_cancel_animation_frame(id: PyIntRef, vm: &VirtualMachine) -> PyResult Ok(vm.get_none()) } +#[pyclass(name = "Promise")] #[derive(Debug)] pub struct PyPromise { value: Promise, @@ -173,6 +174,7 @@ impl PyValue for PyPromise { } } +#[pyimpl] impl PyPromise { pub fn new(value: Promise) -> PyPromise { PyPromise { value } @@ -187,15 +189,16 @@ impl PyPromise { self.value.clone() } + #[pymethod] fn then( - zelf: PyPromiseRef, + &self, on_fulfill: PyFunctionRef, on_reject: OptionalArg, vm: &VirtualMachine, ) -> PyPromiseRef { let weak_vm = weak_vm(vm); - let ret_future = JsFuture::from(zelf.value.clone()).then(move |res| { + let ret_future = JsFuture::from(self.value.clone()).then(move |res| { let stored_vm = &weak_vm .upgrade() .expect("that the vm is valid when the promise resolves"); @@ -220,10 +223,11 @@ impl PyPromise { PyPromise::from_future(ret_future).into_ref(vm) } - fn catch(zelf: PyPromiseRef, on_reject: PyFunctionRef, vm: &VirtualMachine) -> PyPromiseRef { + #[pymethod] + fn catch(&self, on_reject: PyFunctionRef, vm: &VirtualMachine) -> PyPromiseRef { let weak_vm = weak_vm(vm); - let ret_future = JsFuture::from(zelf.value.clone()).then(move |res| { + let ret_future = JsFuture::from(self.value.clone()).then(move |res| { res.or_else(|err| { let stored_vm = weak_vm .upgrade() @@ -239,11 +243,11 @@ impl PyPromise { } } +#[pyclass] #[derive(Debug)] struct Document { doc: web_sys::Document, } -type DocumentRef = PyRef; impl PyValue for Document { fn class(vm: &VirtualMachine) -> PyClassRef { @@ -251,9 +255,11 @@ impl PyValue for Document { } } +#[pyimpl] impl Document { - fn query(zelf: DocumentRef, query: PyStringRef, vm: &VirtualMachine) -> PyResult { - let elem = zelf + #[pymethod] + fn query(&self, query: PyStringRef, vm: &VirtualMachine) -> PyResult { + let elem = self .doc .query_selector(&query.value) .map_err(|err| convert::js_py_typeerror(vm, err))?; @@ -265,11 +271,11 @@ impl Document { } } +#[pyclass] #[derive(Debug)] struct Element { elem: web_sys::Element, } -type ElementRef = PyRef; impl PyValue for Element { fn class(vm: &VirtualMachine) -> PyClassRef { @@ -277,26 +283,24 @@ impl PyValue for Element { } } +#[pyimpl] impl Element { + #[pymethod] fn get_attr( - zelf: ElementRef, + &self, attr: PyStringRef, default: OptionalArg, vm: &VirtualMachine, ) -> PyObjectRef { - match zelf.elem.get_attribute(&attr.value) { + match self.elem.get_attribute(&attr.value) { Some(s) => vm.new_str(s), None => default.into_option().unwrap_or_else(|| vm.get_none()), } } - fn set_attr( - zelf: ElementRef, - attr: PyStringRef, - value: PyStringRef, - vm: &VirtualMachine, - ) -> PyResult<()> { - zelf.elem + #[pymethod] + fn set_attr(&self, attr: PyStringRef, value: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + self.elem .set_attribute(&attr.value, &value.value) .map_err(|err| convert::js_to_py(vm, err)) } @@ -340,14 +344,9 @@ fn browser_prompt( pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; - let promise = py_class!(ctx, "Promise", ctx.object(), { - "then" => ctx.new_rustfunc(PyPromise::then), - "catch" => ctx.new_rustfunc(PyPromise::catch), - }); + let promise = PyPromise::make_class(ctx); - let document_class = py_class!(ctx, "Document", ctx.object(), { - "query" => ctx.new_rustfunc(Document::query), - }); + let document_class = Document::make_class(ctx); let document = PyObject::new( Document { @@ -357,10 +356,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { None, ); - let element = py_class!(ctx, "Element", ctx.object(), { - "get_attr" => ctx.new_rustfunc(Element::get_attr), - "set_attr" => ctx.new_rustfunc(Element::set_attr), - }); + let element = Element::make_class(ctx); py_module!(vm, "browser", { "fetch" => ctx.new_rustfunc(browser_fetch), From 046e4b3fe37648e86ad69436fb7cee26b2a2c3f9 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 9 Apr 2019 20:43:59 +1200 Subject: [PATCH 244/884] Cleanup dict test snippet --- tests/snippets/dict.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index f924bb0e7c..23d6e933b1 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -110,16 +110,10 @@ def __eq__(self, other): y = x.copy() x['c'] = 12 -assert list(y) == ['a', 'b'] -assert y['a'] == 2 -assert y['b'] == 10 +assert dict_eq(y, {'a': 2, 'b': 10}) y.update({'c': 19, "d": -1, 'b': 12}) -assert list(y) == ['a', 'b', 'c', 'd'] -assert y['a'] == 2 -assert y['b'] == 12 -assert y['c'] == 19 -assert y['d'] == -1 +assert dict_eq(y, {'a': 2, 'b': 12, 'c': 19, 'd': -1}) y.update(y) -assert list(y) == ['a', 'b', 'c', 'd'] +assert dict_eq(y, {'a': 2, 'b': 12, 'c': 19, 'd': -1}) # hasn't changed From 23a4b1e9d86bed3acd95ed3917023ace2456fad3 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Tue, 9 Apr 2019 10:13:44 +0100 Subject: [PATCH 245/884] Additional tests for dictionary iteration. --- tests/snippets/dict.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index f57fc37b66..44d1c382f7 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -37,13 +37,36 @@ def dict_eq(d1, d2): res.add(key) assert res == set(['a','b']) +# Deleted values are correctly skipped over: x = {'a': 1, 'b': 2, 'c': 3, 'd': 3} del x['c'] it = iter(x.items()) assert ('a', 1) == next(it) assert ('b', 2) == next(it) assert ('d', 3) == next(it) - +with assertRaises(StopIteration): + next(it) + +# Iterating a dictionary is just its keys: +assert ['a', 'b', 'd'] == list(x) + +# Iterating view captures dictionary when iterated. +data = {1: 2, 3: 4} +items = data.items() +assert list(items) == [(1, 2), (3, 4)] +data[5] = 6 +assert list(items) == [(1, 2), (3, 4), (5, 6)] + +# Values can be changed during iteration. +data = {1: 2, 3: 4} +items = iter(data.items()) +assert (1, 2) == next(items) +data[3] = "changed" +assert (3, "changed") == next(items) + +# View isn't itself an iterator. +with assertRaises(TypeError): + next(data.keys()) x = {} x[1] = 1 From d9216c87f1acde0f0a7f092d943f0ffe8bdebd41 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Tue, 9 Apr 2019 10:17:17 +0100 Subject: [PATCH 246/884] Remove done todo. --- vm/src/obj/objdict.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 612fa2cb7e..53445e01f8 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -360,7 +360,6 @@ pub fn init(context: &PyContext) { "clear" => context.new_rustfunc(PyDictRef::clear), "values" => context.new_rustfunc(PyDictRef::values), "items" => context.new_rustfunc(PyDictRef::items), - // TODO: separate type. `keys` should be a live view over the collection, not an iterator. "keys" => context.new_rustfunc(PyDictRef::keys), "get" => context.new_rustfunc(PyDictRef::get), "copy" => context.new_rustfunc(PyDictRef::copy), From 25d3f83e4df7548010c2d090e8fc5193c3d75b08 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Tue, 9 Apr 2019 10:44:04 +0100 Subject: [PATCH 247/884] Add __len__ to dictionary views. --- tests/snippets/dict.py | 2 ++ vm/src/obj/objdict.rs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 091b034052..45c6c3b430 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -68,6 +68,8 @@ def dict_eq(d1, d2): with assertRaises(TypeError): next(data.keys()) +assert len(data.keys()) == 2 + x = {} x[1] = 1 assert x[1] == 1 diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 53445e01f8..6d2aefd78a 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -265,6 +265,11 @@ macro_rules! dict_iterator { fn iter(&self, _vm: &VirtualMachine) -> $iter_name { $iter_name::new(self.dict.clone()) } + + #[pymethod(name = "__len__")] + fn len(&self, vm: &VirtualMachine) -> usize { + self.dict.clone().len(vm) + } } impl PyValue for $name { From ec5fd550debd3c2bbdc646215484dfc81fb3de3f Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Tue, 9 Apr 2019 11:25:18 +0100 Subject: [PATCH 248/884] Implement IntoIterator for PyDictRef. --- vm/src/dictdatatype.rs | 12 ------- vm/src/frame.rs | 15 ++++----- vm/src/obj/objdict.rs | 73 ++++++++++++++++++++++++++++++----------- vm/src/obj/objmodule.rs | 6 +--- vm/src/obj/objobject.rs | 2 +- vm/src/stdlib/json.rs | 2 +- 6 files changed, 63 insertions(+), 47 deletions(-) diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 0612bc8a35..6dc49125bd 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -131,18 +131,6 @@ impl Dict { None } - pub fn iter_items(&self) -> impl Iterator + '_ { - self.entries - .iter() - .filter(|e| e.is_some()) - .map(|e| e.as_ref().unwrap()) - .map(|e| (e.key.clone(), e.value.clone())) - } - - pub fn get_items(&self) -> Vec<(PyObjectRef, T)> { - self.iter_items().collect() - } - /// Lookup the index for the given key. fn lookup(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { let hash_value = calc_hash(vm, key)?; diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 2570c09345..ff9baba358 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -393,9 +393,8 @@ impl Frame { // Take all key-value pairs from the dict: let dict: PyDictRef = obj.downcast().expect("Need a dictionary to build a map."); - let dict_elements = dict.get_key_value_pairs(); - for (key, value) in dict_elements.iter() { - map_obj.set_item(key.clone(), value.clone(), vm).unwrap(); + for (key, value) in dict { + map_obj.set_item(key, value, vm).unwrap(); } } } else { @@ -636,8 +635,7 @@ impl Frame { let kwargs = if *has_kwargs { let kw_dict: PyDictRef = self.pop_value().downcast().expect("Kwargs must be a dict."); - let dict_elements = kw_dict.get_key_value_pairs(); - dict_elements + kw_dict .into_iter() .map(|elem| (objstr::get_value(&elem.0), elem.1)) .collect() @@ -862,8 +860,8 @@ impl Frame { // Grab all the names from the module and put them in the context if let Some(dict) = &module.dict { - for (k, v) in dict.get_key_value_pairs().iter() { - self.scope.store_name(&vm, &objstr::get_value(k), v.clone()); + for (k, v) in dict { + self.scope.store_name(&vm, &objstr::get_value(&k), v); } } Ok(None) @@ -1230,8 +1228,7 @@ impl fmt::Debug for Frame { .collect::(); let dict = self.scope.get_locals(); let local_str = dict - .get_key_value_pairs() - .iter() + .into_iter() .map(|elem| format!("\n {:?} = {:?}", elem.0, elem.1)) .collect::(); write!( diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 53445e01f8..b0d99d4e75 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -3,8 +3,7 @@ use std::fmt; use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ - IdProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, - PyValue, + IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, PyValue, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -63,9 +62,8 @@ impl PyDictRef { if let OptionalArg::Present(dict_obj) = dict_obj { let dicted: PyResult = dict_obj.clone().downcast(); if let Ok(dict_obj) = dicted { - let mut dict_borrowed = dict.borrow_mut(); - for (key, value) in dict_obj.entries.borrow().iter_items() { - dict_borrowed.insert(vm, &key, value)?; + for (key, value) in dict_obj { + dict.borrow_mut().insert(vm, &key, value)?; } } else { let iter = objiter::get_iter(vm, &dict_obj)?; @@ -105,7 +103,7 @@ impl PyDictRef { fn repr(self, vm: &VirtualMachine) -> PyResult { let s = if let Some(_guard) = ReprGuard::enter(self.as_object()) { let mut str_parts = vec![]; - for (key, value) in self.get_key_value_pairs() { + for (key, value) in self { let key_repr = vm.to_repr(&key)?; let value_repr = vm.to_repr(&value)?; str_parts.push(format!("{}: {}", key_repr.value, value_repr.value)); @@ -146,10 +144,6 @@ impl PyDictRef { PyDictItems::new(self) } - pub fn get_key_value_pairs(&self) -> Vec<(PyObjectRef, PyObjectRef)> { - self.entries.borrow().get_items() - } - fn inner_setitem( self, key: PyObjectRef, @@ -194,24 +188,17 @@ impl PyDictRef { fn update( self, - mut dict_obj: OptionalArg, + dict_obj: OptionalArg, kwargs: KwArgs, vm: &VirtualMachine, ) -> PyResult<()> { - if let OptionalArg::Present(ref other) = dict_obj { - if self.is(other) { - // Updating yourself is a noop, and this avoids a borrow error - dict_obj = OptionalArg::Missing; - } - } - PyDictRef::merge(&self.entries, dict_obj, kwargs, vm) } /// Take a python dictionary and convert it to attributes. pub fn to_attributes(self) -> PyAttributes { let mut attrs = PyAttributes::new(); - for (key, value) in self.get_key_value_pairs() { + for (key, value) in self { let key = objstr::get_value(&key); attrs.insert(key, value); } @@ -247,6 +234,54 @@ impl ItemProtocol for PyDictRef { } } +// Implement IntoIterator so that we can easily iterate dictionaries from rust code. +impl IntoIterator for PyDictRef { + type Item = (PyObjectRef, PyObjectRef); + type IntoIter = DictIterator; + + fn into_iter(self) -> Self::IntoIter { + DictIterator::new(self) + } +} + +impl IntoIterator for &PyDictRef { + type Item = (PyObjectRef, PyObjectRef); + type IntoIter = DictIterator; + + fn into_iter(self) -> Self::IntoIter { + DictIterator::new(self.clone()) + } +} + +pub struct DictIterator { + dict: PyDictRef, + position: Cell, +} + +impl DictIterator { + pub fn new(dict: PyDictRef) -> DictIterator { + DictIterator { + dict, + position: Cell::new(0), + } + } +} + +impl Iterator for DictIterator { + type Item = (PyObjectRef, PyObjectRef); + + fn next(&mut self) -> Option { + self.dict + .entries + .borrow() + .next_entry(self.position.get()) + .map(|(new_position, key, value)| { + self.position.set(new_position); + (key.clone(), value.clone()) + }) + } +} + macro_rules! dict_iterator { ( $name: ident, $iter_name: ident, $class: ident, $iter_class: ident, $class_name: literal, $iter_class_name: literal, $result_fn: expr) => { #[pyclass(name = $class_name, __inside_vm)] diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs index 75749d557e..904487e4d9 100644 --- a/vm/src/obj/objmodule.rs +++ b/vm/src/obj/objmodule.rs @@ -17,11 +17,7 @@ impl PyValue for PyModule { impl PyModuleRef { fn dir(self: PyModuleRef, vm: &VirtualMachine) -> PyResult { if let Some(dict) = &self.into_object().dict { - let keys = dict - .get_key_value_pairs() - .iter() - .map(|(k, _v)| k.clone()) - .collect(); + let keys = dict.into_iter().map(|(k, _v)| k.clone()).collect(); Ok(vm.ctx.new_list(keys)) } else { panic!("Modules should definitely have a dict."); diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index a3bc411038..f8e86d0a1c 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -237,7 +237,7 @@ pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes { // Get instance attributes: if let Some(dict) = &obj.dict { - for (key, value) in dict.get_key_value_pairs() { + for (key, value) in dict.into_iter() { attributes.insert(key.to_string(), value.clone()); } } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index f4061bc2a0..3fa30e16bf 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -64,7 +64,7 @@ impl<'s> serde::Serialize for PyObjectSerializer<'s> { serialize_seq_elements(serializer, &elements) } else if objtype::isinstance(self.pyobject, &self.vm.ctx.dict_type()) { let dict: PyDictRef = self.pyobject.clone().downcast().unwrap(); - let pairs = dict.get_key_value_pairs(); + let pairs: Vec<(PyObjectRef, PyObjectRef)> = dict.into_iter().collect(); let mut map = serializer.serialize_map(Some(pairs.len()))?; for (key, e) in pairs.iter() { map.serialize_entry(&self.clone_with_object(key), &self.clone_with_object(&e))?; From 2940c7cc280143de25777198e1bb3631b3f54104 Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Tue, 9 Apr 2019 14:33:29 +0200 Subject: [PATCH 249/884] add islpaha isalnum isdigit islower isupper isspace istitle --- tests/snippets/bytes.py | 35 +++++++++++++++ vm/src/obj/objbyteinner.rs | 90 ++++++++++++++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 33 ++++++++++++++ 3 files changed, 158 insertions(+) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 770f4f373b..ceb52921cc 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -77,3 +77,38 @@ assert d[-6:] == b"efghij" assert d[1:8:2] == b"bdfh" assert d[8:1:-2] == b"igec" + + +# is_xx methods + +assert bytes(b"1a23").isalnum() +assert not bytes(b"1%a23").isalnum() + +assert bytes(b"abc").isalpha() +assert not bytes(b"abc1").isalpha() + +# travis doesn't like this +# assert bytes(b'xyz').isascii() +# assert not bytes([128, 157, 32]).isascii() + +assert bytes(b"1234567890").isdigit() +assert not bytes(b"12ab").isdigit() + +l = bytes(b"lower") +b = bytes(b"UPPER") + +assert l.islower() +assert not l.isupper() +assert b.isupper() +assert not bytes(b"Super Friends").islower() + +assert bytes(b" \n\t").isspace() +assert not bytes(b"\td\n").isspace() + +assert b.isupper() +assert not b.islower() +assert l.islower() +assert not bytes(b"tuPpEr").isupper() + +assert bytes(b"Is Title Case").istitle() +assert not bytes(b"is Not title casE").istitle() diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index bb9966ebfa..14da2521eb 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -207,6 +207,96 @@ impl PyByteInner { .ctx .new_bytes(self.elements.get_slice_items(vm, slice).unwrap())) } + + pub fn isalnum(&self, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_bool( + !self.elements.is_empty() + && self + .elements + .iter() + .all(|x| char::from(*x).is_alphanumeric()), + )) + } + + pub fn isalpha(&self, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_bool( + !self.elements.is_empty() + && self.elements.iter().all(|x| char::from(*x).is_alphabetic()), + )) + } + + pub fn isascii(&self, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_bool( + !self.elements.is_empty() && self.elements.iter().all(|x| char::from(*x).is_ascii()), + )) + } + + pub fn isdigit(&self, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_bool( + !self.elements.is_empty() && self.elements.iter().all(|x| char::from(*x).is_digit(10)), + )) + } + + pub fn islower(&self, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_bool( + !self.elements.is_empty() + && self + .elements + .iter() + .filter(|x| !char::from(**x).is_whitespace()) + .all(|x| char::from(*x).is_lowercase()), + )) + } + + pub fn isspace(&self, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_bool( + !self.elements.is_empty() + && self.elements.iter().all(|x| char::from(*x).is_whitespace()), + )) + } + + pub fn isupper(&self, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_bool( + !self.elements.is_empty() + && self + .elements + .iter() + .filter(|x| !char::from(**x).is_whitespace()) + .all(|x| char::from(*x).is_uppercase()), + )) + } + + pub fn istitle(&self, vm: &VirtualMachine) -> PyResult { + if self.elements.is_empty() { + return Ok(vm.new_bool(false)); + } + + let mut iter = self.elements.iter().peekable(); + let mut prev_cased = false; + + while let Some(c) = iter.next() { + let current = char::from(*c); + let next = if let Some(k) = iter.peek() { + char::from(**k) + } else if current.is_uppercase() { + return Ok(vm.new_bool(!prev_cased)); + } else { + return Ok(vm.new_bool(prev_cased)); + }; + + let is_cased = current.to_uppercase().next().unwrap() != current + || current.to_lowercase().next().unwrap() != current; + if (is_cased && next.is_uppercase() && !prev_cased) + || (!is_cased && next.is_lowercase()) + { + return Ok(vm.new_bool(false)); + } + + prev_cased = is_cased; + } + + Ok(vm.new_bool(true)) + } } // TODO diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 2c15e60cda..83ce3b8fbd 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -153,6 +153,39 @@ impl PyBytesRef { slice @ PySlice => self.inner.getitem_slice(slice.as_object(), vm), obj => Err(vm.new_type_error(format!("byte indices must be integers or slices, not {}", obj)))) } + + #[pymethod(name = "isalnum")] + fn isalnum(self, vm: &VirtualMachine) -> PyResult { + self.inner.isalnum(vm) + } + #[pymethod(name = "isalpha")] + fn isalpha(self, vm: &VirtualMachine) -> PyResult { + self.inner.isalpha(vm) + } + #[pymethod(name = "isascii")] + fn isascii(self, vm: &VirtualMachine) -> PyResult { + self.inner.isascii(vm) + } + #[pymethod(name = "isdigit")] + fn isdigit(self, vm: &VirtualMachine) -> PyResult { + self.inner.isdigit(vm) + } + #[pymethod(name = "islower")] + fn islower(self, vm: &VirtualMachine) -> PyResult { + self.inner.islower(vm) + } + #[pymethod(name = "isspace")] + fn isspace(self, vm: &VirtualMachine) -> PyResult { + self.inner.isspace(vm) + } + #[pymethod(name = "isupper")] + fn isupper(self, vm: &VirtualMachine) -> PyResult { + self.inner.isupper(vm) + } + #[pymethod(name = "istitle")] + fn istitle(self, vm: &VirtualMachine) -> PyResult { + self.inner.istitle(vm) + } } #[derive(Debug)] From 6c745f68dd6d123fc491cc513429ede529fa3a2e Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Tue, 9 Apr 2019 14:43:13 +0200 Subject: [PATCH 250/884] fix typo, fix bytesinner.add now return Vec[u8] --- vm/src/obj/objbyteinner.rs | 6 +++--- vm/src/obj/objbytes.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 14da2521eb..bcb1f82075 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -55,7 +55,7 @@ impl PyByteInner { } else { return Err(vm.new_type_error("encoding without a string argument".to_string())); } - // On ly one argument + // Only one argument } else { let value = if let OptionalArg::Present(ival) = val_option { match_class!(ival.clone(), @@ -161,14 +161,14 @@ impl PyByteInner { hasher.finish() as usize } - pub fn add(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { + pub fn add(&self, other: &PyByteInner, _vm: &VirtualMachine) -> Vec { let elements: Vec = self .elements .iter() .chain(other.elements.iter()) .cloned() .collect(); - Ok(vm.ctx.new_bytes(elements)) + elements } pub fn contains_bytes(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 83ce3b8fbd..0b257cb773 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -134,7 +134,7 @@ impl PyBytesRef { #[pymethod(name = "__add__")] fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, - bytes @ PyBytes => self.inner.add(&bytes.inner, vm), + bytes @ PyBytes => Ok(vm.ctx.new_bytes(self.inner.add(&bytes.inner, vm))), _ => Ok(vm.ctx.not_implemented())) } From 9b763072fe78d8b2dd9fcdeb31a3d1fafcbcc367 Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Tue, 9 Apr 2019 16:23:56 +0200 Subject: [PATCH 251/884] add upper lower --- tests/snippets/bytes.py | 6 ++++++ vm/src/obj/objbyteinner.rs | 8 ++++++++ vm/src/obj/objbytes.rs | 17 +++++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index ceb52921cc..3ccb2fc234 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -112,3 +112,9 @@ assert bytes(b"Is Title Case").istitle() assert not bytes(b"is Not title casE").istitle() + +# upper lower +l = bytes(b"lower") +b = bytes(b"UPPER") +assert l.lower().islower() +assert b.upper().isupper() diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index bcb1f82075..4771a91bd5 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -297,6 +297,14 @@ impl PyByteInner { Ok(vm.new_bool(true)) } + + pub fn lower(&self, _vm: &VirtualMachine) -> Vec { + self.elements.to_ascii_lowercase() + } + + pub fn upper(&self, _vm: &VirtualMachine) -> Vec { + self.elements.to_ascii_uppercase() + } } // TODO diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 0b257cb773..0be8215005 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -158,34 +158,51 @@ impl PyBytesRef { fn isalnum(self, vm: &VirtualMachine) -> PyResult { self.inner.isalnum(vm) } + #[pymethod(name = "isalpha")] fn isalpha(self, vm: &VirtualMachine) -> PyResult { self.inner.isalpha(vm) } + #[pymethod(name = "isascii")] fn isascii(self, vm: &VirtualMachine) -> PyResult { self.inner.isascii(vm) } + #[pymethod(name = "isdigit")] fn isdigit(self, vm: &VirtualMachine) -> PyResult { self.inner.isdigit(vm) } + #[pymethod(name = "islower")] fn islower(self, vm: &VirtualMachine) -> PyResult { self.inner.islower(vm) } + #[pymethod(name = "isspace")] fn isspace(self, vm: &VirtualMachine) -> PyResult { self.inner.isspace(vm) } + #[pymethod(name = "isupper")] fn isupper(self, vm: &VirtualMachine) -> PyResult { self.inner.isupper(vm) } + #[pymethod(name = "istitle")] fn istitle(self, vm: &VirtualMachine) -> PyResult { self.inner.istitle(vm) } + + #[pymethod(name = "lower")] + fn lower(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.lower(vm))) + } + + #[pymethod(name = "upper")] + fn upper(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.upper(vm))) + } } #[derive(Debug)] From 5cc83a35aa6d9430a8f0d7c793d6cbbad17dfd66 Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Tue, 9 Apr 2019 17:08:38 +0200 Subject: [PATCH 252/884] hex --- tests/snippets/bytes.py | 3 ++- vm/src/obj/objbyteinner.rs | 9 +++++++++ vm/src/obj/objbytes.rs | 5 +++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 3ccb2fc234..bf7906bc1e 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -113,8 +113,9 @@ assert bytes(b"Is Title Case").istitle() assert not bytes(b"is Not title casE").istitle() -# upper lower +# upper lower hex l = bytes(b"lower") b = bytes(b"UPPER") assert l.lower().islower() assert b.upper().isupper() +assert bytes([0, 1, 9, 23, 90, 234]).hex() == "000109175aea" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 4771a91bd5..6994a58ac2 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -305,6 +305,15 @@ impl PyByteInner { pub fn upper(&self, _vm: &VirtualMachine) -> Vec { self.elements.to_ascii_uppercase() } + + pub fn hex(&self, vm: &VirtualMachine) -> PyResult { + let bla = self + .elements + .iter() + .map(|x| format!("{:02x}", x)) + .collect::(); + Ok(vm.ctx.new_str(bla)) + } } // TODO diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 0be8215005..5859b10b4c 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -203,6 +203,11 @@ impl PyBytesRef { fn upper(self, vm: &VirtualMachine) -> PyResult { Ok(vm.ctx.new_bytes(self.inner.upper(vm))) } + + #[pymethod(name = "hex")] + fn hex(self, vm: &VirtualMachine) -> PyResult { + self.inner.hex(vm) + } } #[derive(Debug)] From cbdab226d995fff81892af6eddd34b9e74f8cf48 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 9 Apr 2019 22:07:21 -0500 Subject: [PATCH 253/884] Change #[pymethod(property)] to #[pyproperty] --- derive/src/pyclass.rs | 238 ++++++++++++++++++++++++++++-------------- 1 file changed, 161 insertions(+), 77 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index e533e8009a..710747291c 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -1,91 +1,138 @@ use super::rustpython_path_attr; -use proc_macro2::{Span, TokenStream as TokenStream2}; +use proc_macro2::TokenStream as TokenStream2; use quote::quote; +use std::collections::HashMap; use syn::{Attribute, AttributeArgs, Ident, ImplItem, Item, Lit, Meta, MethodSig, NestedMeta}; -enum MethodKind { +#[derive(PartialEq, Clone, Copy)] +enum ClassItemKind { Method, - Property, + PropertyGetter, + PropertySetter, } -impl MethodKind { - fn to_ctx_constructor_fn(&self) -> Ident { - let f = match self { - MethodKind::Method => "new_rustfunc", - MethodKind::Property => "new_property", - }; - Ident::new(f, Span::call_site()) - } +struct ClassItem { + item_name: Ident, + py_name: String, + kind: ClassItemKind, } -struct Method { - fn_name: Ident, - py_name: String, - kind: MethodKind, +fn meta_to_vec(meta: Meta) -> Result, Meta> { + match meta { + Meta::Word(_) => Ok(Vec::new()), + Meta::List(list) => Ok(list.nested.into_iter().collect()), + Meta::NameValue(_) => Err(meta), + } } -impl Method { - fn from_syn(attrs: &mut Vec, sig: &MethodSig) -> Option { - let mut py_name = None; - let mut kind = MethodKind::Method; - let mut pymethod_to_remove = Vec::new(); - let metas = attrs +impl ClassItem { + fn extract_from_syn(attrs: &mut Vec, sig: &MethodSig) -> Option { + let mut item = None; + let mut attr_idx = None; + for (i, meta) in attrs .iter() + .filter_map(|attr| attr.parse_meta().ok()) .enumerate() - .filter_map(|(i, attr)| { - if attr.path.is_ident("pymethod") { - let meta = attr.parse_meta().expect("Invalid attribute"); - // remove #[pymethod] because there's no actual proc macro - // implementation for it - pymethod_to_remove.push(i); + { + let name = meta.name(); + if name == "pymethod" { + if item.is_some() { + panic!("You can only have one #[py*] attribute on an impl item") + } + let nesteds = meta_to_vec(meta) + .expect("#[pymethod = ...] must be a list, e.g. #[pymethod(...)]"); + let mut py_name = None; + for meta in nesteds { + let meta = match meta { + NestedMeta::Meta(meta) => meta, + NestedMeta::Literal(_) => panic!("Expected a meta, found a literal"), + }; match meta { - Meta::List(list) => Some(list), - Meta::Word(_) => None, - Meta::NameValue(_) => panic!( - "#[pymethod = ...] attribute on a method should be a list, like \ - #[pymethod(...)]" - ), + Meta::NameValue(name_value) => { + if name_value.ident == "name" { + if let Lit::Str(s) = &name_value.lit { + py_name = Some(s.value()); + } else { + panic!("#[pymethod(name = ...)] must be a string"); + } + } + } + _ => {} } - } else { - None } - }) - .flat_map(|attr| attr.nested); - for meta in metas { - if let NestedMeta::Meta(meta) = meta { - match meta { - Meta::NameValue(name_value) => { - if name_value.ident == "name" { - if let Lit::Str(s) = &name_value.lit { - py_name = Some(s.value()); - } else { - panic!("#[pymethod(name = ...)] must be a string"); + item = Some(ClassItem { + item_name: sig.ident.clone(), + py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), + kind: ClassItemKind::Method, + }); + attr_idx = Some(i); + } else if name == "pyproperty" { + if item.is_some() { + panic!("You can only have one #[py*] attribute on an impl item") + } + let nesteds = meta_to_vec(meta) + .expect("#[pyproperty = ...] must be a list, e.g. #[pyproperty(...)]"); + let mut setter = false; + let mut py_name = None; + for meta in nesteds { + let meta = match meta { + NestedMeta::Meta(meta) => meta, + NestedMeta::Literal(_) => panic!("Expected a meta, found a literal"), + }; + match meta { + Meta::NameValue(name_value) => { + if name_value.ident == "name" { + if let Lit::Str(s) = &name_value.lit { + py_name = Some(s.value()); + } else { + panic!("#[pyproperty(name = ...)] must be a string"); + } } } - } - Meta::Word(ident) => { - if ident == "property" { - kind = MethodKind::Property + Meta::Word(ident) => { + if ident == "setter" { + setter = true; + } } + _ => {} } - _ => {} } + let kind = if setter { + ClassItemKind::PropertySetter + } else { + ClassItemKind::PropertyGetter + }; + let py_name = py_name.unwrap_or_else(|| { + let item_name = sig.ident.to_string(); + if item_name.starts_with("set_") { + let name = &item_name["set_".len()..]; + if name.is_empty() { + panic!( + "A #[pyproperty(setter)] fn with a set_* name have something \ + after \"set_\"" + ) + } else { + name.to_string() + } + } else { + panic!( + "A #[pyproperty(setter)] fn must either have a `name` parameter or a \ + fn name along the lines of \"set_*\"" + ) + } + }); + item = Some(ClassItem { + py_name, + item_name: sig.ident.clone(), + kind, + }); + attr_idx = Some(i); } } - // if there are no #[pymethods]s, then it's not a method, so exclude it from - // the final result - if pymethod_to_remove.is_empty() { - return None; - } - for i in pymethod_to_remove { - attrs.remove(i); + if let Some(attr_idx) = attr_idx { + attrs.remove(attr_idx); } - let py_name = py_name.unwrap_or_else(|| sig.ident.to_string()); - Some(Method { - fn_name: sig.ident.clone(), - py_name, - kind, - }) + item } } @@ -98,30 +145,66 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { let rp_path = rustpython_path_attr(&attr); - let methods = imp + let items = imp .items .iter_mut() .filter_map(|item| { if let ImplItem::Method(meth) = item { - Method::from_syn(&mut meth.attrs, &meth.sig) + ClassItem::extract_from_syn(&mut meth.attrs, &meth.sig) } else { None } }) .collect::>(); let ty = &imp.self_ty; - let methods = methods.iter().map( - |Method { - py_name, - fn_name, - kind, - }| { - let constructor_fn = kind.to_ctx_constructor_fn(); - quote! { - class.set_str_attr(#py_name, ctx.#constructor_fn(Self::#fn_name)); + let mut properties: HashMap<&str, (Option<&Ident>, Option<&Ident>)> = HashMap::new(); + for item in items.iter() { + match item.kind { + ClassItemKind::PropertyGetter => { + let (ref mut getter, _) = properties.entry(&item.py_name).or_default(); + if getter.is_some() { + panic!("Multiple property getters with name {:?}", &item.py_name) + } + *getter = Some(&item.item_name); + } + ClassItemKind::PropertySetter => { + let (_, ref mut setter) = properties.entry(&item.py_name).or_default(); + if setter.is_some() { + panic!("Multiple property getters with name {:?}", &item.py_name) + } + *setter = Some(&item.item_name); } - }, - ); + ClassItemKind::Method => {} + } + } + let methods = items.iter().filter_map(|item| { + if let ClassItemKind::Method = item.kind { + let ClassItem { + py_name, item_name, .. + } = item; + Some(quote! { + class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_name)); + }) + } else { + None + } + }); + let properties = properties.iter().map(|(name, prop)| { + let getter = match prop.0 { + Some(getter) => getter, + None => panic!("Property {:?} is missing a getter", name), + }; + let add_setter = prop.1.map(|setter| quote!(.add_setter(Self::#setter))); + quote! { + class.set_str_attr( + #name, + #rp_path::obj::objproperty::PropertyBuilder + .add_getter(Self::#getter) + #add_setter + .create(), + ); + } + }); quote! { #imp @@ -131,6 +214,7 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { class: &#rp_path::obj::objtype::PyClassRef, ) { #(#methods)* + #(#properties)* } } } From 977f56ade1bc9f9aca692ff14dcb352b42508e10 Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Wed, 10 Apr 2019 10:08:10 +0200 Subject: [PATCH 254/884] add bytes.fromhex --- tests/snippets/bytes.py | 14 +++++++++++++- vm/src/obj/objbyteinner.rs | 33 +++++++++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 22 +++++++++++++++++++--- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index bf7906bc1e..acd8d9e506 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -113,9 +113,21 @@ assert bytes(b"Is Title Case").istitle() assert not bytes(b"is Not title casE").istitle() -# upper lower hex +# upper lower l = bytes(b"lower") b = bytes(b"UPPER") assert l.lower().islower() assert b.upper().isupper() + +# hex from hex assert bytes([0, 1, 9, 23, 90, 234]).hex() == "000109175aea" + +bytes.fromhex("62 6c7a 34350a ") == b"blz45\n" +try: + bytes.fromhex("62 a 21") +except ValueError as e: + str(e) == "non-hexadecimal number found in fromhex() arg at position 4" +try: + bytes.fromhex("6Z2") +except ValueError as e: + str(e) == "non-hexadecimal number found in fromhex() arg at position 1" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 6994a58ac2..7504f2b749 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -314,6 +314,39 @@ impl PyByteInner { .collect::(); Ok(vm.ctx.new_str(bla)) } + + pub fn fromhex(string: String, vm: &VirtualMachine) -> Result, PyObjectRef> { + // first check for invalid character + for (i, c) in string.char_indices() { + if !c.is_digit(16) && !c.is_whitespace() { + return Err(vm.new_value_error(format!( + "non-hexadecimal number found in fromhex() arg at position {}", + i + ))); + } + } + + // strip white spaces + let stripped = string.split_whitespace().collect::(); + + // Hex is evaluated on 2 digits + if stripped.len() % 2 != 0 { + return Err(vm.new_value_error(format!( + "non-hexadecimal number found in fromhex() arg at position {}", + stripped.len() - 1 + ))); + } + + // parse even string + Ok(stripped + .chars() + .collect::>() + .chunks(2) + .map(|x| x.to_vec().iter().collect::()) + .map(|x| u8::from_str_radix(&x, 16)) + .map(|x| x.unwrap()) + .collect::>()) + } } // TODO diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 5859b10b4c..2c8edca6ab 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,4 +1,5 @@ use crate::obj::objint::PyInt; +use crate::obj::objstr::PyString; use crate::vm::VirtualMachine; use core::cell::Cell; use std::ops::Deref; @@ -55,11 +56,15 @@ pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a pub fn init(context: &PyContext) { PyBytesRef::extend_class(context, &context.bytes_type); + let bytes_type = &context.bytes_type; + extend_class!(context, bytes_type, { +"fromhex" => context.new_rustfunc(PyBytesRef::fromhex), +}); let bytesiterator_type = &context.bytesiterator_type; extend_class!(context, bytesiterator_type, { - "__next__" => context.new_rustfunc(PyBytesIteratorRef::next), - "__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), - }); +"__next__" => context.new_rustfunc(PyBytesIteratorRef::next), +"__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), +}); } #[pyimpl(__inside_vm)] @@ -208,6 +213,17 @@ impl PyBytesRef { fn hex(self, vm: &VirtualMachine) -> PyResult { self.inner.hex(vm) } + + // #[pymethod(name = "fromhex")] + fn fromhex(string: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match_class!(string, + s @ PyString => { + match PyByteInner::fromhex(s.to_string(), vm) { + Ok(x) => Ok(vm.ctx.new_bytes(x)), + Err(y) => Err(y)}}, + obj => Err(vm.new_type_error(format!("fromhex() argument must be str, not {}", obj ))) + ) + } } #[derive(Debug)] From 9a92159413a9ef6de556e45e47bdea14fbe43dd0 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Wed, 10 Apr 2019 09:26:03 +0100 Subject: [PATCH 255/884] Remove unneeded Cell on DictIter. --- vm/src/obj/objdict.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index b0d99d4e75..35925ff351 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -255,15 +255,12 @@ impl IntoIterator for &PyDictRef { pub struct DictIterator { dict: PyDictRef, - position: Cell, + position: usize, } impl DictIterator { pub fn new(dict: PyDictRef) -> DictIterator { - DictIterator { - dict, - position: Cell::new(0), - } + DictIterator { dict, position: 0 } } } @@ -271,14 +268,13 @@ impl Iterator for DictIterator { type Item = (PyObjectRef, PyObjectRef); fn next(&mut self) -> Option { - self.dict - .entries - .borrow() - .next_entry(self.position.get()) - .map(|(new_position, key, value)| { - self.position.set(new_position); - (key.clone(), value.clone()) - }) + match self.dict.entries.borrow().next_entry(self.position) { + Some((new_position, key, value)) => { + self.position = new_position; + Some((key.clone(), value.clone())) + } + None => None, + } } } From 8f840d537610cb05e545a3545feb2011e230a095 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Wed, 10 Apr 2019 09:26:18 +0100 Subject: [PATCH 256/884] Pick code review nits. --- vm/src/obj/objdict.rs | 18 +++++++++--------- vm/src/obj/objobject.rs | 2 +- vm/src/stdlib/json.rs | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 35925ff351..753baf786b 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -237,34 +237,34 @@ impl ItemProtocol for PyDictRef { // Implement IntoIterator so that we can easily iterate dictionaries from rust code. impl IntoIterator for PyDictRef { type Item = (PyObjectRef, PyObjectRef); - type IntoIter = DictIterator; + type IntoIter = DictIter; fn into_iter(self) -> Self::IntoIter { - DictIterator::new(self) + DictIter::new(self) } } impl IntoIterator for &PyDictRef { type Item = (PyObjectRef, PyObjectRef); - type IntoIter = DictIterator; + type IntoIter = DictIter; fn into_iter(self) -> Self::IntoIter { - DictIterator::new(self.clone()) + DictIter::new(self.clone()) } } -pub struct DictIterator { +pub struct DictIter { dict: PyDictRef, position: usize, } -impl DictIterator { - pub fn new(dict: PyDictRef) -> DictIterator { - DictIterator { dict, position: 0 } +impl DictIter { + pub fn new(dict: PyDictRef) -> DictIter { + DictIter { dict, position: 0 } } } -impl Iterator for DictIterator { +impl Iterator for DictIter { type Item = (PyObjectRef, PyObjectRef); fn next(&mut self) -> Option { diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index f8e86d0a1c..2be4ff4f45 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -237,7 +237,7 @@ pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes { // Get instance attributes: if let Some(dict) = &obj.dict { - for (key, value) in dict.into_iter() { + for (key, value) in dict { attributes.insert(key.to_string(), value.clone()); } } diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 3fa30e16bf..dceec8a7df 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -64,7 +64,7 @@ impl<'s> serde::Serialize for PyObjectSerializer<'s> { serialize_seq_elements(serializer, &elements) } else if objtype::isinstance(self.pyobject, &self.vm.ctx.dict_type()) { let dict: PyDictRef = self.pyobject.clone().downcast().unwrap(); - let pairs: Vec<(PyObjectRef, PyObjectRef)> = dict.into_iter().collect(); + let pairs: Vec<_> = dict.into_iter().collect(); let mut map = serializer.serialize_map(Some(pairs.len()))?; for (key, e) in pairs.iter() { map.serialize_entry(&self.clone_with_object(key), &self.clone_with_object(&e))?; From e04275b1999d42749c92d029a109cfc2aa6699c7 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Wed, 10 Apr 2019 09:48:53 +0100 Subject: [PATCH 257/884] Missed get_key_values_pairs removal in wasm. --- wasm/lib/src/browser_module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index d302ec99ac..478ebb9c52 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -83,7 +83,7 @@ fn browser_fetch(url: PyStringRef, args: FetchArgs, vm: &VirtualMachine) -> PyRe if let Some(headers) = headers { let h = request.headers(); - for (key, value) in headers.get_key_value_pairs() { + for (key, value) in headers { let key = vm.to_str(&key)?; let value = vm.to_str(&value)?; h.set(key.as_str(), value.as_str()) From 751c3d52e2a4deccd0443b84c0c70329a2b76f02 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Wed, 10 Apr 2019 13:24:31 +0200 Subject: [PATCH 258/884] use vec! for int arg in bytes new --- tests/snippets/bytes.py | 6 +++++- vm/src/obj/objbyteinner.rs | 6 +----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index acd8d9e506..0a3521df33 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -4,8 +4,8 @@ assert bytes([1, 2, 3]) assert bytes((1, 2, 3)) assert bytes(range(4)) -assert b"bla" assert bytes(3) +assert b"bla" assert bytes("bla", "utf8") try: bytes("bla") @@ -14,6 +14,9 @@ else: assert False + + + a = b"abcd" b = b"ab" c = b"abcd" @@ -49,6 +52,7 @@ # iter [i for i in b"abcd"] == ["a", "b", "c", "d"] +assert list(bytes(3)) == [0,0,0] # add assert a + b == b"abcdab" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 7504f2b749..fac093762a 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -61,11 +61,7 @@ impl PyByteInner { match_class!(ival.clone(), i @ PyInt => { let size = objint::get_value(&i.into_object()).to_usize().unwrap(); - let mut res: Vec = Vec::with_capacity(size); - for _ in 0..size { - res.push(0) - } - Ok(res)}, + Ok(vec![0; size])}, _l @ PyString=> {return Err(vm.new_type_error(format!( "string argument without an encoding" )));}, From 54c7335f1c32c0215b81f856cac26218a1223f5e Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Wed, 10 Apr 2019 18:05:28 +0200 Subject: [PATCH 259/884] SyntaxError for non ascii char --- parser/src/lexer.rs | 8 ++++++-- tests/snippets/bytes.py | 4 ---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index edf21c973c..561ea4d772 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -540,8 +540,12 @@ where let end_pos = self.get_pos(); let tok = if is_bytes { - Tok::Bytes { - value: string_content.as_bytes().to_vec(), + if string_content.is_ascii() { + Tok::Bytes { + value: string_content.as_bytes().to_vec(), + } + } else { + return Err(LexicalError::StringError); } } else { Tok::String { diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 0a3521df33..816eae4951 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -11,10 +11,6 @@ bytes("bla") except TypeError: assert True -else: - assert False - - a = b"abcd" From 9d25a216de4d8240f72eb6f347228dc9b31269c1 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Wed, 10 Apr 2019 21:39:16 +0200 Subject: [PATCH 260/884] fix some tests, run clippy --- tests/snippets/bytes.py | 12 ++++-------- vm/src/obj/objbyteinner.rs | 30 +++++++++++++++--------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 816eae4951..0ffbc29f8c 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -7,10 +7,8 @@ assert bytes(3) assert b"bla" assert bytes("bla", "utf8") -try: +with assertRaises(TypeError): bytes("bla") -except TypeError: - assert True a = b"abcd" @@ -48,7 +46,7 @@ # iter [i for i in b"abcd"] == ["a", "b", "c", "d"] -assert list(bytes(3)) == [0,0,0] +assert list(bytes(3)) == [0, 0, 0] # add assert a + b == b"abcdab" @@ -62,10 +60,8 @@ assert b"dc" not in b"abcd" assert 97 in b"abcd" assert 150 not in b"abcd" -try: +with assertRaises(ValueError): 350 in b"abcd" -except ValueError: - pass # getitem @@ -113,7 +109,7 @@ assert bytes(b"Is Title Case").istitle() assert not bytes(b"is Not title casE").istitle() -# upper lower +# upper lower l = bytes(b"lower") b = bytes(b"UPPER") assert l.lower().islower() diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index fac093762a..532504b028 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -31,8 +31,8 @@ impl PyByteInner { if let OptionalArg::Present(eval) = val_option { if let Ok(input) = eval.downcast::() { if let Ok(encoding) = enc.clone().downcast::() { - if encoding.value.to_lowercase() == "utf8".to_string() - || encoding.value.to_lowercase() == "utf-8".to_string() + if &encoding.value.to_lowercase() == "utf8" + || &encoding.value.to_lowercase() == "utf-8" // TODO: different encoding { return Ok(PyByteInner { @@ -62,12 +62,10 @@ impl PyByteInner { i @ PyInt => { let size = objint::get_value(&i.into_object()).to_usize().unwrap(); Ok(vec![0; size])}, - _l @ PyString=> {return Err(vm.new_type_error(format!( - "string argument without an encoding" - )));}, + _l @ PyString=> {return Err(vm.new_type_error("string argument without an encoding".to_string()));}, obj => { - let elements = vm.extract_elements(&obj).or_else(|_| {return Err(vm.new_type_error(format!( - "cannot convert {} object to bytes", obj.class().name)));}); + let elements = vm.extract_elements(&obj).or_else(|_| {Err(vm.new_type_error(format!( + "cannot convert {} object to bytes", obj.class().name)))}); let mut data_bytes = vec![]; for elem in elements.unwrap(){ @@ -111,6 +109,10 @@ impl PyByteInner { self.elements.len() } + pub fn is_empty(&self) -> bool { + self.elements.len() == 0 + } + pub fn eq(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { if self.elements == other.elements { Ok(vm.new_bool(true)) @@ -169,10 +171,11 @@ impl PyByteInner { pub fn contains_bytes(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { for (n, i) in self.elements.iter().enumerate() { - if n + other.len() <= self.len() && *i == other.elements[0] { - if &self.elements[n..n + other.len()] == other.elements.as_slice() { - return Ok(vm.new_bool(true)); - } + if n + other.len() <= self.len() + && *i == other.elements[0] + && &self.elements[n..n + other.len()] == other.elements.as_slice() + { + return Ok(vm.new_bool(true)); } } Ok(vm.new_bool(false)) @@ -186,7 +189,7 @@ impl PyByteInner { Ok(vm.new_bool(false)) } } else { - Err(vm.new_value_error("byte mu st be in range(0, 256)".to_string())) + Err(vm.new_value_error("byte must be in range(0, 256)".to_string())) } } @@ -344,6 +347,3 @@ impl PyByteInner { .collect::>()) } } - -// TODO -// fix b"é" not allowed should be bytes("é", "utf8") From 47bd9680f9c473129d331b22f549bc40e18a0a56 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 10 Apr 2019 17:41:25 -0500 Subject: [PATCH 261/884] Change ClassItem to enum --- derive/src/pyclass.rs | 64 ++++++++++++++++++------------------------- 1 file changed, 26 insertions(+), 38 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 710747291c..3415da181e 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -4,17 +4,16 @@ use quote::quote; use std::collections::HashMap; use syn::{Attribute, AttributeArgs, Ident, ImplItem, Item, Lit, Meta, MethodSig, NestedMeta}; -#[derive(PartialEq, Clone, Copy)] -enum ClassItemKind { - Method, - PropertyGetter, - PropertySetter, -} - -struct ClassItem { - item_name: Ident, - py_name: String, - kind: ClassItemKind, +enum ClassItem { + Method { + item_name: Ident, + py_name: String, + }, + Property { + item_name: Ident, + py_name: String, + setter: bool, + }, } fn meta_to_vec(meta: Meta) -> Result, Meta> { @@ -60,10 +59,9 @@ impl ClassItem { _ => {} } } - item = Some(ClassItem { + item = Some(ClassItem::Method { item_name: sig.ident.clone(), py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), - kind: ClassItemKind::Method, }); attr_idx = Some(i); } else if name == "pyproperty" { @@ -97,11 +95,6 @@ impl ClassItem { _ => {} } } - let kind = if setter { - ClassItemKind::PropertySetter - } else { - ClassItemKind::PropertyGetter - }; let py_name = py_name.unwrap_or_else(|| { let item_name = sig.ident.to_string(); if item_name.starts_with("set_") { @@ -121,10 +114,10 @@ impl ClassItem { ) } }); - item = Some(ClassItem { + item = Some(ClassItem::Property { py_name, item_name: sig.ident.clone(), - kind, + setter, }); attr_idx = Some(i); } @@ -159,29 +152,24 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { let ty = &imp.self_ty; let mut properties: HashMap<&str, (Option<&Ident>, Option<&Ident>)> = HashMap::new(); for item in items.iter() { - match item.kind { - ClassItemKind::PropertyGetter => { - let (ref mut getter, _) = properties.entry(&item.py_name).or_default(); - if getter.is_some() { - panic!("Multiple property getters with name {:?}", &item.py_name) - } - *getter = Some(&item.item_name); - } - ClassItemKind::PropertySetter => { - let (_, ref mut setter) = properties.entry(&item.py_name).or_default(); - if setter.is_some() { - panic!("Multiple property getters with name {:?}", &item.py_name) + match item { + ClassItem::Property { + item_name, + py_name, + setter, + } => { + let entry = properties.entry(py_name).or_default(); + let func = if *setter { &mut entry.1 } else { &mut entry.0 }; + if func.is_some() { + panic!("Multiple property accessors with name {:?}", py_name) } - *setter = Some(&item.item_name); + *func = Some(item_name); } - ClassItemKind::Method => {} + _ => {} } } let methods = items.iter().filter_map(|item| { - if let ClassItemKind::Method = item.kind { - let ClassItem { - py_name, item_name, .. - } = item; + if let ClassItem::Method { item_name, py_name } = item { Some(quote! { class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_name)); }) From d099165e3bf2b5e53bf41d087482bb05dd2800f6 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 10 Apr 2019 17:41:45 -0500 Subject: [PATCH 262/884] Rename item_name -> item_ident --- derive/src/pyclass.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 3415da181e..892b298b83 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -6,11 +6,11 @@ use syn::{Attribute, AttributeArgs, Ident, ImplItem, Item, Lit, Meta, MethodSig, enum ClassItem { Method { - item_name: Ident, + item_ident: Ident, py_name: String, }, Property { - item_name: Ident, + item_ident: Ident, py_name: String, setter: bool, }, @@ -60,7 +60,7 @@ impl ClassItem { } } item = Some(ClassItem::Method { - item_name: sig.ident.clone(), + item_ident: sig.ident.clone(), py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), }); attr_idx = Some(i); @@ -96,9 +96,9 @@ impl ClassItem { } } let py_name = py_name.unwrap_or_else(|| { - let item_name = sig.ident.to_string(); - if item_name.starts_with("set_") { - let name = &item_name["set_".len()..]; + let item_ident = sig.ident.to_string(); + if item_ident.starts_with("set_") { + let name = &item_ident["set_".len()..]; if name.is_empty() { panic!( "A #[pyproperty(setter)] fn with a set_* name have something \ @@ -116,7 +116,7 @@ impl ClassItem { }); item = Some(ClassItem::Property { py_name, - item_name: sig.ident.clone(), + item_ident: sig.ident.clone(), setter, }); attr_idx = Some(i); @@ -154,7 +154,7 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { for item in items.iter() { match item { ClassItem::Property { - item_name, + item_ident, py_name, setter, } => { @@ -163,15 +163,19 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { if func.is_some() { panic!("Multiple property accessors with name {:?}", py_name) } - *func = Some(item_name); + *func = Some(item_ident); } _ => {} } } let methods = items.iter().filter_map(|item| { - if let ClassItem::Method { item_name, py_name } = item { + if let ClassItem::Method { + item_ident, + py_name, + } = item + { Some(quote! { - class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_name)); + class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_ident)); }) } else { None From 2bdaa02700f88691826ad7edd666d632e1d1d26d Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 10 Apr 2019 17:45:07 -0500 Subject: [PATCH 263/884] Improve error messages --- derive/src/pyclass.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 892b298b83..2387b4d797 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -28,6 +28,7 @@ impl ClassItem { fn extract_from_syn(attrs: &mut Vec, sig: &MethodSig) -> Option { let mut item = None; let mut attr_idx = None; + // TODO: better error handling throughout this for (i, meta) in attrs .iter() .filter_map(|attr| attr.parse_meta().ok()) @@ -38,13 +39,15 @@ impl ClassItem { if item.is_some() { panic!("You can only have one #[py*] attribute on an impl item") } - let nesteds = meta_to_vec(meta) - .expect("#[pymethod = ...] must be a list, e.g. #[pymethod(...)]"); + let nesteds = meta_to_vec(meta).expect( + "#[pyproperty = \"...\"] cannot be a name/value, you probably meant \ + #[pyproperty(name = \"...\")]", + ); let mut py_name = None; for meta in nesteds { let meta = match meta { NestedMeta::Meta(meta) => meta, - NestedMeta::Literal(_) => panic!("Expected a meta, found a literal"), + NestedMeta::Literal(_) => continue, }; match meta { Meta::NameValue(name_value) => { @@ -68,14 +71,16 @@ impl ClassItem { if item.is_some() { panic!("You can only have one #[py*] attribute on an impl item") } - let nesteds = meta_to_vec(meta) - .expect("#[pyproperty = ...] must be a list, e.g. #[pyproperty(...)]"); + let nesteds = meta_to_vec(meta).expect( + "#[pyproperty = \"...\"] cannot be a name/value, you probably meant \ + #[pyproperty(name = \"...\")]", + ); let mut setter = false; let mut py_name = None; for meta in nesteds { let meta = match meta { NestedMeta::Meta(meta) => meta, - NestedMeta::Literal(_) => panic!("Expected a meta, found a literal"), + NestedMeta::Literal(_) => continue, }; match meta { Meta::NameValue(name_value) => { From c5f4eab1424e4b88493a98bf904e350c55982ebe Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 10 Apr 2019 17:55:24 -0500 Subject: [PATCH 264/884] Change objint to impl style with properties --- derive/src/pyclass.rs | 28 ++++++++++++++++------------ vm/src/obj/objint.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 2387b4d797..433d5911ef 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -102,21 +102,25 @@ impl ClassItem { } let py_name = py_name.unwrap_or_else(|| { let item_ident = sig.ident.to_string(); - if item_ident.starts_with("set_") { - let name = &item_ident["set_".len()..]; - if name.is_empty() { - panic!( - "A #[pyproperty(setter)] fn with a set_* name have something \ - after \"set_\"" - ) + if setter { + if item_ident.starts_with("set_") { + let name = &item_ident["set_".len()..]; + if name.is_empty() { + panic!( + "A #[pyproperty(setter)] fn with a set_* name have something \ + after \"set_\"" + ) + } else { + name.to_string() + } } else { - name.to_string() - } - } else { - panic!( + panic!( "A #[pyproperty(setter)] fn must either have a `name` parameter or a \ fn name along the lines of \"set_*\"" ) + } + } else { + item_ident } }); item = Some(ClassItem::Property { @@ -195,7 +199,7 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { quote! { class.set_str_attr( #name, - #rp_path::obj::objproperty::PropertyBuilder + #rp_path::obj::objproperty::PropertyBuilder::new(ctx) .add_getter(Self::#getter) #add_setter .create(), diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index de211668f3..789d140d66 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -17,6 +17,7 @@ use super::objstr::{PyString, PyStringRef}; use super::objtype; use crate::obj::objtype::PyClassRef; +#[pyclass(__inside_vm)] #[derive(Debug)] pub struct PyInt { value: BigInt, @@ -95,7 +96,9 @@ impl_try_from_object_int!( (u64, to_u64), ); +#[pyimpl(__inside_vm)] impl PyInt { + #[pymethod(name = "__eq__")] fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value == *get_value(&other)) @@ -104,6 +107,7 @@ impl PyInt { } } + #[pymethod(name = "__ne__")] fn ne(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value != *get_value(&other)) @@ -112,6 +116,7 @@ impl PyInt { } } + #[pymethod(name = "__lt__")] fn lt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value < *get_value(&other)) @@ -120,6 +125,7 @@ impl PyInt { } } + #[pymethod(name = "__le__")] fn le(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value <= *get_value(&other)) @@ -128,6 +134,7 @@ impl PyInt { } } + #[pymethod(name = "__gt__")] fn gt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value > *get_value(&other)) @@ -136,6 +143,7 @@ impl PyInt { } } + #[pymethod(name = "__ge__")] fn ge(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_bool(self.value >= *get_value(&other)) @@ -144,6 +152,7 @@ impl PyInt { } } + #[pymethod(name = "__add__")] fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) + get_value(&other)) @@ -152,6 +161,7 @@ impl PyInt { } } + #[pymethod(name = "__sub__")] fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) - get_value(&other)) @@ -160,6 +170,7 @@ impl PyInt { } } + #[pymethod(name = "__rsub__")] fn rsub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int(get_value(&other) - (&self.value)) @@ -168,6 +179,7 @@ impl PyInt { } } + #[pymethod(name = "__mul__")] fn mul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) * get_value(&other)) @@ -176,6 +188,7 @@ impl PyInt { } } + #[pymethod(name = "__truediv__")] fn truediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { div_ints(vm, &self.value, &get_value(&other)) @@ -184,6 +197,7 @@ impl PyInt { } } + #[pymethod(name = "__rtruediv__")] fn rtruediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { div_ints(vm, &get_value(&other), &self.value) @@ -192,6 +206,7 @@ impl PyInt { } } + #[pymethod(name = "__floordiv__")] fn floordiv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other); @@ -205,6 +220,7 @@ impl PyInt { } } + #[pymethod(name = "__lshift__")] fn lshift(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if !objtype::isinstance(&other, &vm.ctx.int_type()) { return Ok(vm.ctx.not_implemented()); @@ -224,6 +240,7 @@ impl PyInt { } } + #[pymethod(name = "__rshift__")] fn rshift(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if !objtype::isinstance(&other, &vm.ctx.int_type()) { return Ok(vm.ctx.not_implemented()); @@ -243,6 +260,7 @@ impl PyInt { } } + #[pymethod(name = "__xor__")] fn xor(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) ^ get_value(&other)) @@ -251,6 +269,7 @@ impl PyInt { } } + #[pymethod(name = "__rxor__")] fn rxor(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int(get_value(&other) ^ (&self.value)) @@ -259,6 +278,7 @@ impl PyInt { } } + #[pymethod(name = "__or__")] fn or(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) | get_value(&other)) @@ -267,6 +287,7 @@ impl PyInt { } } + #[pymethod(name = "__and__")] fn and(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other); @@ -276,6 +297,7 @@ impl PyInt { } } + #[pymethod(name = "__pow__")] fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other).to_u32().unwrap(); @@ -288,6 +310,7 @@ impl PyInt { } } + #[pymethod(name = "__mod__")] fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other); @@ -301,6 +324,7 @@ impl PyInt { } } + #[pymethod(name = "__divmod__")] fn divmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other); @@ -317,20 +341,24 @@ impl PyInt { } } + #[pymethod(name = "__neg__")] fn neg(&self, _vm: &VirtualMachine) -> BigInt { -(&self.value) } + #[pymethod(name = "__hash__")] fn hash(&self, _vm: &VirtualMachine) -> u64 { let mut hasher = std::collections::hash_map::DefaultHasher::new(); self.value.hash(&mut hasher); hasher.finish() } + #[pymethod(name = "__abs__")] fn abs(&self, _vm: &VirtualMachine) -> BigInt { self.value.abs() } + #[pymethod(name = "__round__")] fn round( zelf: PyRef, _precision: OptionalArg, @@ -339,14 +367,17 @@ impl PyInt { zelf } + #[pymethod(name = "__int__")] fn int(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { zelf } + #[pymethod(name = "__pos__")] fn pos(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { zelf } + #[pymethod(name = "__float__")] fn float(&self, vm: &VirtualMachine) -> PyResult { self.value .to_f64() @@ -354,30 +385,37 @@ impl PyInt { .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string())) } + #[pymethod(name = "__trunc__")] fn trunc(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { zelf } + #[pymethod(name = "__floor__")] fn floor(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { zelf } + #[pymethod(name = "__ceil__")] fn ceil(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { zelf } + #[pymethod(name = "__index__")] fn index(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { zelf } + #[pymethod(name = "__invert__")] fn invert(&self, _vm: &VirtualMachine) -> BigInt { !(&self.value) } + #[pymethod(name = "__repr__")] fn repr(&self, _vm: &VirtualMachine) -> String { self.value.to_string() } + #[pymethod(name = "__format__")] fn format(&self, spec: PyStringRef, vm: &VirtualMachine) -> PyResult { let format_spec = FormatSpec::parse(&spec.value); match format_spec.format_int(&self.value) { @@ -386,22 +424,27 @@ impl PyInt { } } + #[pymethod(name = "__bool__")] fn bool(&self, _vm: &VirtualMachine) -> bool { !self.value.is_zero() } + #[pymethod] fn bit_length(&self, _vm: &VirtualMachine) -> usize { self.value.bits() } + #[pymethod] fn conjugate(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { zelf } + #[pyproperty] fn real(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { zelf } + #[pyproperty] fn imag(&self, _vm: &VirtualMachine) -> usize { 0 } From dd4539b1a601f33ac77b1fdfdc74b909423dff73 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 10 Apr 2019 18:02:30 -0500 Subject: [PATCH 265/884] Change to use PyClassImpl::extend_class --- vm/src/obj/objint.rs | 80 ++++++++++---------------------------------- 1 file changed, 18 insertions(+), 62 deletions(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 789d140d66..a519082098 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -8,7 +8,8 @@ use num_traits::{Pow, Signed, ToPrimitive, Zero}; use crate::format::FormatSpec; use crate::function::OptionalArg; use crate::pyobject::{ - IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, + IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, + TypeProtocol, }; use crate::vm::VirtualMachine; @@ -17,6 +18,20 @@ use super::objstr::{PyString, PyStringRef}; use super::objtype; use crate::obj::objtype::PyClassRef; +/// int(x=0) -> integer +/// int(x, base=10) -> integer +/// +/// Convert a number or string to an integer, or return 0 if no arguments +/// are given. If x is a number, return x.__int__(). For floating point +/// numbers, this truncates towards zero. +/// +/// If x is not a number or if base is given, then x must be a string, +/// bytes, or bytearray instance representing an integer literal in the +/// given base. The literal can be preceded by '+' or '-' and be surrounded +/// by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. +/// Base 0 means to interpret the base from the string as an integer literal. +/// >>> int('0b100', base=0) +/// 4 #[pyclass(__inside_vm)] #[derive(Debug)] pub struct PyInt { @@ -543,68 +558,9 @@ fn div_ints(vm: &VirtualMachine, i1: &BigInt, i2: &BigInt) -> PyResult { } } -#[rustfmt::skip] // to avoid line splitting pub fn init(context: &PyContext) { - let int_doc = "int(x=0) -> integer -int(x, base=10) -> integer - -Convert a number or string to an integer, or return 0 if no arguments -are given. If x is a number, return x.__int__(). For floating point -numbers, this truncates towards zero. - -If x is not a number or if base is given, then x must be a string, -bytes, or bytearray instance representing an integer literal in the -given base. The literal can be preceded by '+' or '-' and be surrounded -by whitespace. The base defaults to 10. Valid bases are 0 and 2-36. -Base 0 means to interpret the base from the string as an integer literal. ->>> int('0b100', base=0) -4"; - let int_type = &context.int_type; - extend_class!(context, int_type, { - "__doc__" => context.new_str(int_doc.to_string()), - "__eq__" => context.new_rustfunc(PyInt::eq), - "__ne__" => context.new_rustfunc(PyInt::ne), - "__lt__" => context.new_rustfunc(PyInt::lt), - "__le__" => context.new_rustfunc(PyInt::le), - "__gt__" => context.new_rustfunc(PyInt::gt), - "__ge__" => context.new_rustfunc(PyInt::ge), - "__abs__" => context.new_rustfunc(PyInt::abs), - "__add__" => context.new_rustfunc(PyInt::add), - "__radd__" => context.new_rustfunc(PyInt::add), - "__and__" => context.new_rustfunc(PyInt::and), - "__divmod__" => context.new_rustfunc(PyInt::divmod), - "__float__" => context.new_rustfunc(PyInt::float), - "__round__" => context.new_rustfunc(PyInt::round), - "__ceil__" => context.new_rustfunc(PyInt::ceil), - "__floor__" => context.new_rustfunc(PyInt::floor), - "__index__" => context.new_rustfunc(PyInt::index), - "__trunc__" => context.new_rustfunc(PyInt::trunc), - "__int__" => context.new_rustfunc(PyInt::int), - "__floordiv__" => context.new_rustfunc(PyInt::floordiv), - "__hash__" => context.new_rustfunc(PyInt::hash), - "__lshift__" => context.new_rustfunc(PyInt::lshift), - "__rshift__" => context.new_rustfunc(PyInt::rshift), + PyInt::extend_class(context, &context.int_type); + extend_class!(context, &context.int_type, { "__new__" => context.new_rustfunc(int_new), - "__mod__" => context.new_rustfunc(PyInt::mod_), - "__mul__" => context.new_rustfunc(PyInt::mul), - "__rmul__" => context.new_rustfunc(PyInt::mul), - "__or__" => context.new_rustfunc(PyInt::or), - "__neg__" => context.new_rustfunc(PyInt::neg), - "__pos__" => context.new_rustfunc(PyInt::pos), - "__pow__" => context.new_rustfunc(PyInt::pow), - "__repr__" => context.new_rustfunc(PyInt::repr), - "__sub__" => context.new_rustfunc(PyInt::sub), - "__rsub__" => context.new_rustfunc(PyInt::rsub), - "__format__" => context.new_rustfunc(PyInt::format), - "__truediv__" => context.new_rustfunc(PyInt::truediv), - "__rtruediv__" => context.new_rustfunc(PyInt::rtruediv), - "__xor__" => context.new_rustfunc(PyInt::xor), - "__rxor__" => context.new_rustfunc(PyInt::rxor), - "__bool__" => context.new_rustfunc(PyInt::bool), - "__invert__" => context.new_rustfunc(PyInt::invert), - "bit_length" => context.new_rustfunc(PyInt::bit_length), - "conjugate" => context.new_rustfunc(PyInt::conjugate), - "real" => context.new_property(PyInt::real), - "imag" => context.new_property(PyInt::imag) }); } From 6a190e39f348db73c76fc875fcdedb9211659514 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 10 Apr 2019 20:41:06 -0500 Subject: [PATCH 266/884] Fix __radd__ and __rmul__ --- vm/src/obj/objint.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index a519082098..5be50f6c4e 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -176,6 +176,11 @@ impl PyInt { } } + #[pymethod(name = "__radd__")] + fn radd(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + self.add(other, vm) + } + #[pymethod(name = "__sub__")] fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { @@ -203,6 +208,11 @@ impl PyInt { } } + #[pymethod(name = "__rmul__")] + fn rmul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + self.mul(other, vm) + } + #[pymethod(name = "__truediv__")] fn truediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { From bd5772d9142f8bbd93e65a6ca4ec4cb214bbc4ac Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Tue, 9 Apr 2019 12:03:02 +0100 Subject: [PATCH 267/884] Implement dict.__eq__ --- tests/snippets/dict.py | 26 ++++++++++++++------------ vm/src/obj/objdict.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 45c6c3b430..6507efb19d 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -1,21 +1,23 @@ from testutils import assertRaises -def dict_eq(d1, d2): - return (all(k in d2 and d1[k] == d2[k] for k in d1) - and all(k in d1 and d1[k] == d2[k] for k in d2)) +assert dict(a=2, b=3) == {'a': 2, 'b': 3} +assert dict({'a': 2, 'b': 3}, b=4) == {'a': 2, 'b': 4} +assert dict([('a', 2), ('b', 3)]) == {'a': 2, 'b': 3} - -assert dict_eq(dict(a=2, b=3), {'a': 2, 'b': 3}) -assert dict_eq(dict({'a': 2, 'b': 3}, b=4), {'a': 2, 'b': 4}) -assert dict_eq(dict([('a', 2), ('b', 3)]), {'a': 2, 'b': 3}) +assert {} == {} +assert not {'a': 2} == {} +assert not {} == {'a': 2} +assert not {'b': 2} == {'a': 2} +assert not {'a': 4} == {'a': 2} +assert {'a': 2} == {'a': 2} a = {'g': 5} b = {'a': a, 'd': 9} c = dict(b) c['d'] = 3 c['a']['g'] = 2 -assert dict_eq(a, {'g': 2}) -assert dict_eq(b, {'a': a, 'd': 9}) +assert a == {'g': 2} +assert b == {'a': a, 'd': 9} a.clear() assert len(a) == 0 @@ -142,10 +144,10 @@ def __eq__(self, other): y = x.copy() x['c'] = 12 -assert dict_eq(y, {'a': 2, 'b': 10}) +assert y == {'a': 2, 'b': 10} y.update({'c': 19, "d": -1, 'b': 12}) -assert dict_eq(y, {'a': 2, 'b': 12, 'c': 19, 'd': -1}) +assert y == {'a': 2, 'b': 12, 'c': 19, 'd': -1} y.update(y) -assert dict_eq(y, {'a': 2, 'b': 12, 'c': 19, 'd': -1}) # hasn't changed +assert y == {'a': 2, 'b': 12, 'c': 19, 'd': -1} # hasn't changed diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 4a503fdcff..8e34ca738e 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -7,6 +7,7 @@ use crate::pyobject::{ }; use crate::vm::{ReprGuard, VirtualMachine}; +use super::objbool; use super::objiter; use super::objstr; use crate::dictdatatype; @@ -96,6 +97,35 @@ impl PyDictRef { !self.entries.borrow().is_empty() } + fn inner_eq(self, other: &PyDict, vm: &VirtualMachine) -> PyResult { + if other.entries.borrow().len() != self.entries.borrow().len() { + return Ok(false); + } + for (k, v1) in self { + match other.entries.borrow().get(vm, &k)? { + Some(v2) => { + let value = objbool::boolval(vm, vm._eq(v1, v2)?)?; + if !value { + return Ok(false); + } + } + None => { + return Ok(false); + } + } + } + return Ok(true); + } + + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(other) = other.payload::() { + let eq = self.inner_eq(other, vm)?; + Ok(vm.ctx.new_bool(eq)) + } else { + Ok(vm.ctx.not_implemented()) + } + } + fn len(self, _vm: &VirtualMachine) -> usize { self.entries.borrow().len() } @@ -387,6 +417,7 @@ pub fn init(context: &PyContext) { "__len__" => context.new_rustfunc(PyDictRef::len), "__contains__" => context.new_rustfunc(PyDictRef::contains), "__delitem__" => context.new_rustfunc(PyDictRef::inner_delitem), + "__eq__" => context.new_rustfunc(PyDictRef::eq), "__getitem__" => context.new_rustfunc(PyDictRef::inner_getitem), "__iter__" => context.new_rustfunc(PyDictRef::iter), "__new__" => context.new_rustfunc(PyDictRef::new), From b70f989952f99b535750e34b904f0ea88d32ab7d Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 11 Apr 2019 08:39:03 +0100 Subject: [PATCH 268/884] dict equality - objects always equal themselves. --- tests/snippets/dict.py | 3 +++ vm/src/obj/objdict.rs | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 6507efb19d..4c35af2f44 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -11,6 +11,9 @@ assert not {'a': 4} == {'a': 2} assert {'a': 2} == {'a': 2} +nan = float('nan') +assert {'a': nan} == {'a': nan} + a = {'g': 5} b = {'a': a, 'd': 9} c = dict(b) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 8e34ca738e..be85c7b2fb 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -3,7 +3,8 @@ use std::fmt; use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ - IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + IdProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, + PyValue, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -104,6 +105,9 @@ impl PyDictRef { for (k, v1) in self { match other.entries.borrow().get(vm, &k)? { Some(v2) => { + if v1.is(&v2) { + continue; + } let value = objbool::boolval(vm, vm._eq(v1, v2)?)?; if !value { return Ok(false); From 84eff4a7ac8a0010f6ee151637b1f5a90da4d566 Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 10 Apr 2019 20:56:40 +1200 Subject: [PATCH 269/884] Add __name__ and __module__ to functions --- tests/snippets/class.py | 8 ++++++++ tests/snippets/function.py | 17 +++++++++++++++++ vm/src/frame.rs | 10 +++++++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/tests/snippets/class.py b/tests/snippets/class.py index 9dbce566dc..0acd38fce5 100644 --- a/tests/snippets/class.py +++ b/tests/snippets/class.py @@ -1,3 +1,6 @@ +__name__ = "class" + + class Foo: def __init__(self, x): assert x == 5 @@ -14,6 +17,11 @@ def square(self): assert foo.y == Foo.y assert foo.x == 5 assert foo.square() == 25 +assert Foo.__name__ == "Foo" +assert Foo.__qualname__ == "Foo" +assert Foo.__module__ == "class" +assert Foo.square.__name__ == "square" +assert Foo.square.__qualname__ == "Foo.square" class Bar: diff --git a/tests/snippets/function.py b/tests/snippets/function.py index e4d0ab544a..b499c6776b 100644 --- a/tests/snippets/function.py +++ b/tests/snippets/function.py @@ -1,9 +1,16 @@ + +__name__ = "function" + + def foo(): """test""" return 42 assert foo() == 42 assert foo.__doc__ == "test" +assert foo.__name__ == "foo" +assert foo.__qualname__ == "foo" +assert foo.__module__ == "function" def my_func(a,): return a+2 @@ -55,3 +62,13 @@ def f5(): assert f5.__doc__ == 'abcw00t', f5.__doc__ + +def f6(): + def nested(): + pass + + assert nested.__name__ == "nested" + assert nested.__qualname__ == "f6..nested" + + +f6() diff --git a/vm/src/frame.rs b/vm/src/frame.rs index ff9baba358..d614691c93 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -566,7 +566,7 @@ impl Frame { } } bytecode::Instruction::MakeFunction { flags } => { - let _qualified_name = self.pop_value(); + let qualified_name = self.pop_value(); let code_obj = self .pop_value() .downcast() @@ -606,6 +606,14 @@ impl Frame { .ctx .new_function(code_obj, scope, defaults, kw_only_defaults); + vm.set_attr(&obj, "__name__", qualified_name.clone())?; + vm.set_attr(&obj, "__qualname__", qualified_name)?; + let module = self + .scope + .globals + .get_item_option("__name__", vm)? + .unwrap_or_else(|| vm.get_none()); + vm.set_attr(&obj, "__module__", module)?; vm.set_attr(&obj, "__annotations__", annotations)?; self.push_value(obj); From 2164cb5e65946c1eda326fdc6a5b8ba7817e917a Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 11 Apr 2019 20:11:26 +1200 Subject: [PATCH 270/884] Produce correct value for __qualname__ and add attributes to class. --- tests/snippets/class.py | 2 +- vm/src/builtins.rs | 28 +++++++++++++++++++--------- vm/src/compile.rs | 31 +++++++++++++++++++++++++++++-- vm/src/frame.rs | 14 ++++++++------ vm/src/function.rs | 14 ++++++++++++++ 5 files changed, 71 insertions(+), 18 deletions(-) diff --git a/tests/snippets/class.py b/tests/snippets/class.py index 0acd38fce5..ded02dbfac 100644 --- a/tests/snippets/class.py +++ b/tests/snippets/class.py @@ -22,7 +22,7 @@ def square(self): assert Foo.__module__ == "class" assert Foo.square.__name__ == "square" assert Foo.square.__qualname__ == "Foo.square" - +assert Foo.square.__module__ == "class" class Bar: """ W00t """ diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 27e024aaf2..6a94b83628 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -18,7 +18,7 @@ use crate::obj::objstr::{self, PyString, PyStringRef}; use crate::obj::objtype::{self, PyClassRef}; use crate::frame::Scope; -use crate::function::{Args, OptionalArg, PyFuncArgs}; +use crate::function::{Args, KwArgs, OptionalArg, PyFuncArgs}; use crate::pyobject::{ IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, @@ -783,11 +783,17 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { }); } -pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResult { - let function = args.shift(); - let name_arg = args.shift(); - let bases = args.args.clone(); - let mut metaclass = if let Some(metaclass) = args.get_optional_kwarg("metaclass") { +pub fn builtin_build_class_( + function: PyObjectRef, + qualified_name: PyStringRef, + bases: Args, + mut kwargs: KwArgs, + vm: &VirtualMachine, +) -> PyResult { + let name = qualified_name.value.split('.').next_back().unwrap(); + let name_obj = vm.new_str(name.to_string()); + + let mut metaclass = if let Some(metaclass) = kwargs.pop_kwarg("metaclass") { PyClassRef::try_from_object(vm, metaclass)? } else { vm.get_type() @@ -801,21 +807,25 @@ pub fn builtin_build_class_(vm: &VirtualMachine, mut args: PyFuncArgs) -> PyResu } } - let bases = vm.context().new_tuple(bases); + let bases = bases.into_tuple(vm); // Prepare uses full __getattribute__ resolution chain. let prepare = vm.get_attribute(metaclass.clone().into_object(), "__prepare__")?; - let namespace = vm.invoke(prepare, vec![name_arg.clone(), bases.clone()])?; + let namespace = vm.invoke(prepare, vec![name_obj.clone(), bases.clone()])?; let namespace: PyDictRef = TryFromObject::try_from_object(vm, namespace)?; let cells = vm.ctx.new_dict(); vm.invoke_with_locals(function, cells.clone(), namespace.clone())?; + + namespace.set_item("__name__", name_obj.clone(), vm)?; + namespace.set_item("__qualname__", qualified_name.into_object(), vm)?; + let class = vm.call_method( metaclass.as_object(), "__call__", - vec![name_arg, bases, namespace.into_object()], + vec![name_obj, bases, namespace.into_object()], )?; cells.set_item("__class__", class.clone(), vm)?; Ok(class) diff --git a/vm/src/compile.rs b/vm/src/compile.rs index dd045c3bf9..d5a4b48ecb 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -19,6 +19,7 @@ struct Compiler { nxt_label: usize, source_path: Option, current_source_location: ast::Location, + current_qualified_path: Option, in_loop: bool, in_function_def: bool, } @@ -75,6 +76,7 @@ impl Compiler { nxt_label: 0, source_path: None, current_source_location: ast::Location::default(), + current_qualified_path: None, in_loop: false, in_function_def: false, } @@ -610,6 +612,10 @@ impl Compiler { self.in_loop = false; self.in_function_def = true; + let old_qualified_path = self.current_qualified_path.clone(); + let qualified_name = self.create_qualified_name(name, ""); + self.current_qualified_path = Some(self.create_qualified_name(name, ".")); + self.prepare_decorators(decorator_list)?; let mut flags = self.enter_function(name, args)?; @@ -668,7 +674,7 @@ impl Compiler { }); self.emit(Instruction::LoadConst { value: bytecode::Constant::String { - value: name.to_string(), + value: qualified_name, }, }); @@ -681,6 +687,7 @@ impl Compiler { name: name.to_string(), }); + self.current_qualified_path = old_qualified_path; self.in_loop = was_in_loop; self.in_function_def = was_in_function_def; Ok(()) @@ -696,6 +703,11 @@ impl Compiler { ) -> Result<(), CompileError> { let was_in_loop = self.in_loop; self.in_loop = false; + + let old_qualified_path = self.current_qualified_path.clone(); + let qualified_name = self.create_qualified_name(name, ""); + self.current_qualified_path = Some(qualified_name.clone()); + self.prepare_decorators(decorator_list)?; self.emit(Instruction::LoadBuildClass); let line_number = self.get_source_line_number(); @@ -711,6 +723,12 @@ impl Compiler { let (new_body, doc_str) = get_doc(body); + self.emit(Instruction::LoadName { + name: "__name__".to_string(), + }); + self.emit(Instruction::StoreName { + name: "__module__".to_string(), + }); self.compile_statements(new_body)?; self.emit(Instruction::LoadConst { value: bytecode::Constant::None, @@ -737,7 +755,7 @@ impl Compiler { self.emit(Instruction::LoadConst { value: bytecode::Constant::String { - value: name.to_string(), + value: qualified_name, }, }); @@ -779,6 +797,7 @@ impl Compiler { self.emit(Instruction::StoreName { name: name.to_string(), }); + self.current_qualified_path = old_qualified_path; self.in_loop = was_in_loop; Ok(()) } @@ -1604,6 +1623,14 @@ impl Compiler { self.current_source_location.get_row() } + fn create_qualified_name(&self, name: &str, suffix: &str) -> String { + if let Some(ref qualified_path) = self.current_qualified_path { + format!("{}.{}{}", qualified_path, name, suffix) + } else { + format!("{}{}", name, suffix) + } + } + fn mark_generator(&mut self) { self.current_code_object().is_generator = true; } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index d614691c93..6fde05a590 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -10,7 +10,6 @@ use crate::builtins; use crate::bytecode; use crate::function::PyFuncArgs; use crate::obj::objbool; -use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode::PyCodeRef; use crate::obj::objdict::{PyDict, PyDictRef}; use crate::obj::objint::PyInt; @@ -18,6 +17,7 @@ use crate::obj::objiter; use crate::obj::objlist; use crate::obj::objslice::PySlice; use crate::obj::objstr; +use crate::obj::objstr::PyString; use crate::obj::objtuple::PyTuple; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; @@ -566,7 +566,10 @@ impl Frame { } } bytecode::Instruction::MakeFunction { flags } => { - let qualified_name = self.pop_value(); + let qualified_name = self + .pop_value() + .downcast::() + .expect("qualified name to be a string"); let code_obj = self .pop_value() .downcast() @@ -606,7 +609,8 @@ impl Frame { .ctx .new_function(code_obj, scope, defaults, kw_only_defaults); - vm.set_attr(&obj, "__name__", qualified_name.clone())?; + let name = qualified_name.value.split('.').next_back().unwrap(); + vm.set_attr(&obj, "__name__", vm.new_str(name.to_string()))?; vm.set_attr(&obj, "__qualname__", qualified_name)?; let module = self .scope @@ -747,9 +751,7 @@ impl Frame { Ok(None) } bytecode::Instruction::LoadBuildClass => { - let rustfunc = - PyBuiltinFunction::new(Box::new(builtins::builtin_build_class_)).into_ref(vm); - self.push_value(rustfunc.into_object()); + self.push_value(vm.ctx.new_rustfunc(builtins::builtin_build_class_)); Ok(None) } bytecode::Instruction::UnpackSequence { size } => { diff --git a/vm/src/function.rs b/vm/src/function.rs index 305c4de5ea..705a172cb8 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -244,6 +244,12 @@ pub trait FromArgs: Sized { /// an appropriate FromArgs implementation must be created. pub struct KwArgs(HashMap); +impl KwArgs { + pub fn pop_kwarg(&mut self, name: &str) -> Option { + self.0.remove(name) + } +} + impl FromArgs for KwArgs where T: TryFromObject, @@ -274,8 +280,16 @@ impl IntoIterator for KwArgs { /// /// `Args` optionally accepts a generic type parameter to allow type checks /// or conversions of each argument. +#[derive(Clone)] pub struct Args(Vec); +impl Args> { + pub fn into_tuple(self, vm: &VirtualMachine) -> PyObjectRef { + vm.ctx + .new_tuple(self.0.into_iter().map(|obj| obj.into_object()).collect()) + } +} + impl FromArgs for Args where T: TryFromObject, From 664554bc5e0bf38287dd2bbe12ce1f509c03c359 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 11 Apr 2019 15:21:56 +0100 Subject: [PATCH 271/884] Fix dir bug - not including object attributes correctly. --- tests/snippets/builtin_dir.py | 3 +++ vm/src/obj/objobject.rs | 38 ++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/tests/snippets/builtin_dir.py b/tests/snippets/builtin_dir.py index a121fb718c..b5fbf8d0b1 100644 --- a/tests/snippets/builtin_dir.py +++ b/tests/snippets/builtin_dir.py @@ -8,6 +8,9 @@ def test(): assert "test" in dir(a), "test not in a" assert "test" in dir(A), "test not in A" +a.x = 3 +assert "x" in dir(a), "x not in a" + class B(A): def __dir__(self): return ('q', 'h') diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 2be4ff4f45..8f6c90a36f 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -1,6 +1,6 @@ use super::objdict::PyDictRef; use super::objlist::PyList; -use super::objstr::PyStringRef; +use super::objstr::{PyString, PyStringRef}; use super::objtype; use crate::function::PyFuncArgs; use crate::obj::objproperty::PropertyBuilder; @@ -117,13 +117,29 @@ fn object_repr(zelf: PyObjectRef, _vm: &VirtualMachine) -> String { format!("<{} object at 0x{:x}>", zelf.class().name, zelf.get_id()) } -pub fn object_dir(obj: PyObjectRef, vm: &VirtualMachine) -> PyList { - let attributes = get_attributes(&obj); +pub fn object_dir(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let mut attributes: PyAttributes = objtype::get_attributes(obj.class()); + + // Get instance attributes: + if let Some(dict) = &obj.dict { + for (key, value) in dict { + if let Some(key_string) = key.payload::() { + attributes.insert(key_string.to_string(), value.clone()); + } else { + return Err(vm.new_type_error(format!( + "Attribute is not a string: {:?}", + vm.to_pystr(&key)? + ))); + } + } + } + let attributes: Vec = attributes .keys() .map(|k| vm.ctx.new_str(k.to_string())) .collect(); - PyList::from(attributes) + + Ok(PyList::from(attributes)) } fn object_format( @@ -230,17 +246,3 @@ fn object_getattr( Ok(None) } } - -pub fn get_attributes(obj: &PyObjectRef) -> PyAttributes { - // Get class attributes: - let mut attributes = objtype::get_attributes(obj.class()); - - // Get instance attributes: - if let Some(dict) = &obj.dict { - for (key, value) in dict { - attributes.insert(key.to_string(), value.clone()); - } - } - - attributes -} From aa38a1aa51913f74eeeea4089268650495db23f3 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Wed, 10 Apr 2019 10:59:04 +0100 Subject: [PATCH 272/884] Start refactor of ast. --- vm/src/stdlib/ast.rs | 604 +++++++++++++++---------------------------- 1 file changed, 208 insertions(+), 396 deletions(-) diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index e7ac5308cd..67c58a15db 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -10,14 +10,15 @@ use num_complex::Complex64; use rustpython_parser::{ast, parser}; use crate::function::PyFuncArgs; +use crate::obj::objlist::PyListRef; use crate::obj::objstr; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyObject, PyObjectRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; #[derive(Debug)] struct AstNode; -// type AstNodeRef = PyRef; +type AstNodeRef = PyRef; impl PyValue for AstNode { fn class(vm: &VirtualMachine) -> PyClassRef { @@ -41,32 +42,37 @@ impl AstToPyAst { } */ -fn program_to_ast(vm: &VirtualMachine, program: &ast::Program) -> PyObjectRef { - let mut body = vec![]; - for statement in &program.statements { - body.push(statement_to_ast(&vm, statement)); +macro_rules! node { + ( $vm: expr, $node_name:ident, { $($attr_name:ident => $attr_value:expr),* $(,)* }) => { + { + let node = create_node($vm, stringify!($node_name)); + $( + $vm.set_attr(&node, stringify!($attr_name), $attr_value).unwrap(); + )* + node + } + }; + ( $vm: expr, $node_name:ident) => { + create_node($vm, stringify!($node_name)) } - // TODO: create Module node: - // let ast_node = ctx.new_instance(this.Module); - let ast_node = create_node(vm, "program"); - let py_body = vm.ctx.new_list(body); - vm.set_attr(&ast_node, "body", py_body).unwrap(); - ast_node +} + +fn program_to_ast(vm: &VirtualMachine, program: &ast::Program) -> PyObjectRef { + let py_body = statements_to_ast(vm, &program.statements); + node!(vm, Module, { body => py_body }) } // Create a node class instance -fn create_node(vm: &VirtualMachine, _name: &str) -> PyObjectRef { - // TODO: instantiate a class of type given by name - // TODO: lookup in the current module? - PyObject::new(AstNode, AstNode::class(vm), Some(vm.ctx.new_dict())) +fn create_node(vm: &VirtualMachine, name: &str) -> PyObjectRef { + PyObject::new(AstNode, vm.class("ast", name), Some(vm.ctx.new_dict())) } -fn statements_to_ast(vm: &VirtualMachine, statements: &[ast::LocatedStatement]) -> PyObjectRef { - let mut py_statements = vec![]; - for statement in statements { - py_statements.push(statement_to_ast(vm, statement)); - } - vm.ctx.new_list(py_statements) +fn statements_to_ast(vm: &VirtualMachine, statements: &[ast::LocatedStatement]) -> PyListRef { + let body = statements + .iter() + .map(|statement| statement_to_ast(&vm, statement)) + .collect(); + vm.ctx.new_list(body).downcast().unwrap() } fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> PyObjectRef { @@ -76,22 +82,11 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P body, decorator_list, .. - } => { - let node = create_node(vm, "ClassDef"); - - // Set name: - vm.set_attr(&node, "name", vm.ctx.new_str(name.to_string())) - .unwrap(); - - // Set body: - let py_body = statements_to_ast(vm, body); - vm.set_attr(&node, "body", py_body).unwrap(); - - let py_decorator_list = expressions_to_ast(vm, decorator_list); - vm.set_attr(&node, "decorator_list", py_decorator_list) - .unwrap(); - node - } + } => node!(vm, ClassDef, { + name => vm.ctx.new_str(name.to_string()), + body => statements_to_ast(vm, body), + decorator_list => expressions_to_ast(vm, decorator_list), + }), ast::Statement::FunctionDef { name, args, @@ -99,140 +94,86 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P decorator_list, returns, } => { - let node = create_node(vm, "FunctionDef"); - - // Set name: - vm.set_attr(&node, "name", vm.ctx.new_str(name.to_string())) - .unwrap(); - - vm.set_attr(&node, "args", parameters_to_ast(vm, args)) - .unwrap(); - - // Set body: - let py_body = statements_to_ast(vm, body); - vm.set_attr(&node, "body", py_body).unwrap(); - - let py_decorator_list = expressions_to_ast(vm, decorator_list); - vm.set_attr(&node, "decorator_list", py_decorator_list) - .unwrap(); - let py_returns = if let Some(hint) = returns { expression_to_ast(vm, hint) } else { vm.ctx.none() }; - vm.set_attr(&node, "returns", py_returns).unwrap(); - node - } - ast::Statement::Continue => create_node(vm, "Continue"), - ast::Statement::Break => create_node(vm, "Break"), - ast::Statement::Pass => create_node(vm, "Pass"), + node!(vm, FunctionDef, { + name => vm.ctx.new_str(name.to_string()), + args => parameters_to_ast(vm, args), + body => statements_to_ast(vm, body), + decorator_list => expressions_to_ast(vm, decorator_list), + returns => py_returns + }) + } + ast::Statement::Continue => node!(vm, Continue), + ast::Statement::Break => node!(vm, Break), + ast::Statement::Pass => node!(vm, Pass), ast::Statement::Assert { test, msg } => { - let node = create_node(vm, "Pass"); - - vm.set_attr(&node, "test", expression_to_ast(vm, test)) - .unwrap(); - let py_msg = match msg { Some(msg) => expression_to_ast(vm, msg), None => vm.ctx.none(), }; - vm.set_attr(&node, "msg", py_msg).unwrap(); - - node + node!(vm, Assert, { + test => expression_to_ast(vm, test), + msg => py_msg + }) } ast::Statement::Delete { targets } => { - let node = create_node(vm, "Delete"); - let py_targets = vm .ctx .new_tuple(targets.iter().map(|v| expression_to_ast(vm, v)).collect()); - vm.set_attr(&node, "targets", py_targets).unwrap(); - node + node!(vm, Delete, { targets => py_targets }) } ast::Statement::Return { value } => { - let node = create_node(vm, "Return"); - let py_value = if let Some(value) = value { expression_to_ast(vm, value) } else { vm.ctx.none() }; - vm.set_attr(&node, "value", py_value).unwrap(); - - node - } - ast::Statement::If { test, body, orelse } => { - let node = create_node(vm, "If"); - let py_test = expression_to_ast(vm, test); - vm.set_attr(&node, "test", py_test).unwrap(); - - let py_body = statements_to_ast(vm, body); - vm.set_attr(&node, "body", py_body).unwrap(); - - let py_orelse = if let Some(orelse) = orelse { - statements_to_ast(vm, orelse) - } else { - vm.ctx.none() - }; - vm.set_attr(&node, "orelse", py_orelse).unwrap(); - - node - } + node!(vm, Return, { + value => py_value + }) + } + ast::Statement::If { test, body, orelse } => node!(vm, If, { + test => expression_to_ast(vm, test), + body => statements_to_ast(vm, body), + orelse => if let Some(orelse) = orelse { + statements_to_ast(vm, orelse).into_object() + } else { + vm.ctx.none() + } + }), ast::Statement::For { target, iter, body, orelse, - } => { - let node = create_node(vm, "For"); - - let py_target = expression_to_ast(vm, target); - vm.set_attr(&node, "target", py_target).unwrap(); - - let py_iter = expression_to_ast(vm, iter); - vm.set_attr(&node, "iter", py_iter).unwrap(); - - let py_body = statements_to_ast(vm, body); - vm.set_attr(&node, "body", py_body).unwrap(); - - let py_orelse = if let Some(orelse) = orelse { - statements_to_ast(vm, orelse) + } => node!(vm, For, { + target => expression_to_ast(vm, target), + iter => expression_to_ast(vm, iter), + body => statements_to_ast(vm, body), + or_else => if let Some(orelse) = orelse { + statements_to_ast(vm, orelse).into_object() } else { vm.ctx.none() - }; - vm.set_attr(&node, "orelse", py_orelse).unwrap(); - - node - } - ast::Statement::While { test, body, orelse } => { - let node = create_node(vm, "While"); - - let py_test = expression_to_ast(vm, test); - vm.set_attr(&node, "test", py_test).unwrap(); - - let py_body = statements_to_ast(vm, body); - vm.set_attr(&node, "body", py_body).unwrap(); - - let py_orelse = if let Some(orelse) = orelse { - statements_to_ast(vm, orelse) + } + }), + ast::Statement::While { test, body, orelse } => node!(vm, While, { + test => expression_to_ast(vm, test), + body => statements_to_ast(vm, body), + orelse => if let Some(orelse) = orelse { + statements_to_ast(vm, orelse).into_object() } else { vm.ctx.none() - }; - vm.set_attr(&node, "orelse", py_orelse).unwrap(); - - node - } - ast::Statement::Expression { expression } => { - let node = create_node(vm, "Expr"); - - let value = expression_to_ast(vm, expression); - vm.set_attr(&node, "value", value).unwrap(); - - node - } + } + }), + ast::Statement::Expression { expression } => node!(vm, Expr, { + value => expression_to_ast(vm, expression) + }), x => { unimplemented!("{:?}", x); } @@ -245,35 +186,23 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P node } -fn expressions_to_ast(vm: &VirtualMachine, expressions: &[ast::Expression]) -> PyObjectRef { - let mut py_expression_nodes = vec![]; - for expression in expressions { - py_expression_nodes.push(expression_to_ast(vm, expression)); - } - vm.ctx.new_list(py_expression_nodes) +fn expressions_to_ast(vm: &VirtualMachine, expressions: &[ast::Expression]) -> PyListRef { + let py_expression_nodes = expressions + .iter() + .map(|expression| expression_to_ast(vm, expression)) + .collect(); + vm.ctx.new_list(py_expression_nodes).downcast().unwrap() } fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObjectRef { let node = match &expression { - ast::Expression::Call { function, args, .. } => { - let node = create_node(vm, "Call"); - - let py_func_ast = expression_to_ast(vm, function); - vm.set_attr(&node, "func", py_func_ast).unwrap(); - - let py_args = expressions_to_ast(vm, args); - vm.set_attr(&node, "args", py_args).unwrap(); - - node - } + ast::Expression::Call { function, args, .. } => node!(vm, Call, { + func => expression_to_ast(vm, function), + args => expressions_to_ast(vm, args), + }), ast::Expression::Binop { a, op, b } => { - let node = create_node(vm, "BinOp"); - - let py_a = expression_to_ast(vm, a); - vm.set_attr(&node, "left", py_a).unwrap(); - // Operator: - let str_op = match op { + let op = match op { ast::Operator::Add => "Add", ast::Operator::Sub => "Sub", ast::Operator::Mult => "Mult", @@ -288,53 +217,43 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj ast::Operator::BitAnd => "BitAnd", ast::Operator::FloorDiv => "FloorDiv", }; - let py_op = vm.ctx.new_str(str_op.to_string()); - vm.set_attr(&node, "op", py_op).unwrap(); - - let py_b = expression_to_ast(vm, b); - vm.set_attr(&node, "right", py_b).unwrap(); - node + node!(vm, BinOp, { + left => expression_to_ast(vm, a), + op => vm.ctx.new_str(op.to_string()), + right => expression_to_ast(vm, b), + }) } ast::Expression::Unop { op, a } => { - let node = create_node(vm, "UnaryOp"); - - let str_op = match op { + let op = match op { ast::UnaryOperator::Not => "Not", ast::UnaryOperator::Inv => "Invert", ast::UnaryOperator::Neg => "USub", ast::UnaryOperator::Pos => "UAdd", }; - let py_op = vm.ctx.new_str(str_op.to_string()); - vm.set_attr(&node, "op", py_op).unwrap(); - - let py_a = expression_to_ast(vm, a); - vm.set_attr(&node, "operand", py_a).unwrap(); - - node + node!(vm, UnaryOp, { + op => vm.ctx.new_str(op.to_string()), + operand => expression_to_ast(vm, a), + }) } ast::Expression::BoolOp { a, op, b } => { - let node = create_node(vm, "BoolOp"); - // Attach values: let py_a = expression_to_ast(vm, a); let py_b = expression_to_ast(vm, b); let py_values = vm.ctx.new_tuple(vec![py_a, py_b]); - vm.set_attr(&node, "values", py_values).unwrap(); let str_op = match op { ast::BooleanOperator::And => "And", ast::BooleanOperator::Or => "Or", }; let py_op = vm.ctx.new_str(str_op.to_string()); - vm.set_attr(&node, "op", py_op).unwrap(); - node + node!(vm, BoolOp, { + op => py_op, + values => py_values, + }) } ast::Expression::Compare { vals, ops } => { - let node = create_node(vm, "Compare"); - let py_a = expression_to_ast(vm, &vals[0]); - vm.set_attr(&node, "left", py_a).unwrap(); // Operator: let to_operator = |op: &ast::Comparison| match op { @@ -355,53 +274,31 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj .collect(), ); - vm.set_attr(&node, "ops", py_ops).unwrap(); - let py_b = vm.ctx.new_list( vals.iter() .skip(1) .map(|x| expression_to_ast(vm, x)) .collect(), ); - vm.set_attr(&node, "comparators", py_b).unwrap(); - node - } - ast::Expression::Identifier { name } => { - let node = create_node(vm, "Identifier"); - - // Id: - let py_name = vm.ctx.new_str(name.clone()); - vm.set_attr(&node, "id", py_name).unwrap(); - node - } - ast::Expression::Lambda { args, body } => { - let node = create_node(vm, "Lambda"); - - vm.set_attr(&node, "args", parameters_to_ast(vm, args)) - .unwrap(); - - let py_body = expression_to_ast(vm, body); - vm.set_attr(&node, "body", py_body).unwrap(); - - node - } - ast::Expression::IfExpression { test, body, orelse } => { - let node = create_node(vm, "IfExp"); - - let py_test = expression_to_ast(vm, test); - vm.set_attr(&node, "test", py_test).unwrap(); - - let py_body = expression_to_ast(vm, body); - vm.set_attr(&node, "body", py_body).unwrap(); - - let py_orelse = expression_to_ast(vm, orelse); - vm.set_attr(&node, "orelse", py_orelse).unwrap(); - - node - } + node!(vm, Compare, { + left => py_a, + ops => py_ops, + comparators => py_b, + }) + } + ast::Expression::Identifier { name } => node!(vm, Identifier, { + id => vm.ctx.new_str(name.clone()) + }), + ast::Expression::Lambda { args, body } => node!(vm, Lambda, { + args => parameters_to_ast(vm, args), + body => expression_to_ast(vm, body), + }), + ast::Expression::IfExpression { test, body, orelse } => node!(vm, IfExp, { + text => expression_to_ast(vm, test), + body => expression_to_ast(vm, body), + or_else => expression_to_ast(vm, orelse), + }), ast::Expression::Number { value } => { - let node = create_node(vm, "Num"); - let py_n = match value { ast::Number::Integer { value } => vm.ctx.new_int(value.clone()), ast::Number::Float { value } => vm.ctx.new_float(*value), @@ -409,62 +306,30 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj vm.ctx.new_complex(Complex64::new(*real, *imag)) } }; - vm.set_attr(&node, "n", py_n).unwrap(); - - node - } - ast::Expression::True => { - let node = create_node(vm, "NameConstant"); - - vm.set_attr(&node, "value", vm.ctx.new_bool(true)).unwrap(); - - node - } - ast::Expression::False => { - let node = create_node(vm, "NameConstant"); - - vm.set_attr(&node, "value", vm.ctx.new_bool(false)).unwrap(); - - node - } - ast::Expression::None => { - let node = create_node(vm, "NameConstant"); - - vm.set_attr(&node, "value", vm.ctx.none()).unwrap(); - - node - } - ast::Expression::Ellipsis => create_node(vm, "Ellipsis"), - ast::Expression::List { elements } => { - let node = create_node(vm, "List"); - - let elts = elements.iter().map(|e| expression_to_ast(vm, e)).collect(); - let py_elts = vm.ctx.new_list(elts); - vm.set_attr(&node, "elts", py_elts).unwrap(); - - node - } - ast::Expression::Tuple { elements } => { - let node = create_node(vm, "Tuple"); - - let elts = elements.iter().map(|e| expression_to_ast(vm, e)).collect(); - let py_elts = vm.ctx.new_list(elts); - vm.set_attr(&node, "elts", py_elts).unwrap(); - - node - } - ast::Expression::Set { elements } => { - let node = create_node(vm, "Set"); - - let elts = elements.iter().map(|e| expression_to_ast(vm, e)).collect(); - let py_elts = vm.ctx.new_list(elts); - vm.set_attr(&node, "elts", py_elts).unwrap(); - - node - } + node!(vm, Num, { + n => py_n + }) + } + ast::Expression::True => node!(vm, NameConstant, { + value => vm.ctx.new_bool(true) + }), + ast::Expression::False => node!(vm, NameConstant, { + value => vm.ctx.new_bool(false) + }), + ast::Expression::None => node!(vm, NameConstant, { + value => vm.ctx.none() + }), + ast::Expression::Ellipsis => node!(vm, Ellipsis), + ast::Expression::List { elements } => node!(vm, List, { + elts => expressions_to_ast(vm, &elements) + }), + ast::Expression::Tuple { elements } => node!(vm, Tuple, { + elts => expressions_to_ast(vm, &elements) + }), + ast::Expression::Set { elements } => node!(vm, Set, { + elts => expressions_to_ast(vm, &elements) + }), ast::Expression::Dict { elements } => { - let node = create_node(vm, "Dict"); - let mut keys = Vec::new(); let mut values = Vec::new(); for (k, v) in elements { @@ -472,96 +337,65 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj values.push(expression_to_ast(vm, v)); } - let py_keys = vm.ctx.new_list(keys); - vm.set_attr(&node, "keys", py_keys).unwrap(); - - let py_values = vm.ctx.new_list(values); - vm.set_attr(&node, "values", py_values).unwrap(); - - node + node!(vm, Dict, { + keys => vm.ctx.new_list(keys), + values => vm.ctx.new_list(values), + }) } ast::Expression::Comprehension { kind, generators } => { - let node = match kind.deref() { - ast::ComprehensionKind::GeneratorExpression { .. } => { - create_node(vm, "GeneratorExp") - } - ast::ComprehensionKind::List { .. } => create_node(vm, "ListComp"), - ast::ComprehensionKind::Set { .. } => create_node(vm, "SetComp"), - ast::ComprehensionKind::Dict { .. } => create_node(vm, "DictComp"), - }; - let g = generators .iter() .map(|g| comprehension_to_ast(vm, g)) .collect(); let py_generators = vm.ctx.new_list(g); - vm.set_attr(&node, "generators", py_generators).unwrap(); - node + match kind.deref() { + ast::ComprehensionKind::GeneratorExpression { .. } => { + node!(vm, GeneratorExp, {generators => py_generators}) + } + ast::ComprehensionKind::List { .. } => { + node!(vm, ListComp, {generators => py_generators}) + } + ast::ComprehensionKind::Set { .. } => { + node!(vm, SetComp, {generators => py_generators}) + } + ast::ComprehensionKind::Dict { .. } => { + node!(vm, DictComp, {generators => py_generators}) + } + } } ast::Expression::Yield { value } => { - let node = create_node(vm, "Yield"); - let py_value = match value { Some(value) => expression_to_ast(vm, value), None => vm.ctx.none(), }; - vm.set_attr(&node, "value", py_value).unwrap(); - - node + node!(vm, Yield, { + value => py_value + }) } ast::Expression::YieldFrom { value } => { - let node = create_node(vm, "YieldFrom"); - - let py_value = expression_to_ast(vm, value); - vm.set_attr(&node, "value", py_value).unwrap(); - - node - } - ast::Expression::Subscript { a, b } => { - let node = create_node(vm, "Subscript"); - - let py_value = expression_to_ast(vm, a); - vm.set_attr(&node, "value", py_value).unwrap(); - - let py_slice = expression_to_ast(vm, b); - vm.set_attr(&node, "slice", py_slice).unwrap(); - - node - } - ast::Expression::Attribute { value, name } => { - let node = create_node(vm, "Attribute"); - - let py_value = expression_to_ast(vm, value); - vm.set_attr(&node, "value", py_value).unwrap(); - - let py_attr = vm.ctx.new_str(name.to_string()); - vm.set_attr(&node, "attr", py_attr).unwrap(); - - node - } - ast::Expression::Starred { value } => { - let node = create_node(vm, "Starred"); - let py_value = expression_to_ast(vm, value); - vm.set_attr(&node, "value", py_value).unwrap(); - - node - } - ast::Expression::Slice { elements } => { - let node = create_node(vm, "Slice"); - - let py_value = expressions_to_ast(vm, elements); - vm.set_attr(&node, "bounds", py_value).unwrap(); - - node - } + node!(vm, YieldFrom, { + value => py_value + }) + } + ast::Expression::Subscript { a, b } => node!(vm, Subscript, { + value => expression_to_ast(vm, a), + slice => expression_to_ast(vm, b), + }), + ast::Expression::Attribute { value, name } => node!(vm, Attribute, { + value => expression_to_ast(vm, value), + attr => vm.ctx.new_str(name.to_string()), + }), + ast::Expression::Starred { value } => node!(vm, Starred, { + value => expression_to_ast(vm, value) + }), + ast::Expression::Slice { elements } => node!(vm, Slice, { + bounds => expressions_to_ast(vm, elements) + }), ast::Expression::String { value } => string_to_ast(vm, value), ast::Expression::Bytes { value } => { - let node = create_node(vm, "Bytes"); - vm.set_attr(&node, "s", vm.ctx.new_bytes(value.clone())) - .unwrap(); - node + node!(vm, Bytes, { s => vm.ctx.new_bytes(value.clone()) }) } }; @@ -573,74 +407,50 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj } fn parameters_to_ast(vm: &VirtualMachine, args: &ast::Parameters) -> PyObjectRef { - let node = create_node(vm, "arguments"); - - vm.set_attr( - &node, - "args", - vm.ctx - .new_list(args.args.iter().map(|a| parameter_to_ast(vm, a)).collect()), - ) - .unwrap(); + let args = vm + .ctx + .new_list(args.args.iter().map(|a| parameter_to_ast(vm, a)).collect()); - node + node!(vm, arguments, { args => args }) } fn parameter_to_ast(vm: &VirtualMachine, parameter: &ast::Parameter) -> PyObjectRef { - let node = create_node(vm, "arg"); - - let py_arg = vm.ctx.new_str(parameter.arg.to_string()); - vm.set_attr(&node, "arg", py_arg).unwrap(); - let py_annotation = if let Some(annotation) = ¶meter.annotation { expression_to_ast(vm, annotation) } else { vm.ctx.none() }; - vm.set_attr(&node, "annotation", py_annotation).unwrap(); - node + node!(vm, arg, { + arg => vm.ctx.new_str(parameter.arg.to_string()), + annotation => py_annotation + }) } fn comprehension_to_ast(vm: &VirtualMachine, comprehension: &ast::Comprehension) -> PyObjectRef { - let node = create_node(vm, "comprehension"); - - let py_target = expression_to_ast(vm, &comprehension.target); - vm.set_attr(&node, "target", py_target).unwrap(); - - let py_iter = expression_to_ast(vm, &comprehension.iter); - vm.set_attr(&node, "iter", py_iter).unwrap(); - - let py_ifs = expressions_to_ast(vm, &comprehension.ifs); - vm.set_attr(&node, "ifs", py_ifs).unwrap(); - - node + node!(vm, comprehension, { + target => expression_to_ast(vm, &comprehension.target), + iter => expression_to_ast(vm, &comprehension.iter), + ifs => expressions_to_ast(vm, &comprehension.ifs), + }) } fn string_to_ast(vm: &VirtualMachine, string: &ast::StringGroup) -> PyObjectRef { match string { ast::StringGroup::Constant { value } => { - let node = create_node(vm, "Str"); - vm.set_attr(&node, "s", vm.ctx.new_str(value.clone())) - .unwrap(); - node + node!(vm, Str, { s => vm.ctx.new_str(value.clone()) }) } ast::StringGroup::FormattedValue { value, .. } => { - let node = create_node(vm, "FormattedValue"); - let py_value = expression_to_ast(vm, value); - vm.set_attr(&node, "value", py_value).unwrap(); - node + node!(vm, FormattedValue, { value => expression_to_ast(vm, value) }) } ast::StringGroup::Joined { values } => { - let node = create_node(vm, "JoinedStr"); let py_values = vm.ctx.new_list( values .iter() .map(|value| string_to_ast(vm, value)) .collect(), ); - vm.set_attr(&node, "values", py_values).unwrap(); - node + node!(vm, JoinedStr, { values => py_values }) } } } @@ -659,11 +469,13 @@ fn ast_parse(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; + let ast_base = py_class!(ctx, "_ast.AST", ctx.object(), {}); py_module!(vm, "ast", { "parse" => ctx.new_rustfunc(ast_parse), - "Module" => py_class!(ctx, "_ast.Module", ctx.object(), {}), - "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ctx.object(), {}), - "Call" => py_class!(ctx, "_ast.Call", ctx.object(), {}), - "AST" => py_class!(ctx, "_ast.AST", ctx.object(), {}), + "Module" => py_class!(ctx, "_ast.Module", ast_base.clone(), {}), + "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ast_base.clone(), {}), + "ClassDef" => py_class!(ctx, "_ast.ClassDef", ast_base.clone(), {}), + "Call" => py_class!(ctx, "_ast.Call", ast_base.clone(), {}), + "AST" => ast_base, }) } From d7e0ceec125f8101b9317f56781bd277878a3caa Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 11 Apr 2019 09:34:12 +0100 Subject: [PATCH 273/884] New (PyRef) style types. --- vm/src/stdlib/ast.rs | 104 +++++++++++++++++++------------------------ 1 file changed, 45 insertions(+), 59 deletions(-) diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 67c58a15db..518465fe6b 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -9,11 +9,10 @@ use num_complex::Complex64; use rustpython_parser::{ast, parser}; -use crate::function::PyFuncArgs; use crate::obj::objlist::PyListRef; -use crate::obj::objstr; +use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; #[derive(Debug)] @@ -26,28 +25,12 @@ impl PyValue for AstNode { } } -/* - * Idea: maybe we can create a sort of struct with some helper functions? -struct AstToPyAst { - ctx: &PyContext, -} - -impl AstToPyAst { - fn new(ctx: &PyContext) -> Self { - AstToPyAst { - ctx: ctx, - } - } - -} -*/ - macro_rules! node { ( $vm: expr, $node_name:ident, { $($attr_name:ident => $attr_value:expr),* $(,)* }) => { { let node = create_node($vm, stringify!($node_name)); $( - $vm.set_attr(&node, stringify!($attr_name), $attr_value).unwrap(); + $vm.set_attr(node.as_object(), stringify!($attr_name), $attr_value).unwrap(); )* node } @@ -57,25 +40,27 @@ macro_rules! node { } } -fn program_to_ast(vm: &VirtualMachine, program: &ast::Program) -> PyObjectRef { +fn program_to_ast(vm: &VirtualMachine, program: &ast::Program) -> AstNodeRef { let py_body = statements_to_ast(vm, &program.statements); node!(vm, Module, { body => py_body }) } // Create a node class instance -fn create_node(vm: &VirtualMachine, name: &str) -> PyObjectRef { - PyObject::new(AstNode, vm.class("ast", name), Some(vm.ctx.new_dict())) +fn create_node(vm: &VirtualMachine, name: &str) -> AstNodeRef { + AstNode + .into_ref_with_type(vm, vm.class("ast", name)) + .unwrap() } fn statements_to_ast(vm: &VirtualMachine, statements: &[ast::LocatedStatement]) -> PyListRef { let body = statements .iter() - .map(|statement| statement_to_ast(&vm, statement)) + .map(|statement| statement_to_ast(&vm, statement).into_object()) .collect(); vm.ctx.new_list(body).downcast().unwrap() } -fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> PyObjectRef { +fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> AstNodeRef { let node = match &statement.node { ast::Statement::ClassDef { name, @@ -95,7 +80,7 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P returns, } => { let py_returns = if let Some(hint) = returns { - expression_to_ast(vm, hint) + expression_to_ast(vm, hint).into_object() } else { vm.ctx.none() }; @@ -112,7 +97,7 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P ast::Statement::Pass => node!(vm, Pass), ast::Statement::Assert { test, msg } => { let py_msg = match msg { - Some(msg) => expression_to_ast(vm, msg), + Some(msg) => expression_to_ast(vm, msg).into_object(), None => vm.ctx.none(), }; node!(vm, Assert, { @@ -121,15 +106,18 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P }) } ast::Statement::Delete { targets } => { - let py_targets = vm - .ctx - .new_tuple(targets.iter().map(|v| expression_to_ast(vm, v)).collect()); + let py_targets = vm.ctx.new_tuple( + targets + .iter() + .map(|v| expression_to_ast(vm, v).into_object()) + .collect(), + ); node!(vm, Delete, { targets => py_targets }) } ast::Statement::Return { value } => { let py_value = if let Some(value) = value { - expression_to_ast(vm, value) + expression_to_ast(vm, value).into_object() } else { vm.ctx.none() }; @@ -181,7 +169,7 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P // set lineno on node: let lineno = vm.ctx.new_int(statement.location.get_row()); - vm.set_attr(&node, "lineno", lineno).unwrap(); + vm.set_attr(node.as_object(), "lineno", lineno).unwrap(); node } @@ -189,12 +177,12 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> P fn expressions_to_ast(vm: &VirtualMachine, expressions: &[ast::Expression]) -> PyListRef { let py_expression_nodes = expressions .iter() - .map(|expression| expression_to_ast(vm, expression)) + .map(|expression| expression_to_ast(vm, expression).into_object()) .collect(); vm.ctx.new_list(py_expression_nodes).downcast().unwrap() } -fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObjectRef { +fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> AstNodeRef { let node = match &expression { ast::Expression::Call { function, args, .. } => node!(vm, Call, { func => expression_to_ast(vm, function), @@ -237,8 +225,8 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj } ast::Expression::BoolOp { a, op, b } => { // Attach values: - let py_a = expression_to_ast(vm, a); - let py_b = expression_to_ast(vm, b); + let py_a = expression_to_ast(vm, a).into_object(); + let py_b = expression_to_ast(vm, b).into_object(); let py_values = vm.ctx.new_tuple(vec![py_a, py_b]); let str_op = match op { @@ -277,7 +265,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let py_b = vm.ctx.new_list( vals.iter() .skip(1) - .map(|x| expression_to_ast(vm, x)) + .map(|x| expression_to_ast(vm, x).into_object()) .collect(), ); node!(vm, Compare, { @@ -333,8 +321,8 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj let mut keys = Vec::new(); let mut values = Vec::new(); for (k, v) in elements { - keys.push(expression_to_ast(vm, k)); - values.push(expression_to_ast(vm, v)); + keys.push(expression_to_ast(vm, k).into_object()); + values.push(expression_to_ast(vm, v).into_object()); } node!(vm, Dict, { @@ -345,7 +333,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj ast::Expression::Comprehension { kind, generators } => { let g = generators .iter() - .map(|g| comprehension_to_ast(vm, g)) + .map(|g| comprehension_to_ast(vm, g).into_object()) .collect(); let py_generators = vm.ctx.new_list(g); @@ -366,7 +354,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj } ast::Expression::Yield { value } => { let py_value = match value { - Some(value) => expression_to_ast(vm, value), + Some(value) => expression_to_ast(vm, value).into_object(), None => vm.ctx.none(), }; node!(vm, Yield, { @@ -401,22 +389,24 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyObj // TODO: retrieve correct lineno: let lineno = vm.ctx.new_int(1); - vm.set_attr(&node, "lineno", lineno).unwrap(); - + vm.set_attr(node.as_object(), "lineno", lineno).unwrap(); node } -fn parameters_to_ast(vm: &VirtualMachine, args: &ast::Parameters) -> PyObjectRef { - let args = vm - .ctx - .new_list(args.args.iter().map(|a| parameter_to_ast(vm, a)).collect()); +fn parameters_to_ast(vm: &VirtualMachine, args: &ast::Parameters) -> AstNodeRef { + let args = vm.ctx.new_list( + args.args + .iter() + .map(|a| parameter_to_ast(vm, a).into_object()) + .collect(), + ); node!(vm, arguments, { args => args }) } -fn parameter_to_ast(vm: &VirtualMachine, parameter: &ast::Parameter) -> PyObjectRef { +fn parameter_to_ast(vm: &VirtualMachine, parameter: &ast::Parameter) -> AstNodeRef { let py_annotation = if let Some(annotation) = ¶meter.annotation { - expression_to_ast(vm, annotation) + expression_to_ast(vm, annotation).into_object() } else { vm.ctx.none() }; @@ -427,7 +417,7 @@ fn parameter_to_ast(vm: &VirtualMachine, parameter: &ast::Parameter) -> PyObject }) } -fn comprehension_to_ast(vm: &VirtualMachine, comprehension: &ast::Comprehension) -> PyObjectRef { +fn comprehension_to_ast(vm: &VirtualMachine, comprehension: &ast::Comprehension) -> AstNodeRef { node!(vm, comprehension, { target => expression_to_ast(vm, &comprehension.target), iter => expression_to_ast(vm, &comprehension.iter), @@ -435,7 +425,7 @@ fn comprehension_to_ast(vm: &VirtualMachine, comprehension: &ast::Comprehension) }) } -fn string_to_ast(vm: &VirtualMachine, string: &ast::StringGroup) -> PyObjectRef { +fn string_to_ast(vm: &VirtualMachine, string: &ast::StringGroup) -> AstNodeRef { match string { ast::StringGroup::Constant { value } => { node!(vm, Str, { s => vm.ctx.new_str(value.clone()) }) @@ -447,7 +437,7 @@ fn string_to_ast(vm: &VirtualMachine, string: &ast::StringGroup) -> PyObjectRef let py_values = vm.ctx.new_list( values .iter() - .map(|value| string_to_ast(vm, value)) + .map(|value| string_to_ast(vm, value).into_object()) .collect(), ); node!(vm, JoinedStr, { values => py_values }) @@ -455,15 +445,11 @@ fn string_to_ast(vm: &VirtualMachine, string: &ast::StringGroup) -> PyObjectRef } } -fn ast_parse(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(source, Some(vm.ctx.str_type()))]); - - let source_string = objstr::get_value(source); - let internal_ast = parser::parse_program(&source_string) +fn ast_parse(source: PyStringRef, vm: &VirtualMachine) -> PyResult { + let internal_ast = parser::parse_program(&source.value) .map_err(|err| vm.new_value_error(format!("{}", err)))?; // source.clone(); - let ast_node = program_to_ast(&vm, &internal_ast); - Ok(ast_node) + Ok(program_to_ast(&vm, &internal_ast)) } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { From 8a1466557531880e5999b176cef746f19e1bd1cd Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 11 Apr 2019 14:49:24 +0100 Subject: [PATCH 274/884] Ast refactor: Everything can fail. --- vm/src/stdlib/ast.rs | 228 +++++++++++++++++++++---------------------- 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 518465fe6b..0612c0d80b 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -28,11 +28,11 @@ impl PyValue for AstNode { macro_rules! node { ( $vm: expr, $node_name:ident, { $($attr_name:ident => $attr_value:expr),* $(,)* }) => { { - let node = create_node($vm, stringify!($node_name)); + let node = create_node($vm, stringify!($node_name))?; $( - $vm.set_attr(node.as_object(), stringify!($attr_name), $attr_value).unwrap(); + $vm.set_attr(node.as_object(), stringify!($attr_name), $attr_value)?; )* - node + Ok(node) } }; ( $vm: expr, $node_name:ident) => { @@ -40,27 +40,31 @@ macro_rules! node { } } -fn program_to_ast(vm: &VirtualMachine, program: &ast::Program) -> AstNodeRef { - let py_body = statements_to_ast(vm, &program.statements); +fn program_to_ast(vm: &VirtualMachine, program: &ast::Program) -> PyResult { + let py_body = statements_to_ast(vm, &program.statements)?; node!(vm, Module, { body => py_body }) } // Create a node class instance -fn create_node(vm: &VirtualMachine, name: &str) -> AstNodeRef { - AstNode - .into_ref_with_type(vm, vm.class("ast", name)) - .unwrap() +fn create_node(vm: &VirtualMachine, name: &str) -> PyResult { + AstNode.into_ref_with_type(vm, vm.class("ast", name)) } -fn statements_to_ast(vm: &VirtualMachine, statements: &[ast::LocatedStatement]) -> PyListRef { - let body = statements +fn statements_to_ast( + vm: &VirtualMachine, + statements: &[ast::LocatedStatement], +) -> PyResult { + let body: PyResult> = statements .iter() - .map(|statement| statement_to_ast(&vm, statement).into_object()) + .map(|statement| Ok(statement_to_ast(&vm, statement)?.into_object())) .collect(); - vm.ctx.new_list(body).downcast().unwrap() + Ok(vm.ctx.new_list(body?).downcast().unwrap()) } -fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> AstNodeRef { +fn statement_to_ast( + vm: &VirtualMachine, + statement: &ast::LocatedStatement, +) -> PyResult { let node = match &statement.node { ast::Statement::ClassDef { name, @@ -69,8 +73,8 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> A .. } => node!(vm, ClassDef, { name => vm.ctx.new_str(name.to_string()), - body => statements_to_ast(vm, body), - decorator_list => expressions_to_ast(vm, decorator_list), + body => statements_to_ast(vm, body)?, + decorator_list => expressions_to_ast(vm, decorator_list)?, }), ast::Statement::FunctionDef { name, @@ -80,15 +84,15 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> A returns, } => { let py_returns = if let Some(hint) = returns { - expression_to_ast(vm, hint).into_object() + expression_to_ast(vm, hint)?.into_object() } else { vm.ctx.none() }; node!(vm, FunctionDef, { name => vm.ctx.new_str(name.to_string()), - args => parameters_to_ast(vm, args), - body => statements_to_ast(vm, body), - decorator_list => expressions_to_ast(vm, decorator_list), + args => parameters_to_ast(vm, args)?, + body => statements_to_ast(vm, body)?, + decorator_list => expressions_to_ast(vm, decorator_list)?, returns => py_returns }) } @@ -97,27 +101,25 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> A ast::Statement::Pass => node!(vm, Pass), ast::Statement::Assert { test, msg } => { let py_msg = match msg { - Some(msg) => expression_to_ast(vm, msg).into_object(), + Some(msg) => expression_to_ast(vm, msg)?.into_object(), None => vm.ctx.none(), }; node!(vm, Assert, { - test => expression_to_ast(vm, test), + test => expression_to_ast(vm, test)?, msg => py_msg }) } ast::Statement::Delete { targets } => { - let py_targets = vm.ctx.new_tuple( - targets - .iter() - .map(|v| expression_to_ast(vm, v).into_object()) - .collect(), - ); - + let targets: PyResult<_> = targets + .iter() + .map(|v| Ok(expression_to_ast(vm, v)?.into_object())) + .collect(); + let py_targets = vm.ctx.new_tuple(targets?); node!(vm, Delete, { targets => py_targets }) } ast::Statement::Return { value } => { let py_value = if let Some(value) = value { - expression_to_ast(vm, value).into_object() + expression_to_ast(vm, value)?.into_object() } else { vm.ctx.none() }; @@ -127,10 +129,10 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> A }) } ast::Statement::If { test, body, orelse } => node!(vm, If, { - test => expression_to_ast(vm, test), - body => statements_to_ast(vm, body), + test => expression_to_ast(vm, test)?, + body => statements_to_ast(vm, body)?, orelse => if let Some(orelse) = orelse { - statements_to_ast(vm, orelse).into_object() + statements_to_ast(vm, orelse)?.into_object() } else { vm.ctx.none() } @@ -141,52 +143,52 @@ fn statement_to_ast(vm: &VirtualMachine, statement: &ast::LocatedStatement) -> A body, orelse, } => node!(vm, For, { - target => expression_to_ast(vm, target), - iter => expression_to_ast(vm, iter), - body => statements_to_ast(vm, body), + target => expression_to_ast(vm, target)?, + iter => expression_to_ast(vm, iter)?, + body => statements_to_ast(vm, body)?, or_else => if let Some(orelse) = orelse { - statements_to_ast(vm, orelse).into_object() + statements_to_ast(vm, orelse)?.into_object() } else { vm.ctx.none() } }), ast::Statement::While { test, body, orelse } => node!(vm, While, { - test => expression_to_ast(vm, test), - body => statements_to_ast(vm, body), + test => expression_to_ast(vm, test)?, + body => statements_to_ast(vm, body)?, orelse => if let Some(orelse) = orelse { - statements_to_ast(vm, orelse).into_object() + statements_to_ast(vm, orelse)?.into_object() } else { vm.ctx.none() } }), ast::Statement::Expression { expression } => node!(vm, Expr, { - value => expression_to_ast(vm, expression) + value => expression_to_ast(vm, expression)? }), x => { - unimplemented!("{:?}", x); + return Err(vm.new_type_error(format!("Ast not implemented: {:?}", x))); } - }; + }?; // set lineno on node: let lineno = vm.ctx.new_int(statement.location.get_row()); vm.set_attr(node.as_object(), "lineno", lineno).unwrap(); - node + Ok(node) } -fn expressions_to_ast(vm: &VirtualMachine, expressions: &[ast::Expression]) -> PyListRef { - let py_expression_nodes = expressions +fn expressions_to_ast(vm: &VirtualMachine, expressions: &[ast::Expression]) -> PyResult { + let py_expression_nodes: PyResult<_> = expressions .iter() - .map(|expression| expression_to_ast(vm, expression).into_object()) + .map(|expression| Ok(expression_to_ast(vm, expression)?.into_object())) .collect(); - vm.ctx.new_list(py_expression_nodes).downcast().unwrap() + Ok(vm.ctx.new_list(py_expression_nodes?).downcast().unwrap()) } -fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> AstNodeRef { +fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyResult { let node = match &expression { ast::Expression::Call { function, args, .. } => node!(vm, Call, { - func => expression_to_ast(vm, function), - args => expressions_to_ast(vm, args), + func => expression_to_ast(vm, function)?, + args => expressions_to_ast(vm, args)?, }), ast::Expression::Binop { a, op, b } => { // Operator: @@ -206,9 +208,9 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> AstNo ast::Operator::FloorDiv => "FloorDiv", }; node!(vm, BinOp, { - left => expression_to_ast(vm, a), + left => expression_to_ast(vm, a)?, op => vm.ctx.new_str(op.to_string()), - right => expression_to_ast(vm, b), + right => expression_to_ast(vm, b)?, }) } ast::Expression::Unop { op, a } => { @@ -220,13 +222,13 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> AstNo }; node!(vm, UnaryOp, { op => vm.ctx.new_str(op.to_string()), - operand => expression_to_ast(vm, a), + operand => expression_to_ast(vm, a)?, }) } ast::Expression::BoolOp { a, op, b } => { // Attach values: - let py_a = expression_to_ast(vm, a).into_object(); - let py_b = expression_to_ast(vm, b).into_object(); + let py_a = expression_to_ast(vm, a)?.into_object(); + let py_b = expression_to_ast(vm, b)?.into_object(); let py_values = vm.ctx.new_tuple(vec![py_a, py_b]); let str_op = match op { @@ -241,7 +243,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> AstNo }) } ast::Expression::Compare { vals, ops } => { - let py_a = expression_to_ast(vm, &vals[0]); + let left = expression_to_ast(vm, &vals[0])?; // Operator: let to_operator = |op: &ast::Comparison| match op { @@ -256,35 +258,35 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> AstNo ast::Comparison::Is => "Is", ast::Comparison::IsNot => "IsNot", }; - let py_ops = vm.ctx.new_list( + let ops = vm.ctx.new_list( ops.iter() .map(|x| vm.ctx.new_str(to_operator(x).to_string())) .collect(), ); - let py_b = vm.ctx.new_list( - vals.iter() - .skip(1) - .map(|x| expression_to_ast(vm, x).into_object()) - .collect(), - ); + let comparators: PyResult<_> = vals + .iter() + .skip(1) + .map(|x| Ok(expression_to_ast(vm, x)?.into_object())) + .collect(); + let comparators = vm.ctx.new_list(comparators?); node!(vm, Compare, { - left => py_a, - ops => py_ops, - comparators => py_b, + left => left, + ops => ops, + comparators => comparators, }) } ast::Expression::Identifier { name } => node!(vm, Identifier, { id => vm.ctx.new_str(name.clone()) }), ast::Expression::Lambda { args, body } => node!(vm, Lambda, { - args => parameters_to_ast(vm, args), - body => expression_to_ast(vm, body), + args => parameters_to_ast(vm, args)?, + body => expression_to_ast(vm, body)?, }), ast::Expression::IfExpression { test, body, orelse } => node!(vm, IfExp, { - text => expression_to_ast(vm, test), - body => expression_to_ast(vm, body), - or_else => expression_to_ast(vm, orelse), + text => expression_to_ast(vm, test)?, + body => expression_to_ast(vm, body)?, + or_else => expression_to_ast(vm, orelse)?, }), ast::Expression::Number { value } => { let py_n = match value { @@ -309,20 +311,20 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> AstNo }), ast::Expression::Ellipsis => node!(vm, Ellipsis), ast::Expression::List { elements } => node!(vm, List, { - elts => expressions_to_ast(vm, &elements) + elts => expressions_to_ast(vm, &elements)? }), ast::Expression::Tuple { elements } => node!(vm, Tuple, { - elts => expressions_to_ast(vm, &elements) + elts => expressions_to_ast(vm, &elements)? }), ast::Expression::Set { elements } => node!(vm, Set, { - elts => expressions_to_ast(vm, &elements) + elts => expressions_to_ast(vm, &elements)? }), ast::Expression::Dict { elements } => { let mut keys = Vec::new(); let mut values = Vec::new(); for (k, v) in elements { - keys.push(expression_to_ast(vm, k).into_object()); - values.push(expression_to_ast(vm, v).into_object()); + keys.push(expression_to_ast(vm, k)?.into_object()); + values.push(expression_to_ast(vm, v)?.into_object()); } node!(vm, Dict, { @@ -331,11 +333,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> AstNo }) } ast::Expression::Comprehension { kind, generators } => { - let g = generators - .iter() - .map(|g| comprehension_to_ast(vm, g).into_object()) - .collect(); - let py_generators = vm.ctx.new_list(g); + let py_generators = map_ast(comprehension_to_ast, vm, generators)?; match kind.deref() { ast::ComprehensionKind::GeneratorExpression { .. } => { @@ -354,7 +352,7 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> AstNo } ast::Expression::Yield { value } => { let py_value = match value { - Some(value) => expression_to_ast(vm, value).into_object(), + Some(value) => expression_to_ast(vm, value)?.into_object(), None => vm.ctx.none(), }; node!(vm, Yield, { @@ -362,51 +360,45 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> AstNo }) } ast::Expression::YieldFrom { value } => { - let py_value = expression_to_ast(vm, value); + let py_value = expression_to_ast(vm, value)?; node!(vm, YieldFrom, { value => py_value }) } ast::Expression::Subscript { a, b } => node!(vm, Subscript, { - value => expression_to_ast(vm, a), - slice => expression_to_ast(vm, b), + value => expression_to_ast(vm, a)?, + slice => expression_to_ast(vm, b)?, }), ast::Expression::Attribute { value, name } => node!(vm, Attribute, { - value => expression_to_ast(vm, value), + value => expression_to_ast(vm, value)?, attr => vm.ctx.new_str(name.to_string()), }), ast::Expression::Starred { value } => node!(vm, Starred, { - value => expression_to_ast(vm, value) + value => expression_to_ast(vm, value)? }), ast::Expression::Slice { elements } => node!(vm, Slice, { - bounds => expressions_to_ast(vm, elements) + bounds => expressions_to_ast(vm, elements)? }), ast::Expression::String { value } => string_to_ast(vm, value), ast::Expression::Bytes { value } => { node!(vm, Bytes, { s => vm.ctx.new_bytes(value.clone()) }) } - }; + }?; // TODO: retrieve correct lineno: let lineno = vm.ctx.new_int(1); vm.set_attr(node.as_object(), "lineno", lineno).unwrap(); - node + Ok(node) } -fn parameters_to_ast(vm: &VirtualMachine, args: &ast::Parameters) -> AstNodeRef { - let args = vm.ctx.new_list( - args.args - .iter() - .map(|a| parameter_to_ast(vm, a).into_object()) - .collect(), - ); - +fn parameters_to_ast(vm: &VirtualMachine, args: &ast::Parameters) -> PyResult { + let args = map_ast(parameter_to_ast, vm, &args.args)?; node!(vm, arguments, { args => args }) } -fn parameter_to_ast(vm: &VirtualMachine, parameter: &ast::Parameter) -> AstNodeRef { +fn parameter_to_ast(vm: &VirtualMachine, parameter: &ast::Parameter) -> PyResult { let py_annotation = if let Some(annotation) = ¶meter.annotation { - expression_to_ast(vm, annotation).into_object() + expression_to_ast(vm, annotation)?.into_object() } else { vm.ctx.none() }; @@ -417,29 +409,37 @@ fn parameter_to_ast(vm: &VirtualMachine, parameter: &ast::Parameter) -> AstNodeR }) } -fn comprehension_to_ast(vm: &VirtualMachine, comprehension: &ast::Comprehension) -> AstNodeRef { +fn map_ast( + f: fn(vm: &VirtualMachine, &T) -> PyResult, + vm: &VirtualMachine, + items: &Vec, +) -> PyResult { + let list: PyResult> = + items.iter().map(|x| Ok(f(vm, x)?.into_object())).collect(); + Ok(vm.ctx.new_list(list?)) +} + +fn comprehension_to_ast( + vm: &VirtualMachine, + comprehension: &ast::Comprehension, +) -> PyResult { node!(vm, comprehension, { - target => expression_to_ast(vm, &comprehension.target), - iter => expression_to_ast(vm, &comprehension.iter), - ifs => expressions_to_ast(vm, &comprehension.ifs), + target => expression_to_ast(vm, &comprehension.target)?, + iter => expression_to_ast(vm, &comprehension.iter)?, + ifs => expressions_to_ast(vm, &comprehension.ifs)?, }) } -fn string_to_ast(vm: &VirtualMachine, string: &ast::StringGroup) -> AstNodeRef { +fn string_to_ast(vm: &VirtualMachine, string: &ast::StringGroup) -> PyResult { match string { ast::StringGroup::Constant { value } => { node!(vm, Str, { s => vm.ctx.new_str(value.clone()) }) } ast::StringGroup::FormattedValue { value, .. } => { - node!(vm, FormattedValue, { value => expression_to_ast(vm, value) }) + node!(vm, FormattedValue, { value => expression_to_ast(vm, value)? }) } ast::StringGroup::Joined { values } => { - let py_values = vm.ctx.new_list( - values - .iter() - .map(|value| string_to_ast(vm, value).into_object()) - .collect(), - ); + let py_values = map_ast(string_to_ast, vm, &values)?; node!(vm, JoinedStr, { values => py_values }) } } @@ -449,7 +449,7 @@ fn ast_parse(source: PyStringRef, vm: &VirtualMachine) -> PyResult { let internal_ast = parser::parse_program(&source.value) .map_err(|err| vm.new_value_error(format!("{}", err)))?; // source.clone(); - Ok(program_to_ast(&vm, &internal_ast)) + program_to_ast(&vm, &internal_ast) } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { From 856914575148b0ec850be205a90ebcc82b36d0d2 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 11 Apr 2019 15:03:39 +0100 Subject: [PATCH 275/884] Define classes for all the ast types. --- vm/src/stdlib/ast.rs | 52 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 48 insertions(+), 4 deletions(-) diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 0612c0d80b..816882f31e 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -458,10 +458,54 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ast_base = py_class!(ctx, "_ast.AST", ctx.object(), {}); py_module!(vm, "ast", { "parse" => ctx.new_rustfunc(ast_parse), - "Module" => py_class!(ctx, "_ast.Module", ast_base.clone(), {}), - "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ast_base.clone(), {}), - "ClassDef" => py_class!(ctx, "_ast.ClassDef", ast_base.clone(), {}), + "AST" => ast_base.clone(), + // TODO: There's got to be a better way! + "arg" => py_class!(ctx, "_ast.arg", ast_base.clone(), {}), + "arguments" => py_class!(ctx, "_ast.arguments", ast_base.clone(), {}), + "Assert" => py_class!(ctx, "_ast.Assert", ast_base.clone(), {}), + "Attribute" => py_class!(ctx, "_ast.Attribute", ast_base.clone(), {}), + "BinOp" => py_class!(ctx, "_ast.BinOp", ast_base.clone(), {}), + "BoolOp" => py_class!(ctx, "_ast.BoolOp", ast_base.clone(), {}), + "Break" => py_class!(ctx, "_ast.Break", ast_base.clone(), {}), + "Bytes" => py_class!(ctx, "_ast.Bytes", ast_base.clone(), {}), "Call" => py_class!(ctx, "_ast.Call", ast_base.clone(), {}), - "AST" => ast_base, + "ClassDef" => py_class!(ctx, "_ast.ClassDef", ast_base.clone(), {}), + "Compare" => py_class!(ctx, "_ast.Compare", ast_base.clone(), {}), + "comprehension" => py_class!(ctx, "_ast.comprehension", ast_base.clone(), {}), + "Continue" => py_class!(ctx, "_ast.Continue", ast_base.clone(), {}), + "Delete" => py_class!(ctx, "_ast.Delete", ast_base.clone(), {}), + "Dict" => py_class!(ctx, "_ast.Dict", ast_base.clone(), {}), + "DictComp" => py_class!(ctx, "_ast.DictComp", ast_base.clone(), {}), + "Ellipsis" => py_class!(ctx, "_ast.Ellipsis", ast_base.clone(), {}), + "Expr" => py_class!(ctx, "_ast.Expr", ast_base.clone(), {}), + "For" => py_class!(ctx, "_ast.For", ast_base.clone(), {}), + "FormattedValue" => py_class!(ctx, "_ast.FormattedValue", ast_base.clone(), {}), + "FunctionDef" => py_class!(ctx, "_ast.FunctionDef", ast_base.clone(), {}), + "GeneratorExp" => py_class!(ctx, "_ast.GeneratorExp", ast_base.clone(), {}), + "Identifier" => py_class!(ctx, "_ast.Identifier", ast_base.clone(), {}), + "If" => py_class!(ctx, "_ast.If", ast_base.clone(), {}), + "IfExp" => py_class!(ctx, "_ast.IfExp", ast_base.clone(), {}), + "JoinedStr" => py_class!(ctx, "_ast.JoinedStr", ast_base.clone(), {}), + "Lambda" => py_class!(ctx, "_ast.Lambda", ast_base.clone(), {}), + "List" => py_class!(ctx, "_ast.List", ast_base.clone(), {}), + "ListComp" => py_class!(ctx, "_ast.ListComp", ast_base.clone(), {}), + "Module" => py_class!(ctx, "_ast.Module", ast_base.clone(), {}), + "NameConstant" => py_class!(ctx, "_ast.NameConstant", ast_base.clone(), {}), + "NameConstant" => py_class!(ctx, "_ast.NameConstant", ast_base.clone(), {}), + "NameConstant" => py_class!(ctx, "_ast.NameConstant", ast_base.clone(), {}), + "Num" => py_class!(ctx, "_ast.Num", ast_base.clone(), {}), + "Pass" => py_class!(ctx, "_ast.Pass", ast_base.clone(), {}), + "Return" => py_class!(ctx, "_ast.Return", ast_base.clone(), {}), + "Set" => py_class!(ctx, "_ast.Set", ast_base.clone(), {}), + "SetComp" => py_class!(ctx, "_ast.SetComp", ast_base.clone(), {}), + "Starred" => py_class!(ctx, "_ast.Starred", ast_base.clone(), {}), + "Starred" => py_class!(ctx, "_ast.Starred", ast_base.clone(), {}), + "Str" => py_class!(ctx, "_ast.Str", ast_base.clone(), {}), + "Subscript" => py_class!(ctx, "_ast.Subscript", ast_base.clone(), {}), + "Tuple" => py_class!(ctx, "_ast.Tuple", ast_base.clone(), {}), + "UnaryOp" => py_class!(ctx, "_ast.UnaryOp", ast_base.clone(), {}), + "While" => py_class!(ctx, "_ast.While", ast_base.clone(), {}), + "Yield" => py_class!(ctx, "_ast.Yield", ast_base.clone(), {}), + "YieldFrom" => py_class!(ctx, "_ast.YieldFrom", ast_base.clone(), {}), }) } From 72467069bea77b987ca4961e5cd19e25cca5ad4d Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 8 Apr 2019 19:10:31 +0300 Subject: [PATCH 276/884] Add os.getenv --- vm/src/stdlib/os.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 25937cd5c1..217e4d3813 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,11 +1,11 @@ -use std::fs; use std::fs::File; use std::fs::OpenOptions; use std::io::{ErrorKind, Read, Write}; +use std::{env, fs}; use num_traits::cast::ToPrimitive; -use crate::function::PyFuncArgs; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::obj::objbytes::PyBytesRef; use crate::obj::objint; use crate::obj::objint::PyIntRef; @@ -173,6 +173,13 @@ fn os_listdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } } +fn os_getenv(key: PyStringRef, default: OptionalArg, vm: &VirtualMachine) -> PyResult { + match env::var(&key.value) { + Ok(val) => Ok(vm.new_str(val)), + Err(_) => Ok(default.into_option().unwrap_or(vm.get_none())), + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -194,6 +201,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "mkdirs" => ctx.new_rustfunc(os_mkdirs), "rmdir" => ctx.new_rustfunc(os_rmdir), "listdir" => ctx.new_rustfunc(os_listdir), + "getenv" => ctx.new_rustfunc(os_getenv), "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From 9b475a2c417902ea1bcc5f8f3bb87fc3241181cb Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 8 Apr 2019 19:15:02 +0300 Subject: [PATCH 277/884] Add os.putenv --- vm/src/stdlib/os.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 217e4d3813..0b10ff2d98 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -180,6 +180,10 @@ fn os_getenv(key: PyStringRef, default: OptionalArg, vm: &VirtualMa } } +fn os_putenv(key: PyStringRef, value: PyStringRef, _vm: &VirtualMachine) -> () { + env::set_var(&key.value, &value.value) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -202,6 +206,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "rmdir" => ctx.new_rustfunc(os_rmdir), "listdir" => ctx.new_rustfunc(os_listdir), "getenv" => ctx.new_rustfunc(os_getenv), + "putenv" => ctx.new_rustfunc(os_putenv), "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From 951659a54d71efa99b939f919a278a664ff2864e Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 8 Apr 2019 19:20:58 +0300 Subject: [PATCH 278/884] Add os.unsetenv --- vm/src/stdlib/os.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 0b10ff2d98..6a13003b82 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -184,6 +184,10 @@ fn os_putenv(key: PyStringRef, value: PyStringRef, _vm: &VirtualMachine) -> () { env::set_var(&key.value, &value.value) } +fn os_unsetenv(key: PyStringRef, _vm: &VirtualMachine) -> () { + env::remove_var(&key.value) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -207,6 +211,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "listdir" => ctx.new_rustfunc(os_listdir), "getenv" => ctx.new_rustfunc(os_getenv), "putenv" => ctx.new_rustfunc(os_putenv), + "unsetenv" => ctx.new_rustfunc(os_unsetenv), "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From c33593efa9e97a4ca04cb40b9ea87e21656505f5 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 9 Apr 2019 09:31:31 +0300 Subject: [PATCH 279/884] Remove getenv and add environ --- vm/src/stdlib/os.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 6a13003b82..66698b3cb8 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -5,13 +5,14 @@ use std::{env, fs}; use num_traits::cast::ToPrimitive; -use crate::function::{OptionalArg, PyFuncArgs}; +use crate::function::PyFuncArgs; use crate::obj::objbytes::PyBytesRef; +use crate::obj::objdict::PyDictRef; use crate::obj::objint; use crate::obj::objint::PyIntRef; use crate::obj::objstr; use crate::obj::objstr::PyStringRef; -use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; #[cfg(unix)] @@ -173,13 +174,6 @@ fn os_listdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } } -fn os_getenv(key: PyStringRef, default: OptionalArg, vm: &VirtualMachine) -> PyResult { - match env::var(&key.value) { - Ok(val) => Ok(vm.new_str(val)), - Err(_) => Ok(default.into_option().unwrap_or(vm.get_none())), - } -} - fn os_putenv(key: PyStringRef, value: PyStringRef, _vm: &VirtualMachine) -> () { env::set_var(&key.value, &value.value) } @@ -188,6 +182,14 @@ fn os_unsetenv(key: PyStringRef, _vm: &VirtualMachine) -> () { env::remove_var(&key.value) } +fn _os_environ(vm: &VirtualMachine) -> PyDictRef { + let environ = vm.ctx.new_dict(); + for (key, value) in env::vars() { + environ.set_item(&key, vm.new_str(value), vm).unwrap(); + } + environ +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -197,6 +199,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "posix".to_string() }; + let environ = _os_environ(vm); + py_module!(vm, "os", { "open" => ctx.new_rustfunc(os_open), "close" => ctx.new_rustfunc(os_close), @@ -209,9 +213,9 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "mkdirs" => ctx.new_rustfunc(os_mkdirs), "rmdir" => ctx.new_rustfunc(os_rmdir), "listdir" => ctx.new_rustfunc(os_listdir), - "getenv" => ctx.new_rustfunc(os_getenv), "putenv" => ctx.new_rustfunc(os_putenv), "unsetenv" => ctx.new_rustfunc(os_unsetenv), + "environ" => environ, "name" => ctx.new_str(os_name), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), From 4293b11429195905728cf0d5d1481837d1cea12a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 11 Apr 2019 18:37:42 +0300 Subject: [PATCH 280/884] Add environ class --- Lib/os.py | 67 +++++++++++++++++++++++++++++++++++++ tests/snippets/stdlib_os.py | 14 ++++++++ vm/src/stdlib/mod.rs | 2 +- vm/src/stdlib/os.rs | 2 +- 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 Lib/os.py diff --git a/Lib/os.py b/Lib/os.py new file mode 100644 index 0000000000..badb08ebba --- /dev/null +++ b/Lib/os.py @@ -0,0 +1,67 @@ +from _os import * + +class _Environ(): + def __init__(self, data, putenv, unsetenv): + self.putenv = putenv + self.unsetenv = unsetenv + self._data = data + + def __getitem__(self, key): + return self._data[key] + + def __setitem__(self, key, value): + self.putenv(key, value) + self._data[key] = value + + def __delitem__(self, key): + self.unsetenv(key) + del self._data[key] + + def __iter__(self): + # list() from dict object is an atomic operation + keys = list(self._data) + for key in keys: + yield key + + def __len__(self): + return len(self._data) + + def __repr__(self): + return 'environ({{{}}})'.format(', '.join( + ('{}: {}'.format(key, value) + for key, value in self._data.items()))) + + def copy(self): + return dict(self) + + def setdefault(self, key, value): + if key not in self: + self[key] = value + return self[key] + +try: + _putenv = putenv +except NameError: + _putenv = lambda key, value: None + +try: + _unsetenv = unsetenv +except NameError: + _unsetenv = lambda key: _putenv(key, "") + +def _createenviron(): + return _Environ(environ,_putenv, _unsetenv) + +# unicode environ +environ = _createenviron() +del _createenviron + + +def getenv(key, default=None): + """Get an environment variable, return None if it doesn't exist. + The optional second argument can specify an alternate default. + key, default and the result are str.""" + try: + return environ[key] + except KeyError: + return default diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index ba6033ff8c..15fa176d8d 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -40,3 +40,17 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert os.O_RDONLY == 0 assert os.O_WRONLY == 1 assert os.O_RDWR == 2 + +ENV_KEY = "TEST_ENV_VAR" +ENV_VALUE = "value" + +assert os.getenv(ENV_KEY) == None +assert ENV_KEY not in os.environ +assert os.getenv(ENV_KEY, 5) == 5 +os.environ[ENV_KEY] = ENV_VALUE +assert ENV_KEY in os.environ +assert os.getenv(ENV_KEY) == ENV_VALUE +del os.environ[ENV_KEY] +os.unsetenv(ENV_KEY) +assert ENV_KEY not in os.environ +assert os.getenv(ENV_KEY) == None diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index a69a80d118..9b0d0b345f 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -50,7 +50,7 @@ pub fn get_module_inits() -> HashMap { #[cfg(not(target_arch = "wasm32"))] { modules.insert("io".to_string(), Box::new(io::make_module)); - modules.insert("os".to_string(), Box::new(os::make_module)); + modules.insert("_os".to_string(), Box::new(os::make_module)); modules.insert("socket".to_string(), Box::new(socket::make_module)); } diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 66698b3cb8..26707d3ca9 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -201,7 +201,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let environ = _os_environ(vm); - py_module!(vm, "os", { + py_module!(vm, "_os", { "open" => ctx.new_rustfunc(os_open), "close" => ctx.new_rustfunc(os_close), "error" => ctx.new_rustfunc(os_error), From 69e49e6cba58a43c6cb230ae8be2b733d46c9223 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 11 Apr 2019 18:44:09 +0300 Subject: [PATCH 281/884] Simplify os.py --- Lib/os.py | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index badb08ebba..8b7876c7a2 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -39,23 +39,7 @@ def setdefault(self, key, value): self[key] = value return self[key] -try: - _putenv = putenv -except NameError: - _putenv = lambda key, value: None - -try: - _unsetenv = unsetenv -except NameError: - _unsetenv = lambda key: _putenv(key, "") - -def _createenviron(): - return _Environ(environ,_putenv, _unsetenv) - -# unicode environ -environ = _createenviron() -del _createenviron - +environ = _Environ(environ, putenv, unsetenv) def getenv(key, default=None): """Get an environment variable, return None if it doesn't exist. From eb40e08d8a59c0e43b00f8c2206573bc7f8ad83f Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 11 Apr 2019 19:02:04 +0300 Subject: [PATCH 282/884] Add _collections_abc.py from CPython --- Lib/_collections_abc.py | 1013 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 1013 insertions(+) create mode 100644 Lib/_collections_abc.py diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py new file mode 100644 index 0000000000..c363987970 --- /dev/null +++ b/Lib/_collections_abc.py @@ -0,0 +1,1013 @@ +# Copyright 2007 Google, Inc. All Rights Reserved. +# Licensed to PSF under a Contributor Agreement. + +"""Abstract Base Classes (ABCs) for collections, according to PEP 3119. + +Unit tests are in test_collections. +""" + +from abc import ABCMeta, abstractmethod +import sys + +__all__ = ["Awaitable", "Coroutine", + "AsyncIterable", "AsyncIterator", "AsyncGenerator", + "Hashable", "Iterable", "Iterator", "Generator", "Reversible", + "Sized", "Container", "Callable", "Collection", + "Set", "MutableSet", + "Mapping", "MutableMapping", + "MappingView", "KeysView", "ItemsView", "ValuesView", + "Sequence", "MutableSequence", + "ByteString", + ] + +# This module has been renamed from collections.abc to _collections_abc to +# speed up interpreter startup. Some of the types such as MutableMapping are +# required early but collections module imports a lot of other modules. +# See issue #19218 +__name__ = "collections.abc" + +# Private list of types that we want to register with the various ABCs +# so that they will pass tests like: +# it = iter(somebytearray) +# assert isinstance(it, Iterable) +# Note: in other implementations, these types might not be distinct +# and they may have their own implementation specific types that +# are not included on this list. +bytes_iterator = type(iter(b'')) +bytearray_iterator = type(iter(bytearray())) +#callable_iterator = ??? +dict_keyiterator = type(iter({}.keys())) +dict_valueiterator = type(iter({}.values())) +dict_itemiterator = type(iter({}.items())) +list_iterator = type(iter([])) +list_reverseiterator = type(iter(reversed([]))) +range_iterator = type(iter(range(0))) +longrange_iterator = type(iter(range(1 << 1000))) +set_iterator = type(iter(set())) +str_iterator = type(iter("")) +tuple_iterator = type(iter(())) +zip_iterator = type(iter(zip())) +## views ## +dict_keys = type({}.keys()) +dict_values = type({}.values()) +dict_items = type({}.items()) +## misc ## +mappingproxy = type(type.__dict__) +generator = type((lambda: (yield))()) +## coroutine ## +async def _coro(): pass +_coro = _coro() +coroutine = type(_coro) +_coro.close() # Prevent ResourceWarning +del _coro +## asynchronous generator ## +async def _ag(): yield +_ag = _ag() +async_generator = type(_ag) +del _ag + + +### ONE-TRICK PONIES ### + +def _check_methods(C, *methods): + mro = C.__mro__ + for method in methods: + for B in mro: + if method in B.__dict__: + if B.__dict__[method] is None: + return NotImplemented + break + else: + return NotImplemented + return True + +class Hashable(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __hash__(self): + return 0 + + @classmethod + def __subclasshook__(cls, C): + if cls is Hashable: + return _check_methods(C, "__hash__") + return NotImplemented + + +class Awaitable(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __await__(self): + yield + + @classmethod + def __subclasshook__(cls, C): + if cls is Awaitable: + return _check_methods(C, "__await__") + return NotImplemented + + +class Coroutine(Awaitable): + + __slots__ = () + + @abstractmethod + def send(self, value): + """Send a value into the coroutine. + Return next yielded value or raise StopIteration. + """ + raise StopIteration + + @abstractmethod + def throw(self, typ, val=None, tb=None): + """Raise an exception in the coroutine. + Return next yielded value or raise StopIteration. + """ + if val is None: + if tb is None: + raise typ + val = typ() + if tb is not None: + val = val.with_traceback(tb) + raise val + + def close(self): + """Raise GeneratorExit inside coroutine. + """ + try: + self.throw(GeneratorExit) + except (GeneratorExit, StopIteration): + pass + else: + raise RuntimeError("coroutine ignored GeneratorExit") + + @classmethod + def __subclasshook__(cls, C): + if cls is Coroutine: + return _check_methods(C, '__await__', 'send', 'throw', 'close') + return NotImplemented + + +Coroutine.register(coroutine) + + +class AsyncIterable(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __aiter__(self): + return AsyncIterator() + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncIterable: + return _check_methods(C, "__aiter__") + return NotImplemented + + +class AsyncIterator(AsyncIterable): + + __slots__ = () + + @abstractmethod + async def __anext__(self): + """Return the next item or raise StopAsyncIteration when exhausted.""" + raise StopAsyncIteration + + def __aiter__(self): + return self + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncIterator: + return _check_methods(C, "__anext__", "__aiter__") + return NotImplemented + + +class AsyncGenerator(AsyncIterator): + + __slots__ = () + + async def __anext__(self): + """Return the next item from the asynchronous generator. + When exhausted, raise StopAsyncIteration. + """ + return await self.asend(None) + + @abstractmethod + async def asend(self, value): + """Send a value into the asynchronous generator. + Return next yielded value or raise StopAsyncIteration. + """ + raise StopAsyncIteration + + @abstractmethod + async def athrow(self, typ, val=None, tb=None): + """Raise an exception in the asynchronous generator. + Return next yielded value or raise StopAsyncIteration. + """ + if val is None: + if tb is None: + raise typ + val = typ() + if tb is not None: + val = val.with_traceback(tb) + raise val + + async def aclose(self): + """Raise GeneratorExit inside coroutine. + """ + try: + await self.athrow(GeneratorExit) + except (GeneratorExit, StopAsyncIteration): + pass + else: + raise RuntimeError("asynchronous generator ignored GeneratorExit") + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncGenerator: + return _check_methods(C, '__aiter__', '__anext__', + 'asend', 'athrow', 'aclose') + return NotImplemented + + +AsyncGenerator.register(async_generator) + + +class Iterable(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __iter__(self): + while False: + yield None + + @classmethod + def __subclasshook__(cls, C): + if cls is Iterable: + return _check_methods(C, "__iter__") + return NotImplemented + + +class Iterator(Iterable): + + __slots__ = () + + @abstractmethod + def __next__(self): + 'Return the next item from the iterator. When exhausted, raise StopIteration' + raise StopIteration + + def __iter__(self): + return self + + @classmethod + def __subclasshook__(cls, C): + if cls is Iterator: + return _check_methods(C, '__iter__', '__next__') + return NotImplemented + +Iterator.register(bytes_iterator) +Iterator.register(bytearray_iterator) +#Iterator.register(callable_iterator) +Iterator.register(dict_keyiterator) +Iterator.register(dict_valueiterator) +Iterator.register(dict_itemiterator) +Iterator.register(list_iterator) +Iterator.register(list_reverseiterator) +Iterator.register(range_iterator) +Iterator.register(longrange_iterator) +Iterator.register(set_iterator) +Iterator.register(str_iterator) +Iterator.register(tuple_iterator) +Iterator.register(zip_iterator) + + +class Reversible(Iterable): + + __slots__ = () + + @abstractmethod + def __reversed__(self): + while False: + yield None + + @classmethod + def __subclasshook__(cls, C): + if cls is Reversible: + return _check_methods(C, "__reversed__", "__iter__") + return NotImplemented + + +class Generator(Iterator): + + __slots__ = () + + def __next__(self): + """Return the next item from the generator. + When exhausted, raise StopIteration. + """ + return self.send(None) + + @abstractmethod + def send(self, value): + """Send a value into the generator. + Return next yielded value or raise StopIteration. + """ + raise StopIteration + + @abstractmethod + def throw(self, typ, val=None, tb=None): + """Raise an exception in the generator. + Return next yielded value or raise StopIteration. + """ + if val is None: + if tb is None: + raise typ + val = typ() + if tb is not None: + val = val.with_traceback(tb) + raise val + + def close(self): + """Raise GeneratorExit inside generator. + """ + try: + self.throw(GeneratorExit) + except (GeneratorExit, StopIteration): + pass + else: + raise RuntimeError("generator ignored GeneratorExit") + + @classmethod + def __subclasshook__(cls, C): + if cls is Generator: + return _check_methods(C, '__iter__', '__next__', + 'send', 'throw', 'close') + return NotImplemented + +Generator.register(generator) + + +class Sized(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __len__(self): + return 0 + + @classmethod + def __subclasshook__(cls, C): + if cls is Sized: + return _check_methods(C, "__len__") + return NotImplemented + + +class Container(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __contains__(self, x): + return False + + @classmethod + def __subclasshook__(cls, C): + if cls is Container: + return _check_methods(C, "__contains__") + return NotImplemented + +class Collection(Sized, Iterable, Container): + + __slots__ = () + + @classmethod + def __subclasshook__(cls, C): + if cls is Collection: + return _check_methods(C, "__len__", "__iter__", "__contains__") + return NotImplemented + +class Callable(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __call__(self, *args, **kwds): + return False + + @classmethod + def __subclasshook__(cls, C): + if cls is Callable: + return _check_methods(C, "__call__") + return NotImplemented + + +### SETS ### + + +class Set(Collection): + + """A set is a finite, iterable container. + + This class provides concrete generic implementations of all + methods except for __contains__, __iter__ and __len__. + + To override the comparisons (presumably for speed, as the + semantics are fixed), redefine __le__ and __ge__, + then the other operations will automatically follow suit. + """ + + __slots__ = () + + def __le__(self, other): + if not isinstance(other, Set): + return NotImplemented + if len(self) > len(other): + return False + for elem in self: + if elem not in other: + return False + return True + + def __lt__(self, other): + if not isinstance(other, Set): + return NotImplemented + return len(self) < len(other) and self.__le__(other) + + def __gt__(self, other): + if not isinstance(other, Set): + return NotImplemented + return len(self) > len(other) and self.__ge__(other) + + def __ge__(self, other): + if not isinstance(other, Set): + return NotImplemented + if len(self) < len(other): + return False + for elem in other: + if elem not in self: + return False + return True + + def __eq__(self, other): + if not isinstance(other, Set): + return NotImplemented + return len(self) == len(other) and self.__le__(other) + + @classmethod + def _from_iterable(cls, it): + '''Construct an instance of the class from any iterable input. + + Must override this method if the class constructor signature + does not accept an iterable for an input. + ''' + return cls(it) + + def __and__(self, other): + if not isinstance(other, Iterable): + return NotImplemented + return self._from_iterable(value for value in other if value in self) + + __rand__ = __and__ + + def isdisjoint(self, other): + 'Return True if two sets have a null intersection.' + for value in other: + if value in self: + return False + return True + + def __or__(self, other): + if not isinstance(other, Iterable): + return NotImplemented + chain = (e for s in (self, other) for e in s) + return self._from_iterable(chain) + + __ror__ = __or__ + + def __sub__(self, other): + if not isinstance(other, Set): + if not isinstance(other, Iterable): + return NotImplemented + other = self._from_iterable(other) + return self._from_iterable(value for value in self + if value not in other) + + def __rsub__(self, other): + if not isinstance(other, Set): + if not isinstance(other, Iterable): + return NotImplemented + other = self._from_iterable(other) + return self._from_iterable(value for value in other + if value not in self) + + def __xor__(self, other): + if not isinstance(other, Set): + if not isinstance(other, Iterable): + return NotImplemented + other = self._from_iterable(other) + return (self - other) | (other - self) + + __rxor__ = __xor__ + + def _hash(self): + """Compute the hash value of a set. + + Note that we don't define __hash__: not all sets are hashable. + But if you define a hashable set type, its __hash__ should + call this function. + + This must be compatible __eq__. + + All sets ought to compare equal if they contain the same + elements, regardless of how they are implemented, and + regardless of the order of the elements; so there's not much + freedom for __eq__ or __hash__. We match the algorithm used + by the built-in frozenset type. + """ + MAX = sys.maxsize + MASK = 2 * MAX + 1 + n = len(self) + h = 1927868237 * (n + 1) + h &= MASK + for x in self: + hx = hash(x) + h ^= (hx ^ (hx << 16) ^ 89869747) * 3644798167 + h &= MASK + h = h * 69069 + 907133923 + h &= MASK + if h > MAX: + h -= MASK + 1 + if h == -1: + h = 590923713 + return h + +Set.register(frozenset) + + +class MutableSet(Set): + """A mutable set is a finite, iterable container. + + This class provides concrete generic implementations of all + methods except for __contains__, __iter__, __len__, + add(), and discard(). + + To override the comparisons (presumably for speed, as the + semantics are fixed), all you have to do is redefine __le__ and + then the other operations will automatically follow suit. + """ + + __slots__ = () + + @abstractmethod + def add(self, value): + """Add an element.""" + raise NotImplementedError + + @abstractmethod + def discard(self, value): + """Remove an element. Do not raise an exception if absent.""" + raise NotImplementedError + + def remove(self, value): + """Remove an element. If not a member, raise a KeyError.""" + if value not in self: + raise KeyError(value) + self.discard(value) + + def pop(self): + """Return the popped value. Raise KeyError if empty.""" + it = iter(self) + try: + value = next(it) + except StopIteration: + raise KeyError from None + self.discard(value) + return value + + def clear(self): + """This is slow (creates N new iterators!) but effective.""" + try: + while True: + self.pop() + except KeyError: + pass + + def __ior__(self, it): + for value in it: + self.add(value) + return self + + def __iand__(self, it): + for value in (self - it): + self.discard(value) + return self + + def __ixor__(self, it): + if it is self: + self.clear() + else: + if not isinstance(it, Set): + it = self._from_iterable(it) + for value in it: + if value in self: + self.discard(value) + else: + self.add(value) + return self + + def __isub__(self, it): + if it is self: + self.clear() + else: + for value in it: + self.discard(value) + return self + +MutableSet.register(set) + + +### MAPPINGS ### + + +class Mapping(Collection): + + __slots__ = () + + """A Mapping is a generic container for associating key/value + pairs. + + This class provides concrete generic implementations of all + methods except for __getitem__, __iter__, and __len__. + + """ + + @abstractmethod + def __getitem__(self, key): + raise KeyError + + def get(self, key, default=None): + 'D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.' + try: + return self[key] + except KeyError: + return default + + def __contains__(self, key): + try: + self[key] + except KeyError: + return False + else: + return True + + def keys(self): + "D.keys() -> a set-like object providing a view on D's keys" + return KeysView(self) + + def items(self): + "D.items() -> a set-like object providing a view on D's items" + return ItemsView(self) + + def values(self): + "D.values() -> an object providing a view on D's values" + return ValuesView(self) + + def __eq__(self, other): + if not isinstance(other, Mapping): + return NotImplemented + return dict(self.items()) == dict(other.items()) + + __reversed__ = None + +Mapping.register(mappingproxy) + + +class MappingView(Sized): + + __slots__ = '_mapping', + + def __init__(self, mapping): + self._mapping = mapping + + def __len__(self): + return len(self._mapping) + + def __repr__(self): + return '{0.__class__.__name__}({0._mapping!r})'.format(self) + + +class KeysView(MappingView, Set): + + __slots__ = () + + @classmethod + def _from_iterable(self, it): + return set(it) + + def __contains__(self, key): + return key in self._mapping + + def __iter__(self): + yield from self._mapping + +KeysView.register(dict_keys) + + +class ItemsView(MappingView, Set): + + __slots__ = () + + @classmethod + def _from_iterable(self, it): + return set(it) + + def __contains__(self, item): + key, value = item + try: + v = self._mapping[key] + except KeyError: + return False + else: + return v is value or v == value + + def __iter__(self): + for key in self._mapping: + yield (key, self._mapping[key]) + +ItemsView.register(dict_items) + + +class ValuesView(MappingView, Collection): + + __slots__ = () + + def __contains__(self, value): + for key in self._mapping: + v = self._mapping[key] + if v is value or v == value: + return True + return False + + def __iter__(self): + for key in self._mapping: + yield self._mapping[key] + +ValuesView.register(dict_values) + + +class MutableMapping(Mapping): + + __slots__ = () + + """A MutableMapping is a generic container for associating + key/value pairs. + + This class provides concrete generic implementations of all + methods except for __getitem__, __setitem__, __delitem__, + __iter__, and __len__. + + """ + + @abstractmethod + def __setitem__(self, key, value): + raise KeyError + + @abstractmethod + def __delitem__(self, key): + raise KeyError + + __marker = object() + + def pop(self, key, default=__marker): + '''D.pop(k[,d]) -> v, remove specified key and return the corresponding value. + If key is not found, d is returned if given, otherwise KeyError is raised. + ''' + try: + value = self[key] + except KeyError: + if default is self.__marker: + raise + return default + else: + del self[key] + return value + + def popitem(self): + '''D.popitem() -> (k, v), remove and return some (key, value) pair + as a 2-tuple; but raise KeyError if D is empty. + ''' + try: + key = next(iter(self)) + except StopIteration: + raise KeyError from None + value = self[key] + del self[key] + return key, value + + def clear(self): + 'D.clear() -> None. Remove all items from D.' + try: + while True: + self.popitem() + except KeyError: + pass + + def update(*args, **kwds): + ''' D.update([E, ]**F) -> None. Update D from mapping/iterable E and F. + If E present and has a .keys() method, does: for k in E: D[k] = E[k] + If E present and lacks .keys() method, does: for (k, v) in E: D[k] = v + In either case, this is followed by: for k, v in F.items(): D[k] = v + ''' + if not args: + raise TypeError("descriptor 'update' of 'MutableMapping' object " + "needs an argument") + self, *args = args + if len(args) > 1: + raise TypeError('update expected at most 1 arguments, got %d' % + len(args)) + if args: + other = args[0] + if isinstance(other, Mapping): + for key in other: + self[key] = other[key] + elif hasattr(other, "keys"): + for key in other.keys(): + self[key] = other[key] + else: + for key, value in other: + self[key] = value + for key, value in kwds.items(): + self[key] = value + + def setdefault(self, key, default=None): + 'D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D' + try: + return self[key] + except KeyError: + self[key] = default + return default + +MutableMapping.register(dict) + + +### SEQUENCES ### + + +class Sequence(Reversible, Collection): + + """All the operations on a read-only sequence. + + Concrete subclasses must override __new__ or __init__, + __getitem__, and __len__. + """ + + __slots__ = () + + @abstractmethod + def __getitem__(self, index): + raise IndexError + + def __iter__(self): + i = 0 + try: + while True: + v = self[i] + yield v + i += 1 + except IndexError: + return + + def __contains__(self, value): + for v in self: + if v is value or v == value: + return True + return False + + def __reversed__(self): + for i in reversed(range(len(self))): + yield self[i] + + def index(self, value, start=0, stop=None): + '''S.index(value, [start, [stop]]) -> integer -- return first index of value. + Raises ValueError if the value is not present. + + Supporting start and stop arguments is optional, but + recommended. + ''' + if start is not None and start < 0: + start = max(len(self) + start, 0) + if stop is not None and stop < 0: + stop += len(self) + + i = start + while stop is None or i < stop: + try: + v = self[i] + if v is value or v == value: + return i + except IndexError: + break + i += 1 + raise ValueError + + def count(self, value): + 'S.count(value) -> integer -- return number of occurrences of value' + return sum(1 for v in self if v is value or v == value) + +Sequence.register(tuple) +Sequence.register(str) +Sequence.register(range) +Sequence.register(memoryview) + + +class ByteString(Sequence): + + """This unifies bytes and bytearray. + + XXX Should add all their methods. + """ + + __slots__ = () + +ByteString.register(bytes) +ByteString.register(bytearray) + + +class MutableSequence(Sequence): + + __slots__ = () + + """All the operations on a read-write sequence. + + Concrete subclasses must provide __new__ or __init__, + __getitem__, __setitem__, __delitem__, __len__, and insert(). + + """ + + @abstractmethod + def __setitem__(self, index, value): + raise IndexError + + @abstractmethod + def __delitem__(self, index): + raise IndexError + + @abstractmethod + def insert(self, index, value): + 'S.insert(index, value) -- insert value before index' + raise IndexError + + def append(self, value): + 'S.append(value) -- append value to the end of the sequence' + self.insert(len(self), value) + + def clear(self): + 'S.clear() -> None -- remove all items from S' + try: + while True: + self.pop() + except IndexError: + pass + + def reverse(self): + 'S.reverse() -- reverse *IN PLACE*' + n = len(self) + for i in range(n//2): + self[i], self[n-i-1] = self[n-i-1], self[i] + + def extend(self, values): + 'S.extend(iterable) -- extend sequence by appending elements from the iterable' + if values is self: + values = list(values) + for v in values: + self.append(v) + + def pop(self, index=-1): + '''S.pop([index]) -> item -- remove and return item at index (default last). + Raise IndexError if list is empty or index is out of range. + ''' + v = self[index] + del self[index] + return v + + def remove(self, value): + '''S.remove(value) -- remove first occurrence of value. + Raise ValueError if the value is not present. + ''' + del self[self.index(value)] + + def __iadd__(self, values): + self.extend(values) + return self + +MutableSequence.register(list) +MutableSequence.register(bytearray) # Multiply inheriting, see ByteString From 127282979364ec780e164fa114a9544ce14a3386 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 11 Apr 2019 19:03:08 +0300 Subject: [PATCH 283/884] Comment unsupported code from _collections_abc.py --- Lib/_collections_abc.py | 284 ++++++++++++++++++++-------------------- 1 file changed, 142 insertions(+), 142 deletions(-) diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index c363987970..3158a4f16c 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -40,7 +40,7 @@ dict_valueiterator = type(iter({}.values())) dict_itemiterator = type(iter({}.items())) list_iterator = type(iter([])) -list_reverseiterator = type(iter(reversed([]))) +# list_reverseiterator = type(iter(reversed([]))) range_iterator = type(iter(range(0))) longrange_iterator = type(iter(range(1 << 1000))) set_iterator = type(iter(set())) @@ -53,21 +53,21 @@ dict_items = type({}.items()) ## misc ## mappingproxy = type(type.__dict__) -generator = type((lambda: (yield))()) +# generator = type((lambda: (yield))()) ## coroutine ## -async def _coro(): pass -_coro = _coro() -coroutine = type(_coro) -_coro.close() # Prevent ResourceWarning -del _coro -## asynchronous generator ## -async def _ag(): yield -_ag = _ag() -async_generator = type(_ag) -del _ag +# async def _coro(): pass +# _coro = _coro() +# coroutine = type(_coro) +# _coro.close() # Prevent ResourceWarning +# del _coro +# ## asynchronous generator ## +# async def _ag(): yield +# _ag = _ag() +# async_generator = type(_ag) +# del _ag -### ONE-TRICK PONIES ### +# ## ONE-TRICK PONIES ### def _check_methods(C, *methods): mro = C.__mro__ @@ -96,148 +96,148 @@ def __subclasshook__(cls, C): return NotImplemented -class Awaitable(metaclass=ABCMeta): +# class Awaitable(metaclass=ABCMeta): - __slots__ = () +# __slots__ = () - @abstractmethod - def __await__(self): - yield +# @abstractmethod +# def __await__(self): +# yield - @classmethod - def __subclasshook__(cls, C): - if cls is Awaitable: - return _check_methods(C, "__await__") - return NotImplemented - - -class Coroutine(Awaitable): - - __slots__ = () - - @abstractmethod - def send(self, value): - """Send a value into the coroutine. - Return next yielded value or raise StopIteration. - """ - raise StopIteration - - @abstractmethod - def throw(self, typ, val=None, tb=None): - """Raise an exception in the coroutine. - Return next yielded value or raise StopIteration. - """ - if val is None: - if tb is None: - raise typ - val = typ() - if tb is not None: - val = val.with_traceback(tb) - raise val +# @classmethod +# def __subclasshook__(cls, C): +# if cls is Awaitable: +# return _check_methods(C, "__await__") +# return NotImplemented - def close(self): - """Raise GeneratorExit inside coroutine. - """ - try: - self.throw(GeneratorExit) - except (GeneratorExit, StopIteration): - pass - else: - raise RuntimeError("coroutine ignored GeneratorExit") - @classmethod - def __subclasshook__(cls, C): - if cls is Coroutine: - return _check_methods(C, '__await__', 'send', 'throw', 'close') - return NotImplemented +# class Coroutine(Awaitable): +# __slots__ = () -Coroutine.register(coroutine) +# @abstractmethod +# def send(self, value): +# """Send a value into the coroutine. +# Return next yielded value or raise StopIteration. +# """ +# raise StopIteration +# @abstractmethod +# def throw(self, typ, val=None, tb=None): +# """Raise an exception in the coroutine. +# Return next yielded value or raise StopIteration. +# """ +# if val is None: +# if tb is None: +# raise typ +# val = typ() +# if tb is not None: +# val = val.with_traceback(tb) +# raise val + +# def close(self): +# """Raise GeneratorExit inside coroutine. +# """ +# try: +# self.throw(GeneratorExit) +# except (GeneratorExit, StopIteration): +# pass +# else: +# raise RuntimeError("coroutine ignored GeneratorExit") -class AsyncIterable(metaclass=ABCMeta): +# @classmethod +# def __subclasshook__(cls, C): +# if cls is Coroutine: +# return _check_methods(C, '__await__', 'send', 'throw', 'close') +# return NotImplemented - __slots__ = () - @abstractmethod - def __aiter__(self): - return AsyncIterator() +# Coroutine.register(coroutine) - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncIterable: - return _check_methods(C, "__aiter__") - return NotImplemented +# class AsyncIterable(metaclass=ABCMeta): -class AsyncIterator(AsyncIterable): +# __slots__ = () - __slots__ = () +# @abstractmethod +# def __aiter__(self): +# return AsyncIterator() - @abstractmethod - async def __anext__(self): - """Return the next item or raise StopAsyncIteration when exhausted.""" - raise StopAsyncIteration +# @classmethod +# def __subclasshook__(cls, C): +# if cls is AsyncIterable: +# return _check_methods(C, "__aiter__") +# return NotImplemented - def __aiter__(self): - return self - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncIterator: - return _check_methods(C, "__anext__", "__aiter__") - return NotImplemented +# class AsyncIterator(AsyncIterable): +# __slots__ = () -class AsyncGenerator(AsyncIterator): +# @abstractmethod +# async def __anext__(self): +# """Return the next item or raise StopAsyncIteration when exhausted.""" +# raise StopAsyncIteration - __slots__ = () +# def __aiter__(self): +# return self - async def __anext__(self): - """Return the next item from the asynchronous generator. - When exhausted, raise StopAsyncIteration. - """ - return await self.asend(None) +# @classmethod +# def __subclasshook__(cls, C): +# if cls is AsyncIterator: +# return _check_methods(C, "__anext__", "__aiter__") +# return NotImplemented - @abstractmethod - async def asend(self, value): - """Send a value into the asynchronous generator. - Return next yielded value or raise StopAsyncIteration. - """ - raise StopAsyncIteration - @abstractmethod - async def athrow(self, typ, val=None, tb=None): - """Raise an exception in the asynchronous generator. - Return next yielded value or raise StopAsyncIteration. - """ - if val is None: - if tb is None: - raise typ - val = typ() - if tb is not None: - val = val.with_traceback(tb) - raise val +# class AsyncGenerator(AsyncIterator): - async def aclose(self): - """Raise GeneratorExit inside coroutine. - """ - try: - await self.athrow(GeneratorExit) - except (GeneratorExit, StopAsyncIteration): - pass - else: - raise RuntimeError("asynchronous generator ignored GeneratorExit") +# __slots__ = () - @classmethod - def __subclasshook__(cls, C): - if cls is AsyncGenerator: - return _check_methods(C, '__aiter__', '__anext__', - 'asend', 'athrow', 'aclose') - return NotImplemented +# async def __anext__(self): +# """Return the next item from the asynchronous generator. +# When exhausted, raise StopAsyncIteration. +# """ +# return await self.asend(None) + +# @abstractmethod +# async def asend(self, value): +# """Send a value into the asynchronous generator. +# Return next yielded value or raise StopAsyncIteration. +# """ +# raise StopAsyncIteration + +# @abstractmethod +# async def athrow(self, typ, val=None, tb=None): +# """Raise an exception in the asynchronous generator. +# Return next yielded value or raise StopAsyncIteration. +# """ +# if val is None: +# if tb is None: +# raise typ +# val = typ() +# if tb is not None: +# val = val.with_traceback(tb) +# raise val + +# async def aclose(self): +# """Raise GeneratorExit inside coroutine. +# """ +# try: +# await self.athrow(GeneratorExit) +# except (GeneratorExit, StopAsyncIteration): +# pass +# else: +# raise RuntimeError("asynchronous generator ignored GeneratorExit") + +# @classmethod +# def __subclasshook__(cls, C): +# if cls is AsyncGenerator: +# return _check_methods(C, '__aiter__', '__anext__', +# 'asend', 'athrow', 'aclose') +# return NotImplemented -AsyncGenerator.register(async_generator) +# AsyncGenerator.register(async_generator) class Iterable(metaclass=ABCMeta): @@ -274,20 +274,20 @@ def __subclasshook__(cls, C): return _check_methods(C, '__iter__', '__next__') return NotImplemented -Iterator.register(bytes_iterator) -Iterator.register(bytearray_iterator) -#Iterator.register(callable_iterator) -Iterator.register(dict_keyiterator) -Iterator.register(dict_valueiterator) -Iterator.register(dict_itemiterator) -Iterator.register(list_iterator) -Iterator.register(list_reverseiterator) -Iterator.register(range_iterator) -Iterator.register(longrange_iterator) -Iterator.register(set_iterator) -Iterator.register(str_iterator) -Iterator.register(tuple_iterator) -Iterator.register(zip_iterator) +# Iterator.register(bytes_iterator) +# Iterator.register(bytearray_iterator) +# Iterator.register(callable_iterator) +# Iterator.register(dict_keyiterator) +# Iterator.register(dict_valueiterator) +# Iterator.register(dict_itemiterator) +# Iterator.register(list_iterator) +# Iterator.register(list_reverseiterator) +# Iterator.register(range_iterator) +# Iterator.register(longrange_iterator) +# Iterator.register(set_iterator) +# Iterator.register(str_iterator) +# Iterator.register(tuple_iterator) +# Iterator.register(zip_iterator) class Reversible(Iterable): @@ -353,7 +353,7 @@ def __subclasshook__(cls, C): 'send', 'throw', 'close') return NotImplemented -Generator.register(generator) +# Generator.register(generator) class Sized(metaclass=ABCMeta): From b293956258f45bdade3b97ae73df423d82678e9d Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 11 Apr 2019 19:15:37 +0300 Subject: [PATCH 284/884] Use original _Environ as much as possible --- Lib/os.py | 95 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 83 insertions(+), 12 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index 8b7876c7a2..8c19a89c64 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -1,34 +1,56 @@ from _os import * -class _Environ(): - def __init__(self, data, putenv, unsetenv): +# Change environ to automatically call putenv(), unsetenv if they exist. +from _collections_abc import MutableMapping + +class _Environ(MutableMapping): + def __init__(self, data, encodekey, decodekey, encodevalue, decodevalue, putenv, unsetenv): + self.encodekey = encodekey + self.decodekey = decodekey + self.encodevalue = encodevalue + self.decodevalue = decodevalue self.putenv = putenv self.unsetenv = unsetenv self._data = data def __getitem__(self, key): - return self._data[key] + try: + value = self._data[self.encodekey(key)] + except KeyError: + # raise KeyError with the original key value + # raise KeyError(key) from None + raise KeyError(key) + + return self.decodevalue(value) def __setitem__(self, key, value): + key = self.encodekey(key) + value = self.encodevalue(value) self.putenv(key, value) self._data[key] = value def __delitem__(self, key): - self.unsetenv(key) - del self._data[key] + encodedkey = self.encodekey(key) + self.unsetenv(encodedkey) + try: + del self._data[encodedkey] + except KeyError: + # raise KeyError with the original key value + # raise KeyError(key) from None + raise KeyError(key) def __iter__(self): # list() from dict object is an atomic operation keys = list(self._data) for key in keys: - yield key + yield self.decodekey(key) def __len__(self): return len(self._data) def __repr__(self): return 'environ({{{}}})'.format(', '.join( - ('{}: {}'.format(key, value) + ('{!r}: {!r}'.format(self.decodekey(key), self.decodevalue(value)) for key, value in self._data.items()))) def copy(self): @@ -39,13 +61,62 @@ def setdefault(self, key, value): self[key] = value return self[key] -environ = _Environ(environ, putenv, unsetenv) +try: + _putenv = putenv +except NameError: + _putenv = lambda key, value: None +# else: +# if "putenv" not in __all__: +# __all__.append("putenv") + +try: + _unsetenv = unsetenv +except NameError: + _unsetenv = lambda key: _putenv(key, "") +# else: +# if "unsetenv" not in __all__: +# __all__.append("unsetenv") + +def _createenviron(): + # if name == 'nt': + # # Where Env Var Names Must Be UPPERCASE + # def check_str(value): + # if not isinstance(value, str): + # raise TypeError("str expected, not %s" % type(value).__name__) + # return value + # encode = check_str + # decode = str + # def encodekey(key): + # return encode(key).upper() + # data = {} + # for key, value in environ.items(): + # data[encodekey(key)] = value + # else: + # # Where Env Var Names Can Be Mixed Case + # encoding = sys.getfilesystemencoding() + # def encode(value): + # if not isinstance(value, str): + # raise TypeError("str expected, not %s" % type(value).__name__) + # return value.encode(encoding, 'surrogateescape') + # def decode(value): + # return value.decode(encoding, 'surrogateescape') + # encodekey = encode + decode = str + encode = str + encodekey = encode + data = environ + return _Environ(data, + encodekey, decode, + encode, decode, + _putenv, _unsetenv) + +# unicode environ +environ = _createenviron() +del _createenviron + def getenv(key, default=None): """Get an environment variable, return None if it doesn't exist. The optional second argument can specify an alternate default. key, default and the result are str.""" - try: - return environ[key] - except KeyError: - return default + return environ.get(key, default) From 28cf4c001043490a14cc668efd2d5d8538b27430 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Thu, 11 Apr 2019 22:18:29 +0200 Subject: [PATCH 285/884] Symbol role scan fully covered. --- vm/src/compile.rs | 68 +++++++-- vm/src/symboltable.rs | 333 ++++++++++++++++++++++++++++++------------ 2 files changed, 298 insertions(+), 103 deletions(-) diff --git a/vm/src/compile.rs b/vm/src/compile.rs index cc52661a4f..2221fde7de 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -10,13 +10,14 @@ use crate::error::CompileError; use crate::obj::objcode; use crate::obj::objcode::PyCodeRef; use crate::pyobject::PyValue; -use crate::symboltable::SymbolTable; +use crate::symboltable::{make_symbol_table, statements_to_symbol_table, SymbolRole, SymbolScope}; use crate::VirtualMachine; use num_complex::Complex64; use rustpython_parser::{ast, parser}; struct Compiler { code_object_stack: Vec, + scope_stack: Vec, nxt_label: usize, source_path: Option, current_source_location: ast::Location, @@ -38,17 +39,18 @@ pub fn compile( match mode { Mode::Exec => { let ast = parser::parse_program(source).map_err(CompileError::Parse)?; - let mut symbol_table = SymbolTable::new(); - symbol_table.scan_program(&ast); - compiler.compile_program(&ast) + let symbol_table = make_symbol_table(&ast); + compiler.compile_program(&ast, symbol_table) } Mode::Eval => { let statement = parser::parse_statement(source).map_err(CompileError::Parse)?; - compiler.compile_statement_eval(&statement) + let symbol_table = statements_to_symbol_table(&statement); + compiler.compile_statement_eval(&statement, symbol_table) } Mode::Single => { let ast = parser::parse_program(source).map_err(CompileError::Parse)?; - compiler.compile_program_single(&ast) + let symbol_table = make_symbol_table(&ast); + compiler.compile_program_single(&ast, symbol_table) } }?; @@ -75,6 +77,7 @@ impl Compiler { fn new() -> Self { Compiler { code_object_stack: Vec::new(), + scope_stack: Vec::new(), nxt_label: 0, source_path: None, current_source_location: ast::Location::default(), @@ -97,11 +100,17 @@ impl Compiler { } fn pop_code_object(&mut self) -> CodeObject { + // self.scope_stack.pop().unwrap(); self.code_object_stack.pop().unwrap() } - fn compile_program(&mut self, program: &ast::Program) -> Result<(), CompileError> { + fn compile_program( + &mut self, + program: &ast::Program, + symbol_scope: SymbolScope, + ) -> Result<(), CompileError> { let size_before = self.code_object_stack.len(); + self.scope_stack.push(symbol_scope); self.compile_statements(&program.statements)?; assert!(self.code_object_stack.len() == size_before); @@ -113,7 +122,12 @@ impl Compiler { Ok(()) } - fn compile_program_single(&mut self, program: &ast::Program) -> Result<(), CompileError> { + fn compile_program_single( + &mut self, + program: &ast::Program, + symbol_scope: SymbolScope, + ) -> Result<(), CompileError> { + self.scope_stack.push(symbol_scope); for statement in &program.statements { if let ast::Statement::Expression { ref expression } = statement.node { self.compile_expression(expression)?; @@ -133,7 +147,9 @@ impl Compiler { fn compile_statement_eval( &mut self, statements: &[ast::LocatedStatement], + symbol_table: SymbolScope, ) -> Result<(), CompileError> { + self.scope_stack.push(symbol_table); for statement in statements { if let ast::Statement::Expression { ref expression } = statement.node { self.compile_expression(expression)?; @@ -464,6 +480,7 @@ impl Compiler { line_number, name.to_string(), )); + self.enter_scope(); let mut flags = bytecode::FunctionOpArg::empty(); if have_defaults { @@ -624,6 +641,7 @@ impl Compiler { }); self.emit(Instruction::ReturnValue); let code = self.pop_code_object(); + self.leave_scope(); // Prepare type annotations: let mut num_annotations = 0; @@ -708,6 +726,7 @@ impl Compiler { line_number, name.to_string(), )); + self.enter_scope(); let (new_body, doc_str) = get_doc(body); @@ -718,6 +737,7 @@ impl Compiler { self.emit(Instruction::ReturnValue); let code = self.pop_code_object(); + self.leave_scope(); self.emit(Instruction::LoadConst { value: bytecode::Constant::Code { @@ -1212,6 +1232,8 @@ impl Compiler { }); } ast::Expression::Identifier { name } => { + self.lookup_name(name); + // TODO: if global, do something else! self.emit(Instruction::LoadName { name: name.to_string(), }); @@ -1223,6 +1245,7 @@ impl Compiler { self.compile_expression(body)?; self.emit(Instruction::ReturnValue); let code = self.pop_code_object(); + self.leave_scope(); self.emit(Instruction::LoadConst { value: bytecode::Constant::Code { code: Box::new(code), @@ -1569,6 +1592,30 @@ impl Compiler { Ok(()) } + // Scope helpers: + fn enter_scope(&mut self) { + // println!("Enter scope {:?}", self.scope_stack); + // Enter first subscope! + let scope = self.scope_stack.last_mut().unwrap().sub_scopes.remove(0); + self.scope_stack.push(scope); + } + + fn leave_scope(&mut self) { + // println!("Leave scope {:?}", self.scope_stack); + let scope = self.scope_stack.pop().unwrap(); + assert!(scope.sub_scopes.is_empty()); + } + + fn lookup_name(&self, name: &str) -> &SymbolRole { + // println!("Looking up {:?}", name); + for scope in self.scope_stack.iter().rev() { + if let Some(role) = scope.lookup(name) { + return role; + } + } + unreachable!(); + } + // Low level helper functions: fn emit(&mut self, instruction: Instruction) { let location = self.current_source_location.clone(); @@ -1624,13 +1671,13 @@ fn get_doc(body: &[ast::LocatedStatement]) -> (&[ast::LocatedStatement], Option< (body, None) } - #[cfg(test)] mod tests { use super::Compiler; use crate::bytecode::CodeObject; use crate::bytecode::Constant::*; use crate::bytecode::Instruction::*; + use crate::symboltable::make_symbol_table; use rustpython_parser::parser; fn compile_exec(source: &str) -> CodeObject { @@ -1638,7 +1685,8 @@ mod tests { compiler.source_path = Some("source_path".to_string()); compiler.push_new_code_object("".to_string()); let ast = parser::parse_program(&source.to_string()).unwrap(); - compiler.compile_program(&ast).unwrap(); + let symbol_scope = make_symbol_table(&ast); + compiler.compile_program(&ast, symbol_scope).unwrap(); compiler.pop_code_object() } diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs index 6525e71e14..78655da53a 100644 --- a/vm/src/symboltable.rs +++ b/vm/src/symboltable.rs @@ -8,6 +8,23 @@ load and store instructions for names. use rustpython_parser::ast; use std::collections::HashMap; +pub fn make_symbol_table(program: &ast::Program) -> SymbolScope { + let mut builder = SymbolTableBuilder::new(); + builder.enter_scope(); + builder.scan_program(program).unwrap(); + assert!(builder.scopes.len() == 1); + builder.scopes.pop().unwrap() +} + +pub fn statements_to_symbol_table(statements: &[ast::LocatedStatement]) -> SymbolScope { + let mut builder = SymbolTableBuilder::new(); + builder.enter_scope(); + builder.scan_statements(statements).unwrap(); + assert!(builder.scopes.len() == 1); + builder.scopes.pop().unwrap() +} + +#[derive(Debug)] pub enum SymbolRole { Global, Nonlocal, @@ -15,42 +32,96 @@ pub enum SymbolRole { Assigned, } -pub struct SymbolTable { - // TODO: split-up into nested scopes. - symbols: HashMap, +/// Symbolscope captures all symbols in the current scope, and +/// has a list of subscopes in this scope. +pub struct SymbolScope { + /// A set of symbols present on this scope level. + pub symbols: HashMap, + + /// A list of subscopes in the order as found in the + /// AST nodes. + pub sub_scopes: Vec, } -impl SymbolTable { +type SymbolTableResult = Result<(), String>; + +impl SymbolScope { pub fn new() -> Self { - SymbolTable { + SymbolScope { symbols: HashMap::new(), + sub_scopes: vec![], } } pub fn lookup(&self, name: &str) -> Option<&SymbolRole> { - return self.symbols.get(name); + self.symbols.get(name) } +} - pub fn scan_program(&mut self, program: &ast::Program) { - self.scan_statements(&program.statements); +impl std::fmt::Debug for SymbolScope { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "SymbolScope({:?} symbols, {:?} sub scopes)", + self.symbols.len(), + self.sub_scopes.len() + ) + } +} + +pub struct SymbolTableBuilder { + // Scope stack. + pub scopes: Vec, +} + +impl SymbolTableBuilder { + pub fn new() -> Self { + SymbolTableBuilder { scopes: vec![] } } - pub fn scan_statements(&mut self, statements: &[ast::LocatedStatement]) { + pub fn enter_scope(&mut self) { + // Create new scope and push into scope stack. + let scope = SymbolScope::new(); + self.scopes.push(scope); + } + + pub fn leave_scope(&mut self) { + // Pop scope and add to subscopes of parent scope. + let scope = self.scopes.pop().unwrap(); + self.scopes.last_mut().unwrap().sub_scopes.push(scope); + } + + pub fn scan_program(&mut self, program: &ast::Program) -> SymbolTableResult { + self.scan_statements(&program.statements)?; + Ok(()) + } + + pub fn scan_statements(&mut self, statements: &[ast::LocatedStatement]) -> SymbolTableResult { for statement in statements { - self.scan_statement(statement) + self.scan_statement(statement)?; } + Ok(()) } - fn scan_statement(&mut self, statement: &ast::LocatedStatement) { + fn scan_parameters(&mut self, parameters: &[ast::Parameter]) -> SymbolTableResult { + for parameter in parameters { + if let Some(annotation) = ¶meter.annotation { + self.scan_expression(&annotation)?; + } + } + Ok(()) + } + + fn scan_statement(&mut self, statement: &ast::LocatedStatement) -> SymbolTableResult { match &statement.node { ast::Statement::Global { names } => { for name in names { - self.register_name(name, SymbolRole::Global); + self.register_name(name, SymbolRole::Global)?; } } ast::Statement::Nonlocal { names } => { for name in names { - self.register_name(name, SymbolRole::Nonlocal); + self.register_name(name, SymbolRole::Nonlocal)?; } } ast::Statement::FunctionDef { @@ -60,22 +131,25 @@ impl SymbolTable { decorator_list, returns, } => { - self.scan_expressions(decorator_list); - self.register_name(name, SymbolRole::Assigned); - for parameter in &args.args {} - for parameter in &args.kwonlyargs {} + self.scan_expressions(decorator_list)?; + self.register_name(name, SymbolRole::Assigned)?; + + self.enter_scope(); + self.scan_parameters(&args.args)?; + self.scan_parameters(&args.kwonlyargs)?; - self.scan_expressions(&args.defaults); + self.scan_expressions(&args.defaults)?; for kw_default in &args.kw_defaults { if let Some(expression) = kw_default { - self.scan_expression(&expression); + self.scan_expression(&expression)?; } } - self.scan_statements(body); + self.scan_statements(body)?; if let Some(expression) = returns { - self.scan_expression(expression); + self.scan_expression(expression)?; } + self.leave_scope(); } ast::Statement::ClassDef { name, @@ -84,20 +158,22 @@ impl SymbolTable { keywords, decorator_list, } => { - self.register_name(name, SymbolRole::Assigned); - self.scan_statements(body); - self.scan_expressions(bases); + self.register_name(name, SymbolRole::Assigned)?; + self.enter_scope(); + self.scan_statements(body)?; + self.leave_scope(); + self.scan_expressions(bases)?; for keyword in keywords { - self.scan_expression(&keyword.value); + self.scan_expression(&keyword.value)?; } - self.scan_expressions(decorator_list); + self.scan_expressions(decorator_list)?; } - ast::Statement::Expression { expression } => self.scan_expression(expression), + ast::Statement::Expression { expression } => self.scan_expression(expression)?, ast::Statement::If { test, body, orelse } => { - self.scan_expression(test); - self.scan_statements(body); + self.scan_expression(test)?; + self.scan_statements(body)?; if let Some(code) = orelse { - self.scan_statements(code); + self.scan_statements(code)?; } } ast::Statement::For { @@ -106,58 +182,66 @@ impl SymbolTable { body, orelse, } => { - self.scan_expression(target); - self.scan_expression(iter); - self.scan_statements(body); + self.scan_expression(target)?; + self.scan_expression(iter)?; + self.scan_statements(body)?; if let Some(code) = orelse { - self.scan_statements(code); + self.scan_statements(code)?; } } ast::Statement::While { test, body, orelse } => { - self.scan_expression(test); - self.scan_statements(body); + self.scan_expression(test)?; + self.scan_statements(body)?; if let Some(code) = orelse { - self.scan_statements(code); + self.scan_statements(code)?; } } ast::Statement::Break | ast::Statement::Continue | ast::Statement::Pass => { // No symbols here. } - ast::Statement::Import { import_parts } => for part in import_parts {}, + ast::Statement::Import { import_parts } => { + for part in import_parts { + if let Some(alias) = &part.alias { + self.register_name(alias, SymbolRole::Assigned)?; + } else { + if let Some(symbol) = &part.symbol { + self.register_name(symbol, SymbolRole::Assigned)?; + } else { + self.register_name(&part.module, SymbolRole::Assigned)?; + } + } + } + } ast::Statement::Return { value } => { if let Some(expression) = value { - self.scan_expression(expression); + self.scan_expression(expression)?; } } ast::Statement::Assert { test, msg } => { - self.scan_expression(test); + self.scan_expression(test)?; if let Some(expression) = msg { - self.scan_expression(expression); + self.scan_expression(expression)?; } } ast::Statement::Delete { targets } => { - self.scan_expressions(targets); + self.scan_expressions(targets)?; } ast::Statement::Assign { targets, value } => { - self.scan_expressions(targets); - self.scan_expression(value); + self.scan_expressions(targets)?; + self.scan_expression(value)?; } - ast::Statement::AugAssign { - target, - op: _, - value, - } => { - self.scan_expression(target); - self.scan_expression(value); + ast::Statement::AugAssign { target, value, .. } => { + self.scan_expression(target)?; + self.scan_expression(value)?; } ast::Statement::With { items, body } => { for item in items { - self.scan_expression(&item.context_expr); + self.scan_expression(&item.context_expr)?; if let Some(expression) = &item.optional_vars { - self.scan_expression(expression); + self.scan_expression(expression)?; } } - self.scan_statements(body); + self.scan_statements(body)?; } ast::Statement::Try { body, @@ -165,67 +249,78 @@ impl SymbolTable { orelse, finalbody, } => { - self.scan_statements(body); + self.scan_statements(body)?; + for handler in handlers { + if let Some(expression) = &handler.typ { + self.scan_expression(expression)?; + } + if let Some(name) = &handler.name { + self.register_name(name, SymbolRole::Assigned)?; + } + self.scan_statements(&handler.body)?; + } if let Some(code) = orelse { - self.scan_statements(code); + self.scan_statements(code)?; } if let Some(code) = finalbody { - self.scan_statements(code); + self.scan_statements(code)?; } } ast::Statement::Raise { exception, cause } => { if let Some(expression) = exception { - self.scan_expression(expression); + self.scan_expression(expression)?; } if let Some(expression) = cause { - self.scan_expression(expression); + self.scan_expression(expression)?; } } } + Ok(()) } - fn scan_expressions(&mut self, expressions: &[ast::Expression]) { + fn scan_expressions(&mut self, expressions: &[ast::Expression]) -> SymbolTableResult { for expression in expressions { - self.scan_expression(expression); + self.scan_expression(expression)?; } + Ok(()) } - fn scan_expression(&mut self, expression: &ast::Expression) { + fn scan_expression(&mut self, expression: &ast::Expression) -> SymbolTableResult { match expression { - ast::Expression::Binop { a, op: _, b } => { - self.scan_expression(a); - self.scan_expression(b); + ast::Expression::Binop { a, b, .. } => { + self.scan_expression(a)?; + self.scan_expression(b)?; } - ast::Expression::BoolOp { a, op: _, b } => { - self.scan_expression(a); - self.scan_expression(b); + ast::Expression::BoolOp { a, b, .. } => { + self.scan_expression(a)?; + self.scan_expression(b)?; } - ast::Expression::Compare { vals, ops: _ } => { - self.scan_expressions(vals); + ast::Expression::Compare { vals, .. } => { + self.scan_expressions(vals)?; } ast::Expression::Subscript { a, b } => { - self.scan_expression(a); - self.scan_expression(b); + self.scan_expression(a)?; + self.scan_expression(b)?; } - ast::Expression::Attribute { value, name: _ } => { - self.scan_expression(value); + ast::Expression::Attribute { value, .. } => { + self.scan_expression(value)?; } ast::Expression::Dict { elements } => { for (key, value) in elements { - self.scan_expression(key); - self.scan_expression(value); + self.scan_expression(key)?; + self.scan_expression(value)?; } } ast::Expression::Yield { value } => { if let Some(expression) = value { - self.scan_expression(expression); + self.scan_expression(expression)?; } } ast::Expression::YieldFrom { value } => { - self.scan_expression(value); + self.scan_expression(value)?; } - ast::Expression::Unop { op: _, a } => { - self.scan_expression(a); + ast::Expression::Unop { a, .. } => { + self.scan_expression(a)?; } ast::Expression::True | ast::Expression::False @@ -233,40 +328,92 @@ impl SymbolTable { | ast::Expression::Ellipsis => {} ast::Expression::Number { .. } => {} ast::Expression::Starred { value } => { - self.scan_expression(value); + self.scan_expression(value)?; } ast::Expression::Bytes { .. } => {} ast::Expression::Tuple { elements } | ast::Expression::Set { elements } | ast::Expression::List { elements } | ast::Expression::Slice { elements } => { - self.scan_expressions(elements); + self.scan_expressions(elements)?; + } + ast::Expression::Comprehension { kind, generators } => { + match **kind { + ast::ComprehensionKind::GeneratorExpression { ref element } + | ast::ComprehensionKind::List { ref element } + | ast::ComprehensionKind::Set { ref element } => { + self.scan_expression(element)?; + } + ast::ComprehensionKind::Dict { ref key, ref value } => { + self.scan_expression(&key)?; + self.scan_expression(&value)?; + } + } + + for generator in generators { + self.scan_expression(&generator.target)?; + self.scan_expression(&generator.iter)?; + for if_expr in &generator.ifs { + self.scan_expression(if_expr)?; + } + } } - ast::Expression::Comprehension { kind, generators } => {} ast::Expression::Call { function, args, keywords, } => { - self.scan_expression(function); - self.scan_expressions(args); + self.scan_expression(function)?; + self.scan_expressions(args)?; + for keyword in keywords { + self.scan_expression(&keyword.value)?; + } + } + ast::Expression::String { value } => { + self.scan_string_group(value)?; } - ast::Expression::String { value } => {} ast::Expression::Identifier { name } => { - self.register_name(name, SymbolRole::Used); + self.register_name(name, SymbolRole::Used)?; } ast::Expression::Lambda { args, body } => { - self.scan_expression(body); + self.scan_parameters(&args.args)?; + self.scan_parameters(&args.kwonlyargs)?; + self.enter_scope(); + self.scan_expression(body)?; + self.leave_scope(); } ast::Expression::IfExpression { test, body, orelse } => { - self.scan_expression(test); - self.scan_expression(body); - self.scan_expression(orelse); + self.scan_expression(test)?; + self.scan_expression(body)?; + self.scan_expression(orelse)?; + } + } + Ok(()) + } + + fn scan_string_group(&mut self, group: &ast::StringGroup) -> SymbolTableResult { + match group { + ast::StringGroup::Constant { .. } => {} + ast::StringGroup::FormattedValue { value, .. } => { + self.scan_expression(value)?; + } + ast::StringGroup::Joined { values } => { + for subgroup in values { + self.scan_string_group(subgroup)?; + } } } + Ok(()) } - fn register_name(&mut self, name: &String, role: SymbolRole) { - self.symbols.insert(name.clone(), role); + fn register_name(&mut self, name: &str, role: SymbolRole) -> SymbolTableResult { + let current_scope = self.scopes.last_mut().unwrap(); + if let Some(old_role) = current_scope.symbols.get(name) { + debug!("TODO: {:?}", old_role); + // Role already set.. + } else { + current_scope.symbols.insert(name.to_string(), role); + } + Ok(()) } } From 6c4b09264175e93f9334ab3048fe3d8ea8ce2da6 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 12 Apr 2019 10:50:45 +0300 Subject: [PATCH 286/884] Support exception __cause__ --- tests/snippets/try_exceptions.py | 32 +++++++++++++++++++ vm/src/frame.rs | 54 +++++++++++++++++++------------- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index 4e3b7269aa..5d7509ba5e 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -95,3 +95,35 @@ def __init__(self): l.append(3) print('boom', type(ex)) assert l == [1, 3] + +cause = None +try: + try: + raise ZeroDivisionError + except ZeroDivisionError as ex: + assert ex.__cause__ == None + cause = ex + raise NameError from ex +except NameError as ex2: + assert ex2.__cause__ == cause + +try: + raise ZeroDivisionError from None +except ZeroDivisionError as ex: + assert ex.__cause__ == None + +try: + raise ZeroDivisionError +except ZeroDivisionError as ex: + assert ex.__cause__ == None + +try: + raise ZeroDivisionError from 5 +except TypeError: + pass + +try: + raise ZeroDivisionError from NameError +except ZeroDivisionError as ex: + assert type(ex.__cause__) == NameError + diff --git a/vm/src/frame.rs b/vm/src/frame.rs index ff9baba358..a3dfd6032d 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -677,31 +677,17 @@ impl Frame { } bytecode::Instruction::Raise { argc } => { + let cause = match argc { + 2 => self.get_exception(vm, true)?, + _ => vm.get_none(), + }; let exception = match argc { - 1 => self.pop_value(), - 0 | 2 | 3 => panic!("Not implemented!"), + 1 | 2 => self.get_exception(vm, false)?, + 0 | 3 => panic!("Not implemented!"), _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"), }; - if objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { - info!("Exception raised: {:?}", exception); - Err(exception) - } else if let Ok(exception) = PyClassRef::try_from_object(vm, exception) { - if objtype::issubclass(&exception, &vm.ctx.exceptions.base_exception_type) { - let exception = vm.new_empty_exception(exception)?; - info!("Exception raised: {:?}", exception); - Err(exception) - } else { - let msg = format!( - "Can only raise BaseException derived types, not {}", - exception - ); - let type_error_type = vm.ctx.exceptions.type_error.clone(); - let type_error = vm.new_exception(type_error_type, msg); - Err(type_error) - } - } else { - Err(vm.new_type_error("exceptions must derive from BaseException".to_string())) - } + vm.set_attr(&exception, vm.new_str("__cause__".to_string()), cause)?; + Err(exception) } bytecode::Instruction::Break => { @@ -1204,6 +1190,30 @@ impl Frame { let stack = self.stack.borrow_mut(); stack[stack.len() - depth - 1].clone() } + + fn get_exception(&self, vm: &VirtualMachine, none_allowed: bool) -> PyResult { + let exception = self.pop_value(); + if none_allowed && vm.get_none().is(&exception) { + Ok(exception) + } else if objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { + Ok(exception) + } else if let Ok(exception) = PyClassRef::try_from_object(vm, exception) { + if objtype::issubclass(&exception, &vm.ctx.exceptions.base_exception_type) { + let exception = vm.new_empty_exception(exception)?; + Ok(exception) + } else { + let msg = format!( + "Can only raise BaseException derived types, not {}", + exception + ); + let type_error_type = vm.ctx.exceptions.type_error.clone(); + let type_error = vm.new_exception(type_error_type, msg); + Err(type_error) + } + } else { + Err(vm.new_type_error("exceptions must derive from BaseException".to_string())) + } + } } impl fmt::Debug for Frame { From 63643e1fdba7a621102553597aae8f20bbda5b6a Mon Sep 17 00:00:00 2001 From: ben Date: Fri, 12 Apr 2019 20:30:37 +1200 Subject: [PATCH 287/884] Implement generator.throw --- tests/snippets/generators.py | 22 +++++++++++++ vm/src/frame.rs | 11 +++++++ vm/src/obj/objgenerator.rs | 64 ++++++++++++++++++++++++------------ vm/src/vm.rs | 15 +++++++-- 4 files changed, 89 insertions(+), 23 deletions(-) diff --git a/tests/snippets/generators.py b/tests/snippets/generators.py index 65044c0da9..3b7dc77c39 100644 --- a/tests/snippets/generators.py +++ b/tests/snippets/generators.py @@ -1,3 +1,5 @@ +from testutils import assertRaises + r = [] @@ -42,3 +44,23 @@ def g4(): r = list(g4()) assert r == [None, (2,)] + +def catch_exception(): + try: + yield 1 + except ValueError: + yield 2 + yield 3 + + +g = catch_exception() +assert next(g) == 1 + +assert g.throw(ValueError, ValueError(), None) == 2 +assert next(g) == 3 + +g = catch_exception() +assert next(g) == 1 + +with assertRaises(KeyError): + assert g.throw(KeyError, KeyError(), None) == 2 diff --git a/vm/src/frame.rs b/vm/src/frame.rs index ff9baba358..17322f21f2 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -283,6 +283,17 @@ impl Frame { } } + pub fn throw( + &self, + vm: &VirtualMachine, + exception: PyObjectRef, + ) -> Result { + match self.unwind_exception(vm, exception) { + None => self.run(vm), + Some(exception) => Err(exception), + } + } + pub fn fetch_instruction(&self) -> &bytecode::Instruction { let ins2 = &self.code.instructions[*self.lasti.borrow()]; *self.lasti.borrow_mut() += 1; diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index 9718d29a44..2541263f1a 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -3,12 +3,13 @@ */ use crate::frame::{ExecutionResult, FrameRef}; -use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::obj::objtype::{isinstance, PyClassRef}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; pub type PyGeneratorRef = PyRef; +#[pyclass(name = "generator", __inside_vm)] #[derive(Debug)] pub struct PyGenerator { frame: FrameRef, @@ -20,38 +21,59 @@ impl PyValue for PyGenerator { } } -impl PyGeneratorRef { +#[pyimpl(__inside_vm)] +impl PyGenerator { pub fn new(frame: FrameRef, vm: &VirtualMachine) -> PyGeneratorRef { PyGenerator { frame }.into_ref(vm) } - fn iter(self, _vm: &VirtualMachine) -> PyGeneratorRef { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyGeneratorRef, _vm: &VirtualMachine) -> PyGeneratorRef { + zelf } - fn next(self, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { self.send(vm.get_none(), vm) } - fn send(self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn send(&self, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.frame.push_value(value.clone()); - match vm.run_frame(self.frame.clone())? { - ExecutionResult::Yield(value) => Ok(value), - ExecutionResult::Return(_value) => { - // Stop iteration! - let stop_iteration = vm.ctx.exceptions.stop_iteration.clone(); - Err(vm.new_exception(stop_iteration, "End of generator".to_string())) - } + let result = vm.run_frame(self.frame.clone())?; + handle_execution_result(result, vm) + } + + #[pymethod] + fn throw( + &self, + _exc_type: PyObjectRef, + exc_val: PyObjectRef, + _exc_tb: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + // TODO what should we do with the other parameters? CPython normalises them with + // PyErr_NormalizeException, do we want to do the same. + if !isinstance(&exc_val, &vm.ctx.exceptions.base_exception_type) { + return Err(vm.new_type_error("Can't throw non exception".to_string())); + } + let result = vm.frame_throw(self.frame.clone(), exc_val)?; + handle_execution_result(result, vm) + } +} + +fn handle_execution_result(result: ExecutionResult, vm: &VirtualMachine) -> PyResult { + match result { + ExecutionResult::Yield(value) => Ok(value), + ExecutionResult::Return(_value) => { + // Stop iteration! + let stop_iteration = vm.ctx.exceptions.stop_iteration.clone(); + Err(vm.new_exception(stop_iteration, "End of generator".to_string())) } } } -pub fn init(context: &PyContext) { - let generator_type = &context.generator_type; - extend_class!(context, generator_type, { - "__iter__" => context.new_rustfunc(PyGeneratorRef::iter), - "__next__" => context.new_rustfunc(PyGeneratorRef::next), - "send" => context.new_rustfunc(PyGeneratorRef::send) - }); +pub fn init(ctx: &PyContext) { + PyGenerator::extend_class(ctx, &ctx.generator_type); } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 7be05023be..5ae5b0b50c 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -21,7 +21,7 @@ use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode::PyCodeRef; use crate::obj::objdict::PyDictRef; use crate::obj::objfunction::{PyFunction, PyMethod}; -use crate::obj::objgenerator::PyGeneratorRef; +use crate::obj::objgenerator::PyGenerator; use crate::obj::objiter; use crate::obj::objsequence; use crate::obj::objstr::{PyString, PyStringRef}; @@ -94,6 +94,17 @@ impl VirtualMachine { result } + pub fn frame_throw( + &self, + frame: FrameRef, + exception: PyObjectRef, + ) -> PyResult { + self.frames.borrow_mut().push(frame.clone()); + let result = frame.throw(self, exception); + self.frames.borrow_mut().pop(); + result + } + pub fn current_frame(&self) -> Option> { let frames = self.frames.borrow(); if frames.is_empty() { @@ -374,7 +385,7 @@ impl VirtualMachine { // If we have a generator, create a new generator if code.code.is_generator { - Ok(PyGeneratorRef::new(frame, self).into_object()) + Ok(PyGenerator::new(frame, self).into_object()) } else { self.run_frame_full(frame) } From 80ec464d84f6c5ad931fad41c7c84e64f9a82c45 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Fri, 12 Apr 2019 12:25:19 +0100 Subject: [PATCH 288/884] Use dict.update to combine class and object attrs in object_dir. --- vm/src/obj/objdict.rs | 13 +++++++++++++ vm/src/obj/objobject.rs | 27 ++++++++++----------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index be85c7b2fb..efd38a7fd4 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -239,6 +239,19 @@ impl PyDictRef { attrs } + pub fn from_attributes(attrs: PyAttributes, vm: &VirtualMachine) -> PyResult { + let dict = DictContentType::default(); + let entries = RefCell::new(dict); + + for (key, value) in attrs { + entries + .borrow_mut() + .insert(vm, &vm.ctx.new_str(key), value)?; + } + + Ok(PyDict { entries }.into_ref(vm)) + } + fn hash(self, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error("unhashable type".to_string())) } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 8f6c90a36f..f02bbabd17 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -1,6 +1,6 @@ use super::objdict::PyDictRef; use super::objlist::PyList; -use super::objstr::{PyString, PyStringRef}; +use super::objstr::PyStringRef; use super::objtype; use crate::function::PyFuncArgs; use crate::obj::objproperty::PropertyBuilder; @@ -118,26 +118,19 @@ fn object_repr(zelf: PyObjectRef, _vm: &VirtualMachine) -> String { } pub fn object_dir(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let mut attributes: PyAttributes = objtype::get_attributes(obj.class()); + let attributes: PyAttributes = objtype::get_attributes(obj.class()); + + let dict = PyDictRef::from_attributes(attributes, vm)?; // Get instance attributes: - if let Some(dict) = &obj.dict { - for (key, value) in dict { - if let Some(key_string) = key.payload::() { - attributes.insert(key_string.to_string(), value.clone()); - } else { - return Err(vm.new_type_error(format!( - "Attribute is not a string: {:?}", - vm.to_pystr(&key)? - ))); - } - } + if let Some(object_dict) = &obj.dict { + vm.invoke( + vm.get_attribute(dict.clone().into_object(), "update")?, + object_dict.clone().into_object(), + )?; } - let attributes: Vec = attributes - .keys() - .map(|k| vm.ctx.new_str(k.to_string())) - .collect(); + let attributes: Vec<_> = dict.into_iter().map(|(k, _v)| k.clone()).collect(); Ok(PyList::from(attributes)) } From 908f758cbedcb0bddb3f5b32c99aa4855da668eb Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 12 Apr 2019 15:50:17 +0300 Subject: [PATCH 289/884] Print cause on exception --- vm/src/exceptions.rs | 15 +++++++++++++-- vm/src/frame.rs | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index d81764e250..abd670a149 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -2,7 +2,7 @@ use crate::function::PyFuncArgs; use crate::obj::objsequence; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{create_type, PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{create_type, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -18,8 +18,19 @@ fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } -// Print exception including traceback: +// print excption chain pub fn print_exception(vm: &VirtualMachine, exc: &PyObjectRef) { + if let Ok(cause) = vm.get_attribute(exc.clone(), "__cause__") { + if !vm.get_none().is(&cause) { + print_exception(vm, &cause); + println!("\nThe above exception was the direct cause of the following exception:\n"); + } + } + print_exception_inner(vm, exc) +} + +// Print exception including traceback: +pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) { if let Ok(tb) = vm.get_attribute(exc.clone(), "__traceback__") { println!("Traceback (most recent call last):"); if objtype::isinstance(&tb, &vm.ctx.list_type()) { diff --git a/vm/src/frame.rs b/vm/src/frame.rs index a3dfd6032d..a1da85d3a6 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -686,6 +686,7 @@ impl Frame { 0 | 3 => panic!("Not implemented!"), _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"), }; + info!("Exception raised: {:?} with cause: {:?}", exception, cause); vm.set_attr(&exception, vm.new_str("__cause__".to_string()), cause)?; Err(exception) } From 76c597f2afc21edc1c6a5158120b619cb88e7f49 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Fri, 12 Apr 2019 19:24:23 +0200 Subject: [PATCH 290/884] Add global bytecode and testcase. --- tests/snippets/global_nonlocal.py | 13 ++++++ vm/src/bytecode.rs | 12 ++++- vm/src/compile.rs | 73 ++++++++++++++++++------------- vm/src/frame.rs | 60 ++++++++++++++++++------- vm/src/symboltable.rs | 59 +++++++++++++++++-------- 5 files changed, 148 insertions(+), 69 deletions(-) create mode 100644 tests/snippets/global_nonlocal.py diff --git a/tests/snippets/global_nonlocal.py b/tests/snippets/global_nonlocal.py new file mode 100644 index 0000000000..48d1242108 --- /dev/null +++ b/tests/snippets/global_nonlocal.py @@ -0,0 +1,13 @@ + +# Test global and nonlocal funkyness + +a = 2 + +def b(): + global a + a = 4 + +assert a == 2 +b() +assert a == 4 + diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index b248018616..2725aa5860 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -38,6 +38,12 @@ bitflags! { pub type Label = usize; +#[derive(Debug, Clone, PartialEq)] +pub enum NameScope { + Local, + Global, +} + /// A Single bytecode instruction. #[derive(Debug, Clone, PartialEq)] pub enum Instruction { @@ -50,9 +56,11 @@ pub enum Instruction { }, LoadName { name: String, + scope: NameScope, }, StoreName { name: String, + scope: NameScope, }, DeleteName { name: String, @@ -322,8 +330,8 @@ impl Instruction { match self { Import { name, symbol } => w!(Import, name, format!("{:?}", symbol)), ImportStar { name } => w!(ImportStar, name), - LoadName { name } => w!(LoadName, name), - StoreName { name } => w!(StoreName, name), + LoadName { name, scope } => w!(LoadName, name, format!("{:?}", scope)), + StoreName { name, scope } => w!(StoreName, name, format!("{:?}", scope)), DeleteName { name } => w!(DeleteName, name), StoreSubscript => w!(StoreSubscript), DeleteSubscript => w!(DeleteSubscript), diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 2221fde7de..31e43c04d5 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -171,6 +171,31 @@ impl Compiler { Ok(()) } + fn scope_for_name(&self, name: &str) -> bytecode::NameScope { + let role = self.lookup_name(name); + match role { + SymbolRole::Global => bytecode::NameScope::Global, + _ => bytecode::NameScope::Local, + } + } + + fn load_name(&mut self, name: &str) { + // TODO: if global, do something else! + let scope = self.scope_for_name(name); + self.emit(Instruction::LoadName { + name: name.to_string(), + scope, + }); + } + + fn store_name(&mut self, name: &str) { + let scope = self.scope_for_name(name); + self.emit(Instruction::StoreName { + name: name.to_string(), + scope, + }); + } + fn compile_statement(&mut self, statement: &ast::LocatedStatement) -> Result<(), CompileError> { trace!("Compiling {:?}", statement); self.set_source_location(&statement.location); @@ -194,15 +219,14 @@ impl Compiler { name: module.clone(), symbol: symbol.clone(), }); - self.emit(Instruction::StoreName { - name: match alias { - Some(alias) => alias.clone(), - None => match symbol { - Some(symbol) => symbol.clone(), - None => module.clone(), - }, + let name = match alias { + Some(alias) => alias.clone(), + None => match symbol { + Some(symbol) => symbol.clone(), + None => module.clone(), }, - }); + }; + self.store_name(&name); } } } @@ -337,6 +361,7 @@ impl Compiler { self.compile_test(test, Some(end_label), None, EvalContext::Statement)?; self.emit(Instruction::LoadName { name: String::from("AssertionError"), + scope: bytecode::NameScope::Local, }); match msg { Some(e) => { @@ -544,6 +569,7 @@ impl Compiler { // Check exception type: self.emit(Instruction::LoadName { name: String::from("isinstance"), + scope: bytecode::NameScope::Local, }); self.emit(Instruction::Rotate { amount: 2 }); self.compile_expression(exc_type)?; @@ -558,9 +584,7 @@ impl Compiler { // We have a match, store in name (except x as y) if let Some(alias) = &handler.name { - self.emit(Instruction::StoreName { - name: alias.clone(), - }); + self.store_name(alias); } else { // Drop exception from top of stack: self.emit(Instruction::Pop); @@ -695,9 +719,7 @@ impl Compiler { self.store_docstring(doc_str); self.apply_decorators(decorator_list); - self.emit(Instruction::StoreName { - name: name.to_string(), - }); + self.store_name(name); self.in_loop = was_in_loop; self.in_function_def = was_in_function_def; @@ -796,9 +818,7 @@ impl Compiler { self.store_docstring(doc_str); self.apply_decorators(decorator_list); - self.emit(Instruction::StoreName { - name: name.to_string(), - }); + self.store_name(name); self.in_loop = was_in_loop; Ok(()) } @@ -943,9 +963,7 @@ impl Compiler { fn compile_store(&mut self, target: &ast::Expression) -> Result<(), CompileError> { match target { ast::Expression::Identifier { name } => { - self.emit(Instruction::StoreName { - name: name.to_string(), - }); + self.store_name(name); } ast::Expression::Subscript { a, b } => { self.compile_expression(a)?; @@ -1232,11 +1250,7 @@ impl Compiler { }); } ast::Expression::Identifier { name } => { - self.lookup_name(name); - // TODO: if global, do something else! - self.emit(Instruction::LoadName { - name: name.to_string(), - }); + self.load_name(name); } ast::Expression::Lambda { args, body } => { let name = "".to_string(); @@ -1453,6 +1467,7 @@ impl Compiler { // Load iterator onto stack (passed as first argument): self.emit(Instruction::LoadName { name: String::from(".0"), + scope: bytecode::NameScope::Local, }); } else { // Evaluate iterated item: @@ -1608,12 +1623,8 @@ impl Compiler { fn lookup_name(&self, name: &str) -> &SymbolRole { // println!("Looking up {:?}", name); - for scope in self.scope_stack.iter().rev() { - if let Some(role) = scope.lookup(name) { - return role; - } - } - unreachable!(); + let scope = self.scope_stack.last().unwrap(); + scope.lookup(name).unwrap() } // Low level helper functions: diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 2570c09345..a72ed6a763 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -315,8 +315,14 @@ impl Frame { ref symbol, } => self.import(vm, name, symbol), bytecode::Instruction::ImportStar { ref name } => self.import_star(vm, name), - bytecode::Instruction::LoadName { ref name } => self.load_name(vm, name), - bytecode::Instruction::StoreName { ref name } => self.store_name(vm, name), + bytecode::Instruction::LoadName { + ref name, + ref scope, + } => self.load_name(vm, name, scope), + bytecode::Instruction::StoreName { + ref name, + ref scope, + } => self.store_name(vm, name, scope), bytecode::Instruction::DeleteName { ref name } => self.delete_name(vm, name), bytecode::Instruction::StoreSubscript => self.execute_store_subscript(vm), bytecode::Instruction::DeleteSubscript => self.execute_delete_subscript(vm), @@ -984,9 +990,21 @@ impl Frame { vm.call_method(context_manager, "__exit__", args) } - fn store_name(&self, vm: &VirtualMachine, name: &str) -> FrameResult { + fn store_name( + &self, + vm: &VirtualMachine, + name: &str, + scope: &bytecode::NameScope, + ) -> FrameResult { let obj = self.pop_value(); - self.scope.store_name(&vm, name, obj); + match scope { + bytecode::NameScope::Global => { + self.scope.globals.set_item(name, obj, vm)?; + } + bytecode::NameScope::Local => { + self.scope.store_name(&vm, name, obj); + } + } Ok(None) } @@ -995,19 +1013,27 @@ impl Frame { Ok(None) } - fn load_name(&self, vm: &VirtualMachine, name: &str) -> FrameResult { - match self.scope.load_name(&vm, name) { - Some(value) => { - self.push_value(value); - Ok(None) - } - None => { - let name_error_type = vm.ctx.exceptions.name_error.clone(); - let msg = format!("name '{}' is not defined", name); - let name_error = vm.new_exception(name_error_type, msg); - Err(name_error) - } - } + fn load_name( + &self, + vm: &VirtualMachine, + name: &str, + scope: &bytecode::NameScope, + ) -> FrameResult { + let value = match scope { + bytecode::NameScope::Global => self.scope.globals.get_item(name, vm)?, + bytecode::NameScope::Local => match self.scope.load_name(&vm, name) { + Some(value) => value, + None => { + let name_error_type = vm.ctx.exceptions.name_error.clone(); + let msg = format!("name '{}' is not defined", name); + let name_error = vm.new_exception(name_error_type, msg); + return Err(name_error); + } + }, + }; + + self.push_value(value); + Ok(None) } fn execute_store_subscript(&self, vm: &VirtualMachine) -> FrameResult { diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs index 78655da53a..2d6b498840 100644 --- a/vm/src/symboltable.rs +++ b/vm/src/symboltable.rs @@ -105,9 +105,14 @@ impl SymbolTableBuilder { fn scan_parameters(&mut self, parameters: &[ast::Parameter]) -> SymbolTableResult { for parameter in parameters { - if let Some(annotation) = ¶meter.annotation { - self.scan_expression(&annotation)?; - } + self.scan_parameter(parameter)?; + } + Ok(()) + } + + fn scan_parameter(&mut self, parameter: &ast::Parameter) -> SymbolTableResult { + if let Some(annotation) = ¶meter.annotation { + self.scan_expression(&annotation)?; } Ok(()) } @@ -134,16 +139,7 @@ impl SymbolTableBuilder { self.scan_expressions(decorator_list)?; self.register_name(name, SymbolRole::Assigned)?; - self.enter_scope(); - self.scan_parameters(&args.args)?; - self.scan_parameters(&args.kwonlyargs)?; - - self.scan_expressions(&args.defaults)?; - for kw_default in &args.kw_defaults { - if let Some(expression) = kw_default { - self.scan_expression(&expression)?; - } - } + self.enter_function(args)?; self.scan_statements(body)?; if let Some(expression) = returns { @@ -376,9 +372,7 @@ impl SymbolTableBuilder { self.register_name(name, SymbolRole::Used)?; } ast::Expression::Lambda { args, body } => { - self.scan_parameters(&args.args)?; - self.scan_parameters(&args.kwonlyargs)?; - self.enter_scope(); + self.enter_function(args)?; self.scan_expression(body)?; self.leave_scope(); } @@ -391,6 +385,27 @@ impl SymbolTableBuilder { Ok(()) } + fn enter_function(&mut self, args: &ast::Parameters) -> SymbolTableResult { + // Evaulate eventual default parameters: + self.scan_expressions(&args.defaults)?; + for kw_default in &args.kw_defaults { + if let Some(expression) = kw_default { + self.scan_expression(&expression)?; + } + } + + self.enter_scope(); + self.scan_parameters(&args.args)?; + self.scan_parameters(&args.kwonlyargs)?; + if let ast::Varargs::Named(name) = &args.vararg { + self.scan_parameter(name)?; + } + if let ast::Varargs::Named(name) = &args.kwarg { + self.scan_parameter(name)?; + } + Ok(()) + } + fn scan_string_group(&mut self, group: &ast::StringGroup) -> SymbolTableResult { match group { ast::StringGroup::Constant { .. } => {} @@ -408,9 +423,15 @@ impl SymbolTableBuilder { fn register_name(&mut self, name: &str, role: SymbolRole) -> SymbolTableResult { let current_scope = self.scopes.last_mut().unwrap(); - if let Some(old_role) = current_scope.symbols.get(name) { - debug!("TODO: {:?}", old_role); - // Role already set.. + if let Some(_old_role) = current_scope.symbols.get(name) { + // Role already set.. + // debug!("TODO: {:?}", old_role); + match role { + SymbolRole::Global => return Err("global must appear first".to_string()), + _ => { + // Ok? + } + } } else { current_scope.symbols.insert(name.to_string(), role); } From 585e6e5428c42e0615eb6990315ee0e39a6e5d40 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Fri, 12 Apr 2019 19:28:09 +0200 Subject: [PATCH 291/884] Drop IntoPyNativeFunc impl to fix Nightly/Beta breakages. --- vm/src/function.rs | 6 ------ wasm/lib/src/vm_class.rs | 37 ++++++++++++++++++------------------- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/vm/src/function.rs b/vm/src/function.rs index 305c4de5ea..514fe4cb6e 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -437,12 +437,6 @@ where } } -impl IntoPyNativeFunc for PyNativeFunc { - fn into_func(self) -> PyNativeFunc { - self - } -} - pub struct OwnedParam(std::marker::PhantomData); pub struct RefParam(std::marker::PhantomData); diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 5efa7c849b..ff72dddbb6 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -199,15 +199,15 @@ impl WASMVirtualMachine { fn error() -> JsValue { TypeError::new("Unknown stdout option, please pass a function or 'console'").into() } - let print_fn: Box PyResult> = - if let Some(s) = stdout.as_string() { - match s.as_str() { - "console" => Box::new(wasm_builtins::builtin_print_console), - _ => return Err(error()), - } - } else if stdout.is_function() { - let func = js_sys::Function::from(stdout); - Box::new(move |vm: &VirtualMachine, args: PyFuncArgs| -> PyResult { + let print_fn: PyObjectRef = if let Some(s) = stdout.as_string() { + match s.as_str() { + "console" => vm.ctx.new_rustfunc(wasm_builtins::builtin_print_console), + _ => return Err(error()), + } + } else if stdout.is_function() { + let func = js_sys::Function::from(stdout); + vm.ctx + .new_rustfunc(move |vm: &VirtualMachine, args: PyFuncArgs| -> PyResult { func.call1( &JsValue::UNDEFINED, &wasm_builtins::format_print_args(vm, args)?.into(), @@ -215,16 +215,15 @@ impl WASMVirtualMachine { .map_err(|err| convert::js_to_py(vm, err))?; Ok(vm.get_none()) }) - } else if stdout.is_undefined() || stdout.is_null() { - fn noop(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { - Ok(vm.get_none()) - } - Box::new(noop) - } else { - return Err(error()); - }; - vm.set_attr(&vm.builtins, "print", vm.ctx.new_rustfunc(print_fn)) - .unwrap(); + } else if stdout.is_undefined() || stdout.is_null() { + fn noop(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { + Ok(vm.get_none()) + } + vm.ctx.new_rustfunc(noop) + } else { + return Err(error()); + }; + vm.set_attr(&vm.builtins, "print", print_fn).unwrap(); Ok(()) })? } From a91cfd16bfb9b58057b2507aed14ee1cd3ebfb61 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 12 Apr 2019 14:28:26 -0400 Subject: [PATCH 292/884] Fix WASM import error --- wasm/demo/src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/demo/src/index.js b/wasm/demo/src/index.js index 9b58afe398..602e252417 100644 --- a/wasm/demo/src/index.js +++ b/wasm/demo/src/index.js @@ -1,6 +1,6 @@ import './style.css'; import 'codemirror/lib/codemirror.css'; -import 'xterm/dist/xterm.css'; +import 'xterm/lib/xterm.css'; // A dependency graph that contains any wasm must all be imported // asynchronously. This `index.js` file does the single async import, so From 20e0cc8bd771ccfea1fc0269db031070e03abf76 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 12 Apr 2019 23:06:53 +0300 Subject: [PATCH 293/884] Use assertRaises --- tests/snippets/try_exceptions.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index 5d7509ba5e..821d4e8c85 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -1,4 +1,4 @@ - +from testutils import assertRaises try: raise BaseException() @@ -117,10 +117,8 @@ def __init__(self): except ZeroDivisionError as ex: assert ex.__cause__ == None -try: +with assertRaises(TypeError): raise ZeroDivisionError from 5 -except TypeError: - pass try: raise ZeroDivisionError from NameError From 256abe63b6ccdb5df6f86fe91f8e3ca2c7f3d211 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 12 Apr 2019 23:08:26 +0300 Subject: [PATCH 294/884] Use vm.new_type_error --- vm/src/frame.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index a1da85d3a6..d67fd6b8de 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1207,9 +1207,7 @@ impl Frame { "Can only raise BaseException derived types, not {}", exception ); - let type_error_type = vm.ctx.exceptions.type_error.clone(); - let type_error = vm.new_exception(type_error_type, msg); - Err(type_error) + Err(vm.new_type_error(msg)) } } else { Err(vm.new_type_error("exceptions must derive from BaseException".to_string())) From bd78f7e12bfe89a8a56185b167e599e8bb13b73c Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sat, 13 Apr 2019 08:21:37 +0200 Subject: [PATCH 295/884] add capitalize --- tests/snippets/bytes.py | 6 +++++- vm/src/obj/objbyteinner.rs | 9 +++++++++ vm/src/obj/objbytes.rs | 5 +++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 0ffbc29f8c..1ad23ea186 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -109,11 +109,14 @@ assert bytes(b"Is Title Case").istitle() assert not bytes(b"is Not title casE").istitle() -# upper lower +# upper lower, capitalize l = bytes(b"lower") b = bytes(b"UPPER") assert l.lower().islower() assert b.upper().isupper() +assert l.capitalize() == b"Lower" +assert b.capitalize() == b"Upper" +assert bytes().capitalize() == bytes() # hex from hex assert bytes([0, 1, 9, 23, 90, 234]).hex() == "000109175aea" @@ -127,3 +130,4 @@ bytes.fromhex("6Z2") except ValueError as e: str(e) == "non-hexadecimal number found in fromhex() arg at position 1" + diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 532504b028..9bedb0bf0d 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -305,6 +305,15 @@ impl PyByteInner { self.elements.to_ascii_uppercase() } + pub fn capitalize(&self, _vm: &VirtualMachine) -> Vec { + let mut new: Vec = Vec::new(); + if let Some((first, second)) = self.elements.split_first() { + new.push(first.to_ascii_uppercase()); + second.iter().for_each(|x| new.push(x.to_ascii_lowercase())); + } + new + } + pub fn hex(&self, vm: &VirtualMachine) -> PyResult { let bla = self .elements diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 2c8edca6ab..953e5b2bbd 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -209,6 +209,11 @@ impl PyBytesRef { Ok(vm.ctx.new_bytes(self.inner.upper(vm))) } + #[pymethod(name = "capitalize")] + fn capitalize(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.capitalize(vm))) + } + #[pymethod(name = "hex")] fn hex(self, vm: &VirtualMachine) -> PyResult { self.inner.hex(vm) From 73dbcfbe0108f9a7cffb83ef9ab0d7540e11b8a3 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Fri, 12 Apr 2019 16:30:57 +0200 Subject: [PATCH 296/884] Add bytes.center fix str.center add some tests introduce is_byte --- tests/snippets/bytes.py | 27 ++++++++++++++++++++++++ tests/snippets/strings.py | 23 +++++++++++++++++++++ vm/src/obj/objbyteinner.rs | 40 ++++++++++++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 42 +++++++++++++++++++++++++++++++++++++- vm/src/obj/objstr.rs | 18 ++++++++++++++-- 5 files changed, 147 insertions(+), 3 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 1ad23ea186..d2a60943ca 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -131,3 +131,30 @@ except ValueError as e: str(e) == "non-hexadecimal number found in fromhex() arg at position 1" +# center +assert [b"koki".center(i, b"|") for i in range(3, 10)] == [ + b"koki", + b"koki", + b"|koki", + b"|koki|", + b"||koki|", + b"||koki||", + b"|||koki||", +] + +assert [b"kok".center(i, b"|") for i in range(2, 10)] == [ + b"kok", + b"kok", + b"kok|", + b"|kok|", + b"|kok||", + b"||kok||", + b"||kok|||", + b"|||kok|||", +] +b"kok".center(4) == b" kok" # " test no arg" +with assertRaises(TypeError): + b"b".center(2, "a") +with assertRaises(TypeError): + b"b".center(2, b"ba") +b"kok".center(5, bytearray(b"x")) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 31511db11e..adcc106bbc 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -55,6 +55,29 @@ c = 'hallo' assert c.capitalize() == 'Hallo' assert c.center(11, '-') == '---hallo---' +assert ["koki".center(i, "|") for i in range(3, 10)] == [ + "koki", + "koki", + "|koki", + "|koki|", + "||koki|", + "||koki||", + "|||koki||", +] + + +assert ["kok".center(i, "|") for i in range(2, 10)] == [ + "kok", + "kok", + "kok|", + "|kok|", + "|kok||", + "||kok||", + "||kok|||", + "|||kok|||", +] + + # assert c.isascii() assert c.index('a') == 1 assert c.rindex('l') == 3 diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 9bedb0bf0d..64ac24a3de 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -1,4 +1,5 @@ use crate::pyobject::PyObjectRef; +use num_bigint::BigInt; use crate::function::OptionalArg; @@ -13,8 +14,12 @@ use std::hash::{Hash, Hasher}; use super::objint; use super::objsequence::PySliceableSequence; use crate::obj::objint::PyInt; +use num_integer::Integer; use num_traits::ToPrimitive; +use super::objbytearray::{get_value as get_value_bytearray, PyByteArray}; +use super::objbytes::PyBytes; + #[derive(Debug, Default, Clone)] pub struct PyByteInner { pub elements: Vec, @@ -355,4 +360,39 @@ impl PyByteInner { .map(|x| x.unwrap()) .collect::>()) } + + pub fn center(&self, width: &BigInt, fillbyte: u8, _vm: &VirtualMachine) -> Vec { + let width = width.to_usize().unwrap(); + + // adjust right et left side + if width <= self.len() { + return self.elements.clone(); + } + let diff: usize = width - self.len(); + let mut ln: usize = diff / 2; + let mut rn: usize = ln; + + if diff.is_odd() && self.len() % 2 == 0 { + ln += 1 + } + + if diff.is_odd() && self.len() % 2 != 0 { + rn += 1 + } + + // merge all + let mut res = vec![fillbyte; ln]; + res.extend_from_slice(&self.elements[..]); + res.extend_from_slice(&vec![fillbyte; rn][..]); + + res + } +} + +pub fn is_byte(obj: &PyObjectRef) -> Option> { + match_class!(obj.clone(), + + i @ PyBytes => Some(i.get_value().to_vec()), + j @ PyByteArray => Some(get_value_bytearray(&j.as_object()).to_vec()), + _ => None) } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 953e5b2bbd..f183738c3a 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -7,7 +7,7 @@ use std::ops::Deref; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; -use super::objbyteinner::PyByteInner; +use super::objbyteinner::{is_byte, PyByteInner}; use super::objiter; use super::objslice::PySlice; use super::objtype::PyClassRef; @@ -34,6 +34,10 @@ impl PyBytes { inner: PyByteInner { elements }, } } + + pub fn get_value(&self) -> &[u8] { + &self.inner.elements + } } impl Deref for PyBytes { @@ -229,6 +233,42 @@ impl PyBytesRef { obj => Err(vm.new_type_error(format!("fromhex() argument must be str, not {}", obj ))) ) } + + #[pymethod(name = "center")] + fn center( + self, + width: PyObjectRef, + fillbyte: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let sym = if let OptionalArg::Present(v) = fillbyte { + match is_byte(&v) { + Some(x) => { + if x.len() == 1 { + x[0] + } else { + return Err(vm.new_type_error(format!( + "center() argument 2 must be a byte string of length 1, not {}", + &v + ))); + } + } + None => { + return Err(vm.new_type_error(format!( + "center() argument 2 must be a byte string of length 1, not {}", + &v + ))); + } + } + } else { + 32 // default is space + }; + + match_class!(width, + i @PyInt => Ok(vm.ctx.new_bytes(self.inner.center(i.as_bigint(), sym, vm))), + obj => {Err(vm.new_type_error(format!("{} cannot be interpreted as an integer", obj)))} + ) + } } #[derive(Debug)] diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 62cc923b1e..2d5f1f8e16 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -691,8 +691,22 @@ impl PyStringRef { ) -> PyResult { let value = &self.value; let rep_char = Self::get_fill_char(&rep, vm)?; - let left_buff: usize = (len - value.len()) / 2; - let right_buff = len - value.len() - left_buff; + let value_len = self.value.chars().count(); + + if len <= value_len { + return Ok(value.to_string()); + } + let diff: usize = len - value_len; + let mut left_buff: usize = diff / 2; + let mut right_buff: usize = left_buff; + + if diff % 2 != 0 && value_len % 2 == 0 { + left_buff += 1 + } + + if diff % 2 != 0 && value_len % 2 != 0 { + right_buff += 1 + } Ok(format!( "{}{}{}", rep_char.repeat(left_buff), From 83ea419c63515f1a05910bfcc99209dd4bbb0e66 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 13 Apr 2019 11:55:10 +0200 Subject: [PATCH 297/884] Add load and store of global to name protocol. --- vm/src/frame.rs | 42 +++++++++++++++++++++++++++--------------- vm/src/symboltable.rs | 23 +++++++++++++++++++++++ 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index a72ed6a763..e8276d1c86 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -129,6 +129,8 @@ pub trait NameProtocol { fn store_name(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); fn delete_name(&self, vm: &VirtualMachine, name: &str); fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option; + fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option; + fn store_global(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); } impl NameProtocol for Scope { @@ -162,6 +164,14 @@ impl NameProtocol for Scope { fn delete_name(&self, vm: &VirtualMachine, key: &str) { self.get_locals().del_item(key, vm).unwrap(); } + + fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option { + self.globals.get_item_option(name, vm).unwrap() + } + + fn store_global(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef) { + self.globals.set_item(name, value, vm).unwrap(); + } } #[derive(Clone, Debug)] @@ -994,12 +1004,12 @@ impl Frame { &self, vm: &VirtualMachine, name: &str, - scope: &bytecode::NameScope, + name_scope: &bytecode::NameScope, ) -> FrameResult { let obj = self.pop_value(); - match scope { + match name_scope { bytecode::NameScope::Global => { - self.scope.globals.set_item(name, obj, vm)?; + self.scope.store_global(vm, name, obj); } bytecode::NameScope::Local => { self.scope.store_name(&vm, name, obj); @@ -1017,19 +1027,21 @@ impl Frame { &self, vm: &VirtualMachine, name: &str, - scope: &bytecode::NameScope, + name_scope: &bytecode::NameScope, ) -> FrameResult { - let value = match scope { - bytecode::NameScope::Global => self.scope.globals.get_item(name, vm)?, - bytecode::NameScope::Local => match self.scope.load_name(&vm, name) { - Some(value) => value, - None => { - let name_error_type = vm.ctx.exceptions.name_error.clone(); - let msg = format!("name '{}' is not defined", name); - let name_error = vm.new_exception(name_error_type, msg); - return Err(name_error); - } - }, + let optional_value = match name_scope { + bytecode::NameScope::Global => self.scope.load_global(vm, name), + bytecode::NameScope::Local => self.scope.load_name(&vm, name), + }; + + let value = match optional_value { + Some(value) => value, + None => { + let name_error_type = vm.ctx.exceptions.name_error.clone(); + let msg = format!("name '{}' is not defined", name); + let name_error = vm.new_exception(name_error_type, msg); + return Err(name_error); + } }; self.push_value(value); diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs index 2d6b498840..d7f30c8705 100644 --- a/vm/src/symboltable.rs +++ b/vm/src/symboltable.rs @@ -111,6 +111,17 @@ impl SymbolTableBuilder { } fn scan_parameter(&mut self, parameter: &ast::Parameter) -> SymbolTableResult { + self.register_name(¶meter.arg, SymbolRole::Assigned) + } + + fn scan_parameters_annotations(&mut self, parameters: &[ast::Parameter]) -> SymbolTableResult { + for parameter in parameters { + self.scan_parameter_annotation(parameter)?; + } + Ok(()) + } + + fn scan_parameter_annotation(&mut self, parameter: &ast::Parameter) -> SymbolTableResult { if let Some(annotation) = ¶meter.annotation { self.scan_expression(&annotation)?; } @@ -394,7 +405,19 @@ impl SymbolTableBuilder { } } + // Annotations are scanned in outer scope: + self.scan_parameters_annotations(&args.args)?; + self.scan_parameters_annotations(&args.kwonlyargs)?; + if let ast::Varargs::Named(name) = &args.vararg { + self.scan_parameter_annotation(name)?; + } + if let ast::Varargs::Named(name) = &args.kwarg { + self.scan_parameter_annotation(name)?; + } + self.enter_scope(); + + // Fill scope with parameter names: self.scan_parameters(&args.args)?; self.scan_parameters(&args.kwonlyargs)?; if let ast::Varargs::Named(name) = &args.vararg { From 16644daffa2fc7e140e02f1737e8be32963e6fb0 Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 13 Apr 2019 12:04:33 -0700 Subject: [PATCH 298/884] Fix sub-crate compilation --- derive/src/pyclass.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 433d5911ef..e072db32d7 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -16,11 +16,11 @@ enum ClassItem { }, } -fn meta_to_vec(meta: Meta) -> Result, Meta> { +fn meta_to_vec(meta: Meta) -> Option> { match meta { - Meta::Word(_) => Ok(Vec::new()), - Meta::List(list) => Ok(list.nested.into_iter().collect()), - Meta::NameValue(_) => Err(meta), + Meta::Word(_) => Some(Vec::new()), + Meta::List(list) => Some(list.nested.into_iter().collect()), + Meta::NameValue(_) => None, } } From 883946102849401a5953c4758e0ccffda6e38b61 Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 13 Apr 2019 12:45:09 -0700 Subject: [PATCH 299/884] str: improve {is,}title impl and add tests --- vm/src/obj/objstr.rs | 126 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 106 insertions(+), 20 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 0e16a8b3b8..3e0bcccb6c 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -43,6 +43,14 @@ impl PyString { } } +impl From<&str> for PyString { + fn from(s: &str) -> PyString { + PyString { + value: s.to_string(), + } + } +} + pub type PyStringRef = PyRef; impl fmt::Display for PyString { @@ -396,9 +404,33 @@ impl PyString { } } + /// Return a titlecased version of the string where words start with an + /// uppercase character and the remaining characters are lowercase. #[pymethod] fn title(&self, _vm: &VirtualMachine) -> String { - make_title(&self.value) + let mut title = String::new(); + let mut previous_is_cased = false; + for c in self.value.chars() { + if c.is_lowercase() { + if !previous_is_cased { + title.extend(c.to_uppercase()); + } else { + title.push(c); + } + previous_is_cased = true; + } else if c.is_uppercase() { + if previous_is_cased { + title.extend(c.to_lowercase()); + } else { + title.push(c); + } + previous_is_cased = true; + } else { + previous_is_cased = false; + title.push(c); + } + } + title } #[pymethod] @@ -609,13 +641,34 @@ impl PyString { vm.ctx.new_tuple(new_tup) } + /// Return `true` if the sequence is ASCII titlecase and the sequence is not + /// empty, `false` otherwise. #[pymethod] fn istitle(&self, _vm: &VirtualMachine) -> bool { if self.value.is_empty() { - false - } else { - self.value.split(' ').all(|word| word == make_title(word)) + return false; } + + let mut cased = false; + let mut previous_is_cased = false; + for c in self.value.chars() { + if c.is_uppercase() { + if previous_is_cased { + return false; + } + previous_is_cased = true; + cased = true; + } else if c.is_lowercase() { + if !previous_is_cased { + return false; + } + previous_is_cased = true; + cased = true; + } else { + previous_is_cased = false; + } + } + cased } #[pymethod] @@ -967,22 +1020,55 @@ fn adjust_indices( } } -// helper function to title strings -fn make_title(s: &str) -> String { - let mut titled_str = String::new(); - let mut capitalize_char: bool = true; - for c in s.chars() { - if c.is_alphabetic() { - if !capitalize_char { - titled_str.push(c); - } else if capitalize_char { - titled_str.push(c.to_ascii_uppercase()); - capitalize_char = false; - } - } else { - titled_str.push(c); - capitalize_char = true; +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn str_title() { + let vm = VirtualMachine::new(); + + let tests = vec![ + (" Hello ", " hello "), + ("Hello ", "hello "), + ("Hello ", "Hello "), + ("Format This As Title String", "fOrMaT thIs aS titLe String"), + ("Format,This-As*Title;String", "fOrMaT,thIs-aS*titLe;String"), + ("Getint", "getInt"), + ("Greek Ωppercases ...", "greek ωppercases ..."), + ]; + for (title, input) in tests { + assert_eq!(PyString::from(input).title(&vm), String::from(title)); + } + } + + #[test] + fn str_istitle() { + let vm = VirtualMachine::new(); + + let pos = vec![ + "A", + "A Titlecased Line", + "A\nTitlecased Line", + "A Titlecased, Line", + "Greek Ωppercases ...", + ]; + + for s in pos { + assert!(PyString::from(s).istitle(&vm)); + } + + let neg = vec![ + "", + "a", + "\n", + "Not a capitalized String", + "Not\ta Titlecase String", + "Not--a Titlecase String", + "NOT", + ]; + for s in neg { + assert!(!PyString::from(s).istitle(&vm)); } } - titled_str } From 1f8181b56160b4591f17da22467553750ae00f7e Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 14 Apr 2019 09:40:59 +1200 Subject: [PATCH 300/884] Update formatting to be consistent with rustfmt included in 1.34 --- parser/src/lexer.rs | 6 +++++- vm/src/obj/objbytes.rs | 10 +++++----- vm/src/obj/objsuper.rs | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index e42e3b2482..dfc9ecd51e 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -826,7 +826,11 @@ where } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::DoubleSlash, tok_end))); + return Some(Ok(( + tok_start, + Tok::DoubleSlash, + tok_end, + ))); } } } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 279dbefe06..fd9bfd5bff 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -62,13 +62,13 @@ pub fn init(context: &PyContext) { PyBytesRef::extend_class(context, &context.bytes_type); let bytes_type = &context.bytes_type; extend_class!(context, bytes_type, { -"fromhex" => context.new_rustfunc(PyBytesRef::fromhex), -}); + "fromhex" => context.new_rustfunc(PyBytesRef::fromhex), + }); let bytesiterator_type = &context.bytesiterator_type; extend_class!(context, bytesiterator_type, { -"__next__" => context.new_rustfunc(PyBytesIteratorRef::next), -"__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), -}); + "__next__" => context.new_rustfunc(PyBytesIteratorRef::next), + "__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), + }); } #[pyimpl(__inside_vm)] diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 94fe0fc7b7..194212a492 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -127,8 +127,10 @@ fn super_new( match vm.get_locals().get_item_option(first_arg, vm)? { Some(obj) => obj.clone(), _ => { - return Err(vm - .new_type_error(format!("super arguement {} was not supplied", first_arg))); + return Err(vm.new_type_error(format!( + "super arguement {} was not supplied", + first_arg + ))); } } } else { From 6650ad895a3ca925556f802efc6c1d3b91ef7b52 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 14 Apr 2019 10:07:50 +1200 Subject: [PATCH 301/884] Fix a bunch of clippy warnings --- vm/src/compile.rs | 4 ++-- vm/src/frame.rs | 6 +++--- vm/src/function.rs | 2 +- vm/src/obj/objbyteinner.rs | 3 +-- vm/src/obj/objdict.rs | 2 +- vm/src/obj/objint.rs | 2 +- vm/src/obj/objrange.rs | 4 ++-- vm/src/obj/objset.rs | 12 ++++++------ vm/src/obj/objslice.rs | 2 +- vm/src/obj/objtype.rs | 2 +- vm/src/pyobject.rs | 12 +++--------- vm/src/stdlib/os.rs | 4 ++-- 12 files changed, 24 insertions(+), 31 deletions(-) diff --git a/vm/src/compile.rs b/vm/src/compile.rs index d5a4b48ecb..163e2fcf10 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -871,8 +871,8 @@ impl Compiler { vals: &[ast::Expression], ops: &[ast::Comparison], ) -> Result<(), CompileError> { - assert!(ops.len() > 0); - assert!(vals.len() == ops.len() + 1); + assert!(!ops.is_empty()); + assert_eq!(vals.len(), ops.len() + 1); let to_operator = |op: &ast::Comparison| match op { ast::Comparison::Equal => bytecode::ComparisonOperator::Equal, diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 14154016d1..064951770b 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1215,9 +1215,9 @@ impl Frame { fn get_exception(&self, vm: &VirtualMachine, none_allowed: bool) -> PyResult { let exception = self.pop_value(); - if none_allowed && vm.get_none().is(&exception) { - Ok(exception) - } else if objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { + if none_allowed && vm.get_none().is(&exception) + || objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) + { Ok(exception) } else if let Ok(exception) = PyClassRef::try_from_object(vm, exception) { if objtype::issubclass(&exception, &vm.ctx.exceptions.base_exception_type) { diff --git a/vm/src/function.rs b/vm/src/function.rs index 6e421ac98a..9ed237a508 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -286,7 +286,7 @@ pub struct Args(Vec); impl Args> { pub fn into_tuple(self, vm: &VirtualMachine) -> PyObjectRef { vm.ctx - .new_tuple(self.0.into_iter().map(|obj| obj.into_object()).collect()) + .new_tuple(self.0.into_iter().map(PyRef::into_object).collect()) } } diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 64ac24a3de..d0a63633ac 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -356,8 +356,7 @@ impl PyByteInner { .collect::>() .chunks(2) .map(|x| x.to_vec().iter().collect::()) - .map(|x| u8::from_str_radix(&x, 16)) - .map(|x| x.unwrap()) + .map(|x| u8::from_str_radix(&x, 16).unwrap()) .collect::>()) } diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index efd38a7fd4..fd77107eec 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -118,7 +118,7 @@ impl PyDictRef { } } } - return Ok(true); + Ok(true) } fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 5be50f6c4e..52f321adfc 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -518,7 +518,7 @@ pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult Ok(f.to_f64().to_bigint().unwrap()), s @ PyString => { i32::from_str_radix(s.as_str(), base) - .map(|i| BigInt::from(i)) + .map(BigInt::from) .map_err(|_|vm.new_value_error(format!( "invalid literal for int() with base {}: '{}'", base, s diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 2820dd9b45..00b4e0e2ed 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -144,8 +144,8 @@ impl PyRange { vm: &VirtualMachine, ) -> PyResult { PyRange { - start: start, - stop: stop, + start, + stop, step: step .into_option() .unwrap_or_else(|| PyInt::new(BigInt::one()).into_ref(vm)), diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 5ec4fca46e..b8f9f6d3b5 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -262,7 +262,7 @@ impl PySetInner { perform_action_with_hash(vm, &mut self.elements, &item, &discard) } - fn clear(&mut self) -> () { + fn clear(&mut self) { self.elements.clear(); } @@ -449,11 +449,11 @@ impl PySetRef { fn repr(self, vm: &VirtualMachine) -> PyResult { let inner = self.inner.borrow(); let s = if inner.len() == 0 { - format!("set()") + "set()".to_string() } else if let Some(_guard) = ReprGuard::enter(self.as_object()) { inner.repr(vm)? } else { - format!("set(...)") + "set(...)".to_string() }; Ok(vm.new_str(s)) } @@ -470,7 +470,7 @@ impl PySetRef { self.inner.borrow_mut().discard(&item, vm) } - fn clear(self, _vm: &VirtualMachine) -> () { + fn clear(self, _vm: &VirtualMachine) { self.inner.borrow_mut().clear() } @@ -656,11 +656,11 @@ impl PyFrozenSetRef { fn repr(self, vm: &VirtualMachine) -> PyResult { let inner = &self.inner; let s = if inner.len() == 0 { - format!("frozenset()") + "frozenset()".to_string() } else if let Some(_guard) = ReprGuard::enter(self.as_object()) { format!("frozenset({})", inner.repr(vm)?) } else { - format!("frozenset(...)") + "frozenset(...)".to_string() }; Ok(vm.new_str(s)) } diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index c82a3ca6ef..31fcfa52fe 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -63,7 +63,7 @@ fn slice_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { step: step.map(|x| objint::get_value(x).clone()), } .into_ref_with_type(vm, cls.clone().downcast().unwrap()) - .map(|x| x.into_object()) + .map(PyRef::into_object) } fn get_property_value(vm: &VirtualMachine, value: &Option) -> PyObjectRef { diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index b1b50dfbd0..819f850881 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -235,7 +235,7 @@ pub fn type_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(args.args[1].class().into_object()) } else if args.args.len() == 4 { let (typ, name, bases, dict) = args.bind(vm)?; - type_new_class(vm, typ, name, bases, dict).map(|x| x.into_object()) + type_new_class(vm, typ, name, bases, dict).map(PyRef::into_object) } else { Err(vm.new_type_error(format!(": type_new: {:?}", args))) } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index f918869967..3b21482bad 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -87,7 +87,6 @@ pub type PyAttributes = HashMap; impl fmt::Display for PyObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::TypeProtocol; if let Some(PyClass { ref name, .. }) = self.payload::() { let type_name = self.class().name.clone(); // We don't have access to a vm, so just assume that if its parent's name @@ -698,7 +697,7 @@ impl PyContext { pub fn new_instance(&self, class: PyClassRef, dict: Option) -> PyObjectRef { PyObject { typ: class, - dict: dict, + dict, payload: objobject::PyInstance, } .into_ref() @@ -1065,7 +1064,7 @@ where fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { if let Ok(method) = vm.get_method(obj.clone(), "__iter__") { Ok(PyIterable { - method: method, + method, _item: std::marker::PhantomData, }) } else if vm.get_method(obj.clone(), "__getitem__").is_ok() { @@ -1170,12 +1169,7 @@ where T: Sized + PyObjectPayload, { pub fn new(payload: T, typ: PyClassRef, dict: Option) -> PyObjectRef { - PyObject { - typ, - dict: dict, - payload, - } - .into_ref() + PyObject { typ, dict, payload }.into_ref() } // Move this object into a reference object, transferring ownership. diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 26707d3ca9..f397b2d8a0 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -174,11 +174,11 @@ fn os_listdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } } -fn os_putenv(key: PyStringRef, value: PyStringRef, _vm: &VirtualMachine) -> () { +fn os_putenv(key: PyStringRef, value: PyStringRef, _vm: &VirtualMachine) { env::set_var(&key.value, &value.value) } -fn os_unsetenv(key: PyStringRef, _vm: &VirtualMachine) -> () { +fn os_unsetenv(key: PyStringRef, _vm: &VirtualMachine) { env::remove_var(&key.value) } From d24dd170e9b299839a3c98f03ab21ef45c1c8584 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 14 Apr 2019 12:09:54 +1200 Subject: [PATCH 302/884] Changes to RustPython to support importing and running unittest --- vm/src/builtins.rs | 18 ++++++------------ vm/src/eval.rs | 7 +------ vm/src/import.rs | 11 ++++------- vm/src/obj/objclassmethod.rs | 2 ++ vm/src/obj/objfunction.rs | 6 ++++++ vm/src/obj/objobject.rs | 16 +++++++++++++++- vm/src/pyobject.rs | 4 +++- vm/src/stdlib/io.rs | 6 +++++- vm/src/stdlib/mod.rs | 2 ++ vm/src/stdlib/thread.rs | 30 ++++++++++++++++++++++++++++++ vm/src/stdlib/types.rs | 1 + vm/src/sysmodule.rs | 8 ++++++++ vm/src/vm.rs | 5 +++++ 13 files changed, 88 insertions(+), 28 deletions(-) create mode 100644 vm/src/stdlib/thread.rs diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 6a94b83628..8f7721609c 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -102,10 +102,8 @@ fn builtin_compile( } }; - compile::compile(vm, &source, &mode, filename.value.to_string()).map_err(|err| { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - vm.new_exception(syntax_error, err.to_string()) - }) + compile::compile(vm, &source, &mode, filename.value.to_string()) + .map_err(|err| vm.new_syntax_error(&err)) } fn builtin_delattr(obj: PyObjectRef, attr: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { @@ -151,10 +149,8 @@ fn builtin_eval(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - compile::compile(vm, &source, &mode, "".to_string()).map_err(|err| { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - vm.new_exception(syntax_error, err.to_string()) - })? + compile::compile(vm, &source, &mode, "".to_string()) + .map_err(|err| vm.new_syntax_error(&err))? } else { return Err(vm.new_type_error("code argument must be str or code object".to_string())); }; @@ -181,10 +177,8 @@ fn builtin_exec(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - compile::compile(vm, &source, &mode, "".to_string()).map_err(|err| { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - vm.new_exception(syntax_error, err.to_string()) - })? + compile::compile(vm, &source, &mode, "".to_string()) + .map_err(|err| vm.new_syntax_error(&err))? } else if let Ok(code_obj) = PyCodeRef::try_from_object(vm, source.clone()) { code_obj } else { diff --git a/vm/src/eval.rs b/vm/src/eval.rs index 6c7b3ca549..21bc9dadf5 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -1,7 +1,5 @@ extern crate rustpython_parser; -use std::error::Error; - use crate::compile; use crate::frame::Scope; use crate::pyobject::PyResult; @@ -13,10 +11,7 @@ pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str) debug!("Code object: {:?}", bytecode); vm.run_code_obj(bytecode, scope) } - Err(err) => { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - Err(vm.new_exception(syntax_error, err.description().to_string())) - } + Err(err) => Err(vm.new_syntax_error(&err)), } } diff --git a/vm/src/import.rs b/vm/src/import.rs index 14c0a75665..041fab6504 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -2,7 +2,6 @@ * Import mechanics */ -use std::error::Error; use std::path::PathBuf; use crate::compile; @@ -25,17 +24,14 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s let file_path = find_source(vm, current_path, module) .map_err(|e| vm.new_exception(notfound_error.clone(), e))?; let source = util::read_file(file_path.as_path()) - .map_err(|e| vm.new_exception(import_error.clone(), e.description().to_string()))?; + .map_err(|e| vm.new_exception(import_error.clone(), format!("{}", e)))?; let code_obj = compile::compile( vm, &source, &compile::Mode::Exec, file_path.to_str().unwrap().to_string(), ) - .map_err(|err| { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - vm.new_exception(syntax_error, err.description().to_string()) - })?; + .map_err(|err| vm.new_syntax_error(&err))?; // trace!("Code object: {:?}", code_obj); let attrs = vm.ctx.new_dict(); @@ -64,12 +60,13 @@ fn find_source(vm: &VirtualMachine, current_path: PathBuf, name: &str) -> Result paths.insert(0, current_path); + let rel_name = name.replace('.', "/"); let suffixes = [".py", "/__init__.py"]; let mut file_paths = vec![]; for path in paths { for suffix in suffixes.iter() { let mut file_path = path.clone(); - file_path.push(format!("{}{}", name, suffix)); + file_path.push(format!("{}{}", rel_name, suffix)); file_paths.push(file_path); } } diff --git a/vm/src/obj/objclassmethod.rs b/vm/src/obj/objclassmethod.rs index ffacfdc07a..7a8d90d1c1 100644 --- a/vm/src/obj/objclassmethod.rs +++ b/vm/src/obj/objclassmethod.rs @@ -9,6 +9,8 @@ pub struct PyClassMethod { pub type PyClassMethodRef = PyRef; impl PyValue for PyClassMethod { + const HAVE_DICT: bool = true; + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.classmethod_type() } diff --git a/vm/src/obj/objfunction.rs b/vm/src/obj/objfunction.rs index 01e4b586fd..05b66c3460 100644 --- a/vm/src/obj/objfunction.rs +++ b/vm/src/obj/objfunction.rs @@ -1,4 +1,5 @@ use crate::frame::Scope; +use crate::function::{Args, KwArgs}; use crate::obj::objcode::PyCodeRef; use crate::obj::objdict::PyDictRef; use crate::obj::objtuple::PyTupleRef; @@ -40,6 +41,10 @@ impl PyValue for PyFunction { } impl PyFunctionRef { + fn call(self, args: Args, kwargs: KwArgs, vm: &VirtualMachine) -> PyResult { + vm.invoke(self.into_object(), (&args, &kwargs)) + } + fn code(self, _vm: &VirtualMachine) -> PyCodeRef { self.code.clone() } @@ -76,6 +81,7 @@ pub fn init(context: &PyContext) { let function_type = &context.function_type; extend_class!(context, function_type, { "__get__" => context.new_rustfunc(bind_method), + "__call__" => context.new_rustfunc(PyFunctionRef::call), "__code__" => context.new_property(PyFunctionRef::code), "__defaults__" => context.new_property(PyFunctionRef::defaults), "__kwdefaults__" => context.new_property(PyFunctionRef::kwdefaults), diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index f02bbabd17..47cfa008dd 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -167,7 +167,11 @@ pub fn init(context: &PyContext) { "__ge__" => context.new_rustfunc(object_ge), "__setattr__" => context.new_rustfunc(object_setattr), "__delattr__" => context.new_rustfunc(object_delattr), - "__dict__" => context.new_property(object_dict), + "__dict__" => + PropertyBuilder::new(context) + .add_getter(object_dict) + .add_setter(object_dict_setter) + .create(), "__dir__" => context.new_rustfunc(object_dir), "__hash__" => context.new_rustfunc(object_hash), "__str__" => context.new_rustfunc(object_str), @@ -203,6 +207,16 @@ fn object_dict(object: PyObjectRef, vm: &VirtualMachine) -> PyResult } } +fn object_dict_setter( + _instance: PyObjectRef, + _value: PyObjectRef, + vm: &VirtualMachine, +) -> PyResult { + Err(vm.new_not_implemented_error( + "Setting __dict__ attribute on am object isn't yet implemented".to_string(), + )) +} + fn object_getattribute(obj: PyObjectRef, name_str: PyStringRef, vm: &VirtualMachine) -> PyResult { let name = &name_str.value; trace!("object.__getattribute__({:?}, {:?})", obj, name); diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 3b21482bad..f369bab4aa 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1191,6 +1191,8 @@ impl PyObject { } pub trait PyValue: fmt::Debug + Sized + 'static { + const HAVE_DICT: bool = false; + fn class(vm: &VirtualMachine) -> PyClassRef; fn into_ref(self, vm: &VirtualMachine) -> PyRef { @@ -1203,7 +1205,7 @@ pub trait PyValue: fmt::Debug + Sized + 'static { fn into_ref_with_type(self, vm: &VirtualMachine, cls: PyClassRef) -> PyResult> { let class = Self::class(vm); if objtype::issubclass(&cls, &class) { - let dict = if cls.is(&class) { + let dict = if !Self::HAVE_DICT && cls.is(&class) { None } else { Some(vm.ctx.new_dict()) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index bd33eac753..d864519f57 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -95,6 +95,9 @@ fn io_base_cm_exit(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } +// TODO Check if closed, then if so raise ValueError +fn io_base_flush(_zelf: PyObjectRef, _vm: &VirtualMachine) {} + fn buffered_io_base_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(buffered, None), (raw, None)]); vm.set_attr(buffered, "raw", raw.clone())?; @@ -369,7 +372,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { //IOBase the abstract base class of the IO Module let io_base = py_class!(ctx, "IOBase", ctx.object(), { "__enter__" => ctx.new_rustfunc(io_base_cm_enter), - "__exit__" => ctx.new_rustfunc(io_base_cm_exit) + "__exit__" => ctx.new_rustfunc(io_base_cm_exit), + "flush" => ctx.new_rustfunc(io_base_flush) }); // IOBase Subclasses diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 9b0d0b345f..0933b71926 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -9,6 +9,7 @@ mod random; mod re; pub mod socket; mod string; +mod thread; mod time_module; mod tokenize; mod types; @@ -41,6 +42,7 @@ pub fn get_module_inits() -> HashMap { modules.insert("random".to_string(), Box::new(random::make_module)); modules.insert("string".to_string(), Box::new(string::make_module)); modules.insert("struct".to_string(), Box::new(pystruct::make_module)); + modules.insert("_thread".to_string(), Box::new(thread::make_module)); modules.insert("time".to_string(), Box::new(time_module::make_module)); modules.insert("tokenize".to_string(), Box::new(tokenize::make_module)); modules.insert("types".to_string(), Box::new(types::make_module)); diff --git a/vm/src/stdlib/thread.rs b/vm/src/stdlib/thread.rs new file mode 100644 index 0000000000..8de881893a --- /dev/null +++ b/vm/src/stdlib/thread.rs @@ -0,0 +1,30 @@ +/// Implementation of the _thread module, currently noop implementation as RustPython doesn't yet +/// support threading +use super::super::pyobject::PyObjectRef; +use crate::function::PyFuncArgs; +use crate::pyobject::PyResult; +use crate::vm::VirtualMachine; + +fn rlock_acquire(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { + Ok(vm.get_none()) +} + +fn rlock_release(_zelf: PyObjectRef, _vm: &VirtualMachine) {} + +fn get_ident(_vm: &VirtualMachine) -> u32 { + 1 +} + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + let rlock_type = py_class!(ctx, "_thread.RLock", ctx.object(), { + "acquire" => ctx.new_rustfunc(rlock_acquire), + "release" => ctx.new_rustfunc(rlock_release), + }); + + py_module!(vm, "_thread", { + "RLock" => rlock_type, + "get_ident" => ctx.new_rustfunc(get_ident) + }) +} diff --git a/vm/src/stdlib/types.rs b/vm/src/stdlib/types.rs index ad76a80a42..e224a93c68 100644 --- a/vm/src/stdlib/types.rs +++ b/vm/src/stdlib/types.rs @@ -31,6 +31,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { py_module!(vm, "types", { "new_class" => ctx.new_rustfunc(types_new_class), "FunctionType" => ctx.function_type(), + "MethodType" => ctx.bound_method_type(), "LambdaType" => ctx.function_type(), "CodeType" => ctx.code_type(), "FrameType" => ctx.frame_type() diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 2f86904487..97de7a425c 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -3,6 +3,7 @@ use std::{env, mem}; use crate::frame::FrameRef; use crate::function::{OptionalArg, PyFuncArgs}; +use crate::obj::objstr::PyStringRef; use crate::pyobject::{ItemProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; @@ -39,6 +40,11 @@ fn sys_getsizeof(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_int(size)) } +// TODO implement string interning, this will be key for performance +fn sys_intern(value: PyStringRef, _vm: &VirtualMachine) -> PyStringRef { + value +} + pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectRef) { let ctx = &vm.ctx; @@ -130,6 +136,7 @@ settrace() -- set the global debug tracing function "argv" => argv(ctx), "getrefcount" => ctx.new_rustfunc(sys_getrefcount), "getsizeof" => ctx.new_rustfunc(sys_getsizeof), + "intern" => ctx.new_rustfunc(sys_intern), "maxsize" => ctx.new_int(std::usize::MAX), "path" => path, "ps1" => ctx.new_str(">>>>> ".to_string()), @@ -137,6 +144,7 @@ settrace() -- set the global debug tracing function "__doc__" => ctx.new_str(sys_doc.to_string()), "_getframe" => ctx.new_rustfunc(getframe), "modules" => modules.clone(), + "warnoptions" => ctx.new_list(vec![]), }); modules.set_item("sys", module.clone(), vm).unwrap(); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 5ae5b0b50c..e360b8e5ec 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -234,6 +234,11 @@ impl VirtualMachine { self.new_exception(overflow_error, msg) } + pub fn new_syntax_error(&self, msg: &T) -> PyObjectRef { + let syntax_error = self.ctx.exceptions.syntax_error.clone(); + self.new_exception(syntax_error, msg.to_string()) + } + pub fn get_none(&self) -> PyObjectRef { self.ctx.none() } From a708ce2eb2e9179f957feb87b917110fa33a0539 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 14 Apr 2019 12:15:55 +1200 Subject: [PATCH 303/884] Remove needless format! --- vm/src/import.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index 041fab6504..b59ce64266 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -24,7 +24,7 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s let file_path = find_source(vm, current_path, module) .map_err(|e| vm.new_exception(notfound_error.clone(), e))?; let source = util::read_file(file_path.as_path()) - .map_err(|e| vm.new_exception(import_error.clone(), format!("{}", e)))?; + .map_err(|e| vm.new_exception(import_error.clone(), e.to_string()))?; let code_obj = compile::compile( vm, &source, From 762c281dd48c5c762e95dc25f491ccca5367c4f9 Mon Sep 17 00:00:00 2001 From: Joey Date: Sat, 13 Apr 2019 19:07:15 -0700 Subject: [PATCH 304/884] address feedback --- vm/src/obj/objstr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 3e0bcccb6c..5a133615c6 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1038,7 +1038,7 @@ mod tests { ("Greek Ωppercases ...", "greek ωppercases ..."), ]; for (title, input) in tests { - assert_eq!(PyString::from(input).title(&vm), String::from(title)); + assert_eq!(PyString::from(input).title(&vm).as_str(), title); } } From 307689a15ee8ee6699ec7f9ebe3e2e4659f01247 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 14 Apr 2019 17:47:51 +1200 Subject: [PATCH 305/884] comment out lines that reference mappingproxy as type doesn't yet have a __dict__ --- Lib/_collections_abc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index 3158a4f16c..c9f7daa16f 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -52,7 +52,7 @@ dict_values = type({}.values()) dict_items = type({}.items()) ## misc ## -mappingproxy = type(type.__dict__) +# mappingproxy = type(type.__dict__) # generator = type((lambda: (yield))()) ## coroutine ## # async def _coro(): pass @@ -688,7 +688,7 @@ def __eq__(self, other): __reversed__ = None -Mapping.register(mappingproxy) +# Mapping.register(mappingproxy) class MappingView(Sized): From 2c2925a68435e837af8aa9c8b6dc01bdf0e5666e Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 12 Apr 2019 17:22:52 +0300 Subject: [PATCH 306/884] Support reraise --- tests/snippets/try_exceptions.py | 12 ++++++++++ vm/src/bytecode.rs | 3 ++- vm/src/compile.rs | 40 ++++++++++++++++++++++---------- vm/src/frame.rs | 12 +++++++--- 4 files changed, 51 insertions(+), 16 deletions(-) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index 821d4e8c85..2a6ed7b349 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -125,3 +125,15 @@ def __init__(self): except ZeroDivisionError as ex: assert type(ex.__cause__) == NameError +try: + try: + raise NameError + except: + raise +except NameError: + pass + +try: + raise +except RuntimeError: + pass diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 2725aa5860..10c667890e 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -135,6 +135,7 @@ pub enum Instruction { PopBlock, Raise { argc: usize, + in_exc: bool, }, BuildString { size: usize, @@ -363,7 +364,7 @@ impl Instruction { SetupWith { end } => w!(SetupWith, end), CleanupWith { end } => w!(CleanupWith, end), PopBlock => w!(PopBlock), - Raise { argc } => w!(Raise, argc), + Raise { argc, in_exc } => w!(Raise, argc, in_exc), BuildString { size } => w!(BuildString, size), BuildTuple { size, unpack } => w!(BuildTuple, size, unpack), BuildList { size, unpack } => w!(BuildList, size, unpack), diff --git a/vm/src/compile.rs b/vm/src/compile.rs index fe40962feb..9e8dab905f 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -24,6 +24,7 @@ struct Compiler { current_qualified_path: Option, in_loop: bool, in_function_def: bool, + in_exc_handler: bool, } /// Compile a given sourcecode into a bytecode object. @@ -85,6 +86,7 @@ impl Compiler { current_qualified_path: None, in_loop: false, in_function_def: false, + in_exc_handler: false, } } @@ -325,15 +327,24 @@ impl Compiler { match cause { Some(cause) => { self.compile_expression(cause)?; - self.emit(Instruction::Raise { argc: 2 }); + self.emit(Instruction::Raise { + argc: 2, + in_exc: self.in_exc_handler, + }); } None => { - self.emit(Instruction::Raise { argc: 1 }); + self.emit(Instruction::Raise { + argc: 1, + in_exc: self.in_exc_handler, + }); } } } None => { - self.emit(Instruction::Raise { argc: 0 }); + self.emit(Instruction::Raise { + argc: 0, + in_exc: self.in_exc_handler, + }); } }, ast::Statement::Try { @@ -378,7 +389,10 @@ impl Compiler { }); } } - self.emit(Instruction::Raise { argc: 1 }); + self.emit(Instruction::Raise { + argc: 1, + in_exc: self.in_exc_handler, + }); self.set_label(end_label); } ast::Statement::Break => { @@ -558,6 +572,7 @@ impl Compiler { self.emit(Instruction::Jump { target: else_label }); // except handlers: + self.in_exc_handler = true; self.set_label(handler_label); // Exception is on top of stack now handler_label = self.new_label(); @@ -586,15 +601,10 @@ impl Compiler { // We have a match, store in name (except x as y) if let Some(alias) = &handler.name { + // Duplicate exception for context: + self.emit(Instruction::Duplicate); self.store_name(alias); - } else { - // Drop exception from top of stack: - self.emit(Instruction::Pop); } - } else { - // Catch all! - // Drop exception from top of stack: - self.emit(Instruction::Pop); } // Handler code: @@ -611,6 +621,9 @@ impl Compiler { target: handler_label, }); self.set_label(handler_label); + // Drop exception from top of stack: + self.emit(Instruction::Pop); + self.in_exc_handler = false; // If code flows here, we have an unhandled exception, // emit finally code and raise again! // Duplicate finally code here: @@ -619,7 +632,10 @@ impl Compiler { if let Some(statements) = finalbody { self.compile_statements(statements)?; } - self.emit(Instruction::Raise { argc: 1 }); + self.emit(Instruction::Raise { + argc: 1, + in_exc: false, + }); // We successfully ran the try block: // else: diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 321ee20469..464578d6bd 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -715,14 +715,20 @@ impl Frame { Ok(None) } - bytecode::Instruction::Raise { argc } => { + bytecode::Instruction::Raise { argc, in_exc } => { + if argc.clone() == 0 && in_exc.clone() == false { + return Err(vm.new_exception( + vm.ctx.exceptions.runtime_error.clone(), + "No active exception to reraise".to_string(), + )); + } let cause = match argc { 2 => self.get_exception(vm, true)?, _ => vm.get_none(), }; let exception = match argc { - 1 | 2 => self.get_exception(vm, false)?, - 0 | 3 => panic!("Not implemented!"), + 0 | 1 | 2 => self.get_exception(vm, false)?, + 3 => panic!("Not implemented!"), _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"), }; info!("Exception raised: {:?} with cause: {:?}", exception, cause); From 3ec9b01928ddbd212f54e74d4afdcbb8b98a856b Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 14 Apr 2019 18:22:15 +0300 Subject: [PATCH 307/884] Add exception __context__ --- tests/snippets/try_exceptions.py | 5 +++++ vm/src/compile.rs | 12 ++++++------ vm/src/frame.rs | 30 ++++++++++++++++++++++-------- 3 files changed, 33 insertions(+), 14 deletions(-) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index 2a6ed7b349..e75e730392 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -137,3 +137,8 @@ def __init__(self): raise except RuntimeError: pass + +try: + raise ZeroDivisionError +except ZeroDivisionError as ex: + assert ex.__context__ == None diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 9e8dab905f..ba1f50e7bf 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -572,6 +572,7 @@ impl Compiler { self.emit(Instruction::Jump { target: else_label }); // except handlers: + let was_in_exc_handler = self.in_exc_handler; self.in_exc_handler = true; self.set_label(handler_label); // Exception is on top of stack now @@ -609,6 +610,8 @@ impl Compiler { // Handler code: self.compile_statements(&handler.body)?; + // Drop exception from top of stack: + self.emit(Instruction::Pop); self.emit(Instruction::Jump { target: finally_label, }); @@ -621,9 +624,6 @@ impl Compiler { target: handler_label, }); self.set_label(handler_label); - // Drop exception from top of stack: - self.emit(Instruction::Pop); - self.in_exc_handler = false; // If code flows here, we have an unhandled exception, // emit finally code and raise again! // Duplicate finally code here: @@ -633,8 +633,8 @@ impl Compiler { self.compile_statements(statements)?; } self.emit(Instruction::Raise { - argc: 1, - in_exc: false, + argc: 0, + in_exc: true, }); // We successfully ran the try block: @@ -649,7 +649,7 @@ impl Compiler { if let Some(statements) = finalbody { self.compile_statements(statements)?; } - + self.in_exc_handler = was_in_exc_handler; // unimplemented!(); Ok(()) } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 464578d6bd..2bf8215c1c 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -716,23 +716,37 @@ impl Frame { } bytecode::Instruction::Raise { argc, in_exc } => { - if argc.clone() == 0 && in_exc.clone() == false { - return Err(vm.new_exception( - vm.ctx.exceptions.runtime_error.clone(), - "No active exception to reraise".to_string(), - )); - } let cause = match argc { 2 => self.get_exception(vm, true)?, _ => vm.get_none(), }; let exception = match argc { - 0 | 1 | 2 => self.get_exception(vm, false)?, + 0 => match in_exc { + true => self.get_exception(vm, false)?, + false => { + return Err(vm.new_exception( + vm.ctx.exceptions.runtime_error.clone(), + "No active exception to reraise".to_string(), + )); + } + }, + 1 | 2 => self.get_exception(vm, false)?, 3 => panic!("Not implemented!"), _ => panic!("Invalid parameter for RAISE_VARARGS, must be between 0 to 3"), }; - info!("Exception raised: {:?} with cause: {:?}", exception, cause); + let context = match argc { + 0 => vm.get_none(), // We have already got the exception, + _ => match in_exc { + true => self.get_exception(vm, false)?, + false => vm.get_none(), + }, + }; + info!( + "Exception raised: {:?} with cause: {:?} and context: {:?}", + exception, cause, context + ); vm.set_attr(&exception, vm.new_str("__cause__".to_string()), cause)?; + vm.set_attr(&exception, vm.new_str("__context__".to_string()), context)?; Err(exception) } From 188758929df8b82e5484e87f800ecff5994ae692 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 14 Apr 2019 18:42:23 +0300 Subject: [PATCH 308/884] Add more tests to exception context --- tests/snippets/try_exceptions.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index e75e730392..1af2d9762b 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -106,6 +106,7 @@ def __init__(self): raise NameError from ex except NameError as ex2: assert ex2.__cause__ == cause + assert ex2.__context__ == cause try: raise ZeroDivisionError from None @@ -138,7 +139,25 @@ def __init__(self): except RuntimeError: pass +context = None +try: + try: + raise ZeroDivisionError + except ZeroDivisionError as ex: + assert ex.__context__ == None + context = ex + raise NameError +except NameError as ex2: + assert ex2.__context__ == context + assert type(ex2.__context__) == ZeroDivisionError + try: raise ZeroDivisionError except ZeroDivisionError as ex: assert ex.__context__ == None + +try: + raise ZeroDivisionError from NameError +except ZeroDivisionError as ex: + assert type(ex.__cause__) == NameError + assert ex.__context__ == None From aec7f84960add779f0e81d5cb5e4169cc4aeab2b Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 14 Apr 2019 18:52:01 +0300 Subject: [PATCH 309/884] Print exception context --- vm/src/exceptions.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index abd670a149..7642908eeb 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -20,12 +20,22 @@ fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // print excption chain pub fn print_exception(vm: &VirtualMachine, exc: &PyObjectRef) { + let mut had_casue = false; if let Ok(cause) = vm.get_attribute(exc.clone(), "__cause__") { if !vm.get_none().is(&cause) { + had_casue = true; print_exception(vm, &cause); println!("\nThe above exception was the direct cause of the following exception:\n"); } } + if !had_casue { + if let Ok(context) = vm.get_attribute(exc.clone(), "__context__") { + if !vm.get_none().is(&context) { + print_exception(vm, &context); + println!("\nDuring handling of the above exception, another exception occurred:\n"); + } + } + } print_exception_inner(vm, exc) } From b6ce4073917bcf1d4e581896773aa0abce9a2a0c Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 14 Apr 2019 19:05:28 +0300 Subject: [PATCH 310/884] Remove else and finally from in_exc_handler --- tests/snippets/try_exceptions.py | 10 ++++++++++ vm/src/compile.rs | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index 1af2d9762b..d7785424a5 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -161,3 +161,13 @@ def __init__(self): except ZeroDivisionError as ex: assert type(ex.__cause__) == NameError assert ex.__context__ == None + +try: + try: + raise ZeroDivisionError + except ZeroDivisionError as ex: + pass + finally: + raise NameError +except NameError as ex2: + assert ex2.__context__ == None diff --git a/vm/src/compile.rs b/vm/src/compile.rs index ba1f50e7bf..d28c10feb4 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -637,6 +637,8 @@ impl Compiler { in_exc: true, }); + self.in_exc_handler = was_in_exc_handler; + // We successfully ran the try block: // else: self.set_label(else_label); @@ -649,7 +651,6 @@ impl Compiler { if let Some(statements) = finalbody { self.compile_statements(statements)?; } - self.in_exc_handler = was_in_exc_handler; // unimplemented!(); Ok(()) } From 4068a36ce8a576b3b5c7b19716a8cb0ee0a43421 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 14 Apr 2019 20:38:34 +0300 Subject: [PATCH 311/884] Use assertRaises --- tests/snippets/try_exceptions.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index d7785424a5..83e00c0fe5 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -126,18 +126,14 @@ def __init__(self): except ZeroDivisionError as ex: assert type(ex.__cause__) == NameError -try: +with assertRaises(NameError): try: raise NameError except: raise -except NameError: - pass -try: +with assertRaises(RuntimeError): raise -except RuntimeError: - pass context = None try: From 2ffd1c948447df991e927e6d7a6d88d42d2527a7 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 14 Apr 2019 20:39:28 +0300 Subject: [PATCH 312/884] Fix typo --- vm/src/exceptions.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 7642908eeb..5f577d04fc 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -20,15 +20,15 @@ fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // print excption chain pub fn print_exception(vm: &VirtualMachine, exc: &PyObjectRef) { - let mut had_casue = false; + let mut had_cause = false; if let Ok(cause) = vm.get_attribute(exc.clone(), "__cause__") { if !vm.get_none().is(&cause) { - had_casue = true; + had_cause = true; print_exception(vm, &cause); println!("\nThe above exception was the direct cause of the following exception:\n"); } } - if !had_casue { + if !had_cause { if let Ok(context) = vm.get_attribute(exc.clone(), "__context__") { if !vm.get_none().is(&context) { print_exception(vm, &context); From 79623f4e7a3e9f3e3fdcbbe65f2dda923250b799 Mon Sep 17 00:00:00 2001 From: Joey Date: Sun, 14 Apr 2019 12:23:09 -0700 Subject: [PATCH 313/884] str: proper titlecase support --- Cargo.lock | 7 +++++++ vm/Cargo.toml | 1 + vm/src/obj/objstr.rs | 9 ++++++--- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 864e90ce5a..100f6a3599 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -827,6 +827,7 @@ dependencies = [ "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing)", "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1102,6 +1103,11 @@ dependencies = [ "unic-common 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unicode-casing" +version = "0.1.0" +source = "git+https://github.com/OddCoincidence/unicode-casing#90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb" + [[package]] name = "unicode-normalization" version = "0.1.8" @@ -1431,6 +1437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unic-common 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" "checksum unic-emoji-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d" "checksum unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +"checksum unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing)" = "" "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index ef1a9cbc35..1ba1986b78 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -23,6 +23,7 @@ regex = "1" rustc_version_runtime = "0.1.*" statrs = "0.10.0" caseless = "0.2.1" +unicode-casing = { git = "https://github.com/OddCoincidence/unicode-casing" } unicode-segmentation = "1.2.1" lazy_static = "^1.0.1" lexical = "2.0.0" diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 390e92d27d..b5e7a29cf7 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -5,6 +5,7 @@ use std::str::FromStr; use std::string::ToString; use num_traits::ToPrimitive; +use unicode_casing::CharExt; use unicode_segmentation::UnicodeSegmentation; use crate::format::{FormatParseError, FormatPart, FormatString}; @@ -413,12 +414,12 @@ impl PyString { for c in self.value.chars() { if c.is_lowercase() { if !previous_is_cased { - title.extend(c.to_uppercase()); + title.extend(c.to_titlecase()); } else { title.push(c); } previous_is_cased = true; - } else if c.is_uppercase() { + } else if c.is_uppercase() || c.is_titlecase() { if previous_is_cased { title.extend(c.to_lowercase()); } else { @@ -652,7 +653,7 @@ impl PyString { let mut cased = false; let mut previous_is_cased = false; for c in self.value.chars() { - if c.is_uppercase() { + if c.is_uppercase() || c.is_titlecase() { if previous_is_cased { return false; } @@ -1050,6 +1051,7 @@ mod tests { ("Format,This-As*Title;String", "fOrMaT,thIs-aS*titLe;String"), ("Getint", "getInt"), ("Greek Ωppercases ...", "greek ωppercases ..."), + ("Greek ῼitlecases ...", "greek ῳitlecases ..."), ]; for (title, input) in tests { assert_eq!(PyString::from(input).title(&vm).as_str(), title); @@ -1066,6 +1068,7 @@ mod tests { "A\nTitlecased Line", "A Titlecased, Line", "Greek Ωppercases ...", + "Greek ῼitlecases ...", ]; for s in pos { From daaeb7883146a0cd3611f3c661f951a4e64bbd58 Mon Sep 17 00:00:00 2001 From: Darren Kaste Date: Sun, 14 Apr 2019 17:08:35 -0400 Subject: [PATCH 314/884] Prefix helper macro calls with `$crate` --- vm/src/macros.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 27d1647184..b84643b5f3 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -9,7 +9,7 @@ macro_rules! replace_expr { #[macro_export] macro_rules! count_tts { - ($($tts:tt)*) => {0usize $(+ replace_expr!($tts 1usize))*}; + ($($tts:tt)*) => {0usize $(+ $crate::replace_expr!($tts 1usize))*}; } #[macro_export] @@ -45,14 +45,14 @@ macro_rules! arg_check { } }; ( $vm: ident, $args:ident, required=[$( ($arg_name:ident, $arg_type:expr) ),*] ) => { - arg_check!($vm, $args, required=[$( ($arg_name, $arg_type) ),*], optional=[]); + $crate::arg_check!($vm, $args, required=[$( ($arg_name, $arg_type) ),*], optional=[]); }; ( $vm: ident, $args:ident, required=[$( ($arg_name:ident, $arg_type:expr) ),*], optional=[$( ($optional_arg_name:ident, $optional_arg_type:expr) ),*] ) => { let mut arg_count = 0; // use macro magic to compile-time count number of required and optional arguments - let minimum_arg_count = count_tts!($($arg_name)*); - let maximum_arg_count = minimum_arg_count + count_tts!($($optional_arg_name)*); + let minimum_arg_count = $crate::count_tts!($($arg_name)*); + let maximum_arg_count = minimum_arg_count + $crate::count_tts!($($optional_arg_name)*); // verify that the number of given arguments is right if $args.args.len() < minimum_arg_count || $args.args.len() > maximum_arg_count { @@ -72,7 +72,7 @@ macro_rules! arg_check { // check if the type matches. If not, return with error // assign the arg to a variable $( - type_check!($vm, $args, arg_count, $arg_name, $arg_type); + $crate::type_check!($vm, $args, arg_count, $arg_name, $arg_type); let $arg_name = &$args.args[arg_count]; #[allow(unused_assignments)] { @@ -85,7 +85,7 @@ macro_rules! arg_check { // assign the arg to a variable $( let $optional_arg_name = if arg_count < $args.args.len() { - type_check!($vm, $args, arg_count, $optional_arg_name, $optional_arg_type); + $crate::type_check!($vm, $args, arg_count, $optional_arg_name, $optional_arg_type); let ret = Some(&$args.args[arg_count]); #[allow(unused_assignments)] { @@ -226,7 +226,7 @@ macro_rules! match_class { ($obj:expr, $binding:ident @ $class:ty => $expr:expr, $($rest:tt)*) => { match $obj.downcast::<$class>() { Ok($binding) => $expr, - Err(_obj) => match_class!(_obj, $($rest)*), + Err(_obj) => $crate::match_class!(_obj, $($rest)*), } }; @@ -236,7 +236,7 @@ macro_rules! match_class { if $obj.payload_is::<$class>() { $expr } else { - match_class!($obj, $($rest)*) + $crate::match_class!($obj, $($rest)*) } }; } From a8e064b1c0f32077998a913d00b08a29ebeaf9dc Mon Sep 17 00:00:00 2001 From: Darren Kaste Date: Sun, 14 Apr 2019 17:19:44 -0400 Subject: [PATCH 315/884] Import `TypeProtocol` before use in `type_check!` --- vm/src/macros.rs | 2 ++ vm/src/obj/objbool.rs | 4 +--- vm/src/obj/objellipsis.rs | 2 +- vm/src/obj/objlist.rs | 1 - vm/src/obj/objslice.rs | 2 +- vm/src/stdlib/io.rs | 2 +- vm/src/stdlib/keyword.rs | 2 +- vm/src/stdlib/math.rs | 2 +- vm/src/stdlib/os.rs | 2 +- vm/src/stdlib/pystruct.rs | 2 +- vm/src/stdlib/random.rs | 2 +- vm/src/stdlib/socket.rs | 2 +- vm/src/stdlib/time_module.rs | 2 +- vm/src/stdlib/tokenize.rs | 2 +- vm/src/sysmodule.rs | 2 +- 15 files changed, 15 insertions(+), 16 deletions(-) diff --git a/vm/src/macros.rs b/vm/src/macros.rs index b84643b5f3..3336739852 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -20,6 +20,8 @@ macro_rules! type_check { let arg = &$args.args[$arg_count]; if !$crate::obj::objtype::isinstance(arg, &expected_type) { + use $crate::pyobject::TypeProtocol; + let arg_typ = arg.class(); let expected_type_name = $vm.to_pystr(&expected_type)?; let actual_type = $vm.to_pystr(&arg_typ)?; diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index d4ef4b0fbc..724c8bc5ae 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -1,9 +1,7 @@ use num_traits::Zero; use crate::function::PyFuncArgs; -use crate::pyobject::{ - IntoPyObject, PyContext, PyObjectRef, PyResult, TryFromObject, TypeProtocol, -}; +use crate::pyobject::{IntoPyObject, PyContext, PyObjectRef, PyResult, TryFromObject}; use crate::vm::VirtualMachine; use super::objint::PyInt; diff --git a/vm/src/obj/objellipsis.rs b/vm/src/obj/objellipsis.rs index 6591d81738..cb188e1f11 100644 --- a/vm/src/obj/objellipsis.rs +++ b/vm/src/obj/objellipsis.rs @@ -1,5 +1,5 @@ use crate::function::PyFuncArgs; -use crate::pyobject::{PyContext, PyResult, TypeProtocol}; +use crate::pyobject::{PyContext, PyResult}; use crate::vm::VirtualMachine; pub fn init(context: &PyContext) { diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 276a8ea895..a21bd9190c 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -9,7 +9,6 @@ use num_traits::{One, Signed, ToPrimitive, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ IdProtocol, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, - TypeProtocol, }; use crate::vm::{ReprGuard, VirtualMachine}; diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index 31fcfa52fe..60d911a921 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -1,7 +1,7 @@ use num_bigint::BigInt; use crate::function::PyFuncArgs; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint; diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index d864519f57..b8e72cc15d 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -20,7 +20,7 @@ use crate::obj::objbytes; use crate::obj::objint; use crate::obj::objstr; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{BufferProtocol, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{BufferProtocol, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; fn compute_c_flag(mode: &str) -> u16 { diff --git a/vm/src/stdlib/keyword.rs b/vm/src/stdlib/keyword.rs index 3143af21aa..062f41df74 100644 --- a/vm/src/stdlib/keyword.rs +++ b/vm/src/stdlib/keyword.rs @@ -6,7 +6,7 @@ use rustpython_parser::lexer; use crate::function::PyFuncArgs; use crate::obj::objstr; -use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult}; use crate::vm::VirtualMachine; fn keyword_iskeyword(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index dc7981ab6d..2ee584a799 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -8,7 +8,7 @@ use statrs::function::gamma::{gamma, ln_gamma}; use crate::function::PyFuncArgs; use crate::obj::objfloat; -use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult}; use crate::vm::VirtualMachine; // Helper macro: diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index f397b2d8a0..fae9d6adb7 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -12,7 +12,7 @@ use crate::obj::objint; use crate::obj::objint::PyIntRef; use crate::obj::objstr; use crate::obj::objstr::PyStringRef; -use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult}; use crate::vm::VirtualMachine; #[cfg(unix)] diff --git a/vm/src/stdlib/pystruct.rs b/vm/src/stdlib/pystruct.rs index 2ff61d630a..4a454b0c98 100644 --- a/vm/src/stdlib/pystruct.rs +++ b/vm/src/stdlib/pystruct.rs @@ -15,7 +15,7 @@ use num_traits::ToPrimitive; use crate::function::PyFuncArgs; use crate::obj::{objbool, objbytes, objfloat, objint, objstr, objtype}; -use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult}; use crate::VirtualMachine; #[derive(Debug)] diff --git a/vm/src/stdlib/random.rs b/vm/src/stdlib/random.rs index c6cc44a363..cf3c0ea183 100644 --- a/vm/src/stdlib/random.rs +++ b/vm/src/stdlib/random.rs @@ -4,7 +4,7 @@ use rand::distributions::{Distribution, Normal}; use crate::function::PyFuncArgs; use crate::obj::objfloat; -use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult}; use crate::vm::VirtualMachine; pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index ab5072f7c1..e893e0dab7 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -10,7 +10,7 @@ use crate::obj::objbytes; use crate::obj::objint; use crate::obj::objsequence::get_elements; use crate::obj::objstr; -use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, TryFromObject}; use crate::vm::VirtualMachine; use crate::obj::objtype::PyClassRef; diff --git a/vm/src/stdlib/time_module.rs b/vm/src/stdlib/time_module.rs index 93beabf8ac..6c818689e0 100644 --- a/vm/src/stdlib/time_module.rs +++ b/vm/src/stdlib/time_module.rs @@ -5,7 +5,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::function::PyFuncArgs; use crate::obj::objfloat; -use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult}; use crate::vm::VirtualMachine; fn time_sleep(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/stdlib/tokenize.rs b/vm/src/stdlib/tokenize.rs index 88a26881cc..000dd5e1a3 100644 --- a/vm/src/stdlib/tokenize.rs +++ b/vm/src/stdlib/tokenize.rs @@ -8,7 +8,7 @@ use rustpython_parser::lexer; use crate::function::PyFuncArgs; use crate::obj::objstr; -use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{PyObjectRef, PyResult}; use crate::vm::VirtualMachine; fn tokenize_tokenize(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 97de7a425c..a5bf2a6f4f 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -4,7 +4,7 @@ use std::{env, mem}; use crate::frame::FrameRef; use crate::function::{OptionalArg, PyFuncArgs}; use crate::obj::objstr::PyStringRef; -use crate::pyobject::{ItemProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; +use crate::pyobject::{ItemProtocol, PyContext, PyObjectRef, PyResult}; use crate::vm::VirtualMachine; /* From cc76bf7a1f4c7ecf9d72313107538bf4b20a600f Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 14 Apr 2019 20:09:15 -0500 Subject: [PATCH 316/884] Add weakproxy class --- vm/src/exceptions.rs | 3 +++ vm/src/obj/mod.rs | 1 + vm/src/obj/objweakproxy.rs | 50 ++++++++++++++++++++++++++++++++++++++ vm/src/obj/objweakref.rs | 2 +- vm/src/pyobject.rs | 9 +++++++ vm/src/stdlib/weakref.rs | 3 ++- 6 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 vm/src/obj/objweakproxy.rs diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 5f577d04fc..58a0f929d7 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -118,6 +118,7 @@ pub struct ExceptionZoo { pub os_error: PyClassRef, pub overflow_error: PyClassRef, pub permission_error: PyClassRef, + pub reference_error: PyClassRef, pub runtime_error: PyClassRef, pub stop_iteration: PyClassRef, pub syntax_error: PyClassRef, @@ -152,6 +153,7 @@ impl ExceptionZoo { let name_error = create_type("NameError", &type_type, &exception_type); let os_error = create_type("OSError", &type_type, &exception_type); let runtime_error = create_type("RuntimeError", &type_type, &exception_type); + let reference_error = create_type("ReferenceError", &type_type, &exception_type); let stop_iteration = create_type("StopIteration", &type_type, &exception_type); let syntax_error = create_type("SyntaxError", &type_type, &exception_type); let type_error = create_type("TypeError", &type_type, &exception_type); @@ -208,6 +210,7 @@ impl ExceptionZoo { syntax_warning, resource_warning, runtime_warning, + reference_error, user_warning, } } diff --git a/vm/src/obj/mod.rs b/vm/src/obj/mod.rs index c824e726d8..bfe89fd15a 100644 --- a/vm/src/obj/mod.rs +++ b/vm/src/obj/mod.rs @@ -34,5 +34,6 @@ pub mod objstr; pub mod objsuper; pub mod objtuple; pub mod objtype; +pub mod objweakproxy; pub mod objweakref; pub mod objzip; diff --git a/vm/src/obj/objweakproxy.rs b/vm/src/obj/objweakproxy.rs new file mode 100644 index 0000000000..735670f4e4 --- /dev/null +++ b/vm/src/obj/objweakproxy.rs @@ -0,0 +1,50 @@ +use super::objweakref::PyWeak; +use crate::function::OptionalArg; +use crate::obj::objtype::PyClassRef; +use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::vm::VirtualMachine; + +#[derive(Debug)] +pub struct PyWeakProxy { + weak: PyWeak, +} + +impl PyValue for PyWeakProxy { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.weakproxy_type() + } +} + +pub type PyWeakProxyRef = PyRef; + +impl PyWeakProxyRef { + // TODO callbacks + fn create( + cls: PyClassRef, + referent: PyObjectRef, + _callback: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + PyWeakProxy { + weak: PyWeak::downgrade(&referent), + } + .into_ref_with_type(vm, cls) + } + + fn getattr(self, attr_name: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match self.weak.upgrade() { + Some(obj) => vm.get_attribute(obj, attr_name), + None => Err(vm.new_exception( + vm.ctx.exceptions.reference_error.clone(), + "weakly-referenced object no longer exists".to_string(), + )), + } + } +} + +pub fn init(context: &PyContext) { + extend_class!(context, &context.weakproxy_type, { + "__new__" => context.new_rustfunc(PyWeakProxyRef::create), + "__getattr__" => context.new_rustfunc(PyWeakProxyRef::getattr), + }); +} diff --git a/vm/src/obj/objweakref.rs b/vm/src/obj/objweakref.rs index 05d6147ce7..b3b7e03c9b 100644 --- a/vm/src/obj/objweakref.rs +++ b/vm/src/obj/objweakref.rs @@ -1,9 +1,9 @@ +use crate::function::OptionalArg; use crate::obj::objtype::PyClassRef; use crate::pyobject::PyValue; use crate::pyobject::{PyContext, PyObject, PyObjectPayload, PyObjectRef, PyRef, PyResult}; use crate::vm::VirtualMachine; -use crate::function::OptionalArg; use std::rc::{Rc, Weak}; #[derive(Debug)] diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index f369bab4aa..fdac31b837 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -51,6 +51,7 @@ use crate::obj::objstr; use crate::obj::objsuper; use crate::obj::objtuple::{self, PyTuple, PyTupleRef}; use crate::obj::objtype::{self, PyClass, PyClassRef}; +use crate::obj::objweakproxy; use crate::obj::objweakref; use crate::obj::objzip; use crate::vm::VirtualMachine; @@ -158,6 +159,7 @@ pub struct PyContext { pub module_type: PyClassRef, pub bound_method_type: PyClassRef, pub weakref_type: PyClassRef, + pub weakproxy_type: PyClassRef, pub object: PyClassRef, pub exceptions: exceptions::ExceptionZoo, } @@ -255,6 +257,7 @@ impl PyContext { let readonly_property_type = create_type("readonly_property", &type_type, &object_type); let super_type = create_type("super", &type_type, &object_type); let weakref_type = create_type("ref", &type_type, &object_type); + let weakproxy_type = create_type("weakproxy", &type_type, &object_type); let generator_type = create_type("generator", &type_type, &object_type); let bound_method_type = create_type("method", &type_type, &object_type); let str_type = create_type("str", &type_type, &object_type); @@ -361,6 +364,7 @@ impl PyContext { module_type, bound_method_type, weakref_type, + weakproxy_type, type_type, exceptions, }; @@ -396,6 +400,7 @@ impl PyContext { objcode::init(&context); objframe::init(&context); objweakref::init(&context); + objweakproxy::init(&context); objnone::init(&context); objmodule::init(&context); exceptions::init(&context); @@ -554,6 +559,10 @@ impl PyContext { self.weakref_type.clone() } + pub fn weakproxy_type(&self) -> PyClassRef { + self.weakproxy_type.clone() + } + pub fn type_type(&self) -> PyClassRef { self.type_type.clone() } diff --git a/vm/src/stdlib/weakref.rs b/vm/src/stdlib/weakref.rs index b5f7b756ac..d4529599ef 100644 --- a/vm/src/stdlib/weakref.rs +++ b/vm/src/stdlib/weakref.rs @@ -12,6 +12,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; py_module!(vm, "_weakref", { - "ref" => ctx.weakref_type() + "ref" => ctx.weakref_type(), + "proxy" => ctx.weakproxy_type(), }) } From 1612c954a17e38ed3c4f1fdee40096a9eabf468f Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 14 Apr 2019 20:14:54 -0500 Subject: [PATCH 317/884] Add weakproxy test --- tests/snippets/weakrefs.py | 17 ++++++++++++++++- vm/src/builtins.rs | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/snippets/weakrefs.py b/tests/snippets/weakrefs.py index ffc61f4271..059ddd0000 100644 --- a/tests/snippets/weakrefs.py +++ b/tests/snippets/weakrefs.py @@ -1,4 +1,5 @@ -from _weakref import ref +from _weakref import ref, proxy +from testutils import assert_raises class X: @@ -11,3 +12,17 @@ class X: assert callable(b) assert b() is a + +class G: + def __init__(self, h): + self.h = h + + +g = G(5) +p = proxy(g) + +assert p.h == 5 + +del g + +assert_raises(ReferenceError, lambda: p.h) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 8f7721609c..a96df53e90 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -751,6 +751,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "NameError" => ctx.exceptions.name_error.clone(), "OverflowError" => ctx.exceptions.overflow_error.clone(), "RuntimeError" => ctx.exceptions.runtime_error.clone(), + "ReferenceError" => ctx.exceptions.reference_error.clone(), "NotImplementedError" => ctx.exceptions.not_implemented_error.clone(), "TypeError" => ctx.exceptions.type_error.clone(), "ValueError" => ctx.exceptions.value_error.clone(), From c9b479c43b62ef65bf632bce3d0e78ae9d57395f Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 11 Apr 2019 09:19:23 +0100 Subject: [PATCH 318/884] Guard for changes in dictionary size during iteration. --- tests/snippets/dict.py | 12 ++++++++++++ vm/src/dictdatatype.rs | 29 +++++++++++++++++++++++------ vm/src/obj/objdict.rs | 27 +++++++++++++++++++-------- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 4c35af2f44..05cdd99285 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -69,6 +69,18 @@ data[3] = "changed" assert (3, "changed") == next(items) +# But we can't add or delete items during iteration. +d = {} +a = iter(d.items()) +d['a'] = 2 +b = iter(d.items()) +assert ('a', 2) == next(b) +with assertRaises(RuntimeError): + next(a) +del d['a'] +with assertRaises(RuntimeError): + next(b) + # View isn't itself an iterator. with assertRaises(TypeError): next(data.keys()) diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 6dc49125bd..1f4b4eecdd 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -33,6 +33,12 @@ struct DictEntry { value: T, } +#[derive(Debug)] +pub struct DictSize { + size: usize, + entries_size: usize, +} + impl Dict { pub fn new() -> Self { Dict { @@ -120,17 +126,28 @@ impl Dict { self.len() == 0 } - pub fn next_entry(&self, mut position: usize) -> Option<(usize, &PyObjectRef, &T)> { - while position < self.entries.len() { - if let Some(DictEntry { key, value, .. }) = &self.entries[position] { - return Some((position + 1, key, value)); - } else { - position += 1; + pub fn size(&self) -> DictSize { + DictSize { + size: self.size, + entries_size: self.entries.len(), + } + } + + pub fn next_entry(&self, position: &mut usize) -> Option<(&PyObjectRef, &T)> { + while *position < self.entries.len() { + if let Some(DictEntry { key, value, .. }) = &self.entries[*position] { + *position += 1; + return Some((key, value)); } + *position += 1; } None } + pub fn has_changed_size(&self, position: &DictSize) -> bool { + position.size != self.size || self.entries.len() != position.entries_size + } + /// Lookup the index for the given key. fn lookup(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { let hash_value = calc_hash(vm, key)?; diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index fd77107eec..7b11dd6c19 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -260,6 +260,10 @@ impl PyDictRef { let key = key.into_pyobject(vm).unwrap(); self.entries.borrow().contains(vm, &key).unwrap() } + + pub fn size(&self) -> dictdatatype::DictSize { + self.entries.borrow().size() + } } impl ItemProtocol for PyDictRef { @@ -315,11 +319,8 @@ impl Iterator for DictIter { type Item = (PyObjectRef, PyObjectRef); fn next(&mut self) -> Option { - match self.dict.entries.borrow().next_entry(self.position) { - Some((new_position, key, value)) => { - self.position = new_position; - Some((key.clone(), value.clone())) - } + match self.dict.entries.borrow().next_entry(&mut self.position) { + Some((key, value)) => Some((key.clone(), value.clone())), None => None, } } @@ -360,6 +361,7 @@ macro_rules! dict_iterator { #[derive(Debug)] struct $iter_name { pub dict: PyDictRef, + pub size: dictdatatype::DictSize, pub position: Cell, } @@ -368,15 +370,24 @@ macro_rules! dict_iterator { fn new(dict: PyDictRef) -> Self { $iter_name { position: Cell::new(0), + size: dict.size(), dict, } } #[pymethod(name = "__next__")] fn next(&self, vm: &VirtualMachine) -> PyResult { - match self.dict.entries.borrow().next_entry(self.position.get()) { - Some((new_position, key, value)) => { - self.position.set(new_position); + let mut position = self.position.get(); + let dict = self.dict.entries.borrow(); + if dict.has_changed_size(&self.size) { + return Err(vm.new_exception( + vm.ctx.exceptions.runtime_error.clone(), + "dictionary changed size during iteration".to_string(), + )); + } + match dict.next_entry(&mut position) { + Some((key, value)) => { + self.position.set(position); Ok($result_fn(vm, key, value)) } None => Err(objiter::new_stop_iteration(vm)), From dfbaf3cdf01936c5769467e250ced6365863935e Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Mon, 15 Apr 2019 13:23:15 +0100 Subject: [PATCH 319/884] Remove pub from PyDict.entries (easy TODO) --- vm/src/obj/objdict.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 7b11dd6c19..25df2ed146 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -19,8 +19,7 @@ pub type DictContentType = dictdatatype::Dict; #[derive(Default)] pub struct PyDict { - // TODO: should be private - pub entries: RefCell, + entries: RefCell, } pub type PyDictRef = PyRef; From 08babef619763a3a69347c890cd448f3fe6cf2ea Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 15 Apr 2019 20:24:16 -0500 Subject: [PATCH 320/884] Remove the __inside_vm hack for procedural macros --- derive/src/from_args.rs | 27 +++++++++++---------------- derive/src/lib.rs | 37 +------------------------------------ derive/src/pyclass.rs | 17 ++++++----------- vm/src/builtins.rs | 1 - vm/src/lib.rs | 2 ++ vm/src/obj/objbytes.rs | 4 ++-- vm/src/obj/objdict.rs | 8 ++++---- vm/src/obj/objgenerator.rs | 4 ++-- vm/src/obj/objint.rs | 5 ++--- vm/src/obj/objstr.rs | 4 ++-- 10 files changed, 32 insertions(+), 77 deletions(-) diff --git a/derive/src/from_args.rs b/derive/src/from_args.rs index 040ebf36ca..d017a32e5a 100644 --- a/derive/src/from_args.rs +++ b/derive/src/from_args.rs @@ -1,4 +1,3 @@ -use super::rustpython_path_derive; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use syn::{Attribute, Data, DeriveInput, Expr, Field, Fields, Ident, Lit, Meta, NestedMeta}; @@ -109,7 +108,7 @@ impl ArgAttribute { } } -fn generate_field(field: &Field, rp_path: &syn::Path) -> TokenStream2 { +fn generate_field(field: &Field) -> TokenStream2 { let mut pyarg_attrs = field .attrs .iter() @@ -132,7 +131,7 @@ fn generate_field(field: &Field, rp_path: &syn::Path) -> TokenStream2 { let name = &field.ident; let middle = quote! { - .map(|x| #rp_path::pyobject::TryFromObject::try_from_object(vm, x)).transpose()? + .map(|x| ::rustpython_vm::pyobject::TryFromObject::try_from_object(vm, x)).transpose()? }; let ending = if let Some(default) = attr.default { quote! { @@ -140,16 +139,16 @@ fn generate_field(field: &Field, rp_path: &syn::Path) -> TokenStream2 { } } else if attr.optional { quote! { - .map(#rp_path::function::OptionalArg::Present) - .unwrap_or(#rp_path::function::OptionalArg::Missing) + .map(::rustpython_vm::function::OptionalArg::Present) + .unwrap_or(::rustpython_vm::function::OptionalArg::Missing) } } else { let err = match attr.kind { ParameterKind::PositionalOnly | ParameterKind::PositionalOrKeyword => quote! { - #rp_path::function::ArgumentError::TooFewArgs + ::rustpython_vm::function::ArgumentError::TooFewArgs }, ParameterKind::KeywordOnly => quote! { - #rp_path::function::ArgumentError::RequiredKeywordArgument(tringify!(#name)) + ::rustpython_vm::function::ArgumentError::RequiredKeywordArgument(tringify!(#name)) }, }; quote! { @@ -177,14 +176,10 @@ fn generate_field(field: &Field, rp_path: &syn::Path) -> TokenStream2 { } pub fn impl_from_args(input: DeriveInput) -> TokenStream2 { - let rp_path = rustpython_path_derive(&input); let fields = match input.data { Data::Struct(ref data) => { match data.fields { - Fields::Named(ref fields) => fields - .named - .iter() - .map(|field| generate_field(field, &rp_path)), + Fields::Named(ref fields) => fields.named.iter().map(generate_field), Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message } } @@ -193,11 +188,11 @@ pub fn impl_from_args(input: DeriveInput) -> TokenStream2 { let name = &input.ident; quote! { - impl #rp_path::function::FromArgs for #name { + impl ::rustpython_vm::function::FromArgs for #name { fn from_args( - vm: &#rp_path::VirtualMachine, - args: &mut #rp_path::function::PyFuncArgs - ) -> Result { + vm: &::rustpython_vm::VirtualMachine, + args: &mut ::rustpython_vm::function::PyFuncArgs + ) -> Result { Ok(#name { #(#fields)* }) } } diff --git a/derive/src/lib.rs b/derive/src/lib.rs index adde078f03..26dce935ae 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,47 +1,12 @@ extern crate proc_macro; use proc_macro::TokenStream; -use quote::quote; use syn::{parse_macro_input, AttributeArgs, DeriveInput, Item}; mod from_args; mod pyclass; -fn rustpython_path(inside_vm: bool) -> syn::Path { - let path = if inside_vm { - quote!(crate) - } else { - quote!(::rustpython_vm) - }; - syn::parse2(path).unwrap() -} - -/// Does the item have the #[__inside_vm] attribute on it, signifying that the derive target is -/// being derived from inside the `rustpython_vm` crate. -fn rustpython_path_derive(input: &DeriveInput) -> syn::Path { - rustpython_path( - input - .attrs - .iter() - .any(|attr| attr.path.is_ident("__inside_vm")), - ) -} - -fn rustpython_path_attr(attr: &AttributeArgs) -> syn::Path { - rustpython_path(attr.iter().any(|meta| { - if let syn::NestedMeta::Meta(meta) = meta { - if let syn::Meta::Word(ident) = meta { - ident == "__inside_vm" - } else { - false - } - } else { - false - } - })) -} - -#[proc_macro_derive(FromArgs, attributes(__inside_vm, pyarg))] +#[proc_macro_derive(FromArgs, attributes(pyarg))] pub fn derive_from_args(input: TokenStream) -> TokenStream { let ast: DeriveInput = syn::parse(input).unwrap(); diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index e072db32d7..36ce9df1ac 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -1,4 +1,3 @@ -use super::rustpython_path_attr; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use std::collections::HashMap; @@ -138,15 +137,13 @@ impl ClassItem { } } -pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { +pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> TokenStream2 { let mut imp = if let Item::Impl(imp) = item { imp } else { return quote!(#item); }; - let rp_path = rustpython_path_attr(&attr); - let items = imp .items .iter_mut() @@ -199,7 +196,7 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { quote! { class.set_str_attr( #name, - #rp_path::obj::objproperty::PropertyBuilder::new(ctx) + ::rustpython_vm::obj::objproperty::PropertyBuilder::new(ctx) .add_getter(Self::#getter) #add_setter .create(), @@ -209,10 +206,10 @@ pub fn impl_pyimpl(attr: AttributeArgs, item: Item) -> TokenStream2 { quote! { #imp - impl #rp_path::pyobject::PyClassImpl for #ty { + impl ::rustpython_vm::pyobject::PyClassImpl for #ty { fn impl_extend_class( - ctx: &#rp_path::pyobject::PyContext, - class: &#rp_path::obj::objtype::PyClassRef, + ctx: &::rustpython_vm::pyobject::PyContext, + class: &::rustpython_vm::obj::objtype::PyClassRef, ) { #(#methods)* #(#properties)* @@ -228,8 +225,6 @@ pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { _ => panic!("#[pyclass] can only be on a struct or enum declaration"), }; - let rp_path = rustpython_path_attr(&attr); - let mut class_name = None; for attr in attr { if let NestedMeta::Meta(meta) = attr { @@ -273,7 +268,7 @@ pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { quote! { #item - impl #rp_path::pyobject::PyClassDef for #ident { + impl ::rustpython_vm::pyobject::PyClassDef for #ident { const NAME: &'static str = #class_name; const DOC: Option<&'static str> = #doc; } diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index a96df53e90..f2ca1e4c12 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -530,7 +530,6 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } #[derive(Debug, FromArgs)] -#[__inside_vm] pub struct PrintOptions { #[pyarg(keyword_only, default = "None")] sep: Option, diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 578f1d99a3..8b14454fe7 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -32,6 +32,8 @@ extern crate rustpython_parser; #[macro_use] extern crate rustpython_derive; +extern crate self as rustpython_vm; + pub use rustpython_derive::*; //extern crate eval; use eval::eval::*; diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index fd9bfd5bff..0556de0535 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -21,7 +21,7 @@ use super::objtype::PyClassRef; /// - a text string encoded using the specified encoding\n \ /// - any object implementing the buffer API.\n \ /// - an integer"; -#[pyclass(name = "bytes", __inside_vm)] +#[pyclass(name = "bytes")] #[derive(Clone, Debug)] pub struct PyBytes { inner: PyByteInner, @@ -71,7 +71,7 @@ pub fn init(context: &PyContext) { }); } -#[pyimpl(__inside_vm)] +#[pyimpl] impl PyBytesRef { #[pymethod(name = "__new__")] fn bytes_new( diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 25df2ed146..e5419e932c 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -327,13 +327,13 @@ impl Iterator for DictIter { macro_rules! dict_iterator { ( $name: ident, $iter_name: ident, $class: ident, $iter_class: ident, $class_name: literal, $iter_class_name: literal, $result_fn: expr) => { - #[pyclass(name = $class_name, __inside_vm)] + #[pyclass(name = $class_name)] #[derive(Debug)] struct $name { pub dict: PyDictRef, } - #[pyimpl(__inside_vm)] + #[pyimpl] impl $name { fn new(dict: PyDictRef) -> Self { $name { dict: dict } @@ -356,7 +356,7 @@ macro_rules! dict_iterator { } } - #[pyclass(name = $iter_class_name, __inside_vm)] + #[pyclass(name = $iter_class_name)] #[derive(Debug)] struct $iter_name { pub dict: PyDictRef, @@ -364,7 +364,7 @@ macro_rules! dict_iterator { pub position: Cell, } - #[pyimpl(__inside_vm)] + #[pyimpl] impl $iter_name { fn new(dict: PyDictRef) -> Self { $iter_name { diff --git a/vm/src/obj/objgenerator.rs b/vm/src/obj/objgenerator.rs index 2541263f1a..2c51778fde 100644 --- a/vm/src/obj/objgenerator.rs +++ b/vm/src/obj/objgenerator.rs @@ -9,7 +9,7 @@ use crate::vm::VirtualMachine; pub type PyGeneratorRef = PyRef; -#[pyclass(name = "generator", __inside_vm)] +#[pyclass(name = "generator")] #[derive(Debug)] pub struct PyGenerator { frame: FrameRef, @@ -21,7 +21,7 @@ impl PyValue for PyGenerator { } } -#[pyimpl(__inside_vm)] +#[pyimpl] impl PyGenerator { pub fn new(frame: FrameRef, vm: &VirtualMachine) -> PyGeneratorRef { PyGenerator { frame }.into_ref(vm) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 52f321adfc..20f5de4caf 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -32,7 +32,7 @@ use crate::obj::objtype::PyClassRef; /// Base 0 means to interpret the base from the string as an integer literal. /// >>> int('0b100', base=0) /// 4 -#[pyclass(__inside_vm)] +#[pyclass] #[derive(Debug)] pub struct PyInt { value: BigInt, @@ -111,7 +111,7 @@ impl_try_from_object_int!( (u64, to_u64), ); -#[pyimpl(__inside_vm)] +#[pyimpl] impl PyInt { #[pymethod(name = "__eq__")] fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { @@ -476,7 +476,6 @@ impl PyInt { } #[derive(FromArgs)] -#[__inside_vm] struct IntOptions { #[pyarg(positional_only, optional = true)] val_options: OptionalArg, diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 390e92d27d..1638a712ce 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -30,7 +30,7 @@ use super::objtype::{self, PyClassRef}; /// or repr(object). /// encoding defaults to sys.getdefaultencoding(). /// errors defaults to 'strict'." -#[pyclass(name = "str", __inside_vm)] +#[pyclass(name = "str")] #[derive(Clone, Debug)] pub struct PyString { // TODO: shouldn't be public @@ -74,7 +74,7 @@ impl TryIntoRef for &str { } } -#[pyimpl(__inside_vm)] +#[pyimpl] impl PyString { // TODO: should with following format // class str(object='') From dc63fc8ce7e02ad1728184e446d278a69016e1f4 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 15 Apr 2019 22:14:12 -0500 Subject: [PATCH 321/884] Switch to span-based errors for pyclass and pyimpl --- derive/src/error.rs | 158 ++++++++++++++++++++++++++++++++++++ derive/src/lib.rs | 20 ++++- derive/src/pyclass.rs | 184 +++++++++++++++++++++++++++--------------- 3 files changed, 291 insertions(+), 71 deletions(-) create mode 100644 derive/src/error.rs diff --git a/derive/src/error.rs b/derive/src/error.rs new file mode 100644 index 0000000000..8754b4a514 --- /dev/null +++ b/derive/src/error.rs @@ -0,0 +1,158 @@ +// Taken from https://github.com/rustwasm/wasm-bindgen/blob/master/crates/backend/src/error.rs +// +// Copyright (c) 2014 Alex Crichton +// +// Permission is hereby granted, free of charge, to any +// person obtaining a copy of this software and associated +// documentation files (the "Software"), to deal in the +// Software without restriction, including without +// limitation the rights to use, copy, modify, merge, +// publish, distribute, sublicense, and/or sell copies of +// the Software, and to permit persons to whom the Software +// is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice +// shall be included in all copies or substantial portions +// of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +#![allow(dead_code)] + +use proc_macro2::*; +use quote::{ToTokens, TokenStreamExt}; +use syn::parse::Error; + +macro_rules! err_span { + ($span:expr, $($msg:tt)*) => ( + $crate::Diagnostic::spanned_error(&$span, format!($($msg)*)) + ) +} + +macro_rules! bail_span { + ($($t:tt)*) => ( + return Err(err_span!($($t)*).into()) + ) +} + +macro_rules! push_err_span { + ($diagnostics:expr, $($t:tt)*) => { + $diagnostics.push(err_span!($($t)*)) + }; +} + +#[derive(Debug)] +pub struct Diagnostic { + inner: Repr, +} + +#[derive(Debug)] +enum Repr { + Single { + text: String, + span: Option<(Span, Span)>, + }, + SynError(Error), + Multi { + diagnostics: Vec, + }, +} + +impl Diagnostic { + pub fn error>(text: T) -> Diagnostic { + Diagnostic { + inner: Repr::Single { + text: text.into(), + span: None, + }, + } + } + + pub fn span_error>(span: Span, text: T) -> Diagnostic { + Diagnostic { + inner: Repr::Single { + text: text.into(), + span: Some((span, span)), + }, + } + } + + pub fn spanned_error>(node: &ToTokens, text: T) -> Diagnostic { + Diagnostic { + inner: Repr::Single { + text: text.into(), + span: extract_spans(node), + }, + } + } + + pub fn from_vec(diagnostics: Vec) -> Result<(), Diagnostic> { + if diagnostics.len() == 0 { + Ok(()) + } else { + Err(Diagnostic { + inner: Repr::Multi { diagnostics }, + }) + } + } + + #[allow(unconditional_recursion)] + pub fn panic(&self) -> ! { + match &self.inner { + Repr::Single { text, .. } => panic!("{}", text), + Repr::SynError(error) => panic!("{}", error), + Repr::Multi { diagnostics } => diagnostics[0].panic(), + } + } +} + +impl From for Diagnostic { + fn from(err: Error) -> Diagnostic { + Diagnostic { + inner: Repr::SynError(err), + } + } +} + +fn extract_spans(node: &ToTokens) -> Option<(Span, Span)> { + let mut t = TokenStream::new(); + node.to_tokens(&mut t); + let mut tokens = t.into_iter(); + let start = tokens.next().map(|t| t.span()); + let end = tokens.last().map(|t| t.span()); + start.map(|start| (start, end.unwrap_or(start))) +} + +impl ToTokens for Diagnostic { + fn to_tokens(&self, dst: &mut TokenStream) { + match &self.inner { + Repr::Single { text, span } => { + let cs2 = (Span::call_site(), Span::call_site()); + let (start, end) = span.unwrap_or(cs2); + dst.append(Ident::new("compile_error", start)); + dst.append(Punct::new('!', Spacing::Alone)); + let mut message = TokenStream::new(); + message.append(Literal::string(text)); + let mut group = Group::new(Delimiter::Brace, message); + group.set_span(end); + dst.append(group); + } + Repr::Multi { diagnostics } => { + for diagnostic in diagnostics { + diagnostic.to_tokens(dst); + } + } + Repr::SynError(err) => { + err.to_compile_error().to_tokens(dst); + } + } + } +} diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 26dce935ae..72ab44d4c3 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,10 +1,22 @@ extern crate proc_macro; +#[macro_use] +mod error; +mod from_args; +mod pyclass; + +use error::Diagnostic; use proc_macro::TokenStream; +use proc_macro2::TokenStream as TokenStream2; +use quote::ToTokens; use syn::{parse_macro_input, AttributeArgs, DeriveInput, Item}; -mod from_args; -mod pyclass; +fn result_to_tokens(result: Result) -> TokenStream { + match result { + Ok(tokens) => tokens.into(), + Err(diagnostic) => diagnostic.into_token_stream().into(), + } +} #[proc_macro_derive(FromArgs, attributes(pyarg))] pub fn derive_from_args(input: TokenStream) -> TokenStream { @@ -17,12 +29,12 @@ pub fn derive_from_args(input: TokenStream) -> TokenStream { pub fn pyclass(attr: TokenStream, item: TokenStream) -> TokenStream { let attr = parse_macro_input!(attr as AttributeArgs); let item = parse_macro_input!(item as Item); - pyclass::impl_pyclass(attr, item).into() + result_to_tokens(pyclass::impl_pyclass(attr, item)) } #[proc_macro_attribute] pub fn pyimpl(attr: TokenStream, item: TokenStream) -> TokenStream { let attr = parse_macro_input!(attr as AttributeArgs); let item = parse_macro_input!(item as Item); - pyclass::impl_pyimpl(attr, item).into() + result_to_tokens(pyclass::impl_pyimpl(attr, item)) } diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 36ce9df1ac..a2cf57b4a3 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -1,3 +1,4 @@ +use super::Diagnostic; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use std::collections::HashMap; @@ -15,19 +16,21 @@ enum ClassItem { }, } -fn meta_to_vec(meta: Meta) -> Option> { +fn meta_to_vec(meta: Meta) -> Result, Meta> { match meta { - Meta::Word(_) => Some(Vec::new()), - Meta::List(list) => Some(list.nested.into_iter().collect()), - Meta::NameValue(_) => None, + Meta::Word(_) => Ok(Vec::new()), + Meta::List(list) => Ok(list.nested.into_iter().collect()), + Meta::NameValue(_) => Err(meta), } } impl ClassItem { - fn extract_from_syn(attrs: &mut Vec, sig: &MethodSig) -> Option { + fn extract_from_syn( + attrs: &mut Vec, + sig: &MethodSig, + ) -> Result, Diagnostic> { let mut item = None; let mut attr_idx = None; - // TODO: better error handling throughout this for (i, meta) in attrs .iter() .filter_map(|attr| attr.parse_meta().ok()) @@ -36,12 +39,18 @@ impl ClassItem { let name = meta.name(); if name == "pymethod" { if item.is_some() { - panic!("You can only have one #[py*] attribute on an impl item") + bail_span!( + sig.ident, + "You can only have one #[py*] attribute on an impl item" + ) } - let nesteds = meta_to_vec(meta).expect( - "#[pyproperty = \"...\"] cannot be a name/value, you probably meant \ - #[pyproperty(name = \"...\")]", - ); + let nesteds = meta_to_vec(meta).map_err(|meta| { + err_span!( + meta, + "#[pyproperty = \"...\"] cannot be a name/value, you probably meant \ + #[pyproperty(name = \"...\")]", + ) + })?; let mut py_name = None; for meta in nesteds { let meta = match meta { @@ -54,7 +63,10 @@ impl ClassItem { if let Lit::Str(s) = &name_value.lit { py_name = Some(s.value()); } else { - panic!("#[pymethod(name = ...)] must be a string"); + bail_span!( + &sig.ident, + "#[pymethod(name = ...)] must be a string" + ); } } } @@ -68,12 +80,18 @@ impl ClassItem { attr_idx = Some(i); } else if name == "pyproperty" { if item.is_some() { - panic!("You can only have one #[py*] attribute on an impl item") + bail_span!( + sig.ident, + "You can only have one #[py*] attribute on an impl item" + ) } - let nesteds = meta_to_vec(meta).expect( - "#[pyproperty = \"...\"] cannot be a name/value, you probably meant \ - #[pyproperty(name = \"...\")]", - ); + let nesteds = meta_to_vec(meta).map_err(|meta| { + err_span!( + meta, + "#[pyproperty = \"...\"] cannot be a name/value, you probably meant \ + #[pyproperty(name = \"...\")]" + ) + })?; let mut setter = false; let mut py_name = None; for meta in nesteds { @@ -87,7 +105,10 @@ impl ClassItem { if let Lit::Str(s) = &name_value.lit { py_name = Some(s.value()); } else { - panic!("#[pyproperty(name = ...)] must be a string"); + bail_span!( + &sig.ident, + "#[pyproperty(name = ...)] must be a string" + ); } } } @@ -99,29 +120,34 @@ impl ClassItem { _ => {} } } - let py_name = py_name.unwrap_or_else(|| { - let item_ident = sig.ident.to_string(); - if setter { - if item_ident.starts_with("set_") { - let name = &item_ident["set_".len()..]; - if name.is_empty() { - panic!( - "A #[pyproperty(setter)] fn with a set_* name have something \ - after \"set_\"" - ) + let py_name = match py_name { + Some(py_name) => py_name, + None => { + let item_ident = sig.ident.to_string(); + if setter { + if item_ident.starts_with("set_") { + let name = &item_ident["set_".len()..]; + if name.is_empty() { + bail_span!( + &sig.ident, + "A #[pyproperty(setter)] fn with a set_* name must \ + have something after \"set_\"" + ) + } else { + name.to_string() + } } else { - name.to_string() + bail_span!( + &sig.ident, + "A #[pyproperty(setter)] fn must either have a `name` \ + parameter or a fn name along the lines of \"set_*\"" + ) } } else { - panic!( - "A #[pyproperty(setter)] fn must either have a `name` parameter or a \ - fn name along the lines of \"set_*\"" - ) + item_ident } - } else { - item_ident } - }); + }; item = Some(ClassItem::Property { py_name, item_ident: sig.ident.clone(), @@ -133,23 +159,27 @@ impl ClassItem { if let Some(attr_idx) = attr_idx { attrs.remove(attr_idx); } - item + Ok(item) } } -pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> TokenStream2 { +pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> Result { let mut imp = if let Item::Impl(imp) = item { imp } else { - return quote!(#item); + return Ok(quote!(#item)); }; + let mut diagnostics: Vec = Vec::new(); + let items = imp .items .iter_mut() .filter_map(|item| { if let ImplItem::Method(meth) = item { ClassItem::extract_from_syn(&mut meth.attrs, &meth.sig) + .map_err(|err| diagnostics.push(err)) + .unwrap_or_default() } else { None } @@ -160,14 +190,18 @@ pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> TokenStream2 { for item in items.iter() { match item { ClassItem::Property { - item_ident, - py_name, + ref item_ident, + ref py_name, setter, } => { let entry = properties.entry(py_name).or_default(); let func = if *setter { &mut entry.1 } else { &mut entry.0 }; if func.is_some() { - panic!("Multiple property accessors with name {:?}", py_name) + bail_span!( + item_ident, + "Multiple property accessors with name {:?}", + py_name + ) } *func = Some(item_ident); } @@ -187,24 +221,37 @@ pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> TokenStream2 { None } }); - let properties = properties.iter().map(|(name, prop)| { - let getter = match prop.0 { - Some(getter) => getter, - None => panic!("Property {:?} is missing a getter", name), - }; - let add_setter = prop.1.map(|setter| quote!(.add_setter(Self::#setter))); - quote! { - class.set_str_attr( - #name, - ::rustpython_vm::obj::objproperty::PropertyBuilder::new(ctx) - .add_getter(Self::#getter) - #add_setter - .create(), - ); - } - }); + let properties = properties + .iter() + .map(|(name, prop)| { + let getter = match prop.0 { + Some(getter) => getter, + None => { + push_err_span!( + diagnostics, + prop.1.unwrap(), + "Property {:?} is missing a getter", + name + ); + return TokenStream2::new(); + } + }; + let add_setter = prop.1.map(|setter| quote!(.add_setter(Self::#setter))); + quote! { + class.set_str_attr( + #name, + ::rustpython_vm::obj::objproperty::PropertyBuilder::new(ctx) + .add_getter(Self::#getter) + #add_setter + .create(), + ); + } + }) + .collect::>(); - quote! { + Diagnostic::from_vec(diagnostics)?; + + let ret = quote! { #imp impl ::rustpython_vm::pyobject::PyClassImpl for #ty { fn impl_extend_class( @@ -215,14 +262,18 @@ pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> TokenStream2 { #(#properties)* } } - } + }; + Ok(ret) } -pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { +pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result { let (item, ident, attrs) = match item { Item::Struct(struc) => (quote!(#struc), struc.ident, struc.attrs), Item::Enum(enu) => (quote!(#enu), enu.ident, enu.attrs), - _ => panic!("#[pyclass] can only be on a struct or enum declaration"), + other => bail_span!( + other, + "#[pyclass] can only be on a struct or enum declaration" + ), }; let mut class_name = None; @@ -233,7 +284,7 @@ pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { if let Lit::Str(s) = name_value.lit { class_name = Some(s.value()); } else { - panic!("#[pyclass(name = ...)] must be a string"); + bail_span!(name_value.lit, "#[pyclass(name = ...)] must be a string"); } } } @@ -252,8 +303,6 @@ pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { Some(ref mut doc) => doc.push(val), None => doc = Some(vec![val]), } - } else { - panic!("expected #[doc = ...] to be a string") } } } @@ -266,11 +315,12 @@ pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> TokenStream2 { None => quote!(None), }; - quote! { + let ret = quote! { #item impl ::rustpython_vm::pyobject::PyClassDef for #ident { const NAME: &'static str = #class_name; const DOC: Option<&'static str> = #doc; } - } + }; + Ok(ret) } From 224863583b5cbba1e17ea9223829fa73a503ecb7 Mon Sep 17 00:00:00 2001 From: ben Date: Tue, 16 Apr 2019 19:13:39 +1200 Subject: [PATCH 322/884] Use new_syntax error in main.rs --- src/main.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 750faf98a8..2a673736ef 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,11 +65,8 @@ fn main() { } fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResult { - let code_obj = - compile::compile(vm, source, &compile::Mode::Exec, source_path).map_err(|err| { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - vm.new_exception(syntax_error, err.to_string()) - })?; + let code_obj = compile::compile(vm, source, &compile::Mode::Exec, source_path) + .map_err(|err| vm.new_syntax_error(&err))?; // trace!("Code object: {:?}", code_obj.borrow()); let vars = vm.ctx.new_scope(); // Keep track of local variables vm.run_code_obj(code_obj, vars) @@ -120,8 +117,7 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), Com // Don't inject syntax errors for line continuation Err(err @ CompileError::Parse(ParseError::EOF(_))) => Err(err), Err(err) => { - let syntax_error = vm.context().exceptions.syntax_error.clone(); - let exc = vm.new_exception(syntax_error, format!("{}", err)); + let exc = vm.new_syntax_error(&err); print_exception(vm, &exc); Err(err) } From 6abf1511e99e0eeb543445f0b7d88eb57ea8a7f5 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Tue, 16 Apr 2019 17:19:57 +0200 Subject: [PATCH 323/884] Add nonlocal support. --- tests/snippets/global_nonlocal.py | 10 ++++++++++ vm/src/bytecode.rs | 1 + vm/src/compile.rs | 2 +- vm/src/frame.rs | 17 ++++++++++++++++- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/snippets/global_nonlocal.py b/tests/snippets/global_nonlocal.py index 48d1242108..fd5a61e1cc 100644 --- a/tests/snippets/global_nonlocal.py +++ b/tests/snippets/global_nonlocal.py @@ -11,3 +11,13 @@ def b(): b() assert a == 4 +def x(): + def y(): + nonlocal b + b = 3 + b = 2 + y() + return b + +res = x() +assert res == 3, str(res) diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 10c667890e..73d69dc180 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -41,6 +41,7 @@ pub type Label = usize; #[derive(Debug, Clone, PartialEq)] pub enum NameScope { Local, + NonLocal, Global, } diff --git a/vm/src/compile.rs b/vm/src/compile.rs index d28c10feb4..8635e21ac8 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -179,12 +179,12 @@ impl Compiler { let role = self.lookup_name(name); match role { SymbolRole::Global => bytecode::NameScope::Global, + SymbolRole::Nonlocal => bytecode::NameScope::NonLocal, _ => bytecode::NameScope::Local, } } fn load_name(&mut self, name: &str) { - // TODO: if global, do something else! let scope = self.scope_for_name(name); self.emit(Instruction::LoadName { name: name.to_string(), diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 2bf8215c1c..4425a8a7d5 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -129,6 +129,7 @@ pub trait NameProtocol { fn store_name(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); fn delete_name(&self, vm: &VirtualMachine, name: &str); fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option; + fn store_cell(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option; fn store_global(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); } @@ -157,6 +158,16 @@ impl NameProtocol for Scope { None } + fn store_cell(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef) { + self.locals + .iter() + .skip(1) + .next() + .unwrap() + .set_item(name, value, vm) + .unwrap(); + } + fn store_name(&self, vm: &VirtualMachine, key: &str, value: PyObjectRef) { self.get_locals().set_item(key, value, vm).unwrap(); } @@ -1037,8 +1048,11 @@ impl Frame { bytecode::NameScope::Global => { self.scope.store_global(vm, name, obj); } + bytecode::NameScope::NonLocal => { + self.scope.store_cell(vm, name, obj); + } bytecode::NameScope::Local => { - self.scope.store_name(&vm, name, obj); + self.scope.store_name(vm, name, obj); } } Ok(None) @@ -1057,6 +1071,7 @@ impl Frame { ) -> FrameResult { let optional_value = match name_scope { bytecode::NameScope::Global => self.scope.load_global(vm, name), + bytecode::NameScope::NonLocal => self.scope.load_cell(vm, name), bytecode::NameScope::Local => self.scope.load_name(&vm, name), }; From 96d6c518e902dec2146baf189edc626316769b20 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 16 Apr 2019 19:40:01 -0500 Subject: [PATCH 324/884] Fix os.unsetenv test for windows --- tests/snippets/stdlib_os.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 15fa176d8d..4ee13b274b 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -51,6 +51,11 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert ENV_KEY in os.environ assert os.getenv(ENV_KEY) == ENV_VALUE del os.environ[ENV_KEY] -os.unsetenv(ENV_KEY) assert ENV_KEY not in os.environ assert os.getenv(ENV_KEY) == None + +if os.name == "posix": + os.environ[ENV_KEY] = ENV_VALUE + os.unsetenv(ENV_KEY) + assert ENV_KEY not in os.environ + assert os.getenv(ENV_KEY) == None From 11bf84e8c17ea1a61da3bb68babec8a8842eb914 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 16 Apr 2019 21:59:34 -0500 Subject: [PATCH 325/884] Fix test again --- tests/snippets/stdlib_os.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 4ee13b274b..7b9981bdf1 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -55,7 +55,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert os.getenv(ENV_KEY) == None if os.name == "posix": - os.environ[ENV_KEY] = ENV_VALUE + os.putenv(ENV_KEY, ENV_VALUE) os.unsetenv(ENV_KEY) - assert ENV_KEY not in os.environ assert os.getenv(ENV_KEY) == None From 9b71424d4efe2b5de49a9f087fac7efb2a70ddce Mon Sep 17 00:00:00 2001 From: ben Date: Wed, 17 Apr 2019 20:02:08 +1200 Subject: [PATCH 326/884] Use slice.xxx_index() methods in setslice and delslice --- vm/src/obj/objlist.rs | 14 +++++++------- vm/src/obj/objslice.rs | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index a21bd9190c..bfc763d8cd 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -210,12 +210,12 @@ impl PyListRef { } fn setslice(self, slice: PySliceRef, sec: PyIterable, vm: &VirtualMachine) -> PyResult { - let step = slice.step.clone().unwrap_or_else(BigInt::one); + let step = slice.step_index(vm)?.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(&slice.start, &slice.stop); + let range = self.get_slice_range(&slice.start_index(vm)?, &slice.stop_index(vm)?); if range.start < range.end { match step.to_i32() { Some(1) => self._set_slice(range, sec, vm), @@ -237,14 +237,14 @@ impl PyListRef { } else { // calculate the range for the reverse slice, first the bounds needs to be made // exclusive around stop, the lower number - let start = &slice.start.as_ref().map(|x| { + let start = &slice.start_index(vm)?.as_ref().map(|x| { if *x == (-1).to_bigint().unwrap() { self.get_len() + BigInt::one() //.to_bigint().unwrap() } else { x + 1 } }); - let stop = &slice.stop.as_ref().map(|x| { + let stop = &slice.stop_index(vm)?.as_ref().map(|x| { if *x == (-1).to_bigint().unwrap() { self.get_len().to_bigint().unwrap() } else { @@ -552,9 +552,9 @@ impl PyListRef { } 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); + let start = slice.start_index(vm)?; + let stop = slice.stop_index(vm)?; + let step = slice.step_index(vm)?.unwrap_or_else(BigInt::one); if step.is_zero() { Err(vm.new_value_error("slice step cannot be zero".to_string())) diff --git a/vm/src/obj/objslice.rs b/vm/src/obj/objslice.rs index cebfee3303..82e4cef8cd 100644 --- a/vm/src/obj/objslice.rs +++ b/vm/src/obj/objslice.rs @@ -1,5 +1,5 @@ use crate::function::{OptionalArg, PyFuncArgs}; -use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; use crate::vm::VirtualMachine; use crate::obj::objint::PyInt; From 06563a36f677b07aa7328cc98a4acb76c3b3d32d Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 17 Apr 2019 12:33:09 +0200 Subject: [PATCH 327/884] Support mutual importing modules. --- tests/snippets/import.py | 1 + tests/snippets/import_mutual1.py | 4 ++++ tests/snippets/import_mutual2.py | 3 +++ vm/src/import.rs | 26 ++++++++++++++++++-------- 4 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 tests/snippets/import_mutual1.py create mode 100644 tests/snippets/import_mutual2.py diff --git a/tests/snippets/import.py b/tests/snippets/import.py index a481e3f1ae..4f1164043a 100644 --- a/tests/snippets/import.py +++ b/tests/snippets/import.py @@ -3,6 +3,7 @@ from import_target import func as aliased_func, other_func as aliased_other_func from import_star import * +import import_mutual1 assert import_target.X == import_target.func() assert import_target.X == func() diff --git a/tests/snippets/import_mutual1.py b/tests/snippets/import_mutual1.py new file mode 100644 index 0000000000..0dca4a34e0 --- /dev/null +++ b/tests/snippets/import_mutual1.py @@ -0,0 +1,4 @@ + +# Mutual recursive import: +import import_mutual2 + diff --git a/tests/snippets/import_mutual2.py b/tests/snippets/import_mutual2.py new file mode 100644 index 0000000000..388ce25217 --- /dev/null +++ b/tests/snippets/import_mutual2.py @@ -0,0 +1,3 @@ + +# Mutual recursive import: +import import_mutual1 diff --git a/vm/src/import.rs b/vm/src/import.rs index b59ce64266..a5925a8cad 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -11,9 +11,13 @@ use crate::pyobject::{ItemProtocol, PyResult}; use crate::util; use crate::vm::VirtualMachine; -fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &str) -> PyResult { +fn import_uncached_module( + vm: &VirtualMachine, + current_path: PathBuf, + module_name: &str, +) -> PyResult { // Check for Rust-native modules - if let Some(module) = vm.stdlib_inits.borrow().get(module) { + if let Some(module) = vm.stdlib_inits.borrow().get(module_name) { return Ok(module(vm).clone()); } @@ -21,7 +25,7 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s let import_error = vm.context().exceptions.import_error.clone(); // Time to search for module in any place: - let file_path = find_source(vm, current_path, module) + let file_path = find_source(vm, current_path, module_name) .map_err(|e| vm.new_exception(notfound_error.clone(), e))?; let source = util::read_file(file_path.as_path()) .map_err(|e| vm.new_exception(import_error.clone(), e.to_string()))?; @@ -35,19 +39,25 @@ fn import_uncached_module(vm: &VirtualMachine, current_path: PathBuf, module: &s // trace!("Code object: {:?}", code_obj); let attrs = vm.ctx.new_dict(); - attrs.set_item("__name__", vm.new_str(module.to_string()), vm)?; - vm.run_code_obj(code_obj, Scope::new(None, attrs.clone()))?; - Ok(vm.ctx.new_module(module, attrs)) + attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; + let module = vm.ctx.new_module(module_name, attrs.clone()); + + // Store module in cache to prevent infinite loop with mutual importing libs: + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); + sys_modules.set_item(module_name, module.clone(), vm)?; + + // Execute main code in module: + vm.run_code_obj(code_obj, Scope::new(None, attrs))?; + Ok(module) } pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &str) -> PyResult { // First, see if we already loaded the module: - let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules")?; + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) { return Ok(module); } let module = import_uncached_module(vm, current_path, module_name)?; - sys_modules.set_item(module_name, module.clone(), vm)?; Ok(module) } From 6d1b807c77a5c1d1e5e7afc78729cde9a6cfdef6 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 17 Apr 2019 12:59:56 +0200 Subject: [PATCH 328/884] Improve error message of unwrap operation on nonlocal scope. --- vm/src/frame.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 4425a8a7d5..9d3e28e1d5 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -163,7 +163,7 @@ impl NameProtocol for Scope { .iter() .skip(1) .next() - .unwrap() + .expect("no outer scope for non-local") .set_item(name, value, vm) .unwrap(); } From 2a8b5866226115a3092c662768f0eba7564f7d19 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 17 Apr 2019 14:03:18 +0200 Subject: [PATCH 329/884] Fix caching of rust modules. --- vm/src/import.rs | 75 ++++++++++++++++++++++-------------------------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index a5925a8cad..45c7cd86a2 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -11,54 +11,47 @@ use crate::pyobject::{ItemProtocol, PyResult}; use crate::util; use crate::vm::VirtualMachine; -fn import_uncached_module( - vm: &VirtualMachine, - current_path: PathBuf, - module_name: &str, -) -> PyResult { - // Check for Rust-native modules - if let Some(module) = vm.stdlib_inits.borrow().get(module_name) { - return Ok(module(vm).clone()); - } +pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &str) -> PyResult { + // Cached modules: + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); - let notfound_error = vm.context().exceptions.module_not_found_error.clone(); - let import_error = vm.context().exceptions.import_error.clone(); + // First, see if we already loaded the module: + if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) { + Ok(module) + } else if let Some(make_module_func) = vm.stdlib_inits.borrow().get(module_name) { + let module = make_module_func(vm); + sys_modules.set_item(module_name, module.clone(), vm)?; + Ok(module) + } else { + let notfound_error = vm.context().exceptions.module_not_found_error.clone(); + let import_error = vm.context().exceptions.import_error.clone(); - // Time to search for module in any place: - let file_path = find_source(vm, current_path, module_name) - .map_err(|e| vm.new_exception(notfound_error.clone(), e))?; - let source = util::read_file(file_path.as_path()) - .map_err(|e| vm.new_exception(import_error.clone(), e.to_string()))?; - let code_obj = compile::compile( - vm, - &source, - &compile::Mode::Exec, - file_path.to_str().unwrap().to_string(), - ) - .map_err(|err| vm.new_syntax_error(&err))?; - // trace!("Code object: {:?}", code_obj); + // Time to search for module in any place: + let file_path = find_source(vm, current_path, module_name) + .map_err(|e| vm.new_exception(notfound_error.clone(), e))?; + let source = util::read_file(file_path.as_path()) + .map_err(|e| vm.new_exception(import_error.clone(), e.to_string()))?; + let code_obj = compile::compile( + vm, + &source, + &compile::Mode::Exec, + file_path.to_str().unwrap().to_string(), + ) + .map_err(|err| vm.new_syntax_error(&err))?; + // trace!("Code object: {:?}", code_obj); - let attrs = vm.ctx.new_dict(); - attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; - let module = vm.ctx.new_module(module_name, attrs.clone()); + let attrs = vm.ctx.new_dict(); + attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; + let module = vm.ctx.new_module(module_name, attrs.clone()); - // Store module in cache to prevent infinite loop with mutual importing libs: - let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); - sys_modules.set_item(module_name, module.clone(), vm)?; + // Store module in cache to prevent infinite loop with mutual importing libs: + sys_modules.set_item(module_name, module.clone(), vm)?; - // Execute main code in module: - vm.run_code_obj(code_obj, Scope::new(None, attrs))?; - Ok(module) -} + // Execute main code in module: + vm.run_code_obj(code_obj, Scope::new(None, attrs))?; -pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &str) -> PyResult { - // First, see if we already loaded the module: - let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); - if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) { - return Ok(module); + Ok(module) } - let module = import_uncached_module(vm, current_path, module_name)?; - Ok(module) } fn find_source(vm: &VirtualMachine, current_path: PathBuf, name: &str) -> Result { From 84457e6e92985109b9456bf1900293d0d9b8270d Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 17 Apr 2019 15:04:50 +0200 Subject: [PATCH 330/884] Add some benchmark scripts. --- benchmarks/README.md | 19 +++++ benchmarks/benchmarks/mandelbrot.py | 31 ++++++++ benchmarks/benchmarks/nbody.py | 110 ++++++++++++++++++++++++++++ benchmarks/test_benchmarks.py | 31 ++++++++ vm/src/compile.rs | 2 +- 5 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 benchmarks/README.md create mode 100644 benchmarks/benchmarks/mandelbrot.py create mode 100644 benchmarks/benchmarks/nbody.py create mode 100644 benchmarks/test_benchmarks.py diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 0000000000..464dc65f21 --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,19 @@ + +# Benchmarking + +These are some files to determine performance of rustpython. + +# Usage + +Install pytest and pytest-benchmark: + + $ pip install pytest-benchmark + +Then run: + + $ pytest + +# Benchmark source + +- https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/nbody-python3-2.html + diff --git a/benchmarks/benchmarks/mandelbrot.py b/benchmarks/benchmarks/mandelbrot.py new file mode 100644 index 0000000000..f0106cd74d --- /dev/null +++ b/benchmarks/benchmarks/mandelbrot.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +# coding: utf-8 + +w = 50.0 +h = 50.0 + +y = 0.0 +while y < h: + x = 0.0 + while x < w: + Zr, Zi, Tr, Ti = 0.0, 0.0, 0.0, 0.0 + Cr = 2*x/w - 1.5 + Ci = 2*y/h - 1.0 + + i = 0 + while i < 50 and Tr+Ti <= 4: + Zi = 2*Zr*Zi + Ci + Zr = Tr - Ti + Cr + Tr = Zr * Zr + Ti = Zi * Zi + i = i+1 + + if Tr+Ti <= 4: + print('*', end='') + else: + print('·', end='') + + x = x+1 + + print() + y = y+1 diff --git a/benchmarks/benchmarks/nbody.py b/benchmarks/benchmarks/nbody.py new file mode 100644 index 0000000000..2f36bdf0e3 --- /dev/null +++ b/benchmarks/benchmarks/nbody.py @@ -0,0 +1,110 @@ + +# The Computer Language Benchmarks Game +# https://salsa.debian.org/benchmarksgame-team/benchmarksgame/ +# +# originally by Kevin Carson +# modified by Tupteq, Fredrik Johansson, and Daniel Nanz +# modified by Maciej Fijalkowski +# 2to3 +# modified by Andriy Misyura + +from math import sqrt + +def combinations(l): + result = [] + for x in range(len(l) - 1): + ls = l[x+1:] + for y in ls: + result.append((l[x][0],l[x][1],l[x][2],y[0],y[1],y[2])) + return result + +PI = 3.14159265358979323 +SOLAR_MASS = 4 * PI * PI +DAYS_PER_YEAR = 365.24 + +BODIES = { + 'sun': ([0.0, 0.0, 0.0], [0.0, 0.0, 0.0], SOLAR_MASS), + + 'jupiter': ([4.84143144246472090e+00, + -1.16032004402742839e+00, + -1.03622044471123109e-01], + [1.66007664274403694e-03 * DAYS_PER_YEAR, + 7.69901118419740425e-03 * DAYS_PER_YEAR, + -6.90460016972063023e-05 * DAYS_PER_YEAR], + 9.54791938424326609e-04 * SOLAR_MASS), + + 'saturn': ([8.34336671824457987e+00, + 4.12479856412430479e+00, + -4.03523417114321381e-01], + [-2.76742510726862411e-03 * DAYS_PER_YEAR, + 4.99852801234917238e-03 * DAYS_PER_YEAR, + 2.30417297573763929e-05 * DAYS_PER_YEAR], + 2.85885980666130812e-04 * SOLAR_MASS), + + 'uranus': ([1.28943695621391310e+01, + -1.51111514016986312e+01, + -2.23307578892655734e-01], + [2.96460137564761618e-03 * DAYS_PER_YEAR, + 2.37847173959480950e-03 * DAYS_PER_YEAR, + -2.96589568540237556e-05 * DAYS_PER_YEAR], + 4.36624404335156298e-05 * SOLAR_MASS), + + 'neptune': ([1.53796971148509165e+01, + -2.59193146099879641e+01, + 1.79258772950371181e-01], + [2.68067772490389322e-03 * DAYS_PER_YEAR, + 1.62824170038242295e-03 * DAYS_PER_YEAR, + -9.51592254519715870e-05 * DAYS_PER_YEAR], + 5.15138902046611451e-05 * SOLAR_MASS) } + +SYSTEM = tuple(BODIES.values()) +PAIRS = tuple(combinations(SYSTEM)) + +def advance(dt, n, bodies=SYSTEM, pairs=PAIRS): + for i in range(n): + for ([x1, y1, z1], v1, m1, [x2, y2, z2], v2, m2) in pairs: + dx = x1 - x2 + dy = y1 - y2 + dz = z1 - z2 + dist = sqrt(dx * dx + dy * dy + dz * dz); + mag = dt / (dist*dist*dist) + b1m = m1 * mag + b2m = m2 * mag + v1[0] -= dx * b2m + v1[1] -= dy * b2m + v1[2] -= dz * b2m + v2[2] += dz * b1m + v2[1] += dy * b1m + v2[0] += dx * b1m + for (r, [vx, vy, vz], m) in bodies: + r[0] += dt * vx + r[1] += dt * vy + r[2] += dt * vz + +def report_energy(bodies=SYSTEM, pairs=PAIRS, e=0.0): + for ((x1, y1, z1), v1, m1, (x2, y2, z2), v2, m2) in pairs: + dx = x1 - x2 + dy = y1 - y2 + dz = z1 - z2 + e -= (m1 * m2) / ((dx * dx + dy * dy + dz * dz) ** 0.5) + for (r, [vx, vy, vz], m) in bodies: + e += m * (vx * vx + vy * vy + vz * vz) / 2. + print(f"{e}") + +def offset_momentum(ref, bodies=SYSTEM, px=0.0, py=0.0, pz=0.0): + for (r, [vx, vy, vz], m) in bodies: + px -= vx * m + py -= vy * m + pz -= vz * m + (r, v, m) = ref + v[0] = px / m + v[1] = py / m + v[2] = pz / m + +def main(n, ref='sun'): + offset_momentum(BODIES[ref]) + report_energy() + advance(0.01, n) + report_energy() + +main(500) diff --git a/benchmarks/test_benchmarks.py b/benchmarks/test_benchmarks.py new file mode 100644 index 0000000000..571785e60f --- /dev/null +++ b/benchmarks/test_benchmarks.py @@ -0,0 +1,31 @@ + +import time +import sys + +import pytest +import subprocess + +from benchmarks import nbody + +# Interpreters: +rustpython_exe = '../target/release/rustpython' +cpython_exe = sys.executable +pythons = [ + cpython_exe, + rustpython_exe +] + +# Benchmark scripts: +benchmarks = [ + ['benchmarks/nbody.py'], + ['benchmarks/mandelbrot.py'], +] + +@pytest.mark.parametrize('exe', pythons) +@pytest.mark.parametrize('args', benchmarks) +def test_bench(exe, args, benchmark): + def bench(): + subprocess.run([exe] + args) + + benchmark(bench) + diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 8635e21ac8..b34b36e234 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -1014,7 +1014,7 @@ impl Compiler { name: name.to_string(), }); } - ast::Expression::Tuple { elements } => { + ast::Expression::List { elements } | ast::Expression::Tuple { elements } => { let mut seen_star = false; // Scan for star args: From 9c57ae4046c588633bc2c7bcede52822c6cc8804 Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Wed, 17 Apr 2019 15:28:14 +0200 Subject: [PATCH 331/884] support bytes creation from hex and ascii --- parser/src/lexer.rs | 104 +++++++++++++++++++++++++++++++++++++++- tests/snippets/bytes.py | 4 ++ 2 files changed, 107 insertions(+), 1 deletion(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index dfc9ecd51e..b9e39e7df1 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -542,7 +542,7 @@ where let tok = if is_bytes { if string_content.is_ascii() { Tok::Bytes { - value: string_content.as_bytes().to_vec(), + value: self.lex_byte(string_content)?, } } else { return Err(LexicalError::StringError); @@ -1105,6 +1105,84 @@ where let tok_end = self.get_pos(); Ok((tok_start, ty, tok_end)) } + + fn lex_byte(&self, s: String) -> Result, LexicalError> { + let mut res = vec![]; + let mut escape = false; //flag if previous was \ + let mut hex_on = false; // hex mode on or off + let mut hex_value = String::new(); + + for c in s.chars() { + match c { + '\\' => { + if escape { + res.push(92); + escape = false; + } else { + escape = true; + } + } + + 'x' => { + if escape { + hex_on = true; + } else { + res.push(120); + } + escape = false; + } + 't' => { + if escape { + res.push(9); + } else { + res.push(116); + } + escape = false; + } + 'n' => { + if escape { + res.push(10); + } else { + res.push(110) + } + escape = false; + } + 'r' => { + if escape { + res.push(13); + } else { + res.push(114) + } + escape = false; + } + x => { + if hex_on { + if x.is_ascii_hexdigit() { + if hex_value.is_empty() { + hex_value.push(x); + continue; + } else { + hex_value.push(x); + res.push(u8::from_str_radix(&hex_value, 16).unwrap()); + hex_on = false; + hex_value.clear(); + } + } else { + return Err(LexicalError::StringError); + } + } else { + if escape { + res.push(92); + } + res.push(x as u8); + } + escape = false; + } + } + } + + Ok(res) + } } /* Implement iterator pattern for the get_tok function. @@ -1520,4 +1598,28 @@ mod tests { test_string_continuation_mac_eol: MAC_EOL, test_string_continuation_unix_eol: UNIX_EOL, } + + #[test] + fn test_byte() { + // single quote + let all = r##"b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff'"##; + let source = String::from(all); + let tokens = lex_source(&source); + let res = (0..=255).collect::>(); + assert_eq!(tokens, vec![Tok::Bytes { value: res }]); + + // double quote + let all = r##"b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff""##; + let source = String::from(all); + let tokens = lex_source(&source); + let res = (0..=255).collect::>(); + assert_eq!(tokens, vec![Tok::Bytes { value: res }]); + + // backslash doesnt escape + let all = r##"b"omkmok\Xaa""##; + let source = String::from(all); + let tokens = lex_source(&source); + let res = vec![111, 109, 107, 109, 111, 107, 92, 88, 97, 97]; + assert_eq!(tokens, vec![Tok::Bytes { value: res }]); + } } diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index d2a60943ca..c496bfeffb 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -10,6 +10,10 @@ with assertRaises(TypeError): bytes("bla") +assert b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" == bytes(range(0,256)) +assert b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff' == bytes(range(0,256)) +assert b"omkmok\Xaa" == bytes([111, 109, 107, 109, 111, 107, 92, 88, 97, 97]) + a = b"abcd" b = b"ab" From eb16f1656647e3707364bb422da1ed493cae49d8 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 17 Apr 2019 20:02:24 +0200 Subject: [PATCH 332/884] Improve syntax error with line information. --- src/main.rs | 6 ++-- tests/snippets/invalid_syntax.py | 16 +++++++++++ vm/src/builtins.rs | 1 + vm/src/compile.rs | 48 ++++++++++++++++++++++++-------- vm/src/error.rs | 43 ++++++++++++++++++++-------- vm/src/exceptions.rs | 5 +++- vm/src/vm.rs | 10 +++++-- wasm/lib/src/vm_class.rs | 4 ++- 8 files changed, 101 insertions(+), 32 deletions(-) create mode 100644 tests/snippets/invalid_syntax.py diff --git a/src/main.rs b/src/main.rs index 2a673736ef..326e0bc3e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,7 +10,7 @@ extern crate rustyline; use clap::{App, Arg}; use rustpython_parser::error::ParseError; use rustpython_vm::{ - compile, error::CompileError, frame::Scope, import, obj::objstr, print_exception, + compile, error::CompileError, error::CompileErrorType, frame::Scope, import, obj::objstr, print_exception, pyobject::PyResult, util, VirtualMachine, }; use rustyline::{error::ReadlineError, Editor}; @@ -115,7 +115,7 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), Com Ok(()) } // Don't inject syntax errors for line continuation - Err(err @ CompileError::Parse(ParseError::EOF(_))) => Err(err), + Err(err @ CompileError{ error: CompileErrorType::Parse(ParseError::EOF(_)), .. }) => Err(err), Err(err) => { let exc = vm.new_syntax_error(&err); print_exception(vm, &exc); @@ -188,7 +188,7 @@ fn run_shell(vm: &VirtualMachine) -> PyResult { } match shell_exec(vm, &input, vars.clone()) { - Err(CompileError::Parse(ParseError::EOF(_))) => { + Err(CompileError { error: CompileErrorType::Parse(ParseError::EOF(_)), .. }) => { continuing = true; continue; } diff --git a/tests/snippets/invalid_syntax.py b/tests/snippets/invalid_syntax.py new file mode 100644 index 0000000000..bbbd08a41e --- /dev/null +++ b/tests/snippets/invalid_syntax.py @@ -0,0 +1,16 @@ + + +src = """ +def valid_func(): + pass + +yield 2 +""" + +try: + compile(src, 'test.py', 'exec') +except SyntaxError as ex: + assert ex.lineno == 5 +else: + raise AssertionError("Must throw syntax error") + diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index f2ca1e4c12..d054c34fca 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -751,6 +751,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "OverflowError" => ctx.exceptions.overflow_error.clone(), "RuntimeError" => ctx.exceptions.runtime_error.clone(), "ReferenceError" => ctx.exceptions.reference_error.clone(), + "SyntaxError" => ctx.exceptions.syntax_error.clone(), "NotImplementedError" => ctx.exceptions.not_implemented_error.clone(), "TypeError" => ctx.exceptions.type_error.clone(), "ValueError" => ctx.exceptions.value_error.clone(), diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 8635e21ac8..9042bf576a 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -6,7 +6,7 @@ //! https://github.com/micropython/micropython/blob/master/py/compile.c use crate::bytecode::{self, CallType, CodeObject, Instruction, Varargs}; -use crate::error::CompileError; +use crate::error::{CompileError, CompileErrorType}; use crate::obj::objcode; use crate::obj::objcode::PyCodeRef; use crate::pyobject::PyValue; @@ -40,17 +40,17 @@ pub fn compile( match mode { Mode::Exec => { - let ast = parser::parse_program(source).map_err(CompileError::Parse)?; + let ast = parser::parse_program(source)?; let symbol_table = make_symbol_table(&ast); compiler.compile_program(&ast, symbol_table) } Mode::Eval => { - let statement = parser::parse_statement(source).map_err(CompileError::Parse)?; + let statement = parser::parse_statement(source)?; let symbol_table = statements_to_symbol_table(&statement); compiler.compile_statement_eval(&statement, symbol_table) } Mode::Single => { - let ast = parser::parse_program(source).map_err(CompileError::Parse)?; + let ast = parser::parse_program(source)?; let symbol_table = make_symbol_table(&ast); compiler.compile_program_single(&ast, symbol_table) } @@ -158,7 +158,10 @@ impl Compiler { if let ast::Statement::Expression { ref expression } = statement.node { self.compile_expression(expression)?; } else { - return Err(CompileError::ExpectExpr); + return Err(CompileError { + error: CompileErrorType::ExpectExpr, + location: statement.location.clone(), + }); } } self.emit(Instruction::ReturnValue); @@ -397,19 +400,28 @@ impl Compiler { } ast::Statement::Break => { if !self.in_loop { - return Err(CompileError::InvalidBreak); + return Err(CompileError { + error: CompileErrorType::InvalidBreak, + location: statement.location.clone(), + }); } self.emit(Instruction::Break); } ast::Statement::Continue => { if !self.in_loop { - return Err(CompileError::InvalidContinue); + return Err(CompileError { + error: CompileErrorType::InvalidContinue, + location: statement.location.clone(), + }); } self.emit(Instruction::Continue); } ast::Statement::Return { value } => { if !self.in_function_def { - return Err(CompileError::InvalidReturn); + return Err(CompileError { + error: CompileErrorType::InvalidReturn, + location: statement.location.clone(), + }); } match value { Some(v) => { @@ -462,7 +474,10 @@ impl Compiler { self.emit(Instruction::DeleteSubscript); } _ => { - return Err(CompileError::Delete(target.name())); + return Err(CompileError { + error: CompileErrorType::Delete(target.name()), + location: self.current_source_location.clone(), + }); } } } @@ -1021,7 +1036,10 @@ impl Compiler { for (i, element) in elements.iter().enumerate() { if let ast::Expression::Starred { .. } = element { if seen_star { - return Err(CompileError::StarArgs); + return Err(CompileError { + error: CompileErrorType::StarArgs, + location: self.current_source_location.clone(), + }); } else { seen_star = true; self.emit(Instruction::UnpackEx { @@ -1047,7 +1065,10 @@ impl Compiler { } } _ => { - return Err(CompileError::Assign(target.name())); + return Err(CompileError { + error: CompileErrorType::Assign(target.name()), + location: self.current_source_location.clone(), + }); } } @@ -1237,7 +1258,10 @@ impl Compiler { } ast::Expression::Yield { value } => { if !self.in_function_def { - return Err(CompileError::InvalidYield); + return Err(CompileError { + error: CompileErrorType::InvalidYield, + location: self.current_source_location.clone(), + }); } self.mark_generator(); match value { diff --git a/vm/src/error.rs b/vm/src/error.rs index 06eb387e2e..81836fa444 100644 --- a/vm/src/error.rs +++ b/vm/src/error.rs @@ -1,10 +1,26 @@ use rustpython_parser::error::ParseError; +use rustpython_parser::lexer::Location; use std::error::Error; use std::fmt; #[derive(Debug)] -pub enum CompileError { +pub struct CompileError { + pub error: CompileErrorType, + pub location: Location, +} + +impl From for CompileError { + fn from(error: ParseError) -> Self { + CompileError { + error: CompileErrorType::Parse(error), + location: Default::default(), // TODO: extract location from parse error! + } + } +} + +#[derive(Debug)] +pub enum CompileErrorType { /// Invalid assignment, cannot store value in target. Assign(&'static str), /// Invalid delete @@ -25,17 +41,20 @@ pub enum CompileError { impl fmt::Display for CompileError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - CompileError::Assign(target) => write!(f, "can't assign to {}", target), - CompileError::Delete(target) => write!(f, "can't delete {}", target), - CompileError::ExpectExpr => write!(f, "Expecting expression, got statement"), - CompileError::Parse(err) => write!(f, "{}", err), - CompileError::StarArgs => write!(f, "Two starred expressions in assignment"), - CompileError::InvalidBreak => write!(f, "'break' outside loop"), - CompileError::InvalidContinue => write!(f, "'continue' outside loop"), - CompileError::InvalidReturn => write!(f, "'return' outside function"), - CompileError::InvalidYield => write!(f, "'yield' outside function"), - } + match &self.error { + CompileErrorType::Assign(target) => write!(f, "can't assign to {}", target), + CompileErrorType::Delete(target) => write!(f, "can't delete {}", target), + CompileErrorType::ExpectExpr => write!(f, "Expecting expression, got statement"), + CompileErrorType::Parse(err) => write!(f, "{}", err), + CompileErrorType::StarArgs => write!(f, "Two starred expressions in assignment"), + CompileErrorType::InvalidBreak => write!(f, "'break' outside loop"), + CompileErrorType::InvalidContinue => write!(f, "'continue' outside loop"), + CompileErrorType::InvalidReturn => write!(f, "'return' outside function"), + CompileErrorType::InvalidYield => write!(f, "'yield' outside function"), + }?; + + // Print line number: + write!(f, " at line {:?}", self.location.get_row()) } } diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 58a0f929d7..a8b753e1a3 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -67,7 +67,10 @@ pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) { "".to_string() }; - println!(" File {}, line {}, in {}", filename, lineno, obj_name); + println!( + r##" File "{}", line {}, in {}"##, + filename, lineno, obj_name + ); } else { println!(" File ??"); } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index e360b8e5ec..6e77105168 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -14,6 +14,7 @@ use std::sync::{Mutex, MutexGuard}; use crate::builtins; use crate::bytecode; +use crate::error::CompileError; use crate::frame::{ExecutionResult, Frame, FrameRef, Scope}; use crate::function::PyFuncArgs; use crate::obj::objbool; @@ -234,9 +235,12 @@ impl VirtualMachine { self.new_exception(overflow_error, msg) } - pub fn new_syntax_error(&self, msg: &T) -> PyObjectRef { - let syntax_error = self.ctx.exceptions.syntax_error.clone(); - self.new_exception(syntax_error, msg.to_string()) + pub fn new_syntax_error(&self, error: &CompileError) -> PyObjectRef { + let syntax_error_type = self.ctx.exceptions.syntax_error.clone(); + let syntax_error = self.new_exception(syntax_error_type, error.to_string()); + let lineno = self.new_int(error.location.get_row()); + self.set_attr(&syntax_error, "lineno", lineno).unwrap(); + syntax_error } pub fn get_none(&self) -> PyObjectRef { diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index ff72dddbb6..2c330e6eeb 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -266,7 +266,9 @@ impl WASMVirtualMachine { let code = compile::compile(vm, &source, &mode, "".to_string()); let code = code.map_err(|err| { let js_err = SyntaxError::new(&format!("Error parsing Python code: {}", err)); - if let rustpython_vm::error::CompileError::Parse(ref parse_error) = err { + if let rustpython_vm::error::CompileErrorType::Parse(ref parse_error) = + err.error + { use rustpython_parser::error::ParseError; if let ParseError::EOF(Some(ref loc)) | ParseError::ExtraToken((ref loc, ..)) From b34784e9efdcdc1449299a67f84cc3b07634b156 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 17 Apr 2019 20:52:53 +0200 Subject: [PATCH 333/884] Format main.rs --- src/main.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 326e0bc3e0..24b46821b6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,8 @@ extern crate rustyline; use clap::{App, Arg}; use rustpython_parser::error::ParseError; use rustpython_vm::{ - compile, error::CompileError, error::CompileErrorType, frame::Scope, import, obj::objstr, print_exception, - pyobject::PyResult, util, VirtualMachine, + compile, error::CompileError, error::CompileErrorType, frame::Scope, import, obj::objstr, + print_exception, pyobject::PyResult, util, VirtualMachine, }; use rustyline::{error::ReadlineError, Editor}; use std::path::{Path, PathBuf}; @@ -115,7 +115,12 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), Com Ok(()) } // Don't inject syntax errors for line continuation - Err(err @ CompileError{ error: CompileErrorType::Parse(ParseError::EOF(_)), .. }) => Err(err), + Err( + err @ CompileError { + error: CompileErrorType::Parse(ParseError::EOF(_)), + .. + }, + ) => Err(err), Err(err) => { let exc = vm.new_syntax_error(&err); print_exception(vm, &exc); @@ -188,7 +193,10 @@ fn run_shell(vm: &VirtualMachine) -> PyResult { } match shell_exec(vm, &input, vars.clone()) { - Err(CompileError { error: CompileErrorType::Parse(ParseError::EOF(_)), .. }) => { + Err(CompileError { + error: CompileErrorType::Parse(ParseError::EOF(_)), + .. + }) => { continuing = true; continue; } From eb2d0b01eec8a5ce1d73ce31dda798715d3dedd5 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 08:06:06 +0200 Subject: [PATCH 334/884] refactor lex byte --- parser/src/lexer.rs | 128 +++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 79 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index b9e39e7df1..11d30cbee7 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -542,7 +542,7 @@ where let tok = if is_bytes { if string_content.is_ascii() { Tok::Bytes { - value: self.lex_byte(string_content)?, + value: lex_byte(string_content)?, } } else { return Err(LexicalError::StringError); @@ -1105,84 +1105,6 @@ where let tok_end = self.get_pos(); Ok((tok_start, ty, tok_end)) } - - fn lex_byte(&self, s: String) -> Result, LexicalError> { - let mut res = vec![]; - let mut escape = false; //flag if previous was \ - let mut hex_on = false; // hex mode on or off - let mut hex_value = String::new(); - - for c in s.chars() { - match c { - '\\' => { - if escape { - res.push(92); - escape = false; - } else { - escape = true; - } - } - - 'x' => { - if escape { - hex_on = true; - } else { - res.push(120); - } - escape = false; - } - 't' => { - if escape { - res.push(9); - } else { - res.push(116); - } - escape = false; - } - 'n' => { - if escape { - res.push(10); - } else { - res.push(110) - } - escape = false; - } - 'r' => { - if escape { - res.push(13); - } else { - res.push(114) - } - escape = false; - } - x => { - if hex_on { - if x.is_ascii_hexdigit() { - if hex_value.is_empty() { - hex_value.push(x); - continue; - } else { - hex_value.push(x); - res.push(u8::from_str_radix(&hex_value, 16).unwrap()); - hex_on = false; - hex_value.clear(); - } - } else { - return Err(LexicalError::StringError); - } - } else { - if escape { - res.push(92); - } - res.push(x as u8); - } - escape = false; - } - } - } - - Ok(res) - } } /* Implement iterator pattern for the get_tok function. @@ -1211,6 +1133,54 @@ where } } +fn lex_byte(s: String) -> Result, LexicalError> { + let mut res = vec![]; + let mut escape = false; //flag if previous was \ + let mut hex_on = false; // hex mode on or off + let mut hex_value = String::new(); + + for c in s.chars() { + if hex_on { + if c.is_ascii_hexdigit() { + if hex_value.is_empty() { + hex_value.push(c); + continue; + } else { + hex_value.push(c); + res.push(u8::from_str_radix(&hex_value, 16).unwrap()); + hex_on = false; + hex_value.clear(); + } + } else { + return Err(LexicalError::StringError); + } + } else { + match (c, escape) { + ('\\', true) => res.push(b'\\'), + ('\\', false) => { + escape = true; + continue; + } + ('x', true) => hex_on = true, + ('x', false) => res.push(b'x'), + ('t', true) => res.push(b'\t'), + ('t', false) => res.push(b't'), + ('n', true) => res.push(b'\n'), + ('n', false) => res.push(b'n'), + ('r', true) => res.push(b'\r'), + ('r', false) => res.push(b'r'), + (x, true) => { + res.push(b'\\'); + res.push(x as u8); + } + (x, false) => res.push(x as u8), + } + escape = false; + } + } + Ok(res) +} + #[cfg(test)] mod tests { use super::{make_tokenizer, NewlineHandler, Tok}; From c550360be5f931763f7a44f4f1d35de95377a557 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sat, 13 Apr 2019 08:39:29 +0200 Subject: [PATCH 335/884] add is_bytes_like add bytes.count wthout slice --- tests/snippets/bytes.py | 8 ++++++++ vm/src/obj/objbyteinner.rs | 27 +++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 12 +++++++++++- vm/src/obj/objmemory.rs | 7 +++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index c496bfeffb..64cc003d35 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -162,3 +162,11 @@ with assertRaises(TypeError): b"b".center(2, b"ba") b"kok".center(5, bytearray(b"x")) + +# count +assert b"azeazerazeazopia".count(b"aze") == 3 +assert b"azeazerazeazopia".count(b"az") == 4 +assert b"azeazerazeazopia".count(b"a") == 5 +assert b"123456789".count(b"") == 10 +assert b"azeazerazeazopia".count(bytearray(b"aze")) == 3 +assert b"azeazerazeazopia".count(memoryview(b"aze")) == 3 diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index d0a63633ac..73808f533f 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -19,6 +19,7 @@ use num_traits::ToPrimitive; use super::objbytearray::{get_value as get_value_bytearray, PyByteArray}; use super::objbytes::PyBytes; +use super::objmemory::PyMemoryView; #[derive(Debug, Default, Clone)] pub struct PyByteInner { @@ -386,6 +387,23 @@ impl PyByteInner { res } + + pub fn count(&self, sub: Vec) -> usize { + if sub.is_empty() { + return self.len() + 1; + } + + let mut total: usize = 0; + for (n, i) in self.elements.iter().enumerate() { + if n + sub.len() <= self.len() + && *i == sub[0] + && &self.elements[n..n + sub.len()] == sub.as_slice() + { + total += 1; + } + } + total + } } pub fn is_byte(obj: &PyObjectRef) -> Option> { @@ -395,3 +413,12 @@ pub fn is_byte(obj: &PyObjectRef) -> Option> { j @ PyByteArray => Some(get_value_bytearray(&j.as_object()).to_vec()), _ => None) } + +pub fn is_bytes_like(obj: &PyObjectRef) -> Option> { + match_class!(obj.clone(), + + i @ PyBytes => Some(i.get_value().to_vec()), + j @ PyByteArray => Some(get_value_bytearray(&j.as_object()).to_vec()), + k @ PyMemoryView => Some(k.get_obj_value().unwrap()), + _ => None) +} diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 0556de0535..fd3fad7580 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -7,7 +7,7 @@ use std::ops::Deref; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; -use super::objbyteinner::{is_byte, PyByteInner}; +use super::objbyteinner::{is_byte, is_bytes_like, PyByteInner}; use super::objiter; use super::objslice::PySlice; use super::objtype::PyClassRef; @@ -269,6 +269,16 @@ impl PyBytesRef { obj => {Err(vm.new_type_error(format!("{} cannot be interpreted as an integer", obj)))} ) } + + #[pymethod(name = "count")] + fn count(self, width: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match is_bytes_like(&width) { + Some(value) => Ok(vm.new_int(self.inner.count(value))), + None => { + Err(vm.new_type_error(format!("a bytes-like object is required, not {}", width))) + } + } + } } #[derive(Debug)] diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index cfe44dea38..a0e0613f97 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -1,3 +1,4 @@ +use crate::obj::objbyteinner::is_byte; use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; @@ -9,6 +10,12 @@ pub struct PyMemoryView { obj: PyObjectRef, } +impl PyMemoryView { + pub fn get_obj_value(&self) -> Option> { + is_byte(&self.obj) + } +} + impl PyValue for PyMemoryView { fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.memoryview_type() From 00cf5248147deb8df6fd0ddc0950270141790e1e Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 14 Apr 2019 21:33:53 +0200 Subject: [PATCH 336/884] add slice capability to bytes. add trait IsByte --- tests/snippets/bytes.py | 11 ++++++++ vm/src/obj/objbyteinner.rs | 57 +++++++++++++++++++++++++++++++++----- vm/src/obj/objbytes.rs | 21 +++++++++----- 3 files changed, 75 insertions(+), 14 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 64cc003d35..10de0d9005 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -170,3 +170,14 @@ assert b"123456789".count(b"") == 10 assert b"azeazerazeazopia".count(bytearray(b"aze")) == 3 assert b"azeazerazeazopia".count(memoryview(b"aze")) == 3 +assert b"azeazerazeazopia".count(memoryview(b"aze"),1,9 ) == 1 +assert b"azeazerazeazopia".count(b"aze", None, None) == 3 +assert b"azeazerazeazopia".count(b"aze", 2, None) == 2 +assert b"azeazerazeazopia".count(b"aze", 2) == 2 +assert b"azeazerazeazopia".count(b"aze", None, 7) == 2 +assert b"azeazerazeazopia".count(b"aze", None, 7) == 2 +assert b"azeazerazeazopia".count(b"aze", 2, 7) == 1 +assert b"azeazerazeazopia".count(b"aze", -13, -10) == 1 +assert b"azeazerazeazopia".count(b"aze", 1, 10000) == 2 +with assertRaises(ValueError): + b"ilj".count(355) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 73808f533f..1d72bf209a 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -20,6 +20,7 @@ use num_traits::ToPrimitive; use super::objbytearray::{get_value as get_value_bytearray, PyByteArray}; use super::objbytes::PyBytes; use super::objmemory::PyMemoryView; +use super::objnone::PyNone; #[derive(Debug, Default, Clone)] pub struct PyByteInner { @@ -388,21 +389,52 @@ impl PyByteInner { res } - pub fn count(&self, sub: Vec) -> usize { + pub fn count( + &self, + sub: Vec, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> Result { + let start = if let OptionalArg::Present(st) = start { + match_class!(st, + i @ PyInt => {Some(i.as_bigint().clone())}, + _obj @ PyNone => None, + _=> {return Err(vm.new_type_error("slice indices must be integers or None or have an __index__ method".to_string()));} + ) + } else { + None + }; + let end = if let OptionalArg::Present(e) = end { + match_class!(e, + i @ PyInt => {Some(i.as_bigint().clone())}, + _obj @ PyNone => None, + _=> {return Err(vm.new_type_error("slice indices must be integers or None or have an __index__ method".to_string()));} + ) + } else { + None + }; + + let range = self.elements.get_slice_range(&start, &end); + if sub.is_empty() { - return self.len() + 1; + return Ok(self.len() + 1); } let mut total: usize = 0; - for (n, i) in self.elements.iter().enumerate() { - if n + sub.len() <= self.len() - && *i == sub[0] - && &self.elements[n..n + sub.len()] == sub.as_slice() + let mut i_start = range.start; + let i_end = range.end; + + for i in self.elements.do_slice(range) { + if i_start + sub.len() <= i_end + && i == sub[0] + && &self.elements[i_start..(i_start + sub.len())] == sub.as_slice() { total += 1; } + i_start += 1; } - total + Ok(total) } } @@ -422,3 +454,14 @@ pub fn is_bytes_like(obj: &PyObjectRef) -> Option> { k @ PyMemoryView => Some(k.get_obj_value().unwrap()), _ => None) } + +pub trait IsByte: ToPrimitive { + fn is_byte(&self, vm: &VirtualMachine) -> Result { + match self.to_u8() { + Some(value) => Ok(value), + None => Err(vm.new_value_error("byte must be in range(0, 256)".to_string())), + } + } +} + +impl IsByte for BigInt {} diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index fd3fad7580..338e51cbb5 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -7,7 +7,7 @@ use std::ops::Deref; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; -use super::objbyteinner::{is_byte, is_bytes_like, PyByteInner}; +use super::objbyteinner::{is_byte, is_bytes_like, IsByte, PyByteInner}; use super::objiter; use super::objslice::PySlice; use super::objtype::PyClassRef; @@ -271,12 +271,19 @@ impl PyBytesRef { } #[pymethod(name = "count")] - fn count(self, width: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match is_bytes_like(&width) { - Some(value) => Ok(vm.new_int(self.inner.count(value))), - None => { - Err(vm.new_type_error(format!("a bytes-like object is required, not {}", width))) - } + fn count( + self, + sub: PyObjectRef, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + match is_bytes_like(&sub) { + Some(value) => Ok(vm.new_int(self.inner.count(value, start, end, vm)?)), + None => match_class!(sub, + i @ PyInt => { + Ok(vm.new_int(self.inner.count(vec![i.as_bigint().is_byte(vm)?], start, end, vm)?))}, + obj => {Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)))}), } } } From e6d6b77698ab94e7417d5d2085d37e767f48ef4e Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 14 Apr 2019 21:43:54 +0200 Subject: [PATCH 337/884] fix error msg --- vm/src/obj/objbytes.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 338e51cbb5..e8fdf0e568 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -283,7 +283,7 @@ impl PyBytesRef { None => match_class!(sub, i @ PyInt => { Ok(vm.new_int(self.inner.count(vec![i.as_bigint().is_byte(vm)?], start, end, vm)?))}, - obj => {Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)))}), + obj => {Err(vm.new_type_error(format!("argument should be integer or bytes-like object, not {}", obj)))}), } } } From 5f5d5c4278ef992728a5c71d2d8efaa6836f4613 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 14 Apr 2019 22:52:27 +0200 Subject: [PATCH 338/884] add bytes.join --- tests/snippets/bytes.py | 12 ++++++++++-- vm/src/obj/objbyteinner.rs | 21 ++++++++++++++++++++- vm/src/obj/objbytes.rs | 8 ++++++-- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 10de0d9005..14fe9c2412 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -170,7 +170,7 @@ assert b"123456789".count(b"") == 10 assert b"azeazerazeazopia".count(bytearray(b"aze")) == 3 assert b"azeazerazeazopia".count(memoryview(b"aze")) == 3 -assert b"azeazerazeazopia".count(memoryview(b"aze"),1,9 ) == 1 +assert b"azeazerazeazopia".count(memoryview(b"aze"), 1, 9) == 1 assert b"azeazerazeazopia".count(b"aze", None, None) == 3 assert b"azeazerazeazopia".count(b"aze", 2, None) == 2 assert b"azeazerazeazopia".count(b"aze", 2) == 2 @@ -180,4 +180,12 @@ assert b"azeazerazeazopia".count(b"aze", -13, -10) == 1 assert b"azeazerazeazopia".count(b"aze", 1, 10000) == 2 with assertRaises(ValueError): - b"ilj".count(355) + b"ilj".count(3550) + +# join +assert ( + b"".join((b"jiljl", bytearray(b"kmoomk"), memoryview(b"aaaa"))) + == b"jiljlkmoomkaaaa" +) +with assertRaises(TypeError): + b"".join((b"km", "kl")) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 1d72bf209a..377e93ec5e 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -1,4 +1,4 @@ -use crate::pyobject::PyObjectRef; +use crate::pyobject::{PyIterable, PyObjectRef}; use num_bigint::BigInt; use crate::function::OptionalArg; @@ -436,6 +436,25 @@ impl PyByteInner { } Ok(total) } + + pub fn join(&self, iter: PyIterable, vm: &VirtualMachine) -> PyResult { + let mut refs = vec![]; + for (index, v) in iter.iter(vm)?.enumerate() { + let v = v?; + match is_bytes_like(&v) { + None => { + return Err(vm.new_type_error(format!( + "sequence item {}: expected a bytes-like object, {} found", + index, + &v.class().name, + ))); + } + Some(value) => refs.extend(value), + } + } + + Ok(vm.ctx.new_bytes(refs)) + } } pub fn is_byte(obj: &PyObjectRef) -> Option> { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index e8fdf0e568..e34b4ee5ea 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -5,13 +5,12 @@ use core::cell::Cell; use std::ops::Deref; use crate::function::OptionalArg; -use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; use super::objbyteinner::{is_byte, is_bytes_like, IsByte, PyByteInner}; use super::objiter; use super::objslice::PySlice; use super::objtype::PyClassRef; - /// "bytes(iterable_of_ints) -> bytes\n\ /// bytes(string, encoding[, errors]) -> bytes\n\ /// bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\ @@ -286,6 +285,11 @@ impl PyBytesRef { obj => {Err(vm.new_type_error(format!("argument should be integer or bytes-like object, not {}", obj)))}), } } + + #[pymethod(name = "join")] + fn join(self, iter: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner.join(iter, vm) + } } #[derive(Debug)] From 14658b6236cf0d34be781a3b3664bfa87091edc0 Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Wed, 17 Apr 2019 08:48:20 +0200 Subject: [PATCH 339/884] move is_byte to try_as_byte move trait IsByte to ByteOr refactor center refactor count fix center with negative value add test for count(integer) --- tests/snippets/bytes.py | 2 ++ vm/src/obj/objbyteinner.rs | 64 +++++++++++++++++++++++++++++++------- vm/src/obj/objbytes.rs | 40 +++--------------------- vm/src/obj/objmemory.rs | 4 +-- 4 files changed, 60 insertions(+), 50 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 14fe9c2412..ec8001213e 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -162,6 +162,7 @@ with assertRaises(TypeError): b"b".center(2, b"ba") b"kok".center(5, bytearray(b"x")) +b"kok".center(-5,) # count assert b"azeazerazeazopia".count(b"aze") == 3 @@ -181,6 +182,7 @@ assert b"azeazerazeazopia".count(b"aze", 1, 10000) == 2 with assertRaises(ValueError): b"ilj".count(3550) +assert b"azeazerazeazopia".count(97) == 5 # join assert ( diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 377e93ec5e..26f33c2e68 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -362,12 +362,45 @@ impl PyByteInner { .collect::>()) } - pub fn center(&self, width: &BigInt, fillbyte: u8, _vm: &VirtualMachine) -> Vec { - let width = width.to_usize().unwrap(); + pub fn center(&self, width_a: PyObjectRef, fillbyte: OptionalArg, vm: &VirtualMachine) -> PyResult> { + let fillbyte = if let OptionalArg::Present(v) = fillbyte { + match try_as_byte(&v) { + Some(x) => { + if x.len() == 1 { + x[0] + } else { + return Err(vm.new_type_error(format!( + "center() argument 2 must be a byte string of length 1, not {}", + &v + ))); + } + } + None => { + return Err(vm.new_type_error(format!( + "center() argument 2 must be a byte string of length 1, not {}", + &v + ))); + } + } + } else { + 32 // default is space + }; + + let width_b = match_class!(width_a, + i @PyInt => i, + obj => {return Err(vm.new_type_error(format!("{} cannot be interpreted as an integer", obj)));} + ); + + + // <0 = no change + let width = if let Some(x) = width_b.as_bigint().to_usize() { + x + } else {return Ok(self.elements.clone());}; + // adjust right et left side if width <= self.len() { - return self.elements.clone(); + return Ok(self.elements.clone()); } let diff: usize = width - self.len(); let mut ln: usize = diff / 2; @@ -386,16 +419,23 @@ impl PyByteInner { res.extend_from_slice(&self.elements[..]); res.extend_from_slice(&vec![fillbyte; rn][..]); - res + Ok(res) } pub fn count( &self, - sub: Vec, + sub: PyObjectRef, start: OptionalArg, end: OptionalArg, vm: &VirtualMachine, - ) -> Result { + ) -> PyResult { + let sub = match try_as_bytes_like(&sub) { + Some(value) => value, + None => match_class!(sub, + i @ PyInt => + vec![i.as_bigint().byte_or(vm)?], + obj => {return Err(vm.new_type_error(format!("argument should be integer or bytes-like object, not {}", obj)));}), + }; let start = if let OptionalArg::Present(st) = start { match_class!(st, i @ PyInt => {Some(i.as_bigint().clone())}, @@ -441,7 +481,7 @@ impl PyByteInner { let mut refs = vec![]; for (index, v) in iter.iter(vm)?.enumerate() { let v = v?; - match is_bytes_like(&v) { + match try_as_bytes_like(&v) { None => { return Err(vm.new_type_error(format!( "sequence item {}: expected a bytes-like object, {} found", @@ -457,7 +497,7 @@ impl PyByteInner { } } -pub fn is_byte(obj: &PyObjectRef) -> Option> { +pub fn try_as_byte(obj: &PyObjectRef) -> Option> { match_class!(obj.clone(), i @ PyBytes => Some(i.get_value().to_vec()), @@ -465,7 +505,7 @@ pub fn is_byte(obj: &PyObjectRef) -> Option> { _ => None) } -pub fn is_bytes_like(obj: &PyObjectRef) -> Option> { +pub fn try_as_bytes_like(obj: &PyObjectRef) -> Option> { match_class!(obj.clone(), i @ PyBytes => Some(i.get_value().to_vec()), @@ -474,8 +514,8 @@ pub fn is_bytes_like(obj: &PyObjectRef) -> Option> { _ => None) } -pub trait IsByte: ToPrimitive { - fn is_byte(&self, vm: &VirtualMachine) -> Result { +pub trait ByteOr: ToPrimitive { + fn byte_or(&self, vm: &VirtualMachine) -> Result { match self.to_u8() { Some(value) => Ok(value), None => Err(vm.new_value_error("byte must be in range(0, 256)".to_string())), @@ -483,4 +523,4 @@ pub trait IsByte: ToPrimitive { } } -impl IsByte for BigInt {} +impl ByteOr for BigInt {} diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index e34b4ee5ea..851c8d8ab7 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -7,7 +7,7 @@ use std::ops::Deref; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; -use super::objbyteinner::{is_byte, is_bytes_like, IsByte, PyByteInner}; +use super::objbyteinner::PyByteInner; use super::objiter; use super::objslice::PySlice; use super::objtype::PyClassRef; @@ -240,33 +240,7 @@ impl PyBytesRef { fillbyte: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - let sym = if let OptionalArg::Present(v) = fillbyte { - match is_byte(&v) { - Some(x) => { - if x.len() == 1 { - x[0] - } else { - return Err(vm.new_type_error(format!( - "center() argument 2 must be a byte string of length 1, not {}", - &v - ))); - } - } - None => { - return Err(vm.new_type_error(format!( - "center() argument 2 must be a byte string of length 1, not {}", - &v - ))); - } - } - } else { - 32 // default is space - }; - - match_class!(width, - i @PyInt => Ok(vm.ctx.new_bytes(self.inner.center(i.as_bigint(), sym, vm))), - obj => {Err(vm.new_type_error(format!("{} cannot be interpreted as an integer", obj)))} - ) + Ok(vm.ctx.new_bytes(self.inner.center(width, fillbyte, vm)?)) } #[pymethod(name = "count")] @@ -276,14 +250,8 @@ impl PyBytesRef { start: OptionalArg, end: OptionalArg, vm: &VirtualMachine, - ) -> PyResult { - match is_bytes_like(&sub) { - Some(value) => Ok(vm.new_int(self.inner.count(value, start, end, vm)?)), - None => match_class!(sub, - i @ PyInt => { - Ok(vm.new_int(self.inner.count(vec![i.as_bigint().is_byte(vm)?], start, end, vm)?))}, - obj => {Err(vm.new_type_error(format!("argument should be integer or bytes-like object, not {}", obj)))}), - } + ) -> PyResult { + self.inner.count(sub, start, end, vm) } #[pymethod(name = "join")] diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index a0e0613f97..88040ae14a 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -1,4 +1,4 @@ -use crate::obj::objbyteinner::is_byte; +use crate::obj::objbyteinner::try_as_byte; use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; @@ -12,7 +12,7 @@ pub struct PyMemoryView { impl PyMemoryView { pub fn get_obj_value(&self) -> Option> { - is_byte(&self.obj) + try_as_byte(&self.obj) } } From 8a7c46da7b7a26d9ff2e522a551a1267c8be8dbc Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Wed, 17 Apr 2019 15:55:37 +0200 Subject: [PATCH 340/884] add endswith startswith --- tests/snippets/bytes.py | 18 +++++++++++- vm/src/obj/objbyteinner.rs | 59 +++++++++++++++++++++++++++++++++++++- vm/src/obj/objbytes.rs | 22 ++++++++++++++ vm/src/obj/objsequence.rs | 19 ++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index ec8001213e..755707a766 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -162,7 +162,7 @@ with assertRaises(TypeError): b"b".center(2, b"ba") b"kok".center(5, bytearray(b"x")) -b"kok".center(-5,) +b"kok".center(-5) # count assert b"azeazerazeazopia".count(b"aze") == 3 @@ -191,3 +191,19 @@ ) with assertRaises(TypeError): b"".join((b"km", "kl")) + + +# endswith startswith +assert b"abcde".endswith(b"de") +assert b"abcde".endswith(b"") +assert not b"abcde".endswith(b"zx") +assert b"abcde".endswith(b"bc", 0, 3) +assert not b"abcde".endswith(b"bc", 2, 3) +assert b"abcde".endswith((b"c", b"de")) + +assert b"abcde".startswith(b"ab") +assert b"abcde".startswith(b"") +assert not b"abcde".startswith(b"zx") +assert b"abcde".startswith(b"cd", 2) +assert not b"abcde".startswith(b"cd", 1, 4) +assert b"abcde".startswith((b"a", b"bc")) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 26f33c2e68..cc4111a8b3 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -12,7 +12,8 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use super::objint; -use super::objsequence::PySliceableSequence; +use super::objtype; +use super::objsequence::{PySliceableSequence, is_valid_slice_arg}; use crate::obj::objint::PyInt; use num_integer::Integer; use num_traits::ToPrimitive; @@ -21,6 +22,7 @@ use super::objbytearray::{get_value as get_value_bytearray, PyByteArray}; use super::objbytes::PyBytes; use super::objmemory::PyMemoryView; use super::objnone::PyNone; +use super::objsequence; #[derive(Debug, Default, Clone)] pub struct PyByteInner { @@ -495,6 +497,61 @@ impl PyByteInner { Ok(vm.ctx.new_bytes(refs)) } + + pub fn startsendswith( + &self, + arg: PyObjectRef, + start: OptionalArg, + end: OptionalArg, + endswith: bool, // true for endswith, false for startswith + vm: &VirtualMachine, + ) -> PyResult { + let suff = if objtype::isinstance(&arg, &vm.ctx.tuple_type()) { + let mut flatten = vec![]; + for v in objsequence::get_elements(&arg).to_vec() { + match try_as_bytes_like(&v) { + None => { + return Err(vm.new_type_error(format!( + "a bytes-like object is required, not {}", + &v.class().name, + ))); + } + Some(value) => flatten.extend(value), + } + } + flatten + } else { + match try_as_bytes_like(&arg) { + Some(value) => value, + None => { + return Err(vm.new_type_error(format!( + "endswith first arg must be bytes or a tuple of bytes, not {}", + arg + ))); + } + } + }; + + if suff.is_empty() { + return Ok(vm.new_bool(true)); + } + let range = self.elements.get_slice_range( + &is_valid_slice_arg(start, vm)?, + &is_valid_slice_arg(end, vm)?, + ); + + if range.end - range.start < suff.len() { + return Ok(vm.new_bool(false)); + } + + let offset = if endswith { + (range.end - suff.len())..range.end + } else { + 0..suff.len() + }; + + Ok(vm.new_bool(suff.as_slice() == &self.elements.do_slice(range)[offset])) + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 851c8d8ab7..0c6e5c4b90 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -258,6 +258,28 @@ impl PyBytesRef { fn join(self, iter: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner.join(iter, vm) } + + #[pymethod(name = "endswith")] + fn endswith( + self, + suffix: PyObjectRef, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + self.inner.startsendswith(suffix, start, end, true, vm) + } + + #[pymethod(name = "startswith")] + fn startswith( + self, + suffix: PyObjectRef, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + self.inner.startsendswith(suffix, start, end, false, vm) + } } #[derive(Debug)] diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index 5594ac8586..af20343708 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -1,3 +1,5 @@ +use crate::function::OptionalArg; +use crate::obj::objnone::PyNone; use std::cell::RefCell; use std::marker::Sized; use std::ops::{Deref, DerefMut, Range}; @@ -371,3 +373,20 @@ pub fn get_mut_elements<'a>(obj: &'a PyObjectRef) -> impl DerefMut, + vm: &VirtualMachine, +) -> Result, PyObjectRef> { + if let OptionalArg::Present(value) = arg { + match_class!(value, + i @ PyInt => Ok(Some(i.as_bigint().clone())), + _obj @ PyNone => Ok(None), + _=> {return Err(vm.new_type_error("slice indices must be integers or None or have an __index__ method".to_string()));} + // TODO: check for an __index__ method + ) + } else { + Ok(None) + } +} From bd2166789df931ea393fae0fe666eadb3e9473ed Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Wed, 17 Apr 2019 16:12:44 +0200 Subject: [PATCH 341/884] add bytes index and find --- tests/snippets/bytes.py | 25 +++++++++++++++++++++++++ vm/src/obj/objbyteinner.rs | 38 ++++++++++++++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 89 insertions(+) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 755707a766..dc9a439bcd 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -207,3 +207,28 @@ assert b"abcde".startswith(b"cd", 2) assert not b"abcde".startswith(b"cd", 1, 4) assert b"abcde".startswith((b"a", b"bc")) + + +# index find +assert b"abcd".index(b"cd") == 2 +assert b"abcd".index(b"cd", 0) == 2 +assert b"abcd".index(b"cd", 1) == 2 +assert b"abcd".index(99) == 2 +with assertRaises(ValueError): + b"abcde".index(b"c", 3, 1) +with assertRaises(ValueError): + b"abcd".index(b"cdaaaaa") +with assertRaises(ValueError): + b"abcd".index(b"b", 3, 4) +with assertRaises(ValueError): + b"abcd".index(1) + + +assert b"abcd".find(b"cd") == 2 +assert b"abcd".find(b"cd", 0) == 2 +assert b"abcd".find(b"cd", 1) == 2 +assert b"abcde".find(b"c", 3, 1) == -1 +assert b"abcd".find(b"cdaaaaa") == -1 +assert b"abcd".find(b"b", 3, 4) == -1 +assert b"abcd".find(1) == -1 +assert b"abcd".find(99) == 2 diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index cc4111a8b3..5c5e97c258 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -552,6 +552,44 @@ impl PyByteInner { Ok(vm.new_bool(suff.as_slice() == &self.elements.do_slice(range)[offset])) } + + pub fn find( + &self, + sub: PyObjectRef, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> Result { + let sub = match try_as_bytes_like(&sub) { + Some(value) => value, + None => match_class!(sub, + i @ PyInt => + vec![i.as_bigint().byte_or(vm)?], + obj => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)));}), + }; + + let range = self.elements.get_slice_range( + &is_valid_slice_arg(start, vm)?, + &is_valid_slice_arg(end, vm)?, + ); + + // not allowed for this method + if range.end < range.start { + return Ok(-1isize); + } + + let start = range.start; + + let slice = &self.elements[range]; + for (n, _) in slice.iter().enumerate() { + if n + sub.len() <= slice.len() + && &slice[n..n + sub.len()] == sub.as_slice() + { + return Ok((start+n) as isize); + } + } + Ok(-1isize) + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 0c6e5c4b90..a53b371d09 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -280,6 +280,32 @@ impl PyBytesRef { ) -> PyResult { self.inner.startsendswith(suffix, start, end, false, vm) } + + #[pymethod(name = "find")] + fn find( + self, + sub: PyObjectRef, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + Ok(vm.new_int(self.inner.find(sub, start, end, vm)?)) + } + + #[pymethod(name = "index")] + fn index( + self, + sub: PyObjectRef, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let res = self.inner.find(sub, start, end, vm)?; + if res == -1 { + return Err(vm.new_value_error("substring not found".to_string())); + } + Ok(vm.new_int(res)) + } } #[derive(Debug)] From ced6f15a59155f42f2278c69dd652fc4c0b2f2be Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Wed, 17 Apr 2019 16:16:43 +0200 Subject: [PATCH 342/884] add bytes.makertans --- tests/snippets/bytes.py | 9 +++++++++ vm/src/obj/objbyteinner.rs | 24 ++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 2 ++ 3 files changed, 35 insertions(+) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index dc9a439bcd..ebd3eb877a 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -232,3 +232,12 @@ assert b"abcd".find(b"b", 3, 4) == -1 assert b"abcd".find(1) == -1 assert b"abcd".find(99) == 2 + + +# make trans +# fmt: off +assert ( + bytes.maketrans(memoryview(b"abc"), bytearray(b"zzz")) + == bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 122, 122, 122, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]) +) +# fmt: on diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 5c5e97c258..c5fda16c80 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -590,6 +590,30 @@ impl PyByteInner { } Ok(-1isize) } + + pub fn maketrans(from: PyObjectRef, to: PyObjectRef, vm :&VirtualMachine) -> PyResult{ + let mut res = vec![]; + + let from = match try_as_bytes_like(&from) { + Some(value) => value, + None => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", from)));}, + }; + + let to = match try_as_bytes_like(&to) { + Some(value) => value, + None => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", to)));}, + }; + + for i in 0..=255 { + res.push(if let Some(position) = from.iter().position(|&x| x ==i) { + to[position] + } else { + i + }); + } + + Ok(vm.ctx.new_bytes(res)) + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index a53b371d09..da675547b3 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -62,6 +62,8 @@ pub fn init(context: &PyContext) { let bytes_type = &context.bytes_type; extend_class!(context, bytes_type, { "fromhex" => context.new_rustfunc(PyBytesRef::fromhex), + "maketrans" => context.new_rustfunc(PyByteInner::maketrans), + }); let bytesiterator_type = &context.bytesiterator_type; extend_class!(context, bytesiterator_type, { From 0b283102a6c69022a2d79219c953a3d098456658 Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Wed, 17 Apr 2019 17:52:45 +0200 Subject: [PATCH 343/884] add translate --- tests/snippets/bytes.py | 5 +++++ vm/src/obj/objbyteinner.rs | 30 ++++++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 10 ++++++++++ 3 files changed, 45 insertions(+) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index ebd3eb877a..602ff9000c 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -241,3 +241,8 @@ == bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 122, 122, 122, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]) ) # fmt: on + +# translate +assert b'hjhtuyjyujuyj'.translate(bytes.maketrans(b"hj", b"ab"), b"h") == b'btuybyubuyb' +assert b'hjhtuyjyujuyj'.translate(bytes.maketrans(b"hj", b"ab"), b"a") == b'abatuybyubuyb' +assert b'hjhtuyjyujuyj'.translate(bytes.maketrans(b"hj", b"ab")) == b'abatuybyubuyb' diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index c5fda16c80..aa35ffaa0f 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -612,6 +612,36 @@ impl PyByteInner { }); } + Ok(vm.ctx.new_bytes(res)) + } + pub fn translate(&self, table: PyObjectRef, delete: OptionalArg, vm :&VirtualMachine) -> PyResult{ + let table = match try_as_bytes_like(&table) { + Some(value) => value, + None => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", table)));}, + }; + + if table.len() != 256 { + return Err(vm.new_value_error("translation table must be 256 characters long".to_string())); + } + + + + let delete = if let OptionalArg::Present(value) = delete { + match try_as_bytes_like(&value) { + Some(value) => value, + None => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", value)));} + } + } else {vec![]}; + + + let mut res = vec![]; + + for i in self.elements.iter() { + if !delete.contains(&i) { + res.push(table[*i as usize]); + } + } + Ok(vm.ctx.new_bytes(res)) } } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index da675547b3..90f7adc075 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -308,6 +308,16 @@ impl PyBytesRef { } Ok(vm.new_int(res)) } + + #[pymethod(name = "translate")] + fn translate( + self, + table: PyObjectRef, + delete: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + self.inner.translate(table, delete, vm) + } } #[derive(Debug)] From 8693adcd611f218189de5ef44dbe88dfcfbdc24c Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 08:41:44 +0200 Subject: [PATCH 344/884] handle None case for bytes.translate --- tests/snippets/bytes.py | 2 ++ vm/src/obj/objbyteinner.rs | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 602ff9000c..a4a32273ae 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -246,3 +246,5 @@ assert b'hjhtuyjyujuyj'.translate(bytes.maketrans(b"hj", b"ab"), b"h") == b'btuybyubuyb' assert b'hjhtuyjyujuyj'.translate(bytes.maketrans(b"hj", b"ab"), b"a") == b'abatuybyubuyb' assert b'hjhtuyjyujuyj'.translate(bytes.maketrans(b"hj", b"ab")) == b'abatuybyubuyb' +assert b'hjhtuyfjtyhuhjuyj'.translate(None, b"ht") == b'juyfjyujuyj' + diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index aa35ffaa0f..33a16a6c34 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -617,7 +617,13 @@ impl PyByteInner { pub fn translate(&self, table: PyObjectRef, delete: OptionalArg, vm :&VirtualMachine) -> PyResult{ let table = match try_as_bytes_like(&table) { Some(value) => value, - None => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", table)));}, + None => { + match_class!(table, + + _n @ PyNone => (0..=255).collect::>(), + obj => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)));}, + ) + } }; if table.len() != 256 { From 046bd9b6d84d1112faaaaa5d6d1e8d6e8898f0e4 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 14:01:12 +0200 Subject: [PATCH 345/884] reactor bytes.center --- vm/src/obj/objbyteinner.rs | 122 +++++++++++++++++++++++-------------- 1 file changed, 77 insertions(+), 45 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 33a16a6c34..f666affc5f 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -12,8 +12,8 @@ use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; use super::objint; +use super::objsequence::{is_valid_slice_arg, PySliceableSequence}; use super::objtype; -use super::objsequence::{PySliceableSequence, is_valid_slice_arg}; use crate::obj::objint::PyInt; use num_integer::Integer; use num_traits::ToPrimitive; @@ -364,7 +364,13 @@ impl PyByteInner { .collect::>()) } - pub fn center(&self, width_a: PyObjectRef, fillbyte: OptionalArg, vm: &VirtualMachine) -> PyResult> { + fn get_center_args( + &self, + width_a: PyObjectRef, + fillbyte: OptionalArg, + fn_name: String, + vm: &VirtualMachine, + ) -> PyResult<(u8, usize)> { let fillbyte = if let OptionalArg::Present(v) = fillbyte { match try_as_byte(&v) { Some(x) => { @@ -372,39 +378,52 @@ impl PyByteInner { x[0] } else { return Err(vm.new_type_error(format!( - "center() argument 2 must be a byte string of length 1, not {}", - &v + "{}() argument 2 must be a byte string of length 1, not {}", + &fn_name, &v ))); } } None => { return Err(vm.new_type_error(format!( - "center() argument 2 must be a byte string of length 1, not {}", - &v + "{}() argument 2 must be a byte string of length 1, not {}", + &fn_name, &v ))); } } } else { - 32 // default is space + b' ' // default is space }; - let width_b = match_class!(width_a, + let width_b = match_class!(width_a, i @PyInt => i, obj => {return Err(vm.new_type_error(format!("{} cannot be interpreted as an integer", obj)));} ); - // <0 = no change let width = if let Some(x) = width_b.as_bigint().to_usize() { - x - } else {return Ok(self.elements.clone());}; - + if x <= self.len() { + 0 + } else { + x + } + } else { + 0 + }; + + let diff: usize = if width != 0 { width - self.len() } else { 0 }; + + Ok((fillbyte, diff)) + } + + pub fn center( + &self, + width_a: PyObjectRef, + fillbyte: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + let fn_name = "center".to_string(); + let (fillbyte, diff) = self.get_center_args(width_a, fillbyte, fn_name, vm)?; - // adjust right et left side - if width <= self.len() { - return Ok(self.elements.clone()); - } - let diff: usize = width - self.len(); let mut ln: usize = diff / 2; let mut rn: usize = ln; @@ -434,8 +453,7 @@ impl PyByteInner { let sub = match try_as_bytes_like(&sub) { Some(value) => value, None => match_class!(sub, - i @ PyInt => - vec![i.as_bigint().byte_or(vm)?], + i @ PyInt => vec![i.as_bigint().byte_or(vm)?], obj => {return Err(vm.new_type_error(format!("argument should be integer or bytes-like object, not {}", obj)));}), }; let start = if let OptionalArg::Present(st) = start { @@ -563,8 +581,7 @@ impl PyByteInner { let sub = match try_as_bytes_like(&sub) { Some(value) => value, None => match_class!(sub, - i @ PyInt => - vec![i.as_bigint().byte_or(vm)?], + i @ PyInt => vec![i.as_bigint().byte_or(vm)?], obj => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)));}), }; @@ -582,63 +599,78 @@ impl PyByteInner { let slice = &self.elements[range]; for (n, _) in slice.iter().enumerate() { - if n + sub.len() <= slice.len() - && &slice[n..n + sub.len()] == sub.as_slice() - { - return Ok((start+n) as isize); + if n + sub.len() <= slice.len() && &slice[n..n + sub.len()] == sub.as_slice() { + return Ok((start + n) as isize); } } Ok(-1isize) } - pub fn maketrans(from: PyObjectRef, to: PyObjectRef, vm :&VirtualMachine) -> PyResult{ - let mut res = vec![]; + pub fn maketrans(from: PyObjectRef, to: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let mut res = vec![]; let from = match try_as_bytes_like(&from) { Some(value) => value, - None => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", from)));}, + None => { + return Err( + vm.new_type_error(format!("a bytes-like object is required, not {}", from)) + ); + } }; let to = match try_as_bytes_like(&to) { Some(value) => value, - None => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", to)));}, + None => { + return Err( + vm.new_type_error(format!("a bytes-like object is required, not {}", to)) + ); + } }; for i in 0..=255 { - res.push(if let Some(position) = from.iter().position(|&x| x ==i) { + res.push(if let Some(position) = from.iter().position(|&x| x == i) { to[position] - } else { + } else { i }); } Ok(vm.ctx.new_bytes(res)) } - pub fn translate(&self, table: PyObjectRef, delete: OptionalArg, vm :&VirtualMachine) -> PyResult{ + pub fn translate( + &self, + table: PyObjectRef, + delete: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { let table = match try_as_bytes_like(&table) { Some(value) => value, - None => { - match_class!(table, + None => match_class!(table, - _n @ PyNone => (0..=255).collect::>(), - obj => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)));}, - ) - } + _n @ PyNone => (0..=255).collect::>(), + obj => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)));}, + ), }; if table.len() != 256 { - return Err(vm.new_value_error("translation table must be 256 characters long".to_string())); + return Err( + vm.new_value_error("translation table must be 256 characters long".to_string()) + ); } - - let delete = if let OptionalArg::Present(value) = delete { - match try_as_bytes_like(&value) { + match try_as_bytes_like(&value) { Some(value) => value, - None => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", value)));} + None => { + return Err(vm.new_type_error(format!( + "a bytes-like object is required, not {}", + value + ))); + } } - } else {vec![]}; - + } else { + vec![] + }; let mut res = vec![]; From 84d61a9212be2f23c74c7f5138f23c66d7ec3474 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 14:56:47 +0200 Subject: [PATCH 346/884] bytes.new with kwargs --- tests/snippets/bytes.py | 23 +++++++++++++++-------- vm/src/obj/objbyteinner.rs | 24 +++++++++++++++--------- vm/src/obj/objbytes.rs | 7 +++---- 3 files changed, 33 insertions(+), 21 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index a4a32273ae..a36a9b2fde 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -6,12 +6,18 @@ assert bytes(range(4)) assert bytes(3) assert b"bla" -assert bytes("bla", "utf8") +assert bytes("bla", "utf8") == bytes("bla", encoding="utf-8") == b"bla" with assertRaises(TypeError): bytes("bla") -assert b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" == bytes(range(0,256)) -assert b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff' == bytes(range(0,256)) +assert ( + b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" + == bytes(range(0, 256)) +) +assert ( + b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" + == bytes(range(0, 256)) +) assert b"omkmok\Xaa" == bytes([111, 109, 107, 109, 111, 107, 92, 88, 97, 97]) @@ -243,8 +249,9 @@ # fmt: on # translate -assert b'hjhtuyjyujuyj'.translate(bytes.maketrans(b"hj", b"ab"), b"h") == b'btuybyubuyb' -assert b'hjhtuyjyujuyj'.translate(bytes.maketrans(b"hj", b"ab"), b"a") == b'abatuybyubuyb' -assert b'hjhtuyjyujuyj'.translate(bytes.maketrans(b"hj", b"ab")) == b'abatuybyubuyb' -assert b'hjhtuyfjtyhuhjuyj'.translate(None, b"ht") == b'juyfjyujuyj' - +assert b"hjhtuyjyujuyj".translate(bytes.maketrans(b"hj", b"ab"), b"h") == b"btuybyubuyb" +assert ( + b"hjhtuyjyujuyj".translate(bytes.maketrans(b"hj", b"ab"), b"a") == b"abatuybyubuyb" +) +assert b"hjhtuyjyujuyj".translate(bytes.maketrans(b"hj", b"ab")) == b"abatuybyubuyb" +assert b"hjhtuyfjtyhuhjuyj".translate(None, b"ht") == b"juyfjyujuyj" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index f666affc5f..e65fbb9829 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -29,15 +29,19 @@ pub struct PyByteInner { pub elements: Vec, } -impl PyByteInner { - pub fn new( - val_option: OptionalArg, - enc_option: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { +#[derive(FromArgs)] +pub struct BytesNewOptions { + #[pyarg(positional_only, optional = true)] + val_option: OptionalArg, + #[pyarg(positional_or_keyword, optional = true)] + encoding: OptionalArg, +} + +impl BytesNewOptions { + pub fn get_value(self, vm: &VirtualMachine) -> PyResult { // First handle bytes(string, encoding[, errors]) - if let OptionalArg::Present(enc) = enc_option { - if let OptionalArg::Present(eval) = val_option { + if let OptionalArg::Present(enc) = self.encoding { + if let OptionalArg::Present(eval) = self.val_option { if let Ok(input) = eval.downcast::() { if let Ok(encoding) = enc.clone().downcast::() { if &encoding.value.to_lowercase() == "utf8" @@ -66,7 +70,7 @@ impl PyByteInner { } // Only one argument } else { - let value = if let OptionalArg::Present(ival) = val_option { + let value = if let OptionalArg::Present(ival) = self.val_option { match_class!(ival.clone(), i @ PyInt => { let size = objint::get_value(&i.into_object()).to_usize().unwrap(); @@ -98,7 +102,9 @@ impl PyByteInner { } } } +} +impl PyByteInner { pub fn repr(&self) -> PyResult { let mut res = String::with_capacity(self.elements.len()); for i in self.elements.iter() { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 90f7adc075..85881a887b 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -7,7 +7,7 @@ use std::ops::Deref; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; -use super::objbyteinner::PyByteInner; +use super::objbyteinner::{BytesNewOptions, PyByteInner}; use super::objiter; use super::objslice::PySlice; use super::objtype::PyClassRef; @@ -77,12 +77,11 @@ impl PyBytesRef { #[pymethod(name = "__new__")] fn bytes_new( cls: PyClassRef, - val_option: OptionalArg, - enc_option: OptionalArg, + options: BytesNewOptions, vm: &VirtualMachine, ) -> PyResult { PyBytes { - inner: PyByteInner::new(val_option, enc_option, vm)?, + inner: options.get_value(vm)?, } .into_ref_with_type(vm, cls) } From 473ae241996a6abbc63036d666481bdfab1c7c37 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 17:31:58 +0200 Subject: [PATCH 347/884] use PyStringRef in new --- tests/snippets/bytes.py | 2 ++ vm/src/obj/objbyteinner.rs | 35 ++++++++++++++--------------------- vm/src/obj/objbytes.rs | 4 ++-- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index a36a9b2fde..2e6c658be0 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -9,6 +9,8 @@ assert bytes("bla", "utf8") == bytes("bla", encoding="utf-8") == b"bla" with assertRaises(TypeError): bytes("bla") +with assertRaises(TypeError): + bytes("bla", encoding = b"jilj") assert ( b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index e65fbb9829..ffd26d4835 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -7,7 +7,7 @@ use crate::vm::VirtualMachine; use crate::pyobject::{PyResult, TypeProtocol}; -use crate::obj::objstr::PyString; +use crate::obj::objstr::{PyString, PyStringRef}; use std::collections::hash_map::DefaultHasher; use std::hash::{Hash, Hasher}; @@ -30,37 +30,30 @@ pub struct PyByteInner { } #[derive(FromArgs)] -pub struct BytesNewOptions { +pub struct ByteInnerNewOptions { #[pyarg(positional_only, optional = true)] val_option: OptionalArg, #[pyarg(positional_or_keyword, optional = true)] - encoding: OptionalArg, + encoding: OptionalArg, } -impl BytesNewOptions { +impl ByteInnerNewOptions { pub fn get_value(self, vm: &VirtualMachine) -> PyResult { // First handle bytes(string, encoding[, errors]) if let OptionalArg::Present(enc) = self.encoding { if let OptionalArg::Present(eval) = self.val_option { if let Ok(input) = eval.downcast::() { - if let Ok(encoding) = enc.clone().downcast::() { - if &encoding.value.to_lowercase() == "utf8" - || &encoding.value.to_lowercase() == "utf-8" - // TODO: different encoding - { - return Ok(PyByteInner { - elements: input.value.as_bytes().to_vec(), - }); - } else { - return Err( - vm.new_value_error(format!("unknown encoding: {}", encoding.value)), //should be lookup error - ); - } + let encoding = enc.as_str(); + if encoding.to_lowercase() == "utf8" || encoding.to_lowercase() == "utf-8" + // TODO: different encoding + { + return Ok(PyByteInner { + elements: input.value.as_bytes().to_vec(), + }); } else { - return Err(vm.new_type_error(format!( - "bytes() argument 2 must be str, not {}", - enc.class().name - ))); + return Err( + vm.new_value_error(format!("unknown encoding: {}", encoding)), //should be lookup error + ); } } else { return Err(vm.new_type_error("encoding without a string argument".to_string())); diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 85881a887b..0607327506 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -7,7 +7,7 @@ use std::ops::Deref; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; -use super::objbyteinner::{BytesNewOptions, PyByteInner}; +use super::objbyteinner::{ByteInnerNewOptions, PyByteInner}; use super::objiter; use super::objslice::PySlice; use super::objtype::PyClassRef; @@ -77,7 +77,7 @@ impl PyBytesRef { #[pymethod(name = "__new__")] fn bytes_new( cls: PyClassRef, - options: BytesNewOptions, + options: ByteInnerNewOptions, vm: &VirtualMachine, ) -> PyResult { PyBytes { From 44d58f63abc3c744aa23a7a988e0916a4e540f37 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 19:03:08 +0200 Subject: [PATCH 348/884] refactor contains --- vm/src/obj/objbyteinner.rs | 27 ++++++++++++++++----------- vm/src/obj/objbytes.rs | 5 +---- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index ffd26d4835..8ca0407769 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -177,11 +177,20 @@ impl PyByteInner { elements } - pub fn contains_bytes(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { + pub fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match try_as_bytes_like(&needle) { + Some(value) => self.contains_bytes(&value, vm), + None => match_class!(needle, + i @ PyInt => self.contains_int(&i, vm), + obj => {Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)))}), + } + } + + fn contains_bytes(&self, other: &[u8], vm: &VirtualMachine) -> PyResult { for (n, i) in self.elements.iter().enumerate() { if n + other.len() <= self.len() - && *i == other.elements[0] - && &self.elements[n..n + other.len()] == other.elements.as_slice() + && *i == other[0] + && &self.elements[n..n + other.len()] == other { return Ok(vm.new_bool(true)); } @@ -189,15 +198,11 @@ impl PyByteInner { Ok(vm.new_bool(false)) } - pub fn contains_int(&self, int: &PyInt, vm: &VirtualMachine) -> PyResult { - if let Some(int) = int.as_bigint().to_u8() { - if self.elements.contains(&int) { - Ok(vm.new_bool(true)) - } else { - Ok(vm.new_bool(false)) - } + fn contains_int(&self, int: &PyInt, vm: &VirtualMachine) -> PyResult { + if self.elements.contains(&int.as_bigint().byte_or(vm)?) { + Ok(vm.new_bool(true)) } else { - Err(vm.new_value_error("byte must be in range(0, 256)".to_string())) + Ok(vm.new_bool(false)) } } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 0607327506..00a3987055 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -149,10 +149,7 @@ impl PyBytesRef { #[pymethod(name = "__contains__")] fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(needle, - bytes @ PyBytes => self.inner.contains_bytes(&bytes.inner, vm), - int @ PyInt => self.inner.contains_int(&int, vm), - obj => Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)))) + self.inner.contains(needle, vm) } #[pymethod(name = "__getitem__")] From 4a4d163626e774cf590806c70853a7e5d8b1319b Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 19:10:37 +0200 Subject: [PATCH 349/884] refactor getitem --- vm/src/obj/objbyteinner.rs | 18 ++++++++++++------ vm/src/obj/objbytes.rs | 8 ++------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 8ca0407769..7edfa7dde9 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -1,3 +1,4 @@ +use crate::obj::objslice::PySlice; use crate::pyobject::{PyIterable, PyObjectRef}; use num_bigint::BigInt; @@ -206,18 +207,23 @@ impl PyByteInner { } } - pub fn getitem_int(&self, int: &PyInt, vm: &VirtualMachine) -> PyResult { - if let Some(idx) = self.elements.get_pos(int.as_bigint().to_i32().unwrap()) { + pub fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match_class!(needle, + int @ PyInt => //self.inner.getitem_int(&int, vm), + { + if let Some(idx) = self.elements.get_pos(int.as_bigint().to_i32().unwrap()) { Ok(vm.new_int(self.elements[idx])) } else { Err(vm.new_index_error("index out of range".to_string())) } - } - - pub fn getitem_slice(&self, slice: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + }, + slice @ PySlice => //self.inner.getitem_slice(slice.as_object(), vm), + { Ok(vm .ctx - .new_bytes(self.elements.get_slice_items(vm, slice).unwrap())) + .new_bytes(self.elements.get_slice_items(vm, slice.as_object())?)) + }, + obj => Err(vm.new_type_error(format!("byte indices must be integers or slices, not {}", obj)))) } pub fn isalnum(&self, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 00a3987055..46bd346241 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,4 +1,3 @@ -use crate::obj::objint::PyInt; use crate::obj::objstr::PyString; use crate::vm::VirtualMachine; use core::cell::Cell; @@ -9,7 +8,7 @@ use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, Py use super::objbyteinner::{ByteInnerNewOptions, PyByteInner}; use super::objiter; -use super::objslice::PySlice; + use super::objtype::PyClassRef; /// "bytes(iterable_of_ints) -> bytes\n\ /// bytes(string, encoding[, errors]) -> bytes\n\ @@ -154,10 +153,7 @@ impl PyBytesRef { #[pymethod(name = "__getitem__")] fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(needle, - int @ PyInt => self.inner.getitem_int(&int, vm), - slice @ PySlice => self.inner.getitem_slice(slice.as_object(), vm), - obj => Err(vm.new_type_error(format!("byte indices must be integers or slices, not {}", obj)))) + self.inner.getitem(needle, vm) } #[pymethod(name = "isalnum")] From ba67f3c264ded2514eed37373ca118b1f773ed7d Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 19:24:10 +0200 Subject: [PATCH 350/884] refactore fromhex --- tests/snippets/bytes.py | 3 ++- vm/src/obj/objbyteinner.rs | 2 +- vm/src/obj/objbytes.rs | 13 +++---------- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 2e6c658be0..e3ddcd51d7 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -142,7 +142,8 @@ bytes.fromhex("6Z2") except ValueError as e: str(e) == "non-hexadecimal number found in fromhex() arg at position 1" - +with assertRaises(TypeError): + bytes.fromhex(b'hhjjk') # center assert [b"koki".center(i, b"|") for i in range(3, 10)] == [ b"koki", diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 7edfa7dde9..b3e51084a9 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -342,7 +342,7 @@ impl PyByteInner { Ok(vm.ctx.new_str(bla)) } - pub fn fromhex(string: String, vm: &VirtualMachine) -> Result, PyObjectRef> { + pub fn fromhex(string: &str, vm: &VirtualMachine) -> PyResult> { // first check for invalid character for (i, c) in string.char_indices() { if !c.is_digit(16) && !c.is_whitespace() { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 46bd346241..773675c58a 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,4 +1,4 @@ -use crate::obj::objstr::PyString; +use crate::obj::objstr::PyStringRef; use crate::vm::VirtualMachine; use core::cell::Cell; use std::ops::Deref; @@ -216,15 +216,8 @@ impl PyBytesRef { self.inner.hex(vm) } - // #[pymethod(name = "fromhex")] - fn fromhex(string: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(string, - s @ PyString => { - match PyByteInner::fromhex(s.to_string(), vm) { - Ok(x) => Ok(vm.ctx.new_bytes(x)), - Err(y) => Err(y)}}, - obj => Err(vm.new_type_error(format!("fromhex() argument must be str, not {}", obj ))) - ) + fn fromhex(string: PyStringRef, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(PyByteInner::fromhex(string.as_str(), vm)?)) } #[pymethod(name = "center")] From 8bcdbbf21b0c782274fe80c4e48f4c25aab726f3 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 19:52:17 +0200 Subject: [PATCH 351/884] use PyInt Ref, add ljust, rjust --- tests/snippets/bytes.py | 67 ++++++++++++++++++++++++++++++++++++-- vm/src/obj/objbyteinner.rs | 47 +++++++++++++++++++++----- vm/src/obj/objbytes.rs | 23 ++++++++++++- 3 files changed, 124 insertions(+), 13 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index e3ddcd51d7..ecb29612a0 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -10,7 +10,7 @@ with assertRaises(TypeError): bytes("bla") with assertRaises(TypeError): - bytes("bla", encoding = b"jilj") + bytes("bla", encoding=b"jilj") assert ( b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" @@ -143,7 +143,7 @@ except ValueError as e: str(e) == "non-hexadecimal number found in fromhex() arg at position 1" with assertRaises(TypeError): - bytes.fromhex(b'hhjjk') + bytes.fromhex(b"hhjjk") # center assert [b"koki".center(i, b"|") for i in range(3, 10)] == [ b"koki", @@ -170,9 +170,70 @@ b"b".center(2, "a") with assertRaises(TypeError): b"b".center(2, b"ba") -b"kok".center(5, bytearray(b"x")) +assert b"kok".center(5, bytearray(b"x")) == b"xkokx" b"kok".center(-5) + +# ljust +assert [b"koki".ljust(i, b"|") for i in range(3, 10)] == [ + b"koki", + b"koki", + b"koki|", + b"koki||", + b"koki|||", + b"koki||||", + b"koki|||||", +] +assert [b"kok".ljust(i, b"|") for i in range(2, 10)] == [ + b"kok", + b"kok", + b"kok|", + b"kok||", + b"kok|||", + b"kok||||", + b"kok|||||", + b"kok||||||", +] + +b"kok".ljust(4) == b"kok " # " test no arg" +with assertRaises(TypeError): + b"b".ljust(2, "a") +with assertRaises(TypeError): + b"b".ljust(2, b"ba") +assert b"kok".ljust(5, bytearray(b"x")) == b"kokxx" +assert b"kok".ljust(-5) == b"kok" + +# rjust +assert [b"koki".rjust(i, b"|") for i in range(3, 10)] == [ + b"koki", + b"koki", + b"|koki", + b"||koki", + b"|||koki", + b"||||koki", + b"|||||koki", +] +assert [b"kok".rjust(i, b"|") for i in range(2, 10)] == [ + b"kok", + b"kok", + b"|kok", + b"||kok", + b"|||kok", + b"||||kok", + b"|||||kok", + b"||||||kok", +] + + +b"kok".rjust(4) == b" kok" # " test no arg" +with assertRaises(TypeError): + b"b".rjust(2, "a") +with assertRaises(TypeError): + b"b".rjust(2, b"ba") +assert b"kok".rjust(5, bytearray(b"x")) == b"xxkok" +assert b"kok".rjust(-5) == b"kok" + + # count assert b"azeazerazeazopia".count(b"aze") == 3 assert b"azeazerazeazopia".count(b"az") == 4 diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index b3e51084a9..114742e8c9 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -1,3 +1,4 @@ +use crate::obj::objint::PyIntRef; use crate::obj::objslice::PySlice; use crate::pyobject::{PyIterable, PyObjectRef}; use num_bigint::BigInt; @@ -376,7 +377,7 @@ impl PyByteInner { fn get_center_args( &self, - width_a: PyObjectRef, + width: PyIntRef, fillbyte: OptionalArg, fn_name: String, vm: &VirtualMachine, @@ -404,13 +405,8 @@ impl PyByteInner { b' ' // default is space }; - let width_b = match_class!(width_a, - i @PyInt => i, - obj => {return Err(vm.new_type_error(format!("{} cannot be interpreted as an integer", obj)));} - ); - // <0 = no change - let width = if let Some(x) = width_b.as_bigint().to_usize() { + let width = if let Some(x) = width.as_bigint().to_usize() { if x <= self.len() { 0 } else { @@ -427,12 +423,12 @@ impl PyByteInner { pub fn center( &self, - width_a: PyObjectRef, + width: PyIntRef, fillbyte: OptionalArg, vm: &VirtualMachine, ) -> PyResult> { let fn_name = "center".to_string(); - let (fillbyte, diff) = self.get_center_args(width_a, fillbyte, fn_name, vm)?; + let (fillbyte, diff) = self.get_center_args(width, fillbyte, fn_name, vm)?; let mut ln: usize = diff / 2; let mut rn: usize = ln; @@ -453,6 +449,39 @@ impl PyByteInner { Ok(res) } + pub fn ljust( + &self, + width: PyIntRef, + fillbyte: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + let fn_name = "ljust".to_string(); + let (fillbyte, diff) = self.get_center_args(width, fillbyte, fn_name, vm)?; + + // merge all + let mut res = vec![]; + res.extend_from_slice(&self.elements[..]); + res.extend_from_slice(&vec![fillbyte; diff][..]); + + Ok(res) + } + + pub fn rjust( + &self, + width: PyIntRef, + fillbyte: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + let fn_name = "ljust".to_string(); + let (fillbyte, diff) = self.get_center_args(width, fillbyte, fn_name, vm)?; + + // merge all + let mut res = vec![fillbyte; diff]; + res.extend_from_slice(&self.elements[..]); + + Ok(res) + } + pub fn count( &self, sub: PyObjectRef, diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 773675c58a..f9d17c4e00 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,3 +1,4 @@ +use crate::obj::objint::PyIntRef; use crate::obj::objstr::PyStringRef; use crate::vm::VirtualMachine; use core::cell::Cell; @@ -223,13 +224,33 @@ impl PyBytesRef { #[pymethod(name = "center")] fn center( self, - width: PyObjectRef, + width: PyIntRef, fillbyte: OptionalArg, vm: &VirtualMachine, ) -> PyResult { Ok(vm.ctx.new_bytes(self.inner.center(width, fillbyte, vm)?)) } + #[pymethod(name = "ljust")] + fn ljust( + self, + width: PyIntRef, + fillbyte: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.ljust(width, fillbyte, vm)?)) + } + + #[pymethod(name = "rjust")] + fn rjust( + self, + width: PyIntRef, + fillbyte: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.rjust(width, fillbyte, vm)?)) + } + #[pymethod(name = "count")] fn count( self, From 1669d5280f20bfbf0043595edf1643812935105c Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 20:41:12 +0200 Subject: [PATCH 352/884] refactor count --- vm/src/obj/objbyteinner.rs | 83 +++++++++++++++++++++++--------------- vm/src/obj/objbytes.rs | 12 ++---- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 114742e8c9..32c2f4240e 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -1,6 +1,7 @@ use crate::obj::objint::PyIntRef; use crate::obj::objslice::PySlice; use crate::pyobject::{PyIterable, PyObjectRef}; +use core::ops::Range; use num_bigint::BigInt; use crate::function::OptionalArg; @@ -99,6 +100,53 @@ impl ByteInnerNewOptions { } } +#[derive(FromArgs)] +pub struct ByteInnerFindOptions { + #[pyarg(positional_only, optional = false)] + sub: PyObjectRef, + #[pyarg(positional_only, optional = true)] + start: OptionalArg, + #[pyarg(positional_only, optional = true)] + end: OptionalArg, +} + +impl ByteInnerFindOptions { + pub fn get_value( + self, + elements: &[u8], + vm: &VirtualMachine, + ) -> PyResult<(Vec, Range)> { + let sub = match try_as_bytes_like(&self.sub.clone()) { + Some(value) => value, + None => match_class!(self.sub, + i @ PyInt => vec![i.as_bigint().byte_or(vm)?], + obj => {return Err(vm.new_type_error(format!("argument should be integer or bytes-like object, not {}", obj)));}), + }; + let start = if let OptionalArg::Present(st) = self.start { + match_class!(st, + i @ PyInt => {Some(i.as_bigint().clone())}, + _obj @ PyNone => None, + _=> {return Err(vm.new_type_error("slice indices must be integers or None or have an __index__ method".to_string()));} + ) + } else { + None + }; + let end = if let OptionalArg::Present(e) = self.end { + match_class!(e, + i @ PyInt => {Some(i.as_bigint().clone())}, + _obj @ PyNone => None, + _=> {return Err(vm.new_type_error("slice indices must be integers or None or have an __index__ method".to_string()));} + ) + } else { + None + }; + + let range = elements.to_vec().get_slice_range(&start, &end); + + Ok((sub, range)) + } +} + impl PyByteInner { pub fn repr(&self) -> PyResult { let mut res = String::with_capacity(self.elements.len()); @@ -482,39 +530,8 @@ impl PyByteInner { Ok(res) } - pub fn count( - &self, - sub: PyObjectRef, - start: OptionalArg, - end: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - let sub = match try_as_bytes_like(&sub) { - Some(value) => value, - None => match_class!(sub, - i @ PyInt => vec![i.as_bigint().byte_or(vm)?], - obj => {return Err(vm.new_type_error(format!("argument should be integer or bytes-like object, not {}", obj)));}), - }; - let start = if let OptionalArg::Present(st) = start { - match_class!(st, - i @ PyInt => {Some(i.as_bigint().clone())}, - _obj @ PyNone => None, - _=> {return Err(vm.new_type_error("slice indices must be integers or None or have an __index__ method".to_string()));} - ) - } else { - None - }; - let end = if let OptionalArg::Present(e) = end { - match_class!(e, - i @ PyInt => {Some(i.as_bigint().clone())}, - _obj @ PyNone => None, - _=> {return Err(vm.new_type_error("slice indices must be integers or None or have an __index__ method".to_string()));} - ) - } else { - None - }; - - let range = self.elements.get_slice_range(&start, &end); + pub fn count(&self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + let (sub, range) = options.get_value(&self.elements, vm)?; if sub.is_empty() { return Ok(self.len() + 1); diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index f9d17c4e00..8e8393d129 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -7,7 +7,7 @@ use std::ops::Deref; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; -use super::objbyteinner::{ByteInnerNewOptions, PyByteInner}; +use super::objbyteinner::{ByteInnerFindOptions, ByteInnerNewOptions, PyByteInner}; use super::objiter; use super::objtype::PyClassRef; @@ -252,14 +252,8 @@ impl PyBytesRef { } #[pymethod(name = "count")] - fn count( - self, - sub: PyObjectRef, - start: OptionalArg, - end: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - self.inner.count(sub, start, end, vm) + fn count(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + self.inner.count(options, vm) } #[pymethod(name = "join")] From 56c5790745b892d731c2ebac75d4864e89f2aad3 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 21:00:33 +0200 Subject: [PATCH 353/884] reformat center, ljsut, rjust args --- tests/snippets/bytes.py | 25 ++++---- vm/src/obj/objbyteinner.rs | 119 +++++++++++++++++++------------------ vm/src/obj/objbytes.rs | 32 +++------- 3 files changed, 84 insertions(+), 92 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index ecb29612a0..0a3fb11abc 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -170,6 +170,8 @@ b"b".center(2, "a") with assertRaises(TypeError): b"b".center(2, b"ba") +with assertRaises(TypeError): + b"b".center(b"ba") assert b"kok".center(5, bytearray(b"x")) == b"xkokx" b"kok".center(-5) @@ -200,6 +202,8 @@ b"b".ljust(2, "a") with assertRaises(TypeError): b"b".ljust(2, b"ba") +with assertRaises(TypeError): + b"b".ljust(b"ba") assert b"kok".ljust(5, bytearray(b"x")) == b"kokxx" assert b"kok".ljust(-5) == b"kok" @@ -213,16 +217,15 @@ b"||||koki", b"|||||koki", ] -assert [b"kok".rjust(i, b"|") for i in range(2, 10)] == [ - b"kok", - b"kok", - b"|kok", - b"||kok", - b"|||kok", - b"||||kok", - b"|||||kok", - b"||||||kok", -] +assert [b"kok".rjust(i, b"|") for i in range(2, 10)] == [b'kok', + b'kok', + b'|kok', + b'||kok', + b'|||kok', + b'||||kok', + b'|||||kok', + b'||||||kok'] + b"kok".rjust(4) == b" kok" # " test no arg" @@ -230,6 +233,8 @@ b"b".rjust(2, "a") with assertRaises(TypeError): b"b".rjust(2, b"ba") +with assertRaises(TypeError): + b"b".rjust(b"ba") assert b"kok".rjust(5, bytearray(b"x")) == b"xxkok" assert b"kok".rjust(-5) == b"kok" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 32c2f4240e..b6e9ae49e1 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -116,7 +116,7 @@ impl ByteInnerFindOptions { elements: &[u8], vm: &VirtualMachine, ) -> PyResult<(Vec, Range)> { - let sub = match try_as_bytes_like(&self.sub.clone()) { + let sub = match try_as_bytes_like(&self.sub) { Some(value) => value, None => match_class!(self.sub, i @ PyInt => vec![i.as_bigint().byte_or(vm)?], @@ -147,6 +147,56 @@ impl ByteInnerFindOptions { } } +#[derive(FromArgs)] +pub struct ByteInnerPaddingOptions { + #[pyarg(positional_only, optional = false)] + width: PyIntRef, + #[pyarg(positional_only, optional = true)] + fillbyte: OptionalArg, +} + +impl ByteInnerPaddingOptions { + fn get_value(&self, fn_name: &str, len: usize, vm: &VirtualMachine) -> PyResult<(u8, usize)> { + let fillbyte = if let OptionalArg::Present(v) = &self.fillbyte { + match try_as_byte(&v) { + Some(x) => { + if x.len() == 1 { + x[0] + } else { + return Err(vm.new_type_error(format!( + "{}() argument 2 must be a byte string of length 1, not {}", + fn_name, &v + ))); + } + } + None => { + return Err(vm.new_type_error(format!( + "{}() argument 2 must be a byte string of length 1, not {}", + fn_name, &v + ))); + } + } + } else { + b' ' // default is space + }; + + // <0 = no change + let width = if let Some(x) = self.width.as_bigint().to_usize() { + if x <= len { + 0 + } else { + x + } + } else { + 0 + }; + + let diff: usize = if width != 0 { width - len } else { 0 }; + + Ok((fillbyte, diff)) + } +} + impl PyByteInner { pub fn repr(&self) -> PyResult { let mut res = String::with_capacity(self.elements.len()); @@ -423,60 +473,13 @@ impl PyByteInner { .collect::>()) } - fn get_center_args( - &self, - width: PyIntRef, - fillbyte: OptionalArg, - fn_name: String, - vm: &VirtualMachine, - ) -> PyResult<(u8, usize)> { - let fillbyte = if let OptionalArg::Present(v) = fillbyte { - match try_as_byte(&v) { - Some(x) => { - if x.len() == 1 { - x[0] - } else { - return Err(vm.new_type_error(format!( - "{}() argument 2 must be a byte string of length 1, not {}", - &fn_name, &v - ))); - } - } - None => { - return Err(vm.new_type_error(format!( - "{}() argument 2 must be a byte string of length 1, not {}", - &fn_name, &v - ))); - } - } - } else { - b' ' // default is space - }; - - // <0 = no change - let width = if let Some(x) = width.as_bigint().to_usize() { - if x <= self.len() { - 0 - } else { - x - } - } else { - 0 - }; - - let diff: usize = if width != 0 { width - self.len() } else { 0 }; - - Ok((fillbyte, diff)) - } - pub fn center( &self, - width: PyIntRef, - fillbyte: OptionalArg, + options: ByteInnerPaddingOptions, vm: &VirtualMachine, ) -> PyResult> { - let fn_name = "center".to_string(); - let (fillbyte, diff) = self.get_center_args(width, fillbyte, fn_name, vm)?; + // let fn_name = "center".to_string(); + let (fillbyte, diff) = options.get_value("center", self.len(), vm)?; let mut ln: usize = diff / 2; let mut rn: usize = ln; @@ -499,12 +502,11 @@ impl PyByteInner { pub fn ljust( &self, - width: PyIntRef, - fillbyte: OptionalArg, + options: ByteInnerPaddingOptions, vm: &VirtualMachine, ) -> PyResult> { - let fn_name = "ljust".to_string(); - let (fillbyte, diff) = self.get_center_args(width, fillbyte, fn_name, vm)?; + // let fn_name = "ljust".to_string(); + let (fillbyte, diff) = options.get_value("ljust", self.len(), vm)?; // merge all let mut res = vec![]; @@ -516,12 +518,11 @@ impl PyByteInner { pub fn rjust( &self, - width: PyIntRef, - fillbyte: OptionalArg, + options: ByteInnerPaddingOptions, vm: &VirtualMachine, ) -> PyResult> { - let fn_name = "ljust".to_string(); - let (fillbyte, diff) = self.get_center_args(width, fillbyte, fn_name, vm)?; + // let fn_name = "rjust".to_string(); + let (fillbyte, diff) = options.get_value("rjust", self.len(), vm)?; // merge all let mut res = vec![fillbyte; diff]; diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 8e8393d129..3c1b323c8a 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,4 +1,3 @@ -use crate::obj::objint::PyIntRef; use crate::obj::objstr::PyStringRef; use crate::vm::VirtualMachine; use core::cell::Cell; @@ -7,7 +6,9 @@ use std::ops::Deref; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; -use super::objbyteinner::{ByteInnerFindOptions, ByteInnerNewOptions, PyByteInner}; +use super::objbyteinner::{ + ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, PyByteInner, +}; use super::objiter; use super::objtype::PyClassRef; @@ -222,33 +223,18 @@ impl PyBytesRef { } #[pymethod(name = "center")] - fn center( - self, - width: PyIntRef, - fillbyte: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - Ok(vm.ctx.new_bytes(self.inner.center(width, fillbyte, vm)?)) + fn center(self, options: ByteInnerPaddingOptions, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.center(options, vm)?)) } #[pymethod(name = "ljust")] - fn ljust( - self, - width: PyIntRef, - fillbyte: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - Ok(vm.ctx.new_bytes(self.inner.ljust(width, fillbyte, vm)?)) + fn ljust(self, options: ByteInnerPaddingOptions, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.ljust(options, vm)?)) } #[pymethod(name = "rjust")] - fn rjust( - self, - width: PyIntRef, - fillbyte: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - Ok(vm.ctx.new_bytes(self.inner.rjust(width, fillbyte, vm)?)) + fn rjust(self, options: ByteInnerPaddingOptions, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.rjust(options, vm)?)) } #[pymethod(name = "count")] From 03997273ed630b97806ce804b3363d80cdcdb6d7 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 21:12:45 +0200 Subject: [PATCH 354/884] refactor index/find with ByteInnerOptions --- vm/src/obj/objbyteinner.rs | 21 ++------------------- vm/src/obj/objbytes.rs | 20 ++++---------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index b6e9ae49e1..6d5d491484 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -628,25 +628,8 @@ impl PyByteInner { Ok(vm.new_bool(suff.as_slice() == &self.elements.do_slice(range)[offset])) } - pub fn find( - &self, - sub: PyObjectRef, - start: OptionalArg, - end: OptionalArg, - vm: &VirtualMachine, - ) -> Result { - let sub = match try_as_bytes_like(&sub) { - Some(value) => value, - None => match_class!(sub, - i @ PyInt => vec![i.as_bigint().byte_or(vm)?], - obj => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)));}), - }; - - let range = self.elements.get_slice_range( - &is_valid_slice_arg(start, vm)?, - &is_valid_slice_arg(end, vm)?, - ); - + pub fn find(&self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + let (sub, range) = options.get_value(&self.elements, vm)?; // not allowed for this method if range.end < range.start { return Ok(-1isize); diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 3c1b323c8a..8ecc9dac14 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -270,25 +270,13 @@ impl PyBytesRef { } #[pymethod(name = "find")] - fn find( - self, - sub: PyObjectRef, - start: OptionalArg, - end: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - Ok(vm.new_int(self.inner.find(sub, start, end, vm)?)) + fn find(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + self.inner.find(options, vm) } #[pymethod(name = "index")] - fn index( - self, - sub: PyObjectRef, - start: OptionalArg, - end: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - let res = self.inner.find(sub, start, end, vm)?; + fn index(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + let res = self.inner.find(options, vm)?; if res == -1 { return Err(vm.new_value_error("substring not found".to_string())); } From a42bfae84d07f5743480af26ab8814d486513b5d Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 21:41:46 +0200 Subject: [PATCH 355/884] kwargs for translate --- tests/snippets/bytes.py | 1 + vm/src/obj/objbyteinner.rs | 81 ++++++++++++++++++++++---------------- vm/src/obj/objbytes.rs | 12 ++---- 3 files changed, 51 insertions(+), 43 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 0a3fb11abc..99709e7683 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -324,3 +324,4 @@ ) assert b"hjhtuyjyujuyj".translate(bytes.maketrans(b"hj", b"ab")) == b"abatuybyubuyb" assert b"hjhtuyfjtyhuhjuyj".translate(None, b"ht") == b"juyfjyujuyj" +assert b"hjhtuyfjtyhuhjuyj".translate(None, delete = b"ht") == b"juyfjyujuyj" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 6d5d491484..19a40f0a14 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -154,9 +154,8 @@ pub struct ByteInnerPaddingOptions { #[pyarg(positional_only, optional = true)] fillbyte: OptionalArg, } - impl ByteInnerPaddingOptions { - fn get_value(&self, fn_name: &str, len: usize, vm: &VirtualMachine) -> PyResult<(u8, usize)> { + fn get_value(self, fn_name: &str, len: usize, vm: &VirtualMachine) -> PyResult<(u8, usize)> { let fillbyte = if let OptionalArg::Present(v) = &self.fillbyte { match try_as_byte(&v) { Some(x) => { @@ -197,6 +196,49 @@ impl ByteInnerPaddingOptions { } } +#[derive(FromArgs)] +pub struct ByteInnerTranslateOptions { + #[pyarg(positional_only, optional = false)] + table: PyObjectRef, + #[pyarg(positional_or_keyword, optional = true)] + delete: OptionalArg, +} + +impl ByteInnerTranslateOptions { + pub fn get_value(self, vm: &VirtualMachine) -> PyResult<(Vec, Vec)> { + let table = match try_as_bytes_like(&self.table) { + Some(value) => value, + None => match_class!(self.table, + + _n @ PyNone => (0..=255).collect::>(), + obj => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)));}, + ), + }; + + if table.len() != 256 { + return Err( + vm.new_value_error("translation table must be 256 characters long".to_string()) + ); + } + + let delete = if let OptionalArg::Present(value) = &self.delete { + match try_as_bytes_like(&value) { + Some(value) => value, + None => { + return Err(vm.new_type_error(format!( + "a bytes-like object is required, not {}", + value + ))); + } + } + } else { + vec![] + }; + + Ok((table, delete)) + } +} + impl PyByteInner { pub fn repr(&self) -> PyResult { let mut res = String::with_capacity(self.elements.len()); @@ -677,40 +719,9 @@ impl PyByteInner { Ok(vm.ctx.new_bytes(res)) } - pub fn translate( - &self, - table: PyObjectRef, - delete: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - let table = match try_as_bytes_like(&table) { - Some(value) => value, - None => match_class!(table, - _n @ PyNone => (0..=255).collect::>(), - obj => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)));}, - ), - }; - - if table.len() != 256 { - return Err( - vm.new_value_error("translation table must be 256 characters long".to_string()) - ); - } - - let delete = if let OptionalArg::Present(value) = delete { - match try_as_bytes_like(&value) { - Some(value) => value, - None => { - return Err(vm.new_type_error(format!( - "a bytes-like object is required, not {}", - value - ))); - } - } - } else { - vec![] - }; + pub fn translate(&self, options: ByteInnerTranslateOptions, vm: &VirtualMachine) -> PyResult { + let (table, delete) = options.get_value(vm)?; let mut res = vec![]; diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 8ecc9dac14..c3627c1d76 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -7,7 +7,8 @@ use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; use super::objbyteinner::{ - ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, PyByteInner, + ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerTranslateOptions, + PyByteInner, }; use super::objiter; @@ -284,13 +285,8 @@ impl PyBytesRef { } #[pymethod(name = "translate")] - fn translate( - self, - table: PyObjectRef, - delete: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - self.inner.translate(table, delete, vm) + fn translate(self, options: ByteInnerTranslateOptions, vm: &VirtualMachine) -> PyResult { + self.inner.translate(options, vm) } } From 8a2ce9e260a3949584c7d329f44f6ada322b4982 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 21:58:15 +0200 Subject: [PATCH 356/884] add swapcase --- tests/snippets/bytes.py | 4 +++- vm/src/obj/objbyteinner.rs | 12 ++++++++++++ vm/src/obj/objbytes.rs | 5 +++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 99709e7683..3b3dcb4571 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -121,7 +121,7 @@ assert bytes(b"Is Title Case").istitle() assert not bytes(b"is Not title casE").istitle() -# upper lower, capitalize +# upper lower, capitalize, swapcase l = bytes(b"lower") b = bytes(b"UPPER") assert l.lower().islower() @@ -129,6 +129,8 @@ assert l.capitalize() == b"Lower" assert b.capitalize() == b"Upper" assert bytes().capitalize() == bytes() +assert b"AaBbCc123'@/".swapcase().swapcase() == b"AaBbCc123'@/" +assert b"AaBbCc123'@/".swapcase() == b"aAbBcC123'@/" # hex from hex assert bytes([0, 1, 9, 23, 90, 234]).hex() == "000109175aea" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 19a40f0a14..9b4ec8beaf 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -474,6 +474,18 @@ impl PyByteInner { new } + pub fn swapcase(&self, _vm: &VirtualMachine) -> Vec { + let mut new: Vec = Vec::new(); + for w in &self.elements { + match w { + 65..=90 => new.push(w.to_ascii_lowercase()), + 97..=122 => new.push(w.to_ascii_uppercase()), + x => new.push(*x), + } + } + new + } + pub fn hex(&self, vm: &VirtualMachine) -> PyResult { let bla = self .elements diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index c3627c1d76..964675f57a 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -214,6 +214,11 @@ impl PyBytesRef { Ok(vm.ctx.new_bytes(self.inner.capitalize(vm))) } + #[pymethod(name = "swapcase")] + fn swapcase(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.swapcase(vm))) + } + #[pymethod(name = "hex")] fn hex(self, vm: &VirtualMachine) -> PyResult { self.inner.hex(vm) From 9ea823abf148fd8c8ccf6d77fb9236c536bde83d Mon Sep 17 00:00:00 2001 From: jgirardet Date: Thu, 18 Apr 2019 23:06:11 +0200 Subject: [PATCH 357/884] add rfind, rindex --- tests/snippets/bytes.py | 8 ++++++++ vm/src/obj/objbyteinner.rs | 27 +++++++++++++++++++++------ vm/src/obj/objbytes.rs | 18 ++++++++++++++++-- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 3b3dcb4571..380a193863 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -310,6 +310,14 @@ assert b"abcd".find(1) == -1 assert b"abcd".find(99) == 2 +assert b"abcdabcda".find(b"a") == 0 +assert b"abcdabcda".rfind(b"a") == 8 +assert b"abcdabcda".rfind(b"a", 2, 6) == 4 +assert b"abcdabcda".rfind(b"a", None, 6) == 4 +assert b"abcdabcda".rfind(b"a", 2, None) == 8 +assert b"abcdabcda".index(b"a") == 0 +assert b"abcdabcda".rindex(b"a") == 8 + # make trans # fmt: off diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 9b4ec8beaf..050553ea8f 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -682,7 +682,12 @@ impl PyByteInner { Ok(vm.new_bool(suff.as_slice() == &self.elements.do_slice(range)[offset])) } - pub fn find(&self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + pub fn find( + &self, + options: ByteInnerFindOptions, + reverse: bool, + vm: &VirtualMachine, + ) -> PyResult { let (sub, range) = options.get_value(&self.elements, vm)?; // not allowed for this method if range.end < range.start { @@ -690,13 +695,23 @@ impl PyByteInner { } let start = range.start; + let end = range.end; - let slice = &self.elements[range]; - for (n, _) in slice.iter().enumerate() { - if n + sub.len() <= slice.len() && &slice[n..n + sub.len()] == sub.as_slice() { - return Ok((start + n) as isize); + if reverse { + let slice = self.elements.do_slice_reverse(range); + for (n, _) in slice.iter().enumerate() { + if n + sub.len() <= slice.len() && &slice[n..n + sub.len()] == sub.as_slice() { + return Ok((end - n - 1) as isize); + } } - } + } else { + let slice = self.elements.do_slice(range); + for (n, _) in slice.iter().enumerate() { + if n + sub.len() <= slice.len() && &slice[n..n + sub.len()] == sub.as_slice() { + return Ok((start + n) as isize); + } + } + }; Ok(-1isize) } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 964675f57a..ba23751f7e 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -277,12 +277,26 @@ impl PyBytesRef { #[pymethod(name = "find")] fn find(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { - self.inner.find(options, vm) + self.inner.find(options, false, vm) } #[pymethod(name = "index")] fn index(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { - let res = self.inner.find(options, vm)?; + let res = self.inner.find(options, false, vm)?; + if res == -1 { + return Err(vm.new_value_error("substring not found".to_string())); + } + Ok(vm.new_int(res)) + } + + #[pymethod(name = "rfind")] + fn rfind(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + self.inner.find(options, true, vm) + } + + #[pymethod(name = "rindex")] + fn rindex(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + let res = self.inner.find(options, true, vm)?; if res == -1 { return Err(vm.new_value_error("substring not found".to_string())); } From 61ee15b97b6999484f77da677f517f5114870eb9 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Fri, 19 Apr 2019 00:04:39 +0200 Subject: [PATCH 358/884] add strip lstrip rstrip --- tests/snippets/bytes.py | 30 +++++++++++++++-------- vm/src/obj/objbyteinner.rs | 49 ++++++++++++++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 25 +++++++++++++++++-- 3 files changed, 92 insertions(+), 12 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 380a193863..2733533d8f 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -219,15 +219,16 @@ b"||||koki", b"|||||koki", ] -assert [b"kok".rjust(i, b"|") for i in range(2, 10)] == [b'kok', - b'kok', - b'|kok', - b'||kok', - b'|||kok', - b'||||kok', - b'|||||kok', - b'||||||kok'] - +assert [b"kok".rjust(i, b"|") for i in range(2, 10)] == [ + b"kok", + b"kok", + b"|kok", + b"||kok", + b"|||kok", + b"||||kok", + b"|||||kok", + b"||||||kok", +] b"kok".rjust(4) == b" kok" # " test no arg" @@ -334,4 +335,13 @@ ) assert b"hjhtuyjyujuyj".translate(bytes.maketrans(b"hj", b"ab")) == b"abatuybyubuyb" assert b"hjhtuyfjtyhuhjuyj".translate(None, b"ht") == b"juyfjyujuyj" -assert b"hjhtuyfjtyhuhjuyj".translate(None, delete = b"ht") == b"juyfjyujuyj" +assert b"hjhtuyfjtyhuhjuyj".translate(None, delete=b"ht") == b"juyfjyujuyj" + + +# strip lstrip rstrip +assert b" spacious ".strip() == b"spacious" +assert b"www.example.com".strip(b"cmowz.") == b"example" +assert b" spacious ".lstrip() == b"spacious " +assert b"www.example.com".lstrip(b"cmowz.") == b"example.com" +assert b" spacious ".rstrip() == b" spacious" +assert b"mississippi".rstrip(b"ipz") == b"mississ" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 050553ea8f..46f55ec8db 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -760,6 +760,49 @@ impl PyByteInner { Ok(vm.ctx.new_bytes(res)) } + + pub fn strip( + &self, + chars: OptionalArg, + position: ByteInnerPosition, + vm: &VirtualMachine, + ) -> PyResult> { + let chars = if let OptionalArg::Present(content) = chars { + match try_as_bytes_like(&content) { + Some(value) => value, + None => { + return Err(vm.new_type_error(format!( + "a bytes-like object is required, not {}", + content + ))); + } + } + } else { + vec![b' '] + }; + + let mut start = 0; + let mut end = self.len(); + + if let ByteInnerPosition::Left | ByteInnerPosition::All = position { + for (n, i) in self.elements.iter().enumerate() { + if !chars.contains(i) { + start = n; + break; + } + } + } + + if let ByteInnerPosition::Right | ByteInnerPosition::All = position { + for (n, i) in self.elements.iter().rev().enumerate() { + if !chars.contains(i) { + end = self.len() - n; + break; + } + } + } + Ok(self.elements[start..end].to_vec()) + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { @@ -789,3 +832,9 @@ pub trait ByteOr: ToPrimitive { } impl ByteOr for BigInt {} + +pub enum ByteInnerPosition { + Left, + Right, + All, +} diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index ba23751f7e..bd483be18a 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -7,8 +7,8 @@ use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; use super::objbyteinner::{ - ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerTranslateOptions, - PyByteInner, + ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerPosition, + ByteInnerTranslateOptions, PyByteInner, }; use super::objiter; @@ -307,6 +307,27 @@ impl PyBytesRef { fn translate(self, options: ByteInnerTranslateOptions, vm: &VirtualMachine) -> PyResult { self.inner.translate(options, vm) } + + #[pymethod(name = "strip")] + fn strip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { + Ok(vm + .ctx + .new_bytes(self.inner.strip(chars, ByteInnerPosition::All, vm)?)) + } + + #[pymethod(name = "lstrip")] + fn lstrip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { + Ok(vm + .ctx + .new_bytes(self.inner.strip(chars, ByteInnerPosition::Left, vm)?)) + } + + #[pymethod(name = "rstrip")] + fn rstrip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { + Ok(vm + .ctx + .new_bytes(self.inner.strip(chars, ByteInnerPosition::Right, vm)?)) + } } #[derive(Debug)] From a75e7635d5b39e17d8d3158a04bbcc9807a0eded Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 12:09:11 +0300 Subject: [PATCH 359/884] Add os.sep --- Lib/os.py | 5 +++++ tests/snippets/stdlib_os.py | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/Lib/os.py b/Lib/os.py index 8c19a89c64..3cadb5f616 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -1,5 +1,10 @@ from _os import * +if name == 'nt': + sep = '\\' +else: + sep = '/' + # Change environ to automatically call putenv(), unsetenv if they exist. from _collections_abc import MutableMapping diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 7b9981bdf1..6552596d76 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -58,3 +58,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): os.putenv(ENV_KEY, ENV_VALUE) os.unsetenv(ENV_KEY) assert os.getenv(ENV_KEY) == None + +if os.name == "nt": + assert os.sep == "\\" +else: + assert os.sep == "/" From abb9cde62c5e416954368ae1844158faf0c6d393 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 12:23:45 +0300 Subject: [PATCH 360/884] Use temp dir for os test --- tests/snippets/stdlib_os.py | 64 +++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 6552596d76..476627b386 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -7,33 +7,6 @@ os.close(fd) assert_raises(OSError, lambda: os.read(fd, 10)) - -FNAME = "test_file_that_no_one_will_have_on_disk" -CONTENT = b"testing" -CONTENT2 = b"rustpython" -CONTENT3 = b"BOYA" - -class TestWithFile(): - def __enter__(self): - open(FNAME, "wb") - return FNAME - - def __exit__(self, exc_type, exc_val, exc_tb): - os.remove(FNAME) - - -with TestWithFile() as fname: - fd = os.open(fname, 1) - assert os.write(fd, CONTENT2) == len(CONTENT2) - assert os.write(fd, CONTENT3) == len(CONTENT3) - os.close(fd) - - fd = os.open(fname, 0) - assert os.read(fd, len(CONTENT2)) == CONTENT2 - assert os.read(fd, len(CONTENT3)) == CONTENT3 - os.close(fd) - - assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', 0)) @@ -59,7 +32,44 @@ def __exit__(self, exc_type, exc_val, exc_tb): os.unsetenv(ENV_KEY) assert os.getenv(ENV_KEY) == None + if os.name == "nt": assert os.sep == "\\" else: assert os.sep == "/" + +class TestWithTempDir(): + def __enter__(self): + if os.name == "nt": + base_folder = os.environ["%TEMP%"] + else: + base_folder = "/tmp" + name = base_folder + os.sep + "test_os" + os.mkdir(name) + self.name = name + return name + + def __exit__(self, exc_type, exc_val, exc_tb): + for f in os.listdir(self.name): + # Currently don't support dir delete. + os.remove(self.name + os.sep + f) + os.rmdir(self.name) + + +FILE_NAME = "test1" +CONTENT = b"testing" +CONTENT2 = b"rustpython" +CONTENT3 = b"BOYA" + +with TestWithTempDir() as tmpdir: + fname = tmpdir + os.sep + FILE_NAME + open(fname, "wb") + fd = os.open(fname, 1) + assert os.write(fd, CONTENT2) == len(CONTENT2) + assert os.write(fd, CONTENT3) == len(CONTENT3) + os.close(fd) + + fd = os.open(fname, 0) + assert os.read(fd, len(CONTENT2)) == CONTENT2 + assert os.read(fd, len(CONTENT3)) == CONTENT3 + os.close(fd) From 00e46b7d723e7988fbdd20eeecdb98762f26b098 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 13:50:34 +0300 Subject: [PATCH 361/884] Try to fix windows test --- tests/snippets/stdlib_os.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 476627b386..e812353a5a 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -41,7 +41,7 @@ class TestWithTempDir(): def __enter__(self): if os.name == "nt": - base_folder = os.environ["%TEMP%"] + base_folder = os.environ["TEMP"] else: base_folder = "/tmp" name = base_folder + os.sep + "test_os" From 08f2a5705bf59d413647bfbd1b50fbbf5f03dc9b Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 14:40:01 +0300 Subject: [PATCH 362/884] Add prints for windows debug --- tests/snippets/stdlib_os.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index e812353a5a..f7c47a95c0 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -51,8 +51,10 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): for f in os.listdir(self.name): + print("Remove file", self.name + os.sep + f) # Currently don't support dir delete. os.remove(self.name + os.sep + f) + print("Remove dir", self.name) os.rmdir(self.name) From cf0a9207438c54948988830ef4a96a6ab05bfff3 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 15:50:06 +0300 Subject: [PATCH 363/884] Add socket.fileno for unix --- tests/snippets/stdlib_socket.py | 13 +++++++++++++ vm/src/stdlib/socket.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/tests/snippets/stdlib_socket.py b/tests/snippets/stdlib_socket.py index 79efaa3639..d7afe1bd35 100644 --- a/tests/snippets/stdlib_socket.py +++ b/tests/snippets/stdlib_socket.py @@ -1,4 +1,5 @@ import socket +import os from testutils import assertRaises MESSAGE_A = b'aaaa' @@ -21,6 +22,18 @@ recv_b = connector.recv(len(MESSAGE_B)) assert recv_a == MESSAGE_A assert recv_b == MESSAGE_B + +# fileno +if os.name == "posix": + connector_fd = connector.fileno() + connection_fd = connection.fileno() + os.write(connector_fd, MESSAGE_A) + connection.send(MESSAGE_B) + recv_a = connection.recv(len(MESSAGE_A)) + recv_b = os.read(connector_fd, (len(MESSAGE_B))) + assert recv_a == MESSAGE_A + assert recv_b == MESSAGE_B + connection.close() connector.close() listener.close() diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index e893e0dab7..a7a87d554c 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -111,6 +111,19 @@ impl Write for Connection { } } +#[cfg(unix)] +use std::os::unix::io::{AsRawFd, RawFd}; +#[cfg(unix)] +impl AsRawFd for Connection { + fn as_raw_fd(&self) -> RawFd { + match self { + Connection::TcpListener(con) => con.as_raw_fd(), + Connection::UdpSocket(con) => con.as_raw_fd(), + Connection::TcpStream(con) => con.as_raw_fd(), + } + } +} + #[derive(Debug)] pub struct Socket { address_family: AddressFamily, @@ -387,6 +400,25 @@ fn socket_close(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } +#[cfg(unix)] +fn socket_fileno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + use std::os::unix::io::AsRawFd; + arg_check!(vm, args, required = [(zelf, None)]); + + let socket = get_socket(zelf); + + let fileno = match socket.con.borrow_mut().as_mut() { + Some(v) => v.as_raw_fd(), + None => return Err(vm.new_type_error("".to_string())), + }; + Ok(vm.ctx.new_int(i64::from(fileno))) +} + +#[cfg(all(not(unix)))] +fn socket_fileno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + unimplemented!(); +} + fn socket_getsockname(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, None)]); let socket = get_socket(zelf); @@ -424,6 +456,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "getsockname" => ctx.new_rustfunc(socket_getsockname), "sendto" => ctx.new_rustfunc(socket_sendto), "recvfrom" => ctx.new_rustfunc(socket_recvfrom), + "fileno" => ctx.new_rustfunc(socket_fileno), }); py_module!(vm, "socket", { From e7b059ed9c7c4b961dfce3614a0a135f72305f5a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 16:00:00 +0300 Subject: [PATCH 364/884] Add socket.fileno for windows --- tests/snippets/stdlib_socket.py | 17 ++++++++--------- vm/src/stdlib/socket.rs | 29 ++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/tests/snippets/stdlib_socket.py b/tests/snippets/stdlib_socket.py index d7afe1bd35..16c2f96ce1 100644 --- a/tests/snippets/stdlib_socket.py +++ b/tests/snippets/stdlib_socket.py @@ -24,15 +24,14 @@ assert recv_b == MESSAGE_B # fileno -if os.name == "posix": - connector_fd = connector.fileno() - connection_fd = connection.fileno() - os.write(connector_fd, MESSAGE_A) - connection.send(MESSAGE_B) - recv_a = connection.recv(len(MESSAGE_A)) - recv_b = os.read(connector_fd, (len(MESSAGE_B))) - assert recv_a == MESSAGE_A - assert recv_b == MESSAGE_B +connector_fd = connector.fileno() +connection_fd = connection.fileno() +os.write(connector_fd, MESSAGE_A) +connection.send(MESSAGE_B) +recv_a = connection.recv(len(MESSAGE_A)) +recv_b = os.read(connector_fd, (len(MESSAGE_B))) +assert recv_a == MESSAGE_A +assert recv_b == MESSAGE_B connection.close() connector.close() diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index a7a87d554c..6e65ddfd9b 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -124,6 +124,19 @@ impl AsRawFd for Connection { } } +#[cfg(windows)] +use std::os::windows::io::{AsRawSocket, RawSocket}; +#[cfg(windows)] +impl AsRawSocket for Connection { + fn as_raw_socket(&self) -> RawSocket { + match self { + Connection::TcpListener(con) => con.as_raw_socket(), + Connection::UdpSocket(con) => con.as_raw_socket(), + Connection::TcpStream(con) => con.as_raw_socket(), + } + } +} + #[derive(Debug)] pub struct Socket { address_family: AddressFamily, @@ -414,7 +427,21 @@ fn socket_fileno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_int(i64::from(fileno))) } -#[cfg(all(not(unix)))] +#[cfg(windows)] +fn socket_fileno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + use std::os::windows::io::AsRawSocket; + arg_check!(vm, args, required = [(zelf, None)]); + + let socket = get_socket(zelf); + + let fileno = match socket.con.borrow_mut().as_mut() { + Some(v) => v.as_raw_socket(), + None => return Err(vm.new_type_error("".to_string())), + }; + Ok(vm.ctx.new_int(i64::from(fileno))) +} + +#[cfg(all(not(unix), not(windows)))] fn socket_fileno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { unimplemented!(); } From 2bc72dae9ff03f4afdf357d31e933d04f2db5ae3 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 16:10:55 +0300 Subject: [PATCH 365/884] Move fileno logic to Connection impl --- vm/src/stdlib/socket.rs | 78 +++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 6e65ddfd9b..9d7a703e1f 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -86,6 +86,33 @@ impl Connection { _ => Err(io::Error::new(io::ErrorKind::Other, "oh no!")), } } + + #[cfg(unix)] + fn fileno(&self) -> i64 { + use std::os::unix::io::AsRawFd; + let raw_fd = match self { + Connection::TcpListener(con) => con.as_raw_fd(), + Connection::UdpSocket(con) => con.as_raw_fd(), + Connection::TcpStream(con) => con.as_raw_fd(), + }; + raw_fd as i64 + } + + #[cfg(windows)] + fn fileno(&self) -> i64 { + use std::os::windows::io::AsRawSocket; + let raw_fd = match self { + Connection::TcpListener(con) => con.as_raw_socket(), + Connection::UdpSocket(con) => con.as_raw_socket(), + Connection::TcpStream(con) => con.as_raw_socket(), + }; + raw_fd as i64 + } + + #[cfg(all(not(unix), not(windows)))] + fn fileno(&self) -> i64 { + unimplemented!(); + } } impl Read for Connection { @@ -111,32 +138,6 @@ impl Write for Connection { } } -#[cfg(unix)] -use std::os::unix::io::{AsRawFd, RawFd}; -#[cfg(unix)] -impl AsRawFd for Connection { - fn as_raw_fd(&self) -> RawFd { - match self { - Connection::TcpListener(con) => con.as_raw_fd(), - Connection::UdpSocket(con) => con.as_raw_fd(), - Connection::TcpStream(con) => con.as_raw_fd(), - } - } -} - -#[cfg(windows)] -use std::os::windows::io::{AsRawSocket, RawSocket}; -#[cfg(windows)] -impl AsRawSocket for Connection { - fn as_raw_socket(&self) -> RawSocket { - match self { - Connection::TcpListener(con) => con.as_raw_socket(), - Connection::UdpSocket(con) => con.as_raw_socket(), - Connection::TcpStream(con) => con.as_raw_socket(), - } - } -} - #[derive(Debug)] pub struct Socket { address_family: AddressFamily, @@ -413,37 +414,16 @@ fn socket_close(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } -#[cfg(unix)] -fn socket_fileno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - use std::os::unix::io::AsRawFd; - arg_check!(vm, args, required = [(zelf, None)]); - - let socket = get_socket(zelf); - - let fileno = match socket.con.borrow_mut().as_mut() { - Some(v) => v.as_raw_fd(), - None => return Err(vm.new_type_error("".to_string())), - }; - Ok(vm.ctx.new_int(i64::from(fileno))) -} - -#[cfg(windows)] fn socket_fileno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - use std::os::windows::io::AsRawSocket; arg_check!(vm, args, required = [(zelf, None)]); let socket = get_socket(zelf); let fileno = match socket.con.borrow_mut().as_mut() { - Some(v) => v.as_raw_socket(), + Some(v) => v.fileno(), None => return Err(vm.new_type_error("".to_string())), }; - Ok(vm.ctx.new_int(i64::from(fileno))) -} - -#[cfg(all(not(unix), not(windows)))] -fn socket_fileno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - unimplemented!(); + Ok(vm.ctx.new_int(fileno)) } fn socket_getsockname(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { From aef973802c0cab214a179c2d8166707aaed869c9 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 16:20:44 +0300 Subject: [PATCH 366/884] Close open reference to test file --- tests/snippets/stdlib_os.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index f7c47a95c0..900b2fa5b1 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -51,10 +51,8 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): for f in os.listdir(self.name): - print("Remove file", self.name + os.sep + f) # Currently don't support dir delete. os.remove(self.name + os.sep + f) - print("Remove dir", self.name) os.rmdir(self.name) @@ -65,7 +63,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): with TestWithTempDir() as tmpdir: fname = tmpdir + os.sep + FILE_NAME - open(fname, "wb") + with open(fname, "wb"): + pass fd = os.open(fname, 1) assert os.write(fd, CONTENT2) == len(CONTENT2) assert os.write(fd, CONTENT3) == len(CONTENT3) From f42c2a6ba527901eadc1429a5a3a7008d34395e9 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 16:58:43 +0300 Subject: [PATCH 367/884] Test fileno only on posix --- tests/snippets/stdlib_socket.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/snippets/stdlib_socket.py b/tests/snippets/stdlib_socket.py index 16c2f96ce1..d7afe1bd35 100644 --- a/tests/snippets/stdlib_socket.py +++ b/tests/snippets/stdlib_socket.py @@ -24,14 +24,15 @@ assert recv_b == MESSAGE_B # fileno -connector_fd = connector.fileno() -connection_fd = connection.fileno() -os.write(connector_fd, MESSAGE_A) -connection.send(MESSAGE_B) -recv_a = connection.recv(len(MESSAGE_A)) -recv_b = os.read(connector_fd, (len(MESSAGE_B))) -assert recv_a == MESSAGE_A -assert recv_b == MESSAGE_B +if os.name == "posix": + connector_fd = connector.fileno() + connection_fd = connection.fileno() + os.write(connector_fd, MESSAGE_A) + connection.send(MESSAGE_B) + recv_a = connection.recv(len(MESSAGE_A)) + recv_b = os.read(connector_fd, (len(MESSAGE_B))) + assert recv_a == MESSAGE_A + assert recv_b == MESSAGE_B connection.close() connector.close() From 8215ec0a7c01896ecfa3bc7ab990aec22072b8b0 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 17:04:04 +0300 Subject: [PATCH 368/884] Don't delete test dir --- tests/snippets/stdlib_os.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 900b2fa5b1..f7d0a9a1f0 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -1,4 +1,5 @@ -import os +import os +import time from testutils import assert_raises @@ -44,16 +45,14 @@ def __enter__(self): base_folder = os.environ["TEMP"] else: base_folder = "/tmp" - name = base_folder + os.sep + "test_os" + name = base_folder + os.sep + "test_os_" + str(int(time.time())) os.mkdir(name) self.name = name return name def __exit__(self, exc_type, exc_val, exc_tb): - for f in os.listdir(self.name): - # Currently don't support dir delete. - os.remove(self.name + os.sep + f) - os.rmdir(self.name) + # TODO: Delete temp dir + pass FILE_NAME = "test1" From d704a0fcdab4d06c4b11da64b815918a56976417 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 19 Apr 2019 17:14:14 +0300 Subject: [PATCH 369/884] Rename os test folder --- tests/snippets/stdlib_os.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index f7d0a9a1f0..4d64fc7930 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -45,7 +45,7 @@ def __enter__(self): base_folder = os.environ["TEMP"] else: base_folder = "/tmp" - name = base_folder + os.sep + "test_os_" + str(int(time.time())) + name = base_folder + os.sep + "rustpython_test_os_" + str(int(time.time())) os.mkdir(name) self.name = name return name From 2a74e48d98a5fef053cf82cd267eb4832bd67090 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 19 Apr 2019 10:10:56 -0500 Subject: [PATCH 370/884] Add vm.is_callable --- vm/src/builtins.rs | 4 ++-- vm/src/vm.rs | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index d054c34fca..9f393d333e 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -67,8 +67,8 @@ fn builtin_bin(x: PyIntRef, _vm: &VirtualMachine) -> String { // builtin_breakpoint -fn builtin_callable(obj: PyObjectRef, _vm: &VirtualMachine) -> bool { - objtype::class_has_attr(&obj.class(), "__call__") +fn builtin_callable(obj: PyObjectRef, vm: &VirtualMachine) -> bool { + vm.is_callable(&obj) } fn builtin_chr(i: u32, _vm: &VirtualMachine) -> String { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 6e77105168..81aa5367ba 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -666,6 +666,21 @@ impl VirtualMachine { crate::stdlib::json::de_pyobject(self, s) } + pub fn is_callable(&self, obj: &PyObjectRef) -> bool { + match_class!(obj, + PyFunction => true, + PyMethod => true, + PyBuiltinFunction => true, + obj => { + if let Some(dict) = &obj.dict { + dict.contains_key("__call__", self) + } else { + false + } + }, + ) + } + pub fn _sub(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__sub__", "__rsub__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "-")) From a6f3b9a5ff2ae0e56e7bd9db5a07a2a3b6178d29 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 19 Apr 2019 10:14:57 -0500 Subject: [PATCH 371/884] Add PyCallable --- vm/src/pyobject.rs | 33 +++++++++++++++++++++++++++++++++ vm/src/vm.rs | 33 ++++++++++++++++++--------------- wasm/lib/src/browser_module.rs | 15 ++++++++------- 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index fdac31b837..d172961a35 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -878,6 +878,39 @@ where } } +#[derive(Clone)] +pub struct PyCallable { + obj: PyObjectRef, +} + +impl PyCallable { + #[inline] + pub fn invoke(&self, args: impl Into, vm: &VirtualMachine) -> PyResult { + vm.invoke(self.obj.clone(), args) + } + + #[inline] + pub fn into_object(self) -> PyObjectRef { + self.obj + } +} + +impl TryFromObject for PyCallable { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + if vm.is_callable(&obj) { + Ok(PyCallable { obj }) + } else { + Err(vm.new_type_error(format!("'{}' object is not callable", obj.class().name))) + } + } +} + +impl IntoPyObject for PyCallable { + fn into_pyobject(self, _vm: &VirtualMachine) -> PyResult { + Ok(self.into_object()) + } +} + pub trait IdProtocol { fn get_id(&self) -> usize; fn is(&self, other: &T) -> bool diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 81aa5367ba..3a1aec6fcc 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -341,11 +341,7 @@ impl VirtualMachine { } } - pub fn invoke(&self, func_ref: PyObjectRef, args: T) -> PyResult - where - T: Into, - { - let args = args.into(); + fn _invoke(&self, func_ref: PyObjectRef, args: PyFuncArgs) -> PyResult { trace!("Invoke: {:?} {:?}", func_ref, args); if let Some(PyFunction { ref code, @@ -354,22 +350,29 @@ impl VirtualMachine { ref kw_only_defaults, }) = func_ref.payload() { - return self.invoke_python_function(code, scope, defaults, kw_only_defaults, args); - } - if let Some(PyMethod { + self.invoke_python_function(code, scope, defaults, kw_only_defaults, args) + } else if let Some(PyMethod { ref function, ref object, }) = func_ref.payload() { - return self.invoke(function.clone(), args.insert(object.clone())); - } - if let Some(PyBuiltinFunction { ref value }) = func_ref.payload() { - return value(self, args); + self.invoke(function.clone(), args.insert(object.clone())) + } else if let Some(PyBuiltinFunction { ref value }) = func_ref.payload() { + value(self, args) + } else { + // TODO: is it safe to just invoke __call__ otherwise? + trace!("invoke __call__ for: {:?}", &func_ref.payload); + self.call_method(&func_ref, "__call__", args) } + } - // TODO: is it safe to just invoke __call__ otherwise? - trace!("invoke __call__ for: {:?}", &func_ref.payload); - self.call_method(&func_ref, "__call__", args) + // TODO: make func_ref an &PyObjectRef + #[inline] + pub fn invoke(&self, func_ref: PyObjectRef, args: T) -> PyResult + where + T: Into, + { + self._invoke(func_ref, args.into()) } fn invoke_python_function( diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 478ebb9c52..6fb7c6baa5 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -7,10 +7,11 @@ use wasm_bindgen_futures::{future_to_promise, JsFuture}; use rustpython_vm::function::{OptionalArg, PyFuncArgs}; use rustpython_vm::obj::{ - objdict::PyDictRef, objfunction::PyFunctionRef, objint::PyIntRef, objstr::PyStringRef, - objtype::PyClassRef, + objdict::PyDictRef, objint::PyIntRef, objstr::PyStringRef, objtype::PyClassRef, +}; +use rustpython_vm::pyobject::{ + PyCallable, PyClassImpl, PyObject, PyObjectRef, PyRef, PyResult, PyValue, }; -use rustpython_vm::pyobject::{PyClassImpl, PyObject, PyObjectRef, PyRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; use crate::{convert, vm_class::weak_vm, wasm_builtins::window}; @@ -113,7 +114,7 @@ fn browser_fetch(url: PyStringRef, args: FetchArgs, vm: &VirtualMachine) -> PyRe Ok(PyPromise::from_future(future).into_ref(vm).into_object()) } -fn browser_request_animation_frame(func: PyFunctionRef, vm: &VirtualMachine) -> PyResult { +fn browser_request_animation_frame(func: PyCallable, vm: &VirtualMachine) -> PyResult { use std::{cell::RefCell, rc::Rc}; // this basic setup for request_animation_frame taken from: @@ -192,8 +193,8 @@ impl PyPromise { #[pymethod] fn then( &self, - on_fulfill: PyFunctionRef, - on_reject: OptionalArg, + on_fulfill: PyCallable, + on_reject: OptionalArg, vm: &VirtualMachine, ) -> PyPromiseRef { let weak_vm = weak_vm(vm); @@ -224,7 +225,7 @@ impl PyPromise { } #[pymethod] - fn catch(&self, on_reject: PyFunctionRef, vm: &VirtualMachine) -> PyPromiseRef { + fn catch(&self, on_reject: PyCallable, vm: &VirtualMachine) -> PyPromiseRef { let weak_vm = weak_vm(vm); let ret_future = JsFuture::from(self.value.clone()).then(move |res| { From 29ea307f4c633b61f727689c535fdf33ddc044be Mon Sep 17 00:00:00 2001 From: Joey Date: Fri, 19 Apr 2019 09:26:24 -0700 Subject: [PATCH 372/884] unicode-casing: specify rev explicitly, add todo comment --- Cargo.lock | 6 +++--- vm/Cargo.toml | 6 +++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 100f6a3599..274daba698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -827,7 +827,7 @@ dependencies = [ "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing)", + "unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb)", "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1106,7 +1106,7 @@ dependencies = [ [[package]] name = "unicode-casing" version = "0.1.0" -source = "git+https://github.com/OddCoincidence/unicode-casing#90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb" +source = "git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb#90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb" [[package]] name = "unicode-normalization" @@ -1437,7 +1437,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unic-common 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" "checksum unic-emoji-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d" "checksum unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" -"checksum unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing)" = "" +"checksum unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb)" = "" "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 1ba1986b78..5af648084a 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -23,8 +23,12 @@ regex = "1" rustc_version_runtime = "0.1.*" statrs = "0.10.0" caseless = "0.2.1" -unicode-casing = { git = "https://github.com/OddCoincidence/unicode-casing" } unicode-segmentation = "1.2.1" lazy_static = "^1.0.1" lexical = "2.0.0" itertools = "^0.8.0" + +# TODO: release and publish to crates.io +[dependencies.unicode-casing] +git = "https://github.com/OddCoincidence/unicode-casing" +rev = "90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb" From cf5e106748b6eb11ff54e567f0bfa6dd1590a140 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 19 Apr 2019 11:43:24 -0500 Subject: [PATCH 373/884] Fix is_callable --- vm/src/vm.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 3a1aec6fcc..3538cde30f 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -678,7 +678,7 @@ impl VirtualMachine { if let Some(dict) = &obj.dict { dict.contains_key("__call__", self) } else { - false + objtype::class_has_attr(&obj.class(), "__call__") } }, ) From ac3cfca0fa1a4349257eac547318607da9021af3 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 19 Apr 2019 12:04:22 -0500 Subject: [PATCH 374/884] Fix callable (again) --- vm/src/vm.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 3538cde30f..ab15e14e95 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -674,13 +674,7 @@ impl VirtualMachine { PyFunction => true, PyMethod => true, PyBuiltinFunction => true, - obj => { - if let Some(dict) = &obj.dict { - dict.contains_key("__call__", self) - } else { - objtype::class_has_attr(&obj.class(), "__call__") - } - }, + obj => objtype::class_has_attr(&obj.class(), "__call__"), ) } From a984fd34d9cae5ba06f71b36184e318283371661 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 19 Apr 2019 12:11:18 -0500 Subject: [PATCH 375/884] Update callable test --- tests/snippets/builtin_callable.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/snippets/builtin_callable.py b/tests/snippets/builtin_callable.py index c22db07797..db554df245 100644 --- a/tests/snippets/builtin_callable.py +++ b/tests/snippets/builtin_callable.py @@ -1,9 +1,8 @@ assert not callable(1) def f(): pass -# TODO uncomment when callable types get unified __call__ (or equivalent) -#assert callable(f) -#assert callable(len) -#assert callable(lambda: 1) +assert callable(f) +assert callable(len) +assert callable(lambda: 1) assert callable(int) class C: @@ -13,7 +12,7 @@ def __init__(self): def f(self): pass assert callable(C) assert not callable(C()) -#assert callable(C().f) +assert callable(C().f) class C: def __call__(self): pass From 7822f6d8d8ff12cedcaa3936645ddf20e501d728 Mon Sep 17 00:00:00 2001 From: luozijun Date: Thu, 18 Apr 2019 15:27:51 +0800 Subject: [PATCH 376/884] Add benches --- Cargo.lock | 22 + Cargo.toml | 8 + benchmarks/bench.rs | 125 ++ benchmarks/benchmarks/mandelbrot.py | 8 +- benchmarks/benchmarks/minidom.py | 1981 +++++++++++++++++++++++++++ benchmarks/benchmarks/nbody.py | 2 +- 6 files changed, 2142 insertions(+), 4 deletions(-) create mode 100644 benchmarks/bench.rs create mode 100644 benchmarks/benchmarks/minidom.py diff --git a/Cargo.lock b/Cargo.lock index 864e90ce5a..2a329d025d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -185,6 +185,16 @@ name = "constant_time_eq" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "cpython" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "diff" version = "0.1.11" @@ -551,6 +561,15 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "python3-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quick-error" version = "1.2.2" @@ -771,6 +790,7 @@ name = "rustpython" version = "0.0.1" dependencies = [ "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_parser 0.0.1", @@ -1328,6 +1348,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" +"checksum cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b489034e723e7f5109fecd19b719e664f89ef925be785885252469e9822fa940" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" @@ -1374,6 +1395,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" +"checksum python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "61e4aac43f833fd637e429506cb2ac9d7df672c4b68f2eaaa163649b7fdc0444" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" "checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" diff --git a/Cargo.toml b/Cargo.toml index c109e3fea4..ef2838a1c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,11 @@ edition = "2018" [workspace] members = [".", "derive", "vm", "wasm/lib", "parser"] +[[bench]] +name = "bench" +path = "./benchmarks/bench.rs" + + [dependencies] log="0.4.1" env_logger="0.5.10" @@ -15,3 +20,6 @@ rustpython_parser = {path = "parser"} rustpython_vm = {path = "vm"} rustyline = "2.1.0" xdg = "2.2.0" + +[dev-dependencies.cpython] +version = "0.2" diff --git a/benchmarks/bench.rs b/benchmarks/bench.rs new file mode 100644 index 0000000000..b475da7cb3 --- /dev/null +++ b/benchmarks/bench.rs @@ -0,0 +1,125 @@ +#![feature(test)] + +extern crate cpython; +extern crate rustpython_parser; +extern crate rustpython_vm; +extern crate test; + +use rustpython_vm::pyobject::PyResult; +use rustpython_vm::{compile, VirtualMachine}; + +#[bench] +fn bench_tokenization(b: &mut test::Bencher) { + use rustpython_parser::lexer::{make_tokenizer, Tok}; + + let source = include_str!("./benchmarks/minidom.py"); + + b.bytes = source.len() as _; + b.iter(|| { + let lexer = make_tokenizer(source); + for res in lexer { + let _token: Tok = res.unwrap().1; + } + }) +} + +#[bench] +fn bench_rustpy_parse_to_ast(b: &mut test::Bencher) { + use rustpython_parser::parser::parse_program; + + let source = include_str!("./benchmarks/minidom.py"); + + b.bytes = source.len() as _; + b.iter(|| parse_program(source).unwrap()) +} + +#[bench] +fn bench_cpython_parse_to_ast(b: &mut test::Bencher) { + let source = include_str!("./benchmarks/minidom.py"); + + let gil = cpython::Python::acquire_gil(); + let python = gil.python(); + + let globals = None; + let locals = cpython::PyDict::new(python); + + locals.set_item(python, "SOURCE_CODE", source).unwrap(); + + let code = "compile(SOURCE_CODE, mode=\"exec\", filename=\"minidom.py\")"; + + b.bytes = source.len() as _; + b.iter(|| { + let res: cpython::PyResult = python.eval(code, globals, Some(&locals)); + assert!(res.is_ok()); + }) +} + +#[bench] +fn bench_cpython_nbody(b: &mut test::Bencher) { + let source = include_str!("./benchmarks/nbody.py"); + + let gil = cpython::Python::acquire_gil(); + let python = gil.python(); + + let globals = None; + let locals = None; + + b.iter(|| { + let res: cpython::PyResult<()> = python.run(source, globals, locals); + assert!(res.is_ok()); + }) +} + +#[bench] +fn bench_cpython_mandelbrot(b: &mut test::Bencher) { + let source = include_str!("./benchmarks/mandelbrot.py"); + + let gil = cpython::Python::acquire_gil(); + let python = gil.python(); + + let globals = None; + let locals = None; + + b.iter(|| { + let res: cpython::PyResult<()> = python.run(source, globals, locals); + assert!(res.is_ok()); + }) +} + +#[bench] +fn bench_rustpy_nbody(b: &mut test::Bencher) { + // NOTE: Take long time. + let source = include_str!("./benchmarks/nbody.py"); + + let vm = VirtualMachine::new(); + + let code = match compile::compile(&vm, source, &compile::Mode::Single, "".to_string()) { + Ok(code) => code, + Err(e) => panic!("{:?}", e), + }; + + b.iter(|| { + let scope = vm.ctx.new_scope(); + let res: PyResult = vm.run_code_obj(code.clone(), scope); + assert!(res.is_ok()); + }) +} + +#[bench] +fn bench_rustpy_mandelbrot(b: &mut test::Bencher) { + // NOTE: Take long time. + let source = include_str!("./benchmarks/mandelbrot.py"); + + let vm = VirtualMachine::new(); + + let code = match compile::compile(&vm, source, &compile::Mode::Single, "".to_string()) { + Ok(code) => code, + Err(e) => panic!("{:?}", e), + }; + + b.iter(|| { + let scope = vm.ctx.new_scope(); + let res: PyResult = vm.run_code_obj(code.clone(), scope); + assert!(res.is_ok()); + }) +} diff --git a/benchmarks/benchmarks/mandelbrot.py b/benchmarks/benchmarks/mandelbrot.py index f0106cd74d..88ae04101a 100644 --- a/benchmarks/benchmarks/mandelbrot.py +++ b/benchmarks/benchmarks/mandelbrot.py @@ -21,11 +21,13 @@ i = i+1 if Tr+Ti <= 4: - print('*', end='') + # print('*', end='') + pass else: - print('·', end='') + # print('·', end='') + pass x = x+1 - print() + # print() y = y+1 diff --git a/benchmarks/benchmarks/minidom.py b/benchmarks/benchmarks/minidom.py new file mode 100644 index 0000000000..24957ea14f --- /dev/null +++ b/benchmarks/benchmarks/minidom.py @@ -0,0 +1,1981 @@ +"""Simple implementation of the Level 1 DOM. + +Namespaces and other minor Level 2 features are also supported. + +parse("foo.xml") + +parseString("") + +Todo: +===== + * convenience methods for getting elements and text. + * more testing + * bring some of the writer and linearizer code into conformance with this + interface + * SAX 2 namespaces +""" + +import io +import xml.dom + +from xml.dom import EMPTY_NAMESPACE, EMPTY_PREFIX, XMLNS_NAMESPACE, domreg +from xml.dom.minicompat import * +from xml.dom.xmlbuilder import DOMImplementationLS, DocumentLS + +# This is used by the ID-cache invalidation checks; the list isn't +# actually complete, since the nodes being checked will never be the +# DOCUMENT_NODE or DOCUMENT_FRAGMENT_NODE. (The node being checked is +# the node being added or removed, not the node being modified.) +# +_nodeTypes_with_children = (xml.dom.Node.ELEMENT_NODE, + xml.dom.Node.ENTITY_REFERENCE_NODE) + + +class Node(xml.dom.Node): + namespaceURI = None # this is non-null only for elements and attributes + parentNode = None + ownerDocument = None + nextSibling = None + previousSibling = None + + prefix = EMPTY_PREFIX # non-null only for NS elements and attributes + + def __bool__(self): + return True + + def toxml(self, encoding=None): + return self.toprettyxml("", "", encoding) + + def toprettyxml(self, indent="\t", newl="\n", encoding=None): + if encoding is None: + writer = io.StringIO() + else: + writer = io.TextIOWrapper(io.BytesIO(), + encoding=encoding, + errors="xmlcharrefreplace", + newline='\n') + if self.nodeType == Node.DOCUMENT_NODE: + # Can pass encoding only to document, to put it into XML header + self.writexml(writer, "", indent, newl, encoding) + else: + self.writexml(writer, "", indent, newl) + if encoding is None: + return writer.getvalue() + else: + return writer.detach().getvalue() + + def hasChildNodes(self): + return bool(self.childNodes) + + def _get_childNodes(self): + return self.childNodes + + def _get_firstChild(self): + if self.childNodes: + return self.childNodes[0] + + def _get_lastChild(self): + if self.childNodes: + return self.childNodes[-1] + + def insertBefore(self, newChild, refChild): + if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: + for c in tuple(newChild.childNodes): + self.insertBefore(c, refChild) + ### The DOM does not clearly specify what to return in this case + return newChild + if newChild.nodeType not in self._child_node_types: + raise xml.dom.HierarchyRequestErr( + "%s cannot be child of %s" % (repr(newChild), repr(self))) + if newChild.parentNode is not None: + newChild.parentNode.removeChild(newChild) + if refChild is None: + self.appendChild(newChild) + else: + try: + index = self.childNodes.index(refChild) + except ValueError: + raise xml.dom.NotFoundErr() + if newChild.nodeType in _nodeTypes_with_children: + _clear_id_cache(self) + self.childNodes.insert(index, newChild) + newChild.nextSibling = refChild + refChild.previousSibling = newChild + if index: + node = self.childNodes[index-1] + node.nextSibling = newChild + newChild.previousSibling = node + else: + newChild.previousSibling = None + newChild.parentNode = self + return newChild + + def appendChild(self, node): + if node.nodeType == self.DOCUMENT_FRAGMENT_NODE: + for c in tuple(node.childNodes): + self.appendChild(c) + ### The DOM does not clearly specify what to return in this case + return node + if node.nodeType not in self._child_node_types: + raise xml.dom.HierarchyRequestErr( + "%s cannot be child of %s" % (repr(node), repr(self))) + elif node.nodeType in _nodeTypes_with_children: + _clear_id_cache(self) + if node.parentNode is not None: + node.parentNode.removeChild(node) + _append_child(self, node) + node.nextSibling = None + return node + + def replaceChild(self, newChild, oldChild): + if newChild.nodeType == self.DOCUMENT_FRAGMENT_NODE: + refChild = oldChild.nextSibling + self.removeChild(oldChild) + return self.insertBefore(newChild, refChild) + if newChild.nodeType not in self._child_node_types: + raise xml.dom.HierarchyRequestErr( + "%s cannot be child of %s" % (repr(newChild), repr(self))) + if newChild is oldChild: + return + if newChild.parentNode is not None: + newChild.parentNode.removeChild(newChild) + try: + index = self.childNodes.index(oldChild) + except ValueError: + raise xml.dom.NotFoundErr() + self.childNodes[index] = newChild + newChild.parentNode = self + oldChild.parentNode = None + if (newChild.nodeType in _nodeTypes_with_children + or oldChild.nodeType in _nodeTypes_with_children): + _clear_id_cache(self) + newChild.nextSibling = oldChild.nextSibling + newChild.previousSibling = oldChild.previousSibling + oldChild.nextSibling = None + oldChild.previousSibling = None + if newChild.previousSibling: + newChild.previousSibling.nextSibling = newChild + if newChild.nextSibling: + newChild.nextSibling.previousSibling = newChild + return oldChild + + def removeChild(self, oldChild): + try: + self.childNodes.remove(oldChild) + except ValueError: + raise xml.dom.NotFoundErr() + if oldChild.nextSibling is not None: + oldChild.nextSibling.previousSibling = oldChild.previousSibling + if oldChild.previousSibling is not None: + oldChild.previousSibling.nextSibling = oldChild.nextSibling + oldChild.nextSibling = oldChild.previousSibling = None + if oldChild.nodeType in _nodeTypes_with_children: + _clear_id_cache(self) + + oldChild.parentNode = None + return oldChild + + def normalize(self): + L = [] + for child in self.childNodes: + if child.nodeType == Node.TEXT_NODE: + if not child.data: + # empty text node; discard + if L: + L[-1].nextSibling = child.nextSibling + if child.nextSibling: + child.nextSibling.previousSibling = child.previousSibling + child.unlink() + elif L and L[-1].nodeType == child.nodeType: + # collapse text node + node = L[-1] + node.data = node.data + child.data + node.nextSibling = child.nextSibling + if child.nextSibling: + child.nextSibling.previousSibling = node + child.unlink() + else: + L.append(child) + else: + L.append(child) + if child.nodeType == Node.ELEMENT_NODE: + child.normalize() + self.childNodes[:] = L + + def cloneNode(self, deep): + return _clone_node(self, deep, self.ownerDocument or self) + + def isSupported(self, feature, version): + return self.ownerDocument.implementation.hasFeature(feature, version) + + def _get_localName(self): + # Overridden in Element and Attr where localName can be Non-Null + return None + + # Node interfaces from Level 3 (WD 9 April 2002) + + def isSameNode(self, other): + return self is other + + def getInterface(self, feature): + if self.isSupported(feature, None): + return self + else: + return None + + # The "user data" functions use a dictionary that is only present + # if some user data has been set, so be careful not to assume it + # exists. + + def getUserData(self, key): + try: + return self._user_data[key][0] + except (AttributeError, KeyError): + return None + + def setUserData(self, key, data, handler): + old = None + try: + d = self._user_data + except AttributeError: + d = {} + self._user_data = d + if key in d: + old = d[key][0] + if data is None: + # ignore handlers passed for None + handler = None + if old is not None: + del d[key] + else: + d[key] = (data, handler) + return old + + def _call_user_data_handler(self, operation, src, dst): + if hasattr(self, "_user_data"): + for key, (data, handler) in list(self._user_data.items()): + if handler is not None: + handler.handle(operation, key, data, src, dst) + + # minidom-specific API: + + def unlink(self): + self.parentNode = self.ownerDocument = None + if self.childNodes: + for child in self.childNodes: + child.unlink() + self.childNodes = NodeList() + self.previousSibling = None + self.nextSibling = None + + # A Node is its own context manager, to ensure that an unlink() call occurs. + # This is similar to how a file object works. + def __enter__(self): + return self + + def __exit__(self, et, ev, tb): + self.unlink() + +defproperty(Node, "firstChild", doc="First child node, or None.") +defproperty(Node, "lastChild", doc="Last child node, or None.") +defproperty(Node, "localName", doc="Namespace-local name of this node.") + + +def _append_child(self, node): + # fast path with less checks; usable by DOM builders if careful + childNodes = self.childNodes + if childNodes: + last = childNodes[-1] + node.previousSibling = last + last.nextSibling = node + childNodes.append(node) + node.parentNode = self + +def _in_document(node): + # return True iff node is part of a document tree + while node is not None: + if node.nodeType == Node.DOCUMENT_NODE: + return True + node = node.parentNode + return False + +def _write_data(writer, data): + "Writes datachars to writer." + if data: + data = data.replace("&", "&").replace("<", "<"). \ + replace("\"", """).replace(">", ">") + writer.write(data) + +def _get_elements_by_tagName_helper(parent, name, rc): + for node in parent.childNodes: + if node.nodeType == Node.ELEMENT_NODE and \ + (name == "*" or node.tagName == name): + rc.append(node) + _get_elements_by_tagName_helper(node, name, rc) + return rc + +def _get_elements_by_tagName_ns_helper(parent, nsURI, localName, rc): + for node in parent.childNodes: + if node.nodeType == Node.ELEMENT_NODE: + if ((localName == "*" or node.localName == localName) and + (nsURI == "*" or node.namespaceURI == nsURI)): + rc.append(node) + _get_elements_by_tagName_ns_helper(node, nsURI, localName, rc) + return rc + +class DocumentFragment(Node): + nodeType = Node.DOCUMENT_FRAGMENT_NODE + nodeName = "#document-fragment" + nodeValue = None + attributes = None + parentNode = None + _child_node_types = (Node.ELEMENT_NODE, + Node.TEXT_NODE, + Node.CDATA_SECTION_NODE, + Node.ENTITY_REFERENCE_NODE, + Node.PROCESSING_INSTRUCTION_NODE, + Node.COMMENT_NODE, + Node.NOTATION_NODE) + + def __init__(self): + self.childNodes = NodeList() + + +class Attr(Node): + __slots__=('_name', '_value', 'namespaceURI', + '_prefix', 'childNodes', '_localName', 'ownerDocument', 'ownerElement') + nodeType = Node.ATTRIBUTE_NODE + attributes = None + specified = False + _is_id = False + + _child_node_types = (Node.TEXT_NODE, Node.ENTITY_REFERENCE_NODE) + + def __init__(self, qName, namespaceURI=EMPTY_NAMESPACE, localName=None, + prefix=None): + self.ownerElement = None + self._name = qName + self.namespaceURI = namespaceURI + self._prefix = prefix + self.childNodes = NodeList() + + # Add the single child node that represents the value of the attr + self.childNodes.append(Text()) + + # nodeValue and value are set elsewhere + + def _get_localName(self): + try: + return self._localName + except AttributeError: + return self.nodeName.split(":", 1)[-1] + + def _get_specified(self): + return self.specified + + def _get_name(self): + return self._name + + def _set_name(self, value): + self._name = value + if self.ownerElement is not None: + _clear_id_cache(self.ownerElement) + + nodeName = name = property(_get_name, _set_name) + + def _get_value(self): + return self._value + + def _set_value(self, value): + self._value = value + self.childNodes[0].data = value + if self.ownerElement is not None: + _clear_id_cache(self.ownerElement) + self.childNodes[0].data = value + + nodeValue = value = property(_get_value, _set_value) + + def _get_prefix(self): + return self._prefix + + def _set_prefix(self, prefix): + nsuri = self.namespaceURI + if prefix == "xmlns": + if nsuri and nsuri != XMLNS_NAMESPACE: + raise xml.dom.NamespaceErr( + "illegal use of 'xmlns' prefix for the wrong namespace") + self._prefix = prefix + if prefix is None: + newName = self.localName + else: + newName = "%s:%s" % (prefix, self.localName) + if self.ownerElement: + _clear_id_cache(self.ownerElement) + self.name = newName + + prefix = property(_get_prefix, _set_prefix) + + def unlink(self): + # This implementation does not call the base implementation + # since most of that is not needed, and the expense of the + # method call is not warranted. We duplicate the removal of + # children, but that's all we needed from the base class. + elem = self.ownerElement + if elem is not None: + del elem._attrs[self.nodeName] + del elem._attrsNS[(self.namespaceURI, self.localName)] + if self._is_id: + self._is_id = False + elem._magic_id_nodes -= 1 + self.ownerDocument._magic_id_count -= 1 + for child in self.childNodes: + child.unlink() + del self.childNodes[:] + + def _get_isId(self): + if self._is_id: + return True + doc = self.ownerDocument + elem = self.ownerElement + if doc is None or elem is None: + return False + + info = doc._get_elem_info(elem) + if info is None: + return False + if self.namespaceURI: + return info.isIdNS(self.namespaceURI, self.localName) + else: + return info.isId(self.nodeName) + + def _get_schemaType(self): + doc = self.ownerDocument + elem = self.ownerElement + if doc is None or elem is None: + return _no_type + + info = doc._get_elem_info(elem) + if info is None: + return _no_type + if self.namespaceURI: + return info.getAttributeTypeNS(self.namespaceURI, self.localName) + else: + return info.getAttributeType(self.nodeName) + +defproperty(Attr, "isId", doc="True if this attribute is an ID.") +defproperty(Attr, "localName", doc="Namespace-local name of this attribute.") +defproperty(Attr, "schemaType", doc="Schema type for this attribute.") + + +class NamedNodeMap(object): + """The attribute list is a transient interface to the underlying + dictionaries. Mutations here will change the underlying element's + dictionary. + + Ordering is imposed artificially and does not reflect the order of + attributes as found in an input document. + """ + + __slots__ = ('_attrs', '_attrsNS', '_ownerElement') + + def __init__(self, attrs, attrsNS, ownerElement): + self._attrs = attrs + self._attrsNS = attrsNS + self._ownerElement = ownerElement + + def _get_length(self): + return len(self._attrs) + + def item(self, index): + try: + return self[list(self._attrs.keys())[index]] + except IndexError: + return None + + def items(self): + L = [] + for node in self._attrs.values(): + L.append((node.nodeName, node.value)) + return L + + def itemsNS(self): + L = [] + for node in self._attrs.values(): + L.append(((node.namespaceURI, node.localName), node.value)) + return L + + def __contains__(self, key): + if isinstance(key, str): + return key in self._attrs + else: + return key in self._attrsNS + + def keys(self): + return self._attrs.keys() + + def keysNS(self): + return self._attrsNS.keys() + + def values(self): + return self._attrs.values() + + def get(self, name, value=None): + return self._attrs.get(name, value) + + __len__ = _get_length + + def _cmp(self, other): + if self._attrs is getattr(other, "_attrs", None): + return 0 + else: + return (id(self) > id(other)) - (id(self) < id(other)) + + def __eq__(self, other): + return self._cmp(other) == 0 + + def __ge__(self, other): + return self._cmp(other) >= 0 + + def __gt__(self, other): + return self._cmp(other) > 0 + + def __le__(self, other): + return self._cmp(other) <= 0 + + def __lt__(self, other): + return self._cmp(other) < 0 + + def __getitem__(self, attname_or_tuple): + if isinstance(attname_or_tuple, tuple): + return self._attrsNS[attname_or_tuple] + else: + return self._attrs[attname_or_tuple] + + # same as set + def __setitem__(self, attname, value): + if isinstance(value, str): + try: + node = self._attrs[attname] + except KeyError: + node = Attr(attname) + node.ownerDocument = self._ownerElement.ownerDocument + self.setNamedItem(node) + node.value = value + else: + if not isinstance(value, Attr): + raise TypeError("value must be a string or Attr object") + node = value + self.setNamedItem(node) + + def getNamedItem(self, name): + try: + return self._attrs[name] + except KeyError: + return None + + def getNamedItemNS(self, namespaceURI, localName): + try: + return self._attrsNS[(namespaceURI, localName)] + except KeyError: + return None + + def removeNamedItem(self, name): + n = self.getNamedItem(name) + if n is not None: + _clear_id_cache(self._ownerElement) + del self._attrs[n.nodeName] + del self._attrsNS[(n.namespaceURI, n.localName)] + if hasattr(n, 'ownerElement'): + n.ownerElement = None + return n + else: + raise xml.dom.NotFoundErr() + + def removeNamedItemNS(self, namespaceURI, localName): + n = self.getNamedItemNS(namespaceURI, localName) + if n is not None: + _clear_id_cache(self._ownerElement) + del self._attrsNS[(n.namespaceURI, n.localName)] + del self._attrs[n.nodeName] + if hasattr(n, 'ownerElement'): + n.ownerElement = None + return n + else: + raise xml.dom.NotFoundErr() + + def setNamedItem(self, node): + if not isinstance(node, Attr): + raise xml.dom.HierarchyRequestErr( + "%s cannot be child of %s" % (repr(node), repr(self))) + old = self._attrs.get(node.name) + if old: + old.unlink() + self._attrs[node.name] = node + self._attrsNS[(node.namespaceURI, node.localName)] = node + node.ownerElement = self._ownerElement + _clear_id_cache(node.ownerElement) + return old + + def setNamedItemNS(self, node): + return self.setNamedItem(node) + + def __delitem__(self, attname_or_tuple): + node = self[attname_or_tuple] + _clear_id_cache(node.ownerElement) + node.unlink() + + def __getstate__(self): + return self._attrs, self._attrsNS, self._ownerElement + + def __setstate__(self, state): + self._attrs, self._attrsNS, self._ownerElement = state + +defproperty(NamedNodeMap, "length", + doc="Number of nodes in the NamedNodeMap.") + +AttributeList = NamedNodeMap + + +class TypeInfo(object): + __slots__ = 'namespace', 'name' + + def __init__(self, namespace, name): + self.namespace = namespace + self.name = name + + def __repr__(self): + if self.namespace: + return "<%s %r (from %r)>" % (self.__class__.__name__, self.name, + self.namespace) + else: + return "<%s %r>" % (self.__class__.__name__, self.name) + + def _get_name(self): + return self.name + + def _get_namespace(self): + return self.namespace + +_no_type = TypeInfo(None, None) + +class Element(Node): + __slots__=('ownerDocument', 'parentNode', 'tagName', 'nodeName', 'prefix', + 'namespaceURI', '_localName', 'childNodes', '_attrs', '_attrsNS', + 'nextSibling', 'previousSibling') + nodeType = Node.ELEMENT_NODE + nodeValue = None + schemaType = _no_type + + _magic_id_nodes = 0 + + _child_node_types = (Node.ELEMENT_NODE, + Node.PROCESSING_INSTRUCTION_NODE, + Node.COMMENT_NODE, + Node.TEXT_NODE, + Node.CDATA_SECTION_NODE, + Node.ENTITY_REFERENCE_NODE) + + def __init__(self, tagName, namespaceURI=EMPTY_NAMESPACE, prefix=None, + localName=None): + self.parentNode = None + self.tagName = self.nodeName = tagName + self.prefix = prefix + self.namespaceURI = namespaceURI + self.childNodes = NodeList() + self.nextSibling = self.previousSibling = None + + # Attribute dictionaries are lazily created + # attributes are double-indexed: + # tagName -> Attribute + # URI,localName -> Attribute + # in the future: consider lazy generation + # of attribute objects this is too tricky + # for now because of headaches with + # namespaces. + self._attrs = None + self._attrsNS = None + + def _ensure_attributes(self): + if self._attrs is None: + self._attrs = {} + self._attrsNS = {} + + def _get_localName(self): + try: + return self._localName + except AttributeError: + return self.tagName.split(":", 1)[-1] + + def _get_tagName(self): + return self.tagName + + def unlink(self): + if self._attrs is not None: + for attr in list(self._attrs.values()): + attr.unlink() + self._attrs = None + self._attrsNS = None + Node.unlink(self) + + def getAttribute(self, attname): + if self._attrs is None: + return "" + try: + return self._attrs[attname].value + except KeyError: + return "" + + def getAttributeNS(self, namespaceURI, localName): + if self._attrsNS is None: + return "" + try: + return self._attrsNS[(namespaceURI, localName)].value + except KeyError: + return "" + + def setAttribute(self, attname, value): + attr = self.getAttributeNode(attname) + if attr is None: + attr = Attr(attname) + attr.value = value # also sets nodeValue + attr.ownerDocument = self.ownerDocument + self.setAttributeNode(attr) + elif value != attr.value: + attr.value = value + if attr.isId: + _clear_id_cache(self) + + def setAttributeNS(self, namespaceURI, qualifiedName, value): + prefix, localname = _nssplit(qualifiedName) + attr = self.getAttributeNodeNS(namespaceURI, localname) + if attr is None: + attr = Attr(qualifiedName, namespaceURI, localname, prefix) + attr.value = value + attr.ownerDocument = self.ownerDocument + self.setAttributeNode(attr) + else: + if value != attr.value: + attr.value = value + if attr.isId: + _clear_id_cache(self) + if attr.prefix != prefix: + attr.prefix = prefix + attr.nodeName = qualifiedName + + def getAttributeNode(self, attrname): + if self._attrs is None: + return None + return self._attrs.get(attrname) + + def getAttributeNodeNS(self, namespaceURI, localName): + if self._attrsNS is None: + return None + return self._attrsNS.get((namespaceURI, localName)) + + def setAttributeNode(self, attr): + if attr.ownerElement not in (None, self): + raise xml.dom.InuseAttributeErr("attribute node already owned") + self._ensure_attributes() + old1 = self._attrs.get(attr.name, None) + if old1 is not None: + self.removeAttributeNode(old1) + old2 = self._attrsNS.get((attr.namespaceURI, attr.localName), None) + if old2 is not None and old2 is not old1: + self.removeAttributeNode(old2) + _set_attribute_node(self, attr) + + if old1 is not attr: + # It might have already been part of this node, in which case + # it doesn't represent a change, and should not be returned. + return old1 + if old2 is not attr: + return old2 + + setAttributeNodeNS = setAttributeNode + + def removeAttribute(self, name): + if self._attrsNS is None: + raise xml.dom.NotFoundErr() + try: + attr = self._attrs[name] + except KeyError: + raise xml.dom.NotFoundErr() + self.removeAttributeNode(attr) + + def removeAttributeNS(self, namespaceURI, localName): + if self._attrsNS is None: + raise xml.dom.NotFoundErr() + try: + attr = self._attrsNS[(namespaceURI, localName)] + except KeyError: + raise xml.dom.NotFoundErr() + self.removeAttributeNode(attr) + + def removeAttributeNode(self, node): + if node is None: + raise xml.dom.NotFoundErr() + try: + self._attrs[node.name] + except KeyError: + raise xml.dom.NotFoundErr() + _clear_id_cache(self) + node.unlink() + # Restore this since the node is still useful and otherwise + # unlinked + node.ownerDocument = self.ownerDocument + + removeAttributeNodeNS = removeAttributeNode + + def hasAttribute(self, name): + if self._attrs is None: + return False + return name in self._attrs + + def hasAttributeNS(self, namespaceURI, localName): + if self._attrsNS is None: + return False + return (namespaceURI, localName) in self._attrsNS + + def getElementsByTagName(self, name): + return _get_elements_by_tagName_helper(self, name, NodeList()) + + def getElementsByTagNameNS(self, namespaceURI, localName): + return _get_elements_by_tagName_ns_helper( + self, namespaceURI, localName, NodeList()) + + def __repr__(self): + return "" % (self.tagName, id(self)) + + def writexml(self, writer, indent="", addindent="", newl=""): + # indent = current indentation + # addindent = indentation to add to higher levels + # newl = newline string + writer.write(indent+"<" + self.tagName) + + attrs = self._get_attributes() + a_names = sorted(attrs.keys()) + + for a_name in a_names: + writer.write(" %s=\"" % a_name) + _write_data(writer, attrs[a_name].value) + writer.write("\"") + if self.childNodes: + writer.write(">") + if (len(self.childNodes) == 1 and + self.childNodes[0].nodeType == Node.TEXT_NODE): + self.childNodes[0].writexml(writer, '', '', '') + else: + writer.write(newl) + for node in self.childNodes: + node.writexml(writer, indent+addindent, addindent, newl) + writer.write(indent) + writer.write("%s" % (self.tagName, newl)) + else: + writer.write("/>%s"%(newl)) + + def _get_attributes(self): + self._ensure_attributes() + return NamedNodeMap(self._attrs, self._attrsNS, self) + + def hasAttributes(self): + if self._attrs: + return True + else: + return False + + # DOM Level 3 attributes, based on the 22 Oct 2002 draft + + def setIdAttribute(self, name): + idAttr = self.getAttributeNode(name) + self.setIdAttributeNode(idAttr) + + def setIdAttributeNS(self, namespaceURI, localName): + idAttr = self.getAttributeNodeNS(namespaceURI, localName) + self.setIdAttributeNode(idAttr) + + def setIdAttributeNode(self, idAttr): + if idAttr is None or not self.isSameNode(idAttr.ownerElement): + raise xml.dom.NotFoundErr() + if _get_containing_entref(self) is not None: + raise xml.dom.NoModificationAllowedErr() + if not idAttr._is_id: + idAttr._is_id = True + self._magic_id_nodes += 1 + self.ownerDocument._magic_id_count += 1 + _clear_id_cache(self) + +defproperty(Element, "attributes", + doc="NamedNodeMap of attributes on the element.") +defproperty(Element, "localName", + doc="Namespace-local name of this element.") + + +def _set_attribute_node(element, attr): + _clear_id_cache(element) + element._ensure_attributes() + element._attrs[attr.name] = attr + element._attrsNS[(attr.namespaceURI, attr.localName)] = attr + + # This creates a circular reference, but Element.unlink() + # breaks the cycle since the references to the attribute + # dictionaries are tossed. + attr.ownerElement = element + +class Childless: + """Mixin that makes childless-ness easy to implement and avoids + the complexity of the Node methods that deal with children. + """ + __slots__ = () + + attributes = None + childNodes = EmptyNodeList() + firstChild = None + lastChild = None + + def _get_firstChild(self): + return None + + def _get_lastChild(self): + return None + + def appendChild(self, node): + raise xml.dom.HierarchyRequestErr( + self.nodeName + " nodes cannot have children") + + def hasChildNodes(self): + return False + + def insertBefore(self, newChild, refChild): + raise xml.dom.HierarchyRequestErr( + self.nodeName + " nodes do not have children") + + def removeChild(self, oldChild): + raise xml.dom.NotFoundErr( + self.nodeName + " nodes do not have children") + + def normalize(self): + # For childless nodes, normalize() has nothing to do. + pass + + def replaceChild(self, newChild, oldChild): + raise xml.dom.HierarchyRequestErr( + self.nodeName + " nodes do not have children") + + +class ProcessingInstruction(Childless, Node): + nodeType = Node.PROCESSING_INSTRUCTION_NODE + __slots__ = ('target', 'data') + + def __init__(self, target, data): + self.target = target + self.data = data + + # nodeValue is an alias for data + def _get_nodeValue(self): + return self.data + def _set_nodeValue(self, value): + self.data = value + nodeValue = property(_get_nodeValue, _set_nodeValue) + + # nodeName is an alias for target + def _get_nodeName(self): + return self.target + def _set_nodeName(self, value): + self.target = value + nodeName = property(_get_nodeName, _set_nodeName) + + def writexml(self, writer, indent="", addindent="", newl=""): + writer.write("%s%s" % (indent,self.target, self.data, newl)) + + +class CharacterData(Childless, Node): + __slots__=('_data', 'ownerDocument','parentNode', 'previousSibling', 'nextSibling') + + def __init__(self): + self.ownerDocument = self.parentNode = None + self.previousSibling = self.nextSibling = None + self._data = '' + Node.__init__(self) + + def _get_length(self): + return len(self.data) + __len__ = _get_length + + def _get_data(self): + return self._data + def _set_data(self, data): + self._data = data + + data = nodeValue = property(_get_data, _set_data) + + def __repr__(self): + data = self.data + if len(data) > 10: + dotdotdot = "..." + else: + dotdotdot = "" + return '' % ( + self.__class__.__name__, data[0:10], dotdotdot) + + def substringData(self, offset, count): + if offset < 0: + raise xml.dom.IndexSizeErr("offset cannot be negative") + if offset >= len(self.data): + raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") + if count < 0: + raise xml.dom.IndexSizeErr("count cannot be negative") + return self.data[offset:offset+count] + + def appendData(self, arg): + self.data = self.data + arg + + def insertData(self, offset, arg): + if offset < 0: + raise xml.dom.IndexSizeErr("offset cannot be negative") + if offset >= len(self.data): + raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") + if arg: + self.data = "%s%s%s" % ( + self.data[:offset], arg, self.data[offset:]) + + def deleteData(self, offset, count): + if offset < 0: + raise xml.dom.IndexSizeErr("offset cannot be negative") + if offset >= len(self.data): + raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") + if count < 0: + raise xml.dom.IndexSizeErr("count cannot be negative") + if count: + self.data = self.data[:offset] + self.data[offset+count:] + + def replaceData(self, offset, count, arg): + if offset < 0: + raise xml.dom.IndexSizeErr("offset cannot be negative") + if offset >= len(self.data): + raise xml.dom.IndexSizeErr("offset cannot be beyond end of data") + if count < 0: + raise xml.dom.IndexSizeErr("count cannot be negative") + if count: + self.data = "%s%s%s" % ( + self.data[:offset], arg, self.data[offset+count:]) + +defproperty(CharacterData, "length", doc="Length of the string data.") + + +class Text(CharacterData): + __slots__ = () + + nodeType = Node.TEXT_NODE + nodeName = "#text" + attributes = None + + def splitText(self, offset): + if offset < 0 or offset > len(self.data): + raise xml.dom.IndexSizeErr("illegal offset value") + newText = self.__class__() + newText.data = self.data[offset:] + newText.ownerDocument = self.ownerDocument + next = self.nextSibling + if self.parentNode and self in self.parentNode.childNodes: + if next is None: + self.parentNode.appendChild(newText) + else: + self.parentNode.insertBefore(newText, next) + self.data = self.data[:offset] + return newText + + def writexml(self, writer, indent="", addindent="", newl=""): + _write_data(writer, "%s%s%s" % (indent, self.data, newl)) + + # DOM Level 3 (WD 9 April 2002) + + def _get_wholeText(self): + L = [self.data] + n = self.previousSibling + while n is not None: + if n.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE): + L.insert(0, n.data) + n = n.previousSibling + else: + break + n = self.nextSibling + while n is not None: + if n.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE): + L.append(n.data) + n = n.nextSibling + else: + break + return ''.join(L) + + def replaceWholeText(self, content): + # XXX This needs to be seriously changed if minidom ever + # supports EntityReference nodes. + parent = self.parentNode + n = self.previousSibling + while n is not None: + if n.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE): + next = n.previousSibling + parent.removeChild(n) + n = next + else: + break + n = self.nextSibling + if not content: + parent.removeChild(self) + while n is not None: + if n.nodeType in (Node.TEXT_NODE, Node.CDATA_SECTION_NODE): + next = n.nextSibling + parent.removeChild(n) + n = next + else: + break + if content: + self.data = content + return self + else: + return None + + def _get_isWhitespaceInElementContent(self): + if self.data.strip(): + return False + elem = _get_containing_element(self) + if elem is None: + return False + info = self.ownerDocument._get_elem_info(elem) + if info is None: + return False + else: + return info.isElementContent() + +defproperty(Text, "isWhitespaceInElementContent", + doc="True iff this text node contains only whitespace" + " and is in element content.") +defproperty(Text, "wholeText", + doc="The text of all logically-adjacent text nodes.") + + +def _get_containing_element(node): + c = node.parentNode + while c is not None: + if c.nodeType == Node.ELEMENT_NODE: + return c + c = c.parentNode + return None + +def _get_containing_entref(node): + c = node.parentNode + while c is not None: + if c.nodeType == Node.ENTITY_REFERENCE_NODE: + return c + c = c.parentNode + return None + + +class Comment(CharacterData): + nodeType = Node.COMMENT_NODE + nodeName = "#comment" + + def __init__(self, data): + CharacterData.__init__(self) + self._data = data + + def writexml(self, writer, indent="", addindent="", newl=""): + if "--" in self.data: + raise ValueError("'--' is not allowed in a comment node") + writer.write("%s%s" % (indent, self.data, newl)) + + +class CDATASection(Text): + __slots__ = () + + nodeType = Node.CDATA_SECTION_NODE + nodeName = "#cdata-section" + + def writexml(self, writer, indent="", addindent="", newl=""): + if self.data.find("]]>") >= 0: + raise ValueError("']]>' not allowed in a CDATA section") + writer.write("" % self.data) + + +class ReadOnlySequentialNamedNodeMap(object): + __slots__ = '_seq', + + def __init__(self, seq=()): + # seq should be a list or tuple + self._seq = seq + + def __len__(self): + return len(self._seq) + + def _get_length(self): + return len(self._seq) + + def getNamedItem(self, name): + for n in self._seq: + if n.nodeName == name: + return n + + def getNamedItemNS(self, namespaceURI, localName): + for n in self._seq: + if n.namespaceURI == namespaceURI and n.localName == localName: + return n + + def __getitem__(self, name_or_tuple): + if isinstance(name_or_tuple, tuple): + node = self.getNamedItemNS(*name_or_tuple) + else: + node = self.getNamedItem(name_or_tuple) + if node is None: + raise KeyError(name_or_tuple) + return node + + def item(self, index): + if index < 0: + return None + try: + return self._seq[index] + except IndexError: + return None + + def removeNamedItem(self, name): + raise xml.dom.NoModificationAllowedErr( + "NamedNodeMap instance is read-only") + + def removeNamedItemNS(self, namespaceURI, localName): + raise xml.dom.NoModificationAllowedErr( + "NamedNodeMap instance is read-only") + + def setNamedItem(self, node): + raise xml.dom.NoModificationAllowedErr( + "NamedNodeMap instance is read-only") + + def setNamedItemNS(self, node): + raise xml.dom.NoModificationAllowedErr( + "NamedNodeMap instance is read-only") + + def __getstate__(self): + return [self._seq] + + def __setstate__(self, state): + self._seq = state[0] + +defproperty(ReadOnlySequentialNamedNodeMap, "length", + doc="Number of entries in the NamedNodeMap.") + + +class Identified: + """Mix-in class that supports the publicId and systemId attributes.""" + + __slots__ = 'publicId', 'systemId' + + def _identified_mixin_init(self, publicId, systemId): + self.publicId = publicId + self.systemId = systemId + + def _get_publicId(self): + return self.publicId + + def _get_systemId(self): + return self.systemId + +class DocumentType(Identified, Childless, Node): + nodeType = Node.DOCUMENT_TYPE_NODE + nodeValue = None + name = None + publicId = None + systemId = None + internalSubset = None + + def __init__(self, qualifiedName): + self.entities = ReadOnlySequentialNamedNodeMap() + self.notations = ReadOnlySequentialNamedNodeMap() + if qualifiedName: + prefix, localname = _nssplit(qualifiedName) + self.name = localname + self.nodeName = self.name + + def _get_internalSubset(self): + return self.internalSubset + + def cloneNode(self, deep): + if self.ownerDocument is None: + # it's ok + clone = DocumentType(None) + clone.name = self.name + clone.nodeName = self.name + operation = xml.dom.UserDataHandler.NODE_CLONED + if deep: + clone.entities._seq = [] + clone.notations._seq = [] + for n in self.notations._seq: + notation = Notation(n.nodeName, n.publicId, n.systemId) + clone.notations._seq.append(notation) + n._call_user_data_handler(operation, n, notation) + for e in self.entities._seq: + entity = Entity(e.nodeName, e.publicId, e.systemId, + e.notationName) + entity.actualEncoding = e.actualEncoding + entity.encoding = e.encoding + entity.version = e.version + clone.entities._seq.append(entity) + e._call_user_data_handler(operation, e, entity) + self._call_user_data_handler(operation, self, clone) + return clone + else: + return None + + def writexml(self, writer, indent="", addindent="", newl=""): + writer.write(""+newl) + +class Entity(Identified, Node): + attributes = None + nodeType = Node.ENTITY_NODE + nodeValue = None + + actualEncoding = None + encoding = None + version = None + + def __init__(self, name, publicId, systemId, notation): + self.nodeName = name + self.notationName = notation + self.childNodes = NodeList() + self._identified_mixin_init(publicId, systemId) + + def _get_actualEncoding(self): + return self.actualEncoding + + def _get_encoding(self): + return self.encoding + + def _get_version(self): + return self.version + + def appendChild(self, newChild): + raise xml.dom.HierarchyRequestErr( + "cannot append children to an entity node") + + def insertBefore(self, newChild, refChild): + raise xml.dom.HierarchyRequestErr( + "cannot insert children below an entity node") + + def removeChild(self, oldChild): + raise xml.dom.HierarchyRequestErr( + "cannot remove children from an entity node") + + def replaceChild(self, newChild, oldChild): + raise xml.dom.HierarchyRequestErr( + "cannot replace children of an entity node") + +class Notation(Identified, Childless, Node): + nodeType = Node.NOTATION_NODE + nodeValue = None + + def __init__(self, name, publicId, systemId): + self.nodeName = name + self._identified_mixin_init(publicId, systemId) + + +class DOMImplementation(DOMImplementationLS): + _features = [("core", "1.0"), + ("core", "2.0"), + ("core", None), + ("xml", "1.0"), + ("xml", "2.0"), + ("xml", None), + ("ls-load", "3.0"), + ("ls-load", None), + ] + + def hasFeature(self, feature, version): + if version == "": + version = None + return (feature.lower(), version) in self._features + + def createDocument(self, namespaceURI, qualifiedName, doctype): + if doctype and doctype.parentNode is not None: + raise xml.dom.WrongDocumentErr( + "doctype object owned by another DOM tree") + doc = self._create_document() + + add_root_element = not (namespaceURI is None + and qualifiedName is None + and doctype is None) + + if not qualifiedName and add_root_element: + # The spec is unclear what to raise here; SyntaxErr + # would be the other obvious candidate. Since Xerces raises + # InvalidCharacterErr, and since SyntaxErr is not listed + # for createDocument, that seems to be the better choice. + # XXX: need to check for illegal characters here and in + # createElement. + + # DOM Level III clears this up when talking about the return value + # of this function. If namespaceURI, qName and DocType are + # Null the document is returned without a document element + # Otherwise if doctype or namespaceURI are not None + # Then we go back to the above problem + raise xml.dom.InvalidCharacterErr("Element with no name") + + if add_root_element: + prefix, localname = _nssplit(qualifiedName) + if prefix == "xml" \ + and namespaceURI != "http://www.w3.org/XML/1998/namespace": + raise xml.dom.NamespaceErr("illegal use of 'xml' prefix") + if prefix and not namespaceURI: + raise xml.dom.NamespaceErr( + "illegal use of prefix without namespaces") + element = doc.createElementNS(namespaceURI, qualifiedName) + if doctype: + doc.appendChild(doctype) + doc.appendChild(element) + + if doctype: + doctype.parentNode = doctype.ownerDocument = doc + + doc.doctype = doctype + doc.implementation = self + return doc + + def createDocumentType(self, qualifiedName, publicId, systemId): + doctype = DocumentType(qualifiedName) + doctype.publicId = publicId + doctype.systemId = systemId + return doctype + + # DOM Level 3 (WD 9 April 2002) + + def getInterface(self, feature): + if self.hasFeature(feature, None): + return self + else: + return None + + # internal + def _create_document(self): + return Document() + +class ElementInfo(object): + """Object that represents content-model information for an element. + + This implementation is not expected to be used in practice; DOM + builders should provide implementations which do the right thing + using information available to it. + + """ + + __slots__ = 'tagName', + + def __init__(self, name): + self.tagName = name + + def getAttributeType(self, aname): + return _no_type + + def getAttributeTypeNS(self, namespaceURI, localName): + return _no_type + + def isElementContent(self): + return False + + def isEmpty(self): + """Returns true iff this element is declared to have an EMPTY + content model.""" + return False + + def isId(self, aname): + """Returns true iff the named attribute is a DTD-style ID.""" + return False + + def isIdNS(self, namespaceURI, localName): + """Returns true iff the identified attribute is a DTD-style ID.""" + return False + + def __getstate__(self): + return self.tagName + + def __setstate__(self, state): + self.tagName = state + +def _clear_id_cache(node): + if node.nodeType == Node.DOCUMENT_NODE: + node._id_cache.clear() + node._id_search_stack = None + elif _in_document(node): + node.ownerDocument._id_cache.clear() + node.ownerDocument._id_search_stack= None + +class Document(Node, DocumentLS): + __slots__ = ('_elem_info', 'doctype', + '_id_search_stack', 'childNodes', '_id_cache') + _child_node_types = (Node.ELEMENT_NODE, Node.PROCESSING_INSTRUCTION_NODE, + Node.COMMENT_NODE, Node.DOCUMENT_TYPE_NODE) + + implementation = DOMImplementation() + nodeType = Node.DOCUMENT_NODE + nodeName = "#document" + nodeValue = None + attributes = None + parentNode = None + previousSibling = nextSibling = None + + + # Document attributes from Level 3 (WD 9 April 2002) + + actualEncoding = None + encoding = None + standalone = None + version = None + strictErrorChecking = False + errorHandler = None + documentURI = None + + _magic_id_count = 0 + + def __init__(self): + self.doctype = None + self.childNodes = NodeList() + # mapping of (namespaceURI, localName) -> ElementInfo + # and tagName -> ElementInfo + self._elem_info = {} + self._id_cache = {} + self._id_search_stack = None + + def _get_elem_info(self, element): + if element.namespaceURI: + key = element.namespaceURI, element.localName + else: + key = element.tagName + return self._elem_info.get(key) + + def _get_actualEncoding(self): + return self.actualEncoding + + def _get_doctype(self): + return self.doctype + + def _get_documentURI(self): + return self.documentURI + + def _get_encoding(self): + return self.encoding + + def _get_errorHandler(self): + return self.errorHandler + + def _get_standalone(self): + return self.standalone + + def _get_strictErrorChecking(self): + return self.strictErrorChecking + + def _get_version(self): + return self.version + + def appendChild(self, node): + if node.nodeType not in self._child_node_types: + raise xml.dom.HierarchyRequestErr( + "%s cannot be child of %s" % (repr(node), repr(self))) + if node.parentNode is not None: + # This needs to be done before the next test since this + # may *be* the document element, in which case it should + # end up re-ordered to the end. + node.parentNode.removeChild(node) + + if node.nodeType == Node.ELEMENT_NODE \ + and self._get_documentElement(): + raise xml.dom.HierarchyRequestErr( + "two document elements disallowed") + return Node.appendChild(self, node) + + def removeChild(self, oldChild): + try: + self.childNodes.remove(oldChild) + except ValueError: + raise xml.dom.NotFoundErr() + oldChild.nextSibling = oldChild.previousSibling = None + oldChild.parentNode = None + if self.documentElement is oldChild: + self.documentElement = None + + return oldChild + + def _get_documentElement(self): + for node in self.childNodes: + if node.nodeType == Node.ELEMENT_NODE: + return node + + def unlink(self): + if self.doctype is not None: + self.doctype.unlink() + self.doctype = None + Node.unlink(self) + + def cloneNode(self, deep): + if not deep: + return None + clone = self.implementation.createDocument(None, None, None) + clone.encoding = self.encoding + clone.standalone = self.standalone + clone.version = self.version + for n in self.childNodes: + childclone = _clone_node(n, deep, clone) + assert childclone.ownerDocument.isSameNode(clone) + clone.childNodes.append(childclone) + if childclone.nodeType == Node.DOCUMENT_NODE: + assert clone.documentElement is None + elif childclone.nodeType == Node.DOCUMENT_TYPE_NODE: + assert clone.doctype is None + clone.doctype = childclone + childclone.parentNode = clone + self._call_user_data_handler(xml.dom.UserDataHandler.NODE_CLONED, + self, clone) + return clone + + def createDocumentFragment(self): + d = DocumentFragment() + d.ownerDocument = self + return d + + def createElement(self, tagName): + e = Element(tagName) + e.ownerDocument = self + return e + + def createTextNode(self, data): + if not isinstance(data, str): + raise TypeError("node contents must be a string") + t = Text() + t.data = data + t.ownerDocument = self + return t + + def createCDATASection(self, data): + if not isinstance(data, str): + raise TypeError("node contents must be a string") + c = CDATASection() + c.data = data + c.ownerDocument = self + return c + + def createComment(self, data): + c = Comment(data) + c.ownerDocument = self + return c + + def createProcessingInstruction(self, target, data): + p = ProcessingInstruction(target, data) + p.ownerDocument = self + return p + + def createAttribute(self, qName): + a = Attr(qName) + a.ownerDocument = self + a.value = "" + return a + + def createElementNS(self, namespaceURI, qualifiedName): + prefix, localName = _nssplit(qualifiedName) + e = Element(qualifiedName, namespaceURI, prefix) + e.ownerDocument = self + return e + + def createAttributeNS(self, namespaceURI, qualifiedName): + prefix, localName = _nssplit(qualifiedName) + a = Attr(qualifiedName, namespaceURI, localName, prefix) + a.ownerDocument = self + a.value = "" + return a + + # A couple of implementation-specific helpers to create node types + # not supported by the W3C DOM specs: + + def _create_entity(self, name, publicId, systemId, notationName): + e = Entity(name, publicId, systemId, notationName) + e.ownerDocument = self + return e + + def _create_notation(self, name, publicId, systemId): + n = Notation(name, publicId, systemId) + n.ownerDocument = self + return n + + def getElementById(self, id): + if id in self._id_cache: + return self._id_cache[id] + if not (self._elem_info or self._magic_id_count): + return None + + stack = self._id_search_stack + if stack is None: + # we never searched before, or the cache has been cleared + stack = [self.documentElement] + self._id_search_stack = stack + elif not stack: + # Previous search was completed and cache is still valid; + # no matching node. + return None + + result = None + while stack: + node = stack.pop() + # add child elements to stack for continued searching + stack.extend([child for child in node.childNodes + if child.nodeType in _nodeTypes_with_children]) + # check this node + info = self._get_elem_info(node) + if info: + # We have to process all ID attributes before + # returning in order to get all the attributes set to + # be IDs using Element.setIdAttribute*(). + for attr in node.attributes.values(): + if attr.namespaceURI: + if info.isIdNS(attr.namespaceURI, attr.localName): + self._id_cache[attr.value] = node + if attr.value == id: + result = node + elif not node._magic_id_nodes: + break + elif info.isId(attr.name): + self._id_cache[attr.value] = node + if attr.value == id: + result = node + elif not node._magic_id_nodes: + break + elif attr._is_id: + self._id_cache[attr.value] = node + if attr.value == id: + result = node + elif node._magic_id_nodes == 1: + break + elif node._magic_id_nodes: + for attr in node.attributes.values(): + if attr._is_id: + self._id_cache[attr.value] = node + if attr.value == id: + result = node + if result is not None: + break + return result + + def getElementsByTagName(self, name): + return _get_elements_by_tagName_helper(self, name, NodeList()) + + def getElementsByTagNameNS(self, namespaceURI, localName): + return _get_elements_by_tagName_ns_helper( + self, namespaceURI, localName, NodeList()) + + def isSupported(self, feature, version): + return self.implementation.hasFeature(feature, version) + + def importNode(self, node, deep): + if node.nodeType == Node.DOCUMENT_NODE: + raise xml.dom.NotSupportedErr("cannot import document nodes") + elif node.nodeType == Node.DOCUMENT_TYPE_NODE: + raise xml.dom.NotSupportedErr("cannot import document type nodes") + return _clone_node(node, deep, self) + + def writexml(self, writer, indent="", addindent="", newl="", encoding=None): + if encoding is None: + writer.write(''+newl) + else: + writer.write('%s' % ( + encoding, newl)) + for node in self.childNodes: + node.writexml(writer, indent, addindent, newl) + + # DOM Level 3 (WD 9 April 2002) + + def renameNode(self, n, namespaceURI, name): + if n.ownerDocument is not self: + raise xml.dom.WrongDocumentErr( + "cannot rename nodes from other documents;\n" + "expected %s,\nfound %s" % (self, n.ownerDocument)) + if n.nodeType not in (Node.ELEMENT_NODE, Node.ATTRIBUTE_NODE): + raise xml.dom.NotSupportedErr( + "renameNode() only applies to element and attribute nodes") + if namespaceURI != EMPTY_NAMESPACE: + if ':' in name: + prefix, localName = name.split(':', 1) + if ( prefix == "xmlns" + and namespaceURI != xml.dom.XMLNS_NAMESPACE): + raise xml.dom.NamespaceErr( + "illegal use of 'xmlns' prefix") + else: + if ( name == "xmlns" + and namespaceURI != xml.dom.XMLNS_NAMESPACE + and n.nodeType == Node.ATTRIBUTE_NODE): + raise xml.dom.NamespaceErr( + "illegal use of the 'xmlns' attribute") + prefix = None + localName = name + else: + prefix = None + localName = None + if n.nodeType == Node.ATTRIBUTE_NODE: + element = n.ownerElement + if element is not None: + is_id = n._is_id + element.removeAttributeNode(n) + else: + element = None + n.prefix = prefix + n._localName = localName + n.namespaceURI = namespaceURI + n.nodeName = name + if n.nodeType == Node.ELEMENT_NODE: + n.tagName = name + else: + # attribute node + n.name = name + if element is not None: + element.setAttributeNode(n) + if is_id: + element.setIdAttributeNode(n) + # It's not clear from a semantic perspective whether we should + # call the user data handlers for the NODE_RENAMED event since + # we're re-using the existing node. The draft spec has been + # interpreted as meaning "no, don't call the handler unless a + # new node is created." + return n + +defproperty(Document, "documentElement", + doc="Top-level element of this document.") + + +def _clone_node(node, deep, newOwnerDocument): + """ + Clone a node and give it the new owner document. + Called by Node.cloneNode and Document.importNode + """ + if node.ownerDocument.isSameNode(newOwnerDocument): + operation = xml.dom.UserDataHandler.NODE_CLONED + else: + operation = xml.dom.UserDataHandler.NODE_IMPORTED + if node.nodeType == Node.ELEMENT_NODE: + clone = newOwnerDocument.createElementNS(node.namespaceURI, + node.nodeName) + for attr in node.attributes.values(): + clone.setAttributeNS(attr.namespaceURI, attr.nodeName, attr.value) + a = clone.getAttributeNodeNS(attr.namespaceURI, attr.localName) + a.specified = attr.specified + + if deep: + for child in node.childNodes: + c = _clone_node(child, deep, newOwnerDocument) + clone.appendChild(c) + + elif node.nodeType == Node.DOCUMENT_FRAGMENT_NODE: + clone = newOwnerDocument.createDocumentFragment() + if deep: + for child in node.childNodes: + c = _clone_node(child, deep, newOwnerDocument) + clone.appendChild(c) + + elif node.nodeType == Node.TEXT_NODE: + clone = newOwnerDocument.createTextNode(node.data) + elif node.nodeType == Node.CDATA_SECTION_NODE: + clone = newOwnerDocument.createCDATASection(node.data) + elif node.nodeType == Node.PROCESSING_INSTRUCTION_NODE: + clone = newOwnerDocument.createProcessingInstruction(node.target, + node.data) + elif node.nodeType == Node.COMMENT_NODE: + clone = newOwnerDocument.createComment(node.data) + elif node.nodeType == Node.ATTRIBUTE_NODE: + clone = newOwnerDocument.createAttributeNS(node.namespaceURI, + node.nodeName) + clone.specified = True + clone.value = node.value + elif node.nodeType == Node.DOCUMENT_TYPE_NODE: + assert node.ownerDocument is not newOwnerDocument + operation = xml.dom.UserDataHandler.NODE_IMPORTED + clone = newOwnerDocument.implementation.createDocumentType( + node.name, node.publicId, node.systemId) + clone.ownerDocument = newOwnerDocument + if deep: + clone.entities._seq = [] + clone.notations._seq = [] + for n in node.notations._seq: + notation = Notation(n.nodeName, n.publicId, n.systemId) + notation.ownerDocument = newOwnerDocument + clone.notations._seq.append(notation) + if hasattr(n, '_call_user_data_handler'): + n._call_user_data_handler(operation, n, notation) + for e in node.entities._seq: + entity = Entity(e.nodeName, e.publicId, e.systemId, + e.notationName) + entity.actualEncoding = e.actualEncoding + entity.encoding = e.encoding + entity.version = e.version + entity.ownerDocument = newOwnerDocument + clone.entities._seq.append(entity) + if hasattr(e, '_call_user_data_handler'): + e._call_user_data_handler(operation, e, entity) + else: + # Note the cloning of Document and DocumentType nodes is + # implementation specific. minidom handles those cases + # directly in the cloneNode() methods. + raise xml.dom.NotSupportedErr("Cannot clone node %s" % repr(node)) + + # Check for _call_user_data_handler() since this could conceivably + # used with other DOM implementations (one of the FourThought + # DOMs, perhaps?). + if hasattr(node, '_call_user_data_handler'): + node._call_user_data_handler(operation, node, clone) + return clone + + +def _nssplit(qualifiedName): + fields = qualifiedName.split(':', 1) + if len(fields) == 2: + return fields + else: + return (None, fields[0]) + + +def _do_pulldom_parse(func, args, kwargs): + events = func(*args, **kwargs) + toktype, rootNode = events.getEvent() + events.expandNode(rootNode) + events.clear() + return rootNode + +def parse(file, parser=None, bufsize=None): + """Parse a file into a DOM by filename or file object.""" + if parser is None and not bufsize: + from xml.dom import expatbuilder + return expatbuilder.parse(file) + else: + from xml.dom import pulldom + return _do_pulldom_parse(pulldom.parse, (file,), + {'parser': parser, 'bufsize': bufsize}) + +def parseString(string, parser=None): + """Parse a file into a DOM from a string.""" + if parser is None: + from xml.dom import expatbuilder + return expatbuilder.parseString(string) + else: + from xml.dom import pulldom + return _do_pulldom_parse(pulldom.parseString, (string,), + {'parser': parser}) + +def getDOMImplementation(features=None): + if features: + if isinstance(features, str): + features = domreg._parse_feature_string(features) + for f, v in features: + if not Document.implementation.hasFeature(f, v): + return None + return Document.implementation diff --git a/benchmarks/benchmarks/nbody.py b/benchmarks/benchmarks/nbody.py index 2f36bdf0e3..b9ab142df9 100644 --- a/benchmarks/benchmarks/nbody.py +++ b/benchmarks/benchmarks/nbody.py @@ -89,7 +89,7 @@ def report_energy(bodies=SYSTEM, pairs=PAIRS, e=0.0): e -= (m1 * m2) / ((dx * dx + dy * dy + dz * dz) ** 0.5) for (r, [vx, vy, vz], m) in bodies: e += m * (vx * vx + vy * vy + vz * vz) / 2. - print(f"{e}") + # print(f"{e}") def offset_momentum(ref, bodies=SYSTEM, px=0.0, py=0.0, pz=0.0): for (r, [vx, vy, vz], m) in bodies: From de3900bcad3253ad52e68033d0caf75d8d59d7a4 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 19 Apr 2019 15:17:03 -0500 Subject: [PATCH 377/884] Upgrade deps --- wasm/tests/Pipfile.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/wasm/tests/Pipfile.lock b/wasm/tests/Pipfile.lock index 7f8b97f879..32e0040b00 100644 --- a/wasm/tests/Pipfile.lock +++ b/wasm/tests/Pipfile.lock @@ -32,11 +32,11 @@ }, "more-itertools": { "hashes": [ - "sha256:0125e8f60e9e031347105eb1682cef932f5e97d7b9a1a28d9bf00c22a5daef40", - "sha256:590044e3942351a1bdb1de960b739ff4ce277960f2425ad4509446dbace8d9d1" + "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7", + "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a" ], "markers": "python_version > '2.7'", - "version": "==6.0.0" + "version": "==7.0.0" }, "pluggy": { "hashes": [ @@ -54,11 +54,11 @@ }, "pytest": { "hashes": [ - "sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523", - "sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4" + "sha256:3773f4c235918987d51daf1db66d51c99fac654c81d6f2f709a046ab446d5e5d", + "sha256:b7802283b70ca24d7119b32915efa7c409982f59913c1a6c0640aacf118b95f5" ], "index": "pypi", - "version": "==4.3.1" + "version": "==4.4.1" }, "selenium": { "hashes": [ @@ -77,10 +77,10 @@ }, "urllib3": { "hashes": [ - "sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39", - "sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22" + "sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0", + "sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3" ], - "version": "==1.24.1" + "version": "==1.24.2" } }, "develop": {} From 3828652da61db2775e18c099d8b01fe958be6e08 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 21 Apr 2019 11:17:36 +0300 Subject: [PATCH 378/884] Add os.scandir --- vm/src/stdlib/os.rs | 76 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index fae9d6adb7..c103b3d461 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::fs::File; use std::fs::OpenOptions; use std::io::{ErrorKind, Read, Write}; @@ -10,9 +11,11 @@ use crate::obj::objbytes::PyBytesRef; use crate::obj::objdict::PyDictRef; use crate::obj::objint; use crate::obj::objint::PyIntRef; +use crate::obj::objiter; use crate::obj::objstr; use crate::obj::objstr::PyStringRef; -use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult}; +use crate::obj::objtype::PyClassRef; +use crate::pyobject::{ItemProtocol, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; #[cfg(unix)] @@ -190,6 +193,65 @@ fn _os_environ(vm: &VirtualMachine) -> PyDictRef { environ } +#[derive(Debug)] +struct DirEntry { + entry: fs::DirEntry, +} + +type DirEntryRef = PyRef; + +impl PyValue for DirEntry { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("os", "DirEntry") + } +} + +impl DirEntryRef { + fn name(self, _vm: &VirtualMachine) -> String { + self.entry.file_name().into_string().unwrap() + } +} + +#[derive(Debug)] +pub struct ScandirIterator { + entries: RefCell, +} + +impl PyValue for ScandirIterator { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("os", "ScandirIter") + } +} + +type ScandirIteratorRef = PyRef; + +impl ScandirIteratorRef { + fn next(self, vm: &VirtualMachine) -> PyResult { + match self.entries.borrow_mut().next() { + Some(entry) => match entry { + Ok(entry) => Ok(DirEntry { entry }.into_ref(vm).into_object()), + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + None => Err(objiter::new_stop_iteration(vm)), + } + } + + fn iter(self, _vm: &VirtualMachine) -> Self { + self + } +} + +fn os_scandir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + match fs::read_dir(&path.value) { + Ok(iter) => Ok(ScandirIterator { + entries: RefCell::new(iter), + } + .into_ref(vm) + .into_object()), + Err(s) => Err(vm.new_os_error(s.to_string())), + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -201,6 +263,15 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let environ = _os_environ(vm); + let scandir_iter = py_class!(ctx, "ScandirIter", ctx.object(), { + "__iter__" => ctx.new_rustfunc(ScandirIteratorRef::iter), + "__next__" => ctx.new_rustfunc(ScandirIteratorRef::next), + }); + + let dir_entry = py_class!(ctx, "DirEntry", ctx.object(), { + "name" => ctx.new_rustfunc(DirEntryRef::name), + }); + py_module!(vm, "_os", { "open" => ctx.new_rustfunc(os_open), "close" => ctx.new_rustfunc(os_close), @@ -217,6 +288,9 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "unsetenv" => ctx.new_rustfunc(os_unsetenv), "environ" => environ, "name" => ctx.new_str(os_name), + "scandir" => ctx.new_rustfunc(os_scandir), + "ScandirIter" => scandir_iter, + "DirEntry" => dir_entry, "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), "O_RDWR" => ctx.new_int(2), From 858a6d03b097863384d9b9c8f2e4f1e7e739b310 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 21 Apr 2019 11:42:57 +0300 Subject: [PATCH 379/884] Add DirEntry.{name, path} --- tests/snippets/stdlib_os.py | 14 ++++++++++++++ vm/src/stdlib/os.rs | 7 ++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 4d64fc7930..237acf94fb 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -56,6 +56,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): FILE_NAME = "test1" +FILE_NAME2 = "test2" CONTENT = b"testing" CONTENT2 = b"rustpython" CONTENT3 = b"BOYA" @@ -73,3 +74,16 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert os.read(fd, len(CONTENT2)) == CONTENT2 assert os.read(fd, len(CONTENT3)) == CONTENT3 os.close(fd) + + + fname2 = tmpdir + os.sep + FILE_NAME2 + with open(fname2, "wb"): + pass + files = set() + paths = set() + for dir_entry in os.scandir(tmpdir): + files.add(dir_entry.name) + paths.add(dir_entry.path) + + assert files == set([FILE_NAME, FILE_NAME2]) + assert paths == set([fname, fname2]) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index c103b3d461..395e269d59 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -210,6 +210,10 @@ impl DirEntryRef { fn name(self, _vm: &VirtualMachine) -> String { self.entry.file_name().into_string().unwrap() } + + fn path(self, _vm: &VirtualMachine) -> String { + self.entry.path().to_str().unwrap().to_string() + } } #[derive(Debug)] @@ -269,7 +273,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { }); let dir_entry = py_class!(ctx, "DirEntry", ctx.object(), { - "name" => ctx.new_rustfunc(DirEntryRef::name), + "name" => ctx.new_property(DirEntryRef::name), + "path" => ctx.new_property(DirEntryRef::path), }); py_module!(vm, "_os", { From 548f3f34c48ff55e63223fdb1a99f44fbae627fb Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 21 Apr 2019 12:04:12 +0300 Subject: [PATCH 380/884] Add DirEntry.{is_dir} --- tests/snippets/stdlib_os.py | 17 ++++++++++++----- vm/src/stdlib/os.rs | 9 +++++++++ 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 237acf94fb..4bc50ee41e 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -57,6 +57,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): FILE_NAME = "test1" FILE_NAME2 = "test2" +FOLDER = "dir1" CONTENT = b"testing" CONTENT2 = b"rustpython" CONTENT3 = b"BOYA" @@ -75,15 +76,21 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert os.read(fd, len(CONTENT3)) == CONTENT3 os.close(fd) - fname2 = tmpdir + os.sep + FILE_NAME2 with open(fname2, "wb"): pass - files = set() + folder = tmpdir + os.sep + FOLDER + os.mkdir(folder) + + names = set() paths = set() + dirs = set() for dir_entry in os.scandir(tmpdir): - files.add(dir_entry.name) + names.add(dir_entry.name) paths.add(dir_entry.path) + if dir_entry.is_dir(): + dirs.add(dir_entry.name) - assert files == set([FILE_NAME, FILE_NAME2]) - assert paths == set([fname, fname2]) + assert names == set([FILE_NAME, FILE_NAME2, FOLDER]) + assert paths == set([fname, fname2, folder]) + assert dirs == set([FOLDER]) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 395e269d59..43c65129af 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -214,6 +214,14 @@ impl DirEntryRef { fn path(self, _vm: &VirtualMachine) -> String { self.entry.path().to_str().unwrap().to_string() } + + fn is_dir(self, vm: &VirtualMachine) -> PyResult { + Ok(self + .entry + .file_type() + .map_err(|s| vm.new_os_error(s.to_string()))? + .is_dir()) + } } #[derive(Debug)] @@ -275,6 +283,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let dir_entry = py_class!(ctx, "DirEntry", ctx.object(), { "name" => ctx.new_property(DirEntryRef::name), "path" => ctx.new_property(DirEntryRef::path), + "is_dir" => ctx.new_rustfunc(DirEntryRef::is_dir), }); py_module!(vm, "_os", { From 64e6ea001662ad93b6bc4757c8331e05e2aed4ed Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 21 Apr 2019 12:06:49 +0300 Subject: [PATCH 381/884] Add DirEntry.{is_file} --- tests/snippets/stdlib_os.py | 4 ++++ vm/src/stdlib/os.rs | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 4bc50ee41e..0de5b52b61 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -85,12 +85,16 @@ def __exit__(self, exc_type, exc_val, exc_tb): names = set() paths = set() dirs = set() + files = set() for dir_entry in os.scandir(tmpdir): names.add(dir_entry.name) paths.add(dir_entry.path) if dir_entry.is_dir(): dirs.add(dir_entry.name) + if dir_entry.is_file(): + files.add(dir_entry.name) assert names == set([FILE_NAME, FILE_NAME2, FOLDER]) assert paths == set([fname, fname2, folder]) assert dirs == set([FOLDER]) + assert files == set([FILE_NAME, FILE_NAME2]) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 43c65129af..9717c8f22e 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -222,6 +222,14 @@ impl DirEntryRef { .map_err(|s| vm.new_os_error(s.to_string()))? .is_dir()) } + + fn is_file(self, vm: &VirtualMachine) -> PyResult { + Ok(self + .entry + .file_type() + .map_err(|s| vm.new_os_error(s.to_string()))? + .is_file()) + } } #[derive(Debug)] @@ -284,6 +292,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "name" => ctx.new_property(DirEntryRef::name), "path" => ctx.new_property(DirEntryRef::path), "is_dir" => ctx.new_rustfunc(DirEntryRef::is_dir), + "is_file" => ctx.new_rustfunc(DirEntryRef::is_file), }); py_module!(vm, "_os", { From 3986e2f87d5c6396f8d7acc55c3334fe9eb5b8c0 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 21 Apr 2019 16:41:36 +0300 Subject: [PATCH 382/884] Add set.isdisjoint --- tests/snippets/set.py | 8 ++++++++ vm/src/obj/objset.rs | 24 ++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index 393db07549..ba20fbae62 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -79,6 +79,10 @@ def __hash__(self): assert set([1,2,3]) ^ set([1,2,3,4,5]) == set([4,5]) assert_raises(TypeError, lambda: set([1,2,3]) ^ [1,2,3,4,5]) +assert set([1,2,3]).isdisjoint(set([5,6])) == True +assert set([1,2,3]).isdisjoint(set([2,5,6])) == False +assert set([1,2,3]).isdisjoint([5,6]) == True + assert_raises(TypeError, lambda: set([[]])) assert_raises(TypeError, lambda: set().add([])) @@ -227,6 +231,10 @@ def __hash__(self): assert frozenset([1,2,3]) ^ frozenset([1,2,3,4,5]) == frozenset([4,5]) assert_raises(TypeError, lambda: frozenset([1,2,3]) ^ [1,2,3,4,5]) +assert frozenset([1,2,3]).isdisjoint(frozenset([5,6])) == True +assert frozenset([1,2,3]).isdisjoint(frozenset([2,5,6])) == False +assert frozenset([1,2,3]).isdisjoint([5,6]) == True + assert_raises(TypeError, lambda: frozenset([[]])) a = frozenset([1,2,3]) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index b8f9f6d3b5..b4f3bfabc2 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -222,6 +222,16 @@ impl PySetInner { Ok(new_inner) } + fn isdisjoint(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + for item in other.iter(vm)? { + let obj = item?; + if self.contains(obj.clone(), vm)? { + return Ok(false); + } + } + Ok(true) + } + fn iter(&self, vm: &VirtualMachine) -> PyListIterator { let items = self.elements.values().cloned().collect(); let set_list = vm.ctx.new_list(items); @@ -426,6 +436,10 @@ impl PySetRef { )) } + fn isdisjoint(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().isdisjoint(other, vm) + } + fn or(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.union(other.iterable, vm) } @@ -633,6 +647,10 @@ impl PyFrozenSetRef { )) } + fn isdisjoint(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner.isdisjoint(other, vm) + } + fn or(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.union(other.iterable, vm) } @@ -788,7 +806,8 @@ pub fn init(context: &PyContext) { "__isub__" => context.new_rustfunc(PySetRef::isub), "symmetric_difference_update" => context.new_rustfunc(PySetRef::symmetric_difference_update), "__ixor__" => context.new_rustfunc(PySetRef::ixor), - "__iter__" => context.new_rustfunc(PySetRef::iter) + "__iter__" => context.new_rustfunc(PySetRef::iter), + "isdisjoint" => context.new_rustfunc(PySetRef::isdisjoint), }); let frozenset_type = &context.frozenset_type; @@ -819,6 +838,7 @@ pub fn init(context: &PyContext) { "__doc__" => context.new_str(frozenset_doc.to_string()), "__repr__" => context.new_rustfunc(PyFrozenSetRef::repr), "copy" => context.new_rustfunc(PyFrozenSetRef::copy), - "__iter__" => context.new_rustfunc(PyFrozenSetRef::iter) + "__iter__" => context.new_rustfunc(PyFrozenSetRef::iter), + "isdisjoint" => context.new_rustfunc(PyFrozenSetRef::isdisjoint), }); } From 206ba4dbfb3afa30cf9835d30edf625c5a89907d Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 21 Apr 2019 17:20:33 +0300 Subject: [PATCH 383/884] Change socket to new args style --- vm/src/stdlib/socket.rs | 192 +++++++++++++++++----------------------- 1 file changed, 79 insertions(+), 113 deletions(-) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 9d7a703e1f..96d3829709 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -7,9 +7,10 @@ use std::ops::Deref; use crate::function::PyFuncArgs; use crate::obj::objbytes; +use crate::obj::objbytes::PyBytesRef; use crate::obj::objint; -use crate::obj::objsequence::get_elements; use crate::obj::objstr; +use crate::obj::objtuple::PyTupleRef; use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, TryFromObject}; use crate::vm::VirtualMachine; @@ -167,88 +168,94 @@ fn get_socket<'a>(obj: &'a PyObjectRef) -> impl Deref + 'a { type SocketRef = PyRef; -fn socket_new( - cls: PyClassRef, - family: AddressFamily, - kind: SocketKind, - vm: &VirtualMachine, -) -> PyResult { - Socket::new(family, kind).into_ref_with_type(vm, cls) -} - -fn socket_connect(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, None), (address, Some(vm.ctx.tuple_type()))] - ); - - let address_string = get_address_string(vm, address)?; +impl SocketRef { + fn new( + cls: PyClassRef, + family: AddressFamily, + kind: SocketKind, + vm: &VirtualMachine, + ) -> PyResult { + Socket::new(family, kind).into_ref_with_type(vm, cls) + } - let socket = get_socket(zelf); + fn connect(self, address: PyTupleRef, vm: &VirtualMachine) -> PyResult { + let address_string = get_address_string(vm, address)?; - match socket.socket_kind { - SocketKind::Stream => match TcpStream::connect(address_string) { - Ok(stream) => { - socket - .con - .borrow_mut() - .replace(Connection::TcpStream(stream)); - Ok(vm.get_none()) - } - Err(s) => Err(vm.new_os_error(s.to_string())), - }, - SocketKind::Dgram => { - if let Some(Connection::UdpSocket(con)) = socket.con.borrow().as_ref() { - match con.connect(address_string) { - Ok(_) => Ok(vm.get_none()), - Err(s) => Err(vm.new_os_error(s.to_string())), + match self.socket_kind { + SocketKind::Stream => match TcpStream::connect(address_string) { + Ok(stream) => { + self.con.borrow_mut().replace(Connection::TcpStream(stream)); + Ok(vm.get_none()) + } + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + SocketKind::Dgram => { + if let Some(Connection::UdpSocket(con)) = self.con.borrow().as_ref() { + match con.connect(address_string) { + Ok(_) => Ok(vm.get_none()), + Err(s) => Err(vm.new_os_error(s.to_string())), + } + } else { + Err(vm.new_type_error("".to_string())) } - } else { - Err(vm.new_type_error("".to_string())) } } } -} -fn socket_bind(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, None), (address, Some(vm.ctx.tuple_type()))] - ); + fn bind(self, address: PyTupleRef, vm: &VirtualMachine) -> PyResult { + let address_string = get_address_string(vm, address)?; - let address_string = get_address_string(vm, address)?; + match self.socket_kind { + SocketKind::Stream => match TcpListener::bind(address_string) { + Ok(stream) => { + self.con + .borrow_mut() + .replace(Connection::TcpListener(stream)); + Ok(vm.get_none()) + } + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + SocketKind::Dgram => match UdpSocket::bind(address_string) { + Ok(dgram) => { + self.con.borrow_mut().replace(Connection::UdpSocket(dgram)); + Ok(vm.get_none()) + } + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + } + } - let socket = get_socket(zelf); + fn sendto(self, bytes: PyBytesRef, address: PyTupleRef, vm: &VirtualMachine) -> PyResult { + let address_string = get_address_string(vm, address)?; - match socket.socket_kind { - SocketKind::Stream => match TcpListener::bind(address_string) { - Ok(stream) => { - socket - .con - .borrow_mut() - .replace(Connection::TcpListener(stream)); - Ok(vm.get_none()) - } - Err(s) => Err(vm.new_os_error(s.to_string())), - }, - SocketKind::Dgram => match UdpSocket::bind(address_string) { - Ok(dgram) => { - socket - .con - .borrow_mut() - .replace(Connection::UdpSocket(dgram)); - Ok(vm.get_none()) + match self.socket_kind { + SocketKind::Dgram => { + if let Some(v) = self.con.borrow().as_ref() { + return match v.send_to(&bytes, address_string) { + Ok(_) => Ok(vm.get_none()), + Err(s) => Err(vm.new_os_error(s.to_string())), + }; + } + // Doing implicit bind + match UdpSocket::bind("0.0.0.0:0") { + Ok(dgram) => match dgram.send_to(&bytes, address_string) { + Ok(_) => { + self.con.borrow_mut().replace(Connection::UdpSocket(dgram)); + Ok(vm.get_none()) + } + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + Err(s) => Err(vm.new_os_error(s.to_string())), + } } - Err(s) => Err(vm.new_os_error(s.to_string())), - }, + _ => Err(vm.new_not_implemented_error("".to_string())), + } } } -fn get_address_string(vm: &VirtualMachine, address: &PyObjectRef) -> Result { +fn get_address_string(vm: &VirtualMachine, address: PyTupleRef) -> Result { let args = PyFuncArgs { - args: get_elements(address).to_vec(), + args: address.elements.borrow().to_vec(), kwargs: vec![], }; arg_check!( @@ -365,47 +372,6 @@ fn socket_send(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } -fn socket_sendto(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [ - (zelf, None), - (bytes, Some(vm.ctx.bytes_type())), - (address, Some(vm.ctx.tuple_type())) - ] - ); - let address_string = get_address_string(vm, address)?; - - let socket = get_socket(zelf); - - match socket.socket_kind { - SocketKind::Dgram => { - if let Some(v) = socket.con.borrow().as_ref() { - return match v.send_to(&objbytes::get_value(&bytes), address_string) { - Ok(_) => Ok(vm.get_none()), - Err(s) => Err(vm.new_os_error(s.to_string())), - }; - } - // Doing implicit bind - match UdpSocket::bind("0.0.0.0:0") { - Ok(dgram) => match dgram.send_to(&objbytes::get_value(&bytes), address_string) { - Ok(_) => { - socket - .con - .borrow_mut() - .replace(Connection::UdpSocket(dgram)); - Ok(vm.get_none()) - } - Err(s) => Err(vm.new_os_error(s.to_string())), - }, - Err(s) => Err(vm.new_os_error(s.to_string())), - } - } - _ => Err(vm.new_not_implemented_error("".to_string())), - } -} - fn socket_close(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(zelf, None)]); @@ -452,16 +418,16 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let socket = py_class!(ctx, "socket", ctx.object(), { - "__new__" => ctx.new_rustfunc(socket_new), - "connect" => ctx.new_rustfunc(socket_connect), + "__new__" => ctx.new_rustfunc(SocketRef::new), + "connect" => ctx.new_rustfunc(SocketRef::connect), "recv" => ctx.new_rustfunc(socket_recv), "send" => ctx.new_rustfunc(socket_send), - "bind" => ctx.new_rustfunc(socket_bind), + "bind" => ctx.new_rustfunc(SocketRef::bind), "accept" => ctx.new_rustfunc(socket_accept), "listen" => ctx.new_rustfunc(socket_listen), "close" => ctx.new_rustfunc(socket_close), "getsockname" => ctx.new_rustfunc(socket_getsockname), - "sendto" => ctx.new_rustfunc(socket_sendto), + "sendto" => ctx.new_rustfunc(SocketRef::sendto), "recvfrom" => ctx.new_rustfunc(socket_recvfrom), "fileno" => ctx.new_rustfunc(socket_fileno), }); From 9954361df9fc7565efeaddf1d57ceeaca7567b29 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 21 Apr 2019 17:28:41 +0300 Subject: [PATCH 384/884] Move more socket methods to new arg style --- vm/src/stdlib/socket.rs | 99 +++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 58 deletions(-) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 96d3829709..dc165d3162 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -9,6 +9,7 @@ use crate::function::PyFuncArgs; use crate::obj::objbytes; use crate::obj::objbytes::PyBytesRef; use crate::obj::objint; +use crate::obj::objint::PyIntRef; use crate::obj::objstr; use crate::obj::objtuple::PyTupleRef; use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, TryFromObject}; @@ -251,6 +252,43 @@ impl SocketRef { _ => Err(vm.new_not_implemented_error("".to_string())), } } + + fn listen(self, _num: PyIntRef, _vm: &VirtualMachine) -> () {} + + fn accept(self, vm: &VirtualMachine) -> PyResult { + let ret = match self.con.borrow_mut().as_mut() { + Some(v) => v.accept(), + None => return Err(vm.new_type_error("".to_string())), + }; + + let (tcp_stream, addr) = match ret { + Ok((socket, addr)) => (socket, addr), + Err(s) => return Err(vm.new_os_error(s.to_string())), + }; + + let socket = Socket { + address_family: self.address_family, + socket_kind: self.socket_kind, + con: RefCell::new(Some(Connection::TcpStream(tcp_stream))), + } + .into_ref(vm); + + let addr_tuple = get_addr_tuple(vm, addr)?; + + Ok(vm.ctx.new_tuple(vec![socket.into_object(), addr_tuple])) + } + + fn recv(self, bufsize: PyIntRef, vm: &VirtualMachine) -> PyResult { + let mut buffer = vec![0u8; bufsize.as_bigint().to_usize().unwrap()]; + match self.con.borrow_mut().as_mut() { + Some(v) => match v.read_exact(&mut buffer) { + Ok(_) => (), + Err(s) => return Err(vm.new_os_error(s.to_string())), + }, + None => return Err(vm.new_type_error("".to_string())), + }; + Ok(vm.ctx.new_bytes(buffer)) + } } fn get_address_string(vm: &VirtualMachine, address: PyTupleRef) -> Result { @@ -274,61 +312,6 @@ fn get_address_string(vm: &VirtualMachine, address: PyTupleRef) -> Result PyResult { - arg_check!( - vm, - args, - required = [(_zelf, None), (_num, Some(vm.ctx.int_type()))] - ); - Ok(vm.get_none()) -} - -fn socket_accept(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, None)]); - - let socket = get_socket(zelf); - - let ret = match socket.con.borrow_mut().as_mut() { - Some(v) => v.accept(), - None => return Err(vm.new_type_error("".to_string())), - }; - - let (tcp_stream, addr) = match ret { - Ok((socket, addr)) => (socket, addr), - Err(s) => return Err(vm.new_os_error(s.to_string())), - }; - - let socket = Socket { - address_family: socket.address_family, - socket_kind: socket.socket_kind, - con: RefCell::new(Some(Connection::TcpStream(tcp_stream))), - } - .into_ref(vm); - - let addr_tuple = get_addr_tuple(vm, addr)?; - - Ok(vm.ctx.new_tuple(vec![socket.into_object(), addr_tuple])) -} - -fn socket_recv(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, None), (bufsize, Some(vm.ctx.int_type()))] - ); - let socket = get_socket(zelf); - - let mut buffer = vec![0u8; objint::get_value(bufsize).to_usize().unwrap()]; - match socket.con.borrow_mut().as_mut() { - Some(v) => match v.read_exact(&mut buffer) { - Ok(_) => (), - Err(s) => return Err(vm.new_os_error(s.to_string())), - }, - None => return Err(vm.new_type_error("".to_string())), - }; - Ok(vm.ctx.new_bytes(buffer)) -} - fn socket_recvfrom(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -420,11 +403,11 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let socket = py_class!(ctx, "socket", ctx.object(), { "__new__" => ctx.new_rustfunc(SocketRef::new), "connect" => ctx.new_rustfunc(SocketRef::connect), - "recv" => ctx.new_rustfunc(socket_recv), + "recv" => ctx.new_rustfunc(SocketRef::recv), "send" => ctx.new_rustfunc(socket_send), "bind" => ctx.new_rustfunc(SocketRef::bind), - "accept" => ctx.new_rustfunc(socket_accept), - "listen" => ctx.new_rustfunc(socket_listen), + "accept" => ctx.new_rustfunc(SocketRef::accept), + "listen" => ctx.new_rustfunc(SocketRef::listen), "close" => ctx.new_rustfunc(socket_close), "getsockname" => ctx.new_rustfunc(socket_getsockname), "sendto" => ctx.new_rustfunc(SocketRef::sendto), From 1f733932558611219a09f843469cc4fe2b37b34b Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 21 Apr 2019 17:35:20 +0300 Subject: [PATCH 385/884] Move more socket methods to new arg style --- vm/src/stdlib/socket.rs | 201 +++++++++++++++++----------------------- 1 file changed, 85 insertions(+), 116 deletions(-) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index dc165d3162..4082fca4f0 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -3,10 +3,8 @@ use std::io; use std::io::Read; use std::io::Write; use std::net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs, UdpSocket}; -use std::ops::Deref; use crate::function::PyFuncArgs; -use crate::obj::objbytes; use crate::obj::objbytes::PyBytesRef; use crate::obj::objint; use crate::obj::objint::PyIntRef; @@ -163,10 +161,6 @@ impl Socket { } } -fn get_socket<'a>(obj: &'a PyObjectRef) -> impl Deref + 'a { - obj.payload::().unwrap() -} - type SocketRef = PyRef; impl SocketRef { @@ -226,33 +220,6 @@ impl SocketRef { } } - fn sendto(self, bytes: PyBytesRef, address: PyTupleRef, vm: &VirtualMachine) -> PyResult { - let address_string = get_address_string(vm, address)?; - - match self.socket_kind { - SocketKind::Dgram => { - if let Some(v) = self.con.borrow().as_ref() { - return match v.send_to(&bytes, address_string) { - Ok(_) => Ok(vm.get_none()), - Err(s) => Err(vm.new_os_error(s.to_string())), - }; - } - // Doing implicit bind - match UdpSocket::bind("0.0.0.0:0") { - Ok(dgram) => match dgram.send_to(&bytes, address_string) { - Ok(_) => { - self.con.borrow_mut().replace(Connection::UdpSocket(dgram)); - Ok(vm.get_none()) - } - Err(s) => Err(vm.new_os_error(s.to_string())), - }, - Err(s) => Err(vm.new_os_error(s.to_string())), - } - } - _ => Err(vm.new_not_implemented_error("".to_string())), - } - } - fn listen(self, _num: PyIntRef, _vm: &VirtualMachine) -> () {} fn accept(self, vm: &VirtualMachine) -> PyResult { @@ -289,6 +256,86 @@ impl SocketRef { }; Ok(vm.ctx.new_bytes(buffer)) } + + fn recvfrom(self, bufsize: PyIntRef, vm: &VirtualMachine) -> PyResult { + let mut buffer = vec![0u8; bufsize.as_bigint().to_usize().unwrap()]; + let ret = match self.con.borrow().as_ref() { + Some(v) => v.recv_from(&mut buffer), + None => return Err(vm.new_type_error("".to_string())), + }; + + let addr = match ret { + Ok((_size, addr)) => addr, + Err(s) => return Err(vm.new_os_error(s.to_string())), + }; + + let addr_tuple = get_addr_tuple(vm, addr)?; + + Ok(vm.ctx.new_tuple(vec![vm.ctx.new_bytes(buffer), addr_tuple])) + } + + fn send(self, bytes: PyBytesRef, vm: &VirtualMachine) -> PyResult { + match self.con.borrow_mut().as_mut() { + Some(v) => match v.write(&bytes) { + Ok(_) => (), + Err(s) => return Err(vm.new_os_error(s.to_string())), + }, + None => return Err(vm.new_type_error("".to_string())), + }; + Ok(vm.get_none()) + } + + fn sendto(self, bytes: PyBytesRef, address: PyTupleRef, vm: &VirtualMachine) -> PyResult { + let address_string = get_address_string(vm, address)?; + + match self.socket_kind { + SocketKind::Dgram => { + if let Some(v) = self.con.borrow().as_ref() { + return match v.send_to(&bytes, address_string) { + Ok(_) => Ok(vm.get_none()), + Err(s) => Err(vm.new_os_error(s.to_string())), + }; + } + // Doing implicit bind + match UdpSocket::bind("0.0.0.0:0") { + Ok(dgram) => match dgram.send_to(&bytes, address_string) { + Ok(_) => { + self.con.borrow_mut().replace(Connection::UdpSocket(dgram)); + Ok(vm.get_none()) + } + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + Err(s) => Err(vm.new_os_error(s.to_string())), + } + } + _ => Err(vm.new_not_implemented_error("".to_string())), + } + } + + fn close(self, vm: &VirtualMachine) -> PyResult { + self.con.borrow_mut().take(); + Ok(vm.get_none()) + } + + fn fileno(self, vm: &VirtualMachine) -> PyResult { + let fileno = match self.con.borrow_mut().as_mut() { + Some(v) => v.fileno(), + None => return Err(vm.new_type_error("".to_string())), + }; + Ok(vm.ctx.new_int(fileno)) + } + + fn getsockname(self, vm: &VirtualMachine) -> PyResult { + let addr = match self.con.borrow().as_ref() { + Some(v) => v.local_addr(), + None => return Err(vm.new_type_error("".to_string())), + }; + + match addr { + Ok(addr) => get_addr_tuple(vm, addr), + Err(s) => Err(vm.new_os_error(s.to_string())), + } + } } fn get_address_string(vm: &VirtualMachine, address: PyTupleRef) -> Result { @@ -312,84 +359,6 @@ fn get_address_string(vm: &VirtualMachine, address: PyTupleRef) -> Result PyResult { - arg_check!( - vm, - args, - required = [(zelf, None), (bufsize, Some(vm.ctx.int_type()))] - ); - - let socket = get_socket(zelf); - - let mut buffer = vec![0u8; objint::get_value(bufsize).to_usize().unwrap()]; - let ret = match socket.con.borrow().as_ref() { - Some(v) => v.recv_from(&mut buffer), - None => return Err(vm.new_type_error("".to_string())), - }; - - let addr = match ret { - Ok((_size, addr)) => addr, - Err(s) => return Err(vm.new_os_error(s.to_string())), - }; - - let addr_tuple = get_addr_tuple(vm, addr)?; - - Ok(vm.ctx.new_tuple(vec![vm.ctx.new_bytes(buffer), addr_tuple])) -} - -fn socket_send(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(zelf, None), (bytes, Some(vm.ctx.bytes_type()))] - ); - let socket = get_socket(zelf); - - match socket.con.borrow_mut().as_mut() { - Some(v) => match v.write(&objbytes::get_value(&bytes)) { - Ok(_) => (), - Err(s) => return Err(vm.new_os_error(s.to_string())), - }, - None => return Err(vm.new_type_error("".to_string())), - }; - Ok(vm.get_none()) -} - -fn socket_close(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, None)]); - - let socket = get_socket(zelf); - socket.con.borrow_mut().take(); - Ok(vm.get_none()) -} - -fn socket_fileno(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, None)]); - - let socket = get_socket(zelf); - - let fileno = match socket.con.borrow_mut().as_mut() { - Some(v) => v.fileno(), - None => return Err(vm.new_type_error("".to_string())), - }; - Ok(vm.ctx.new_int(fileno)) -} - -fn socket_getsockname(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(zelf, None)]); - let socket = get_socket(zelf); - - let addr = match socket.con.borrow().as_ref() { - Some(v) => v.local_addr(), - None => return Err(vm.new_type_error("".to_string())), - }; - - match addr { - Ok(addr) => get_addr_tuple(vm, addr), - Err(s) => Err(vm.new_os_error(s.to_string())), - } -} - fn get_addr_tuple(vm: &VirtualMachine, addr: SocketAddr) -> PyResult { let port = vm.ctx.new_int(addr.port()); let ip = vm.ctx.new_str(addr.ip().to_string()); @@ -404,15 +373,15 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "__new__" => ctx.new_rustfunc(SocketRef::new), "connect" => ctx.new_rustfunc(SocketRef::connect), "recv" => ctx.new_rustfunc(SocketRef::recv), - "send" => ctx.new_rustfunc(socket_send), + "send" => ctx.new_rustfunc(SocketRef::send), "bind" => ctx.new_rustfunc(SocketRef::bind), "accept" => ctx.new_rustfunc(SocketRef::accept), "listen" => ctx.new_rustfunc(SocketRef::listen), - "close" => ctx.new_rustfunc(socket_close), - "getsockname" => ctx.new_rustfunc(socket_getsockname), + "close" => ctx.new_rustfunc(SocketRef::close), + "getsockname" => ctx.new_rustfunc(SocketRef::getsockname), "sendto" => ctx.new_rustfunc(SocketRef::sendto), - "recvfrom" => ctx.new_rustfunc(socket_recvfrom), - "fileno" => ctx.new_rustfunc(socket_fileno), + "recvfrom" => ctx.new_rustfunc(SocketRef::recvfrom), + "fileno" => ctx.new_rustfunc(SocketRef::fileno), }); py_module!(vm, "socket", { From b61cd6f011d79ea2c2583ab39113309dbf070a78 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 21 Apr 2019 17:44:41 +0300 Subject: [PATCH 386/884] Simplify return values --- vm/src/stdlib/socket.rs | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 4082fca4f0..25edf7a65a 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -173,21 +173,21 @@ impl SocketRef { Socket::new(family, kind).into_ref_with_type(vm, cls) } - fn connect(self, address: PyTupleRef, vm: &VirtualMachine) -> PyResult { + fn connect(self, address: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> { let address_string = get_address_string(vm, address)?; match self.socket_kind { SocketKind::Stream => match TcpStream::connect(address_string) { Ok(stream) => { self.con.borrow_mut().replace(Connection::TcpStream(stream)); - Ok(vm.get_none()) + Ok(()) } Err(s) => Err(vm.new_os_error(s.to_string())), }, SocketKind::Dgram => { if let Some(Connection::UdpSocket(con)) = self.con.borrow().as_ref() { match con.connect(address_string) { - Ok(_) => Ok(vm.get_none()), + Ok(_) => Ok(()), Err(s) => Err(vm.new_os_error(s.to_string())), } } else { @@ -197,7 +197,7 @@ impl SocketRef { } } - fn bind(self, address: PyTupleRef, vm: &VirtualMachine) -> PyResult { + fn bind(self, address: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> { let address_string = get_address_string(vm, address)?; match self.socket_kind { @@ -206,14 +206,14 @@ impl SocketRef { self.con .borrow_mut() .replace(Connection::TcpListener(stream)); - Ok(vm.get_none()) + Ok(()) } Err(s) => Err(vm.new_os_error(s.to_string())), }, SocketKind::Dgram => match UdpSocket::bind(address_string) { Ok(dgram) => { self.con.borrow_mut().replace(Connection::UdpSocket(dgram)); - Ok(vm.get_none()) + Ok(()) } Err(s) => Err(vm.new_os_error(s.to_string())), }, @@ -274,7 +274,7 @@ impl SocketRef { Ok(vm.ctx.new_tuple(vec![vm.ctx.new_bytes(buffer), addr_tuple])) } - fn send(self, bytes: PyBytesRef, vm: &VirtualMachine) -> PyResult { + fn send(self, bytes: PyBytesRef, vm: &VirtualMachine) -> PyResult<()> { match self.con.borrow_mut().as_mut() { Some(v) => match v.write(&bytes) { Ok(_) => (), @@ -282,17 +282,17 @@ impl SocketRef { }, None => return Err(vm.new_type_error("".to_string())), }; - Ok(vm.get_none()) + Ok(()) } - fn sendto(self, bytes: PyBytesRef, address: PyTupleRef, vm: &VirtualMachine) -> PyResult { + fn sendto(self, bytes: PyBytesRef, address: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> { let address_string = get_address_string(vm, address)?; match self.socket_kind { SocketKind::Dgram => { if let Some(v) = self.con.borrow().as_ref() { return match v.send_to(&bytes, address_string) { - Ok(_) => Ok(vm.get_none()), + Ok(_) => Ok(()), Err(s) => Err(vm.new_os_error(s.to_string())), }; } @@ -301,7 +301,7 @@ impl SocketRef { Ok(dgram) => match dgram.send_to(&bytes, address_string) { Ok(_) => { self.con.borrow_mut().replace(Connection::UdpSocket(dgram)); - Ok(vm.get_none()) + Ok(()) } Err(s) => Err(vm.new_os_error(s.to_string())), }, @@ -312,9 +312,8 @@ impl SocketRef { } } - fn close(self, vm: &VirtualMachine) -> PyResult { + fn close(self, _vm: &VirtualMachine) -> () { self.con.borrow_mut().take(); - Ok(vm.get_none()) } fn fileno(self, vm: &VirtualMachine) -> PyResult { From 87f9a8e634c79d99ffd06542a91bfe095e6ac846 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Sun, 21 Apr 2019 23:48:13 +0900 Subject: [PATCH 387/884] Implement deleting tuples --- tests/snippets/delete.py | 3 +++ vm/src/compile.rs | 58 +++++++++++++++++++++++----------------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/tests/snippets/delete.py b/tests/snippets/delete.py index 078b6dcd9f..29f8719fde 100644 --- a/tests/snippets/delete.py +++ b/tests/snippets/delete.py @@ -10,3 +10,6 @@ class MyObject: pass assert not hasattr(foo, 'bar') +x = 1 +y = 2 +del (x, y) diff --git a/vm/src/compile.rs b/vm/src/compile.rs index e7953ad675..b7df813134 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -456,30 +456,7 @@ impl Compiler { } ast::Statement::Delete { targets } => { for target in targets { - match target { - ast::Expression::Identifier { name } => { - self.emit(Instruction::DeleteName { - name: name.to_string(), - }); - } - ast::Expression::Attribute { value, name } => { - self.compile_expression(value)?; - self.emit(Instruction::DeleteAttr { - name: name.to_string(), - }); - } - ast::Expression::Subscript { a, b } => { - self.compile_expression(a)?; - self.compile_expression(b)?; - self.emit(Instruction::DeleteSubscript); - } - _ => { - return Err(CompileError { - error: CompileErrorType::Delete(target.name()), - location: self.current_source_location.clone(), - }); - } - } + self.compile_delete(target)?; } } ast::Statement::Pass => { @@ -489,6 +466,39 @@ impl Compiler { Ok(()) } + fn compile_delete(&mut self, expression: &ast::Expression) -> Result<(), CompileError> { + match expression { + ast::Expression::Identifier { name } => { + self.emit(Instruction::DeleteName { + name: name.to_string(), + }); + } + ast::Expression::Attribute { value, name } => { + self.compile_expression(value)?; + self.emit(Instruction::DeleteAttr { + name: name.to_string(), + }); + } + ast::Expression::Subscript { a, b } => { + self.compile_expression(a)?; + self.compile_expression(b)?; + self.emit(Instruction::DeleteSubscript); + } + ast::Expression::Tuple { elements } => { + for element in elements { + self.compile_delete(element)?; + } + } + _ => { + return Err(CompileError { + error: CompileErrorType::Delete(expression.name()), + location: self.current_source_location.clone(), + }); + } + } + Ok(()) + } + fn enter_function( &mut self, name: &str, From 2aeb0ad59b2f05dbcdd782631eb4ca9e0f0e143d Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Mon, 22 Apr 2019 00:26:30 +0900 Subject: [PATCH 388/884] Assert deleted variables are really deleted --- tests/snippets/delete.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/snippets/delete.py b/tests/snippets/delete.py index 29f8719fde..57d432ff6f 100644 --- a/tests/snippets/delete.py +++ b/tests/snippets/delete.py @@ -1,3 +1,4 @@ +from testutils import assert_raises a = 1 del a @@ -13,3 +14,5 @@ class MyObject: pass x = 1 y = 2 del (x, y) +assert_raises(NameError, lambda: x) +assert_raises(NameError, lambda: y) From 801c8646451625c4792d32c87256d41b91c58460 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 21 Apr 2019 11:30:40 -0500 Subject: [PATCH 389/884] Bump WASM npm version --- Cargo.lock | 2 +- wasm/lib/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73fd4ba764..33e9ddcfd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -853,7 +853,7 @@ dependencies = [ [[package]] name = "rustpython_wasm" -version = "0.1.0-pre-alpha.1" +version = "0.1.0-pre-alpha.2" dependencies = [ "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 769455aea1..cebc753a83 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rustpython_wasm" -version = "0.1.0-pre-alpha.1" +version = "0.1.0-pre-alpha.2" authors = ["Ryan Liddle "] license = "MIT" description = "A Python-3 (CPython >= 3.5.0) Interpreter written in Rust, compiled to WASM" From ce8ea805ae8abda95f99892c920f25caad76a826 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 21 Apr 2019 19:40:14 +0200 Subject: [PATCH 390/884] Add check for nonlocal on top level. --- tests/snippets/global_nonlocal.py | 35 +++++++++++++++++++ vm/src/compile.rs | 8 ++--- vm/src/error.rs | 2 ++ vm/src/symboltable.rs | 57 ++++++++++++++++++++++++++----- 4 files changed, 89 insertions(+), 13 deletions(-) diff --git a/tests/snippets/global_nonlocal.py b/tests/snippets/global_nonlocal.py index fd5a61e1cc..03e0d386e5 100644 --- a/tests/snippets/global_nonlocal.py +++ b/tests/snippets/global_nonlocal.py @@ -11,6 +11,7 @@ def b(): b() assert a == 4 + def x(): def y(): nonlocal b @@ -21,3 +22,37 @@ def y(): res = x() assert res == 3, str(res) + +# Invalid syntax: +src = """ +b = 2 +global b +""" + +try: + exec(src) +except Exception as ex: + # print(ex) + pass +else: + raise RuntimeError('Must raise') + +# Invalid syntax: + +src = """ +nonlocal c +""" + +try: + exec(src) +except Exception as ex: + # print(ex) + pass +else: + raise RuntimeError('Must raise') + + +# class X: +# nonlocal c +# c = 2 + diff --git a/vm/src/compile.rs b/vm/src/compile.rs index b7df813134..44f064456f 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -41,17 +41,17 @@ pub fn compile( match mode { Mode::Exec => { let ast = parser::parse_program(source)?; - let symbol_table = make_symbol_table(&ast); + let symbol_table = make_symbol_table(&ast)?; compiler.compile_program(&ast, symbol_table) } Mode::Eval => { let statement = parser::parse_statement(source)?; - let symbol_table = statements_to_symbol_table(&statement); + let symbol_table = statements_to_symbol_table(&statement)?; compiler.compile_statement_eval(&statement, symbol_table) } Mode::Single => { let ast = parser::parse_program(source)?; - let symbol_table = make_symbol_table(&ast); + let symbol_table = make_symbol_table(&ast)?; compiler.compile_program_single(&ast, symbol_table) } }?; @@ -1776,7 +1776,7 @@ mod tests { compiler.source_path = Some("source_path".to_string()); compiler.push_new_code_object("".to_string()); let ast = parser::parse_program(&source.to_string()).unwrap(); - let symbol_scope = make_symbol_table(&ast); + let symbol_scope = make_symbol_table(&ast).unwrap(); compiler.compile_program(&ast, symbol_scope).unwrap(); compiler.pop_code_object() } diff --git a/vm/src/error.rs b/vm/src/error.rs index 81836fa444..af763168da 100644 --- a/vm/src/error.rs +++ b/vm/src/error.rs @@ -29,6 +29,7 @@ pub enum CompileErrorType { ExpectExpr, /// Parser error Parse(ParseError), + SyntaxError(String), /// Multiple `*` detected StarArgs, /// Break statement outside of loop. @@ -46,6 +47,7 @@ impl fmt::Display for CompileError { CompileErrorType::Delete(target) => write!(f, "can't delete {}", target), CompileErrorType::ExpectExpr => write!(f, "Expecting expression, got statement"), CompileErrorType::Parse(err) => write!(f, "{}", err), + CompileErrorType::SyntaxError(err) => write!(f, "{}", err), CompileErrorType::StarArgs => write!(f, "Two starred expressions in assignment"), CompileErrorType::InvalidBreak => write!(f, "'break' outside loop"), CompileErrorType::InvalidContinue => write!(f, "'continue' outside loop"), diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs index d7f30c8705..ca10382e49 100644 --- a/vm/src/symboltable.rs +++ b/vm/src/symboltable.rs @@ -5,23 +5,27 @@ Then the compiler can use the symbol table to generate proper load and store instructions for names. */ +use crate::error::{CompileError, CompileErrorType}; use rustpython_parser::ast; +use rustpython_parser::lexer::Location; use std::collections::HashMap; -pub fn make_symbol_table(program: &ast::Program) -> SymbolScope { +pub fn make_symbol_table(program: &ast::Program) -> Result { let mut builder = SymbolTableBuilder::new(); builder.enter_scope(); - builder.scan_program(program).unwrap(); + builder.scan_program(program)?; assert!(builder.scopes.len() == 1); - builder.scopes.pop().unwrap() + Ok(builder.scopes.pop().unwrap()) } -pub fn statements_to_symbol_table(statements: &[ast::LocatedStatement]) -> SymbolScope { +pub fn statements_to_symbol_table( + statements: &[ast::LocatedStatement], +) -> Result { let mut builder = SymbolTableBuilder::new(); builder.enter_scope(); - builder.scan_statements(statements).unwrap(); + builder.scan_statements(statements)?; assert!(builder.scopes.len() == 1); - builder.scopes.pop().unwrap() + Ok(builder.scopes.pop().unwrap()) } #[derive(Debug)] @@ -43,7 +47,22 @@ pub struct SymbolScope { pub sub_scopes: Vec, } -type SymbolTableResult = Result<(), String>; +#[derive(Debug)] +pub struct SymbolTableError { + error: String, + location: Location, +} + +impl From for CompileError { + fn from(error: SymbolTableError) -> Self { + CompileError { + error: CompileErrorType::SyntaxError(error.error), + location: error.location, + } + } +} + +type SymbolTableResult = Result<(), SymbolTableError>; impl SymbolScope { pub fn new() -> Self { @@ -85,7 +104,7 @@ impl SymbolTableBuilder { self.scopes.push(scope); } - pub fn leave_scope(&mut self) { + fn leave_scope(&mut self) { // Pop scope and add to subscopes of parent scope. let scope = self.scopes.pop().unwrap(); self.scopes.last_mut().unwrap().sub_scopes.push(scope); @@ -445,17 +464,37 @@ impl SymbolTableBuilder { } fn register_name(&mut self, name: &str, role: SymbolRole) -> SymbolTableResult { + let scope_depth = self.scopes.len(); let current_scope = self.scopes.last_mut().unwrap(); + let location = Default::default(); if let Some(_old_role) = current_scope.symbols.get(name) { // Role already set.. // debug!("TODO: {:?}", old_role); match role { - SymbolRole::Global => return Err("global must appear first".to_string()), + SymbolRole::Global => { + return Err(SymbolTableError { + error: format!("name '{}' is used prior to global declaration", name), + location, + }) + } _ => { // Ok? } } } else { + match role { + SymbolRole::Nonlocal => { + if scope_depth < 2 { + return Err(SymbolTableError { + error: format!("name '{}' is used prior to global declaration", name), + location, + }); + } + } + _ => { + // Ok! + } + } current_scope.symbols.insert(name.to_string(), role); } Ok(()) From cb322019047ede2014e848e1b7d06d213b1de8fb Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 22 Apr 2019 00:18:19 +0300 Subject: [PATCH 391/884] Add Address --- vm/src/stdlib/socket.rs | 63 +++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 28 deletions(-) diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 25edf7a65a..edab574b38 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -4,11 +4,9 @@ use std::io::Read; use std::io::Write; use std::net::{SocketAddr, TcpListener, TcpStream, ToSocketAddrs, UdpSocket}; -use crate::function::PyFuncArgs; use crate::obj::objbytes::PyBytesRef; -use crate::obj::objint; use crate::obj::objint::PyIntRef; -use crate::obj::objstr; +use crate::obj::objstr::PyStringRef; use crate::obj::objtuple::PyTupleRef; use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue, TryFromObject}; use crate::vm::VirtualMachine; @@ -173,8 +171,8 @@ impl SocketRef { Socket::new(family, kind).into_ref_with_type(vm, cls) } - fn connect(self, address: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> { - let address_string = get_address_string(vm, address)?; + fn connect(self, address: Address, vm: &VirtualMachine) -> PyResult<()> { + let address_string = address.get_address_string(); match self.socket_kind { SocketKind::Stream => match TcpStream::connect(address_string) { @@ -197,8 +195,8 @@ impl SocketRef { } } - fn bind(self, address: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> { - let address_string = get_address_string(vm, address)?; + fn bind(self, address: Address, vm: &VirtualMachine) -> PyResult<()> { + let address_string = address.get_address_string(); match self.socket_kind { SocketKind::Stream => match TcpListener::bind(address_string) { @@ -285,8 +283,8 @@ impl SocketRef { Ok(()) } - fn sendto(self, bytes: PyBytesRef, address: PyTupleRef, vm: &VirtualMachine) -> PyResult<()> { - let address_string = get_address_string(vm, address)?; + fn sendto(self, bytes: PyBytesRef, address: Address, vm: &VirtualMachine) -> PyResult<()> { + let address_string = address.get_address_string(); match self.socket_kind { SocketKind::Dgram => { @@ -337,25 +335,34 @@ impl SocketRef { } } -fn get_address_string(vm: &VirtualMachine, address: PyTupleRef) -> Result { - let args = PyFuncArgs { - args: address.elements.borrow().to_vec(), - kwargs: vec![], - }; - arg_check!( - vm, - args, - required = [ - (host, Some(vm.ctx.str_type())), - (port, Some(vm.ctx.int_type())) - ] - ); - - Ok(format!( - "{}:{}", - objstr::get_value(host), - objint::get_value(port).to_string() - )) +struct Address { + host: String, + port: usize, +} + +impl Address { + fn get_address_string(self) -> String { + format!("{}:{}", self.host, self.port.to_string()) + } +} + +impl TryFromObject for Address { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + let tuple = PyTupleRef::try_from_object(vm, obj)?; + if tuple.elements.borrow().len() != 2 { + Err(vm.new_type_error("Address tuple should have only 2 values".to_string())) + } else { + Ok(Address { + host: PyStringRef::try_from_object(vm, tuple.elements.borrow()[0].clone())? + .value + .to_string(), + port: PyIntRef::try_from_object(vm, tuple.elements.borrow()[1].clone())? + .as_bigint() + .to_usize() + .unwrap(), + }) + } + } } fn get_addr_tuple(vm: &VirtualMachine, addr: SocketAddr) -> PyResult { From 9f7676589fb62ea8cdaf1e8ab0381bf0ec80f558 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 22 Apr 2019 21:30:57 +0900 Subject: [PATCH 392/884] Fix README not to refer moved file `cargo run tests/snippets/whats_left_to_implement.py` -> `./whats_left.sh` --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3febdc13cd..3c64397b81 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ Another approach is to checkout the source code: builtin functions and object me and easiest way to contribute. You can also simply run -`cargo run tests/snippets/whats_left_to_implement.py` to assist in finding any +`./whats_left.sh` to assist in finding any unimplemented method. # Testing From 384bf5b5451d78e5933b1eda2de7e6b943a56904 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 22 Apr 2019 18:27:39 +0300 Subject: [PATCH 393/884] Add os.stat --- tests/snippets/stdlib_os.py | 3 ++ vm/src/stdlib/os.rs | 58 ++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 0de5b52b61..cae8d8c8e1 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -98,3 +98,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert paths == set([fname, fname2, folder]) assert dirs == set([FOLDER]) assert files == set([FILE_NAME, FILE_NAME2]) + + # Stat + stat_res = os.stat(fname) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 9717c8f22e..7506a99872 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -233,7 +233,7 @@ impl DirEntryRef { } #[derive(Debug)] -pub struct ScandirIterator { +struct ScandirIterator { entries: RefCell, } @@ -272,6 +272,56 @@ fn os_scandir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } } +#[derive(Debug)] +struct StatResult { + st_mode: u32, +} + +impl PyValue for StatResult { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("os", "stat_result") + } +} + +type StatResultRef = PyRef; + +impl StatResultRef { + fn st_mode(self, _vm: &VirtualMachine) -> u32 { + self.st_mode + } +} + +#[cfg(unix)] +fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + use std::os::linux::fs::MetadataExt; + match fs::metadata(&path.value) { + Ok(meta) => Ok(StatResult { + st_mode: meta.st_mode(), + } + .into_ref(vm) + .into_object()), + Err(s) => Err(vm.new_os_error(s.to_string())), + } +} + +#[cfg(windows)] +fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + use std::os::windows::fs::MetadataExt; + match fs::metadata(&path.value) { + Ok(meta) => Ok(StatResult { + st_mode: meta.file_attributes(), + } + .into_ref(vm) + .into_object()), + Err(s) => Err(vm.new_os_error(s.to_string())), + } +} + +#[cfg(all(not(unix), not(windows)))] +fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + unimplemented!(); +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -295,6 +345,10 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "is_file" => ctx.new_rustfunc(DirEntryRef::is_file), }); + let stat_result = py_class!(ctx, "stat_result", ctx.object(), { + "st_mode" => ctx.new_property(StatResultRef::st_mode) + }); + py_module!(vm, "_os", { "open" => ctx.new_rustfunc(os_open), "close" => ctx.new_rustfunc(os_close), @@ -314,6 +368,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "scandir" => ctx.new_rustfunc(os_scandir), "ScandirIter" => scandir_iter, "DirEntry" => dir_entry, + "stat_result" => stat_result, + "stat" => ctx.new_rustfunc(os_stat), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), "O_RDWR" => ctx.new_int(2), From 9a1caa08c373eac1b7e89ae5d1fb541475685d02 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 22 Apr 2019 18:35:38 +0300 Subject: [PATCH 394/884] Add stat_result.{st_ino, st_dev} --- tests/snippets/stdlib_os.py | 3 +++ vm/src/stdlib/os.rs | 18 +++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index cae8d8c8e1..405f999a6e 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -101,3 +101,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Stat stat_res = os.stat(fname) + print(stat_res.st_mode) + print(stat_res.st_ino) + print(stat_res.st_dev) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 7506a99872..a2c55bd8f7 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -275,6 +275,8 @@ fn os_scandir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { #[derive(Debug)] struct StatResult { st_mode: u32, + st_ino: u64, + st_dev: u64, } impl PyValue for StatResult { @@ -289,6 +291,14 @@ impl StatResultRef { fn st_mode(self, _vm: &VirtualMachine) -> u32 { self.st_mode } + + fn st_ino(self, _vm: &VirtualMachine) -> u64 { + self.st_ino + } + + fn st_dev(self, _vm: &VirtualMachine) -> u64 { + self.st_dev + } } #[cfg(unix)] @@ -297,6 +307,8 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { match fs::metadata(&path.value) { Ok(meta) => Ok(StatResult { st_mode: meta.st_mode(), + st_ino: meta.st_ino(), + st_dev: meta.st_dev(), } .into_ref(vm) .into_object()), @@ -310,6 +322,8 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { match fs::metadata(&path.value) { Ok(meta) => Ok(StatResult { st_mode: meta.file_attributes(), + st_ino: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_dev: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. } .into_ref(vm) .into_object()), @@ -346,7 +360,9 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { }); let stat_result = py_class!(ctx, "stat_result", ctx.object(), { - "st_mode" => ctx.new_property(StatResultRef::st_mode) + "st_mode" => ctx.new_property(StatResultRef::st_mode), + "st_ino" => ctx.new_property(StatResultRef::st_ino), + "st_dev" => ctx.new_property(StatResultRef::st_dev), }); py_module!(vm, "_os", { From 56958552f48ea63dc7fff8b9436646bcb49c839f Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 22 Apr 2019 18:47:10 +0300 Subject: [PATCH 395/884] Add stat_result.{st_nlink, st_uid, st_gid} --- tests/snippets/stdlib_os.py | 3 +++ vm/src/stdlib/os.rs | 28 ++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 405f999a6e..cca39e3bf1 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -104,3 +104,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): print(stat_res.st_mode) print(stat_res.st_ino) print(stat_res.st_dev) + print(stat_res.st_nlink) + print(stat_res.st_uid) + print(stat_res.st_gid) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index a2c55bd8f7..49eff7dd2e 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -277,6 +277,9 @@ struct StatResult { st_mode: u32, st_ino: u64, st_dev: u64, + st_nlink: u64, + st_uid: u32, + st_gid: u32, } impl PyValue for StatResult { @@ -299,6 +302,18 @@ impl StatResultRef { fn st_dev(self, _vm: &VirtualMachine) -> u64 { self.st_dev } + + fn st_nlink(self, _vm: &VirtualMachine) -> u64 { + self.st_nlink + } + + fn st_uid(self, _vm: &VirtualMachine) -> u32 { + self.st_uid + } + + fn st_gid(self, _vm: &VirtualMachine) -> u32 { + self.st_gid + } } #[cfg(unix)] @@ -309,6 +324,9 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { st_mode: meta.st_mode(), st_ino: meta.st_ino(), st_dev: meta.st_dev(), + st_nlink: meta.st_nlink(), + st_uid: meta.st_uid(), + st_gid: meta.st_gid(), } .into_ref(vm) .into_object()), @@ -322,8 +340,11 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { match fs::metadata(&path.value) { Ok(meta) => Ok(StatResult { st_mode: meta.file_attributes(), - st_ino: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. - st_dev: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_ino: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_dev: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_nlink: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_uid: 0, // 0 on windows + st_gid: 0, // 0 on windows } .into_ref(vm) .into_object()), @@ -363,6 +384,9 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "st_mode" => ctx.new_property(StatResultRef::st_mode), "st_ino" => ctx.new_property(StatResultRef::st_ino), "st_dev" => ctx.new_property(StatResultRef::st_dev), + "st_nlink" => ctx.new_property(StatResultRef::st_nlink), + "st_uid" => ctx.new_property(StatResultRef::st_uid), + "st_gid" => ctx.new_property(StatResultRef::st_gid), }); py_module!(vm, "_os", { From 55bd00b6009012e695d6525cce9f0c0d1daea72b Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 22 Apr 2019 18:52:35 +0300 Subject: [PATCH 396/884] Add stat_result.{st_size} --- tests/snippets/stdlib_os.py | 2 ++ vm/src/stdlib/os.rs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index cca39e3bf1..0fb44cbb59 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -107,3 +107,5 @@ def __exit__(self, exc_type, exc_val, exc_tb): print(stat_res.st_nlink) print(stat_res.st_uid) print(stat_res.st_gid) + print(stat_res.st_size) + assert stat_res.st_size == len(CONTENT2) + len(CONTENT3) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 49eff7dd2e..796578bb18 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -280,6 +280,7 @@ struct StatResult { st_nlink: u64, st_uid: u32, st_gid: u32, + st_size: u64, } impl PyValue for StatResult { @@ -314,6 +315,10 @@ impl StatResultRef { fn st_gid(self, _vm: &VirtualMachine) -> u32 { self.st_gid } + + fn st_size(self, _vm: &VirtualMachine) -> u64 { + self.st_size + } } #[cfg(unix)] @@ -327,6 +332,7 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { st_nlink: meta.st_nlink(), st_uid: meta.st_uid(), st_gid: meta.st_gid(), + st_size: meta.st_size(), } .into_ref(vm) .into_object()), @@ -345,6 +351,7 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { st_nlink: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. st_uid: 0, // 0 on windows st_gid: 0, // 0 on windows + st_size: meta.file_size(), } .into_ref(vm) .into_object()), @@ -387,6 +394,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "st_nlink" => ctx.new_property(StatResultRef::st_nlink), "st_uid" => ctx.new_property(StatResultRef::st_uid), "st_gid" => ctx.new_property(StatResultRef::st_gid), + "st_size" => ctx.new_property(StatResultRef::st_size), }); py_module!(vm, "_os", { From 1d5adc4353622d08daa63b46894a8cf97b83fe59 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 23 Apr 2019 00:17:42 +0900 Subject: [PATCH 397/884] Add built-in dict.pop --- tests/snippets/dict.py | 8 ++++++++ vm/src/dictdatatype.rs | 27 ++++++++++++++++++++++----- vm/src/obj/objdict.rs | 5 +++++ 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 05cdd99285..774ca0fd74 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -166,3 +166,11 @@ def __eq__(self, other): y.update(y) assert y == {'a': 2, 'b': 12, 'c': 19, 'd': -1} # hasn't changed + +x = {1: 'a', '1': None} +assert x.pop(1) == 'a' +assert x.pop('1') is None +assert x == {} + +with assertRaises(KeyError): + x.pop("not here") diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 1f4b4eecdd..b0bd4894f5 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -87,14 +87,18 @@ impl Dict { } } + fn unchecked_get(&self, index: usize) -> T { + if let Some(entry) = &self.entries[index] { + entry.value.clone() + } else { + panic!("Lookup returned invalid index into entries!"); + } + } + /// Retrieve a key pub fn get(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult> { if let LookupResult::Existing(index) = self.lookup(vm, key)? { - if let Some(entry) = &self.entries[index] { - Ok(Some(entry.value.clone())) - } else { - panic!("Lookup returned invalid index into entries!"); - } + Ok(Some(self.unchecked_get(index))) } else { Ok(None) } @@ -190,6 +194,19 @@ impl Dict { // warn!("Perturb value: {}", i); } } + + /// Retrieve and delete a key + pub fn pop(&mut self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { + if let LookupResult::Existing(index) = self.lookup(vm, key)? { + let value = self.unchecked_get(index); + self.entries[index] = None; + self.size -= 1; + Ok(value) + } else { + let key_repr = vm.to_pystr(key)?; + Err(vm.new_key_error(format!("Key not found: {}", key_repr))) + } + } } enum LookupResult { diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index e5419e932c..306040f390 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -228,6 +228,10 @@ impl PyDictRef { PyDictRef::merge(&self.entries, dict_obj, kwargs, vm) } + fn pop(self, key: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.entries.borrow_mut().pop(vm, &key) + } + /// Take a python dictionary and convert it to attributes. pub fn to_attributes(self) -> PyAttributes { let mut attrs = PyAttributes::new(); @@ -458,6 +462,7 @@ pub fn init(context: &PyContext) { "get" => context.new_rustfunc(PyDictRef::get), "copy" => context.new_rustfunc(PyDictRef::copy), "update" => context.new_rustfunc(PyDictRef::update), + "pop" => context.new_rustfunc(PyDictRef::pop), }); PyDictKeys::extend_class(context, &context.dictkeys_type); From 2eb97aa1e815c852922e13f96771671fe7df01a5 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 23 Apr 2019 01:23:27 +0900 Subject: [PATCH 398/884] Add __main__.py support --- src/main.rs | 33 +++++++++++++++++++++++++---- tests/snippets/dir_main/__main__.py | 1 + 2 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 tests/snippets/dir_main/__main__.py diff --git a/src/main.rs b/src/main.rs index 24b46821b6..98fd5d221f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,7 +14,7 @@ use rustpython_vm::{ print_exception, pyobject::PyResult, util, VirtualMachine, }; use rustyline::{error::ReadlineError, Editor}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; fn main() { env_logger::init(); @@ -96,11 +96,36 @@ fn run_module(vm: &VirtualMachine, module: &str) -> PyResult { fn run_script(vm: &VirtualMachine, script_file: &str) -> PyResult { debug!("Running file {}", script_file); // Parse an ast from it: - let file_path = Path::new(script_file); - match util::read_file(file_path) { + let file_path = PathBuf::from(script_file); + let file_path = if file_path.is_file() { + file_path + } else if file_path.is_dir() { + let main_file_path = file_path.join("__main__.py"); + if main_file_path.is_file() { + main_file_path + } else { + error!( + "can't find '__main__' module in '{}'", + file_path.to_str().unwrap() + ); + std::process::exit(1); + } + } else { + error!( + "can't open file '{}': No such file or directory", + file_path.to_str().unwrap() + ); + std::process::exit(1); + }; + + match util::read_file(&file_path) { Ok(source) => _run_string(vm, &source, file_path.to_str().unwrap().to_string()), Err(err) => { - error!("Failed reading file: {:?}", err.kind()); + error!( + "Failed reading file '{}': {:?}", + file_path.to_str().unwrap(), + err.kind() + ); std::process::exit(1); } } diff --git a/tests/snippets/dir_main/__main__.py b/tests/snippets/dir_main/__main__.py new file mode 100644 index 0000000000..c324c2e6e5 --- /dev/null +++ b/tests/snippets/dir_main/__main__.py @@ -0,0 +1 @@ +print('Hello') From 29c0d890334383bf7fd25b1a80f455758d7407b4 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 24 Apr 2019 02:38:00 +0900 Subject: [PATCH 399/884] test_run_script for filr/dir --- src/main.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main.rs b/src/main.rs index 98fd5d221f..5620d851e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -131,6 +131,19 @@ fn run_script(vm: &VirtualMachine, script_file: &str) -> PyResult { } } +#[test] +fn test_run_script() { + let vm = VirtualMachine::new(); + + // test file run + let r = run_script(&vm, "tests/snippets/dir_main/__main__.py"); + assert!(r.is_ok()); + + // test module run + let r = run_script(&vm, "tests/snippets/dir_main"); + assert!(r.is_ok()); +} + fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), CompileError> { match compile::compile(vm, source, &compile::Mode::Single, "".to_string()) { Ok(code) => { From f9124f41a81cae25d3f63deffd85da60041d247d Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Wed, 24 Apr 2019 04:00:52 +0900 Subject: [PATCH 400/884] Test integer literals --- tests/snippets/literals.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/snippets/literals.py diff --git a/tests/snippets/literals.py b/tests/snippets/literals.py new file mode 100644 index 0000000000..f481b33f5d --- /dev/null +++ b/tests/snippets/literals.py @@ -0,0 +1,8 @@ +# Integer literals +assert 0b101010 == 42 +assert 0B101010 == 42 +assert 0o777 == 511 +assert 0O777 == 511 +assert 0xcafebabe == 3405691582 +assert 0Xcafebabe == 3405691582 +assert 0xCAFEBABE == 3405691582 From fb62653c5c54a62b39de3ccb54039546e2dd66a9 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Wed, 24 Apr 2019 19:28:21 +0900 Subject: [PATCH 401/884] Test error cases of comparisons --- tests/snippets/comparisons.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/snippets/comparisons.py b/tests/snippets/comparisons.py index 1a4597ef6e..d68dcaf3e5 100644 --- a/tests/snippets/comparisons.py +++ b/tests/snippets/comparisons.py @@ -1,3 +1,4 @@ +from testutils import assert_raises assert 1 < 2 assert 1 < 2 < 3 @@ -11,3 +12,12 @@ assert not 1 < 2 < 3 > 4 assert not 1 < 2 > 3 < 4 assert not 1 > 2 < 3 < 4 + +def test_type_error(x, y): + assert_raises(TypeError, lambda: x < y) + assert_raises(TypeError, lambda: x <= y) + assert_raises(TypeError, lambda: x > y) + assert_raises(TypeError, lambda: x >= y) + +test_type_error([], 0) +test_type_error((), 0) From 8982f567b0b1fe81272b296f29b438430b1ba691 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 24 Apr 2019 15:19:52 +0200 Subject: [PATCH 402/884] Add nonlocal binding check. --- tests/snippets/global_nonlocal.py | 28 +++++++------ vm/src/symboltable.rs | 70 +++++++++++++++++++++++++++++-- 2 files changed, 82 insertions(+), 16 deletions(-) diff --git a/tests/snippets/global_nonlocal.py b/tests/snippets/global_nonlocal.py index 03e0d386e5..ddf5e1ed22 100644 --- a/tests/snippets/global_nonlocal.py +++ b/tests/snippets/global_nonlocal.py @@ -1,3 +1,4 @@ +from testutils import assertRaises # Test global and nonlocal funkyness @@ -29,27 +30,28 @@ def y(): global b """ -try: +with assertRaises(SyntaxError): exec(src) -except Exception as ex: - # print(ex) - pass -else: - raise RuntimeError('Must raise') # Invalid syntax: - src = """ nonlocal c """ -try: +with assertRaises(SyntaxError): + exec(src) + + +# Invalid syntax: +src = """ +def f(): + def x(): + nonlocal c +c = 2 +""" + +with assertRaises(SyntaxError): exec(src) -except Exception as ex: - # print(ex) - pass -else: - raise RuntimeError('Must raise') # class X: diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs index ca10382e49..6bdec1e96c 100644 --- a/vm/src/symboltable.rs +++ b/vm/src/symboltable.rs @@ -3,6 +3,8 @@ This ensures that global and nonlocal keywords are picked up. Then the compiler can use the symbol table to generate proper load and store instructions for names. + +Inspirational file: https://github.com/python/cpython/blob/master/Python/symtable.c */ use crate::error::{CompileError, CompileErrorType}; @@ -15,7 +17,9 @@ pub fn make_symbol_table(program: &ast::Program) -> Result, +) -> SymbolTableResult { + // println!("Analyzing {:?}, parent={:?} symbols={:?}", symbol_scope, parent_symbol_scope, symbol_scope.symbols); + // Analyze sub scopes: + for sub_scope in &symbol_scope.sub_scopes { + analyze_symbol_table(&sub_scope, Some(symbol_scope))?; + } + + // Analyze symbols: + for (symbol_name, symbol_role) in &symbol_scope.symbols { + analyze_symbol(symbol_name, symbol_role, parent_symbol_scope)?; + } + + Ok(()) +} + +fn analyze_symbol( + symbol_name: &str, + symbol_role: &SymbolRole, + parent_symbol_scope: Option<&SymbolScope>, +) -> SymbolTableResult { + match symbol_role { + SymbolRole::Nonlocal => { + // check if name is defined in parent scope! + if let Some(parent_symbol_scope) = parent_symbol_scope { + if !parent_symbol_scope.symbols.contains_key(symbol_name) { + return Err(SymbolTableError { + error: format!("no binding for nonlocal '{}' found", symbol_name), + location: Default::default(), + }); + } + } else { + return Err(SymbolTableError { + error: format!( + "nonlocal {} defined at place without an enclosing scope", + symbol_name + ), + location: Default::default(), + }); + } + } + // TODO: add more checks for globals + _ => {} + } + Ok(()) +} + pub struct SymbolTableBuilder { // Scope stack. pub scopes: Vec, @@ -477,6 +535,12 @@ impl SymbolTableBuilder { location, }) } + SymbolRole::Nonlocal => { + return Err(SymbolTableError { + error: format!("name '{}' is used prior to nonlocal declaration", name), + location, + }) + } _ => { // Ok? } @@ -486,7 +550,7 @@ impl SymbolTableBuilder { SymbolRole::Nonlocal => { if scope_depth < 2 { return Err(SymbolTableError { - error: format!("name '{}' is used prior to global declaration", name), + error: format!("cannot define nonlocal '{}' at top level.", name), location, }); } From d84b8b52a5897b1e8fa2c2a03f43198c443447cd Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 25 Apr 2019 10:37:59 +1200 Subject: [PATCH 403/884] Fix the tuple from rpartition being in wrong order when sub isn't contained within the string --- tests/snippets/strings.py | 2 ++ vm/src/obj/objstr.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index adcc106bbc..86fbe69f3e 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -132,7 +132,9 @@ assert '-'.join(['1', '2', '3']) == '1-2-3' assert 'HALLO'.isupper() assert "hello, my name is".partition("my ") == ('hello, ', 'my ', 'name is') +assert "hello".partition("is") == ('hello', '', '') assert "hello, my name is".rpartition("is") == ('hello, my name ', 'is', '') +assert "hello".rpartition("is") == ('', '', 'hello') assert not ''.isdecimal() assert '123'.isdecimal() assert not '\u00B2'.isdecimal() diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index b52a91409d..dd610f4c83 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -635,9 +635,9 @@ impl PyString { new_tup.swap(0, 1); // so it's in the right order new_tup.insert(1, vm.ctx.new_str(sub.clone())); } else { - new_tup.push(vm.ctx.new_str(value.clone())); new_tup.push(vm.ctx.new_str("".to_string())); new_tup.push(vm.ctx.new_str("".to_string())); + new_tup.push(vm.ctx.new_str(value.clone())); } vm.ctx.new_tuple(new_tup) } From ceac014a6c4eb8e9b0f5d1c895e43b0eee821537 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Wed, 24 Apr 2019 13:32:29 +0100 Subject: [PATCH 404/884] Add dict.popitem. --- tests/snippets/dict.py | 6 ++++++ vm/src/obj/objdict.rs | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 774ca0fd74..488295ed01 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -174,3 +174,9 @@ def __eq__(self, other): with assertRaises(KeyError): x.pop("not here") + +x = {1: 'a'} +assert (1, 'a') == x.popitem() +with assertRaises(KeyError): + x.popitem() +assert x == {} diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 306040f390..c3737de156 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -232,6 +232,19 @@ impl PyDictRef { self.entries.borrow_mut().pop(vm, &key) } + fn popitem(self, vm: &VirtualMachine) -> PyResult { + let mut entries = self.entries.borrow_mut(); + let (key, value) = match entries.next_entry(&mut 0) { + Some((key, value)) => (key.clone(), value.clone()), + None => { + return Err(vm.new_key_error("popitem(): dictionary is empty".to_string())); + } + }; + + entries.delete(vm, &key)?; + Ok(vm.ctx.new_tuple(vec![key, value])) + } + /// Take a python dictionary and convert it to attributes. pub fn to_attributes(self) -> PyAttributes { let mut attrs = PyAttributes::new(); @@ -463,6 +476,7 @@ pub fn init(context: &PyContext) { "copy" => context.new_rustfunc(PyDictRef::copy), "update" => context.new_rustfunc(PyDictRef::update), "pop" => context.new_rustfunc(PyDictRef::pop), + "popitem" => context.new_rustfunc(PyDictRef::popitem), }); PyDictKeys::extend_class(context, &context.dictkeys_type); From 1c43a6bfbeb1fbbcf9e6f43f31499978ec9be55a Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Wed, 24 Apr 2019 15:40:05 +0100 Subject: [PATCH 405/884] Add dict.setdefault. --- tests/snippets/dict.py | 8 ++++++++ vm/src/obj/objdict.rs | 21 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 488295ed01..2b52c5f6d6 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -180,3 +180,11 @@ def __eq__(self, other): with assertRaises(KeyError): x.popitem() assert x == {} + +x = {'a': 4} +assert 4 == x.setdefault('a', 0) +assert x['a'] == 4 +assert 0 == x.setdefault('b', 0) +assert x['b'] == 0 +assert None == x.setdefault('c') +assert x['c'] is None diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index c3737de156..a9cbcc41f3 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -213,6 +213,26 @@ impl PyDictRef { } } + fn setdefault( + self, + key: PyObjectRef, + default: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let mut entries = self.entries.borrow_mut(); + match entries.get(vm, &key)? { + Some(value) => Ok(value), + None => { + let set_value = match default { + OptionalArg::Present(value) => value, + OptionalArg::Missing => vm.ctx.none(), + }; + entries.insert(vm, &key, set_value.clone())?; + Ok(set_value) + } + } + } + fn copy(self, _vm: &VirtualMachine) -> PyDict { PyDict { entries: self.entries.clone(), @@ -473,6 +493,7 @@ pub fn init(context: &PyContext) { "items" => context.new_rustfunc(PyDictRef::items), "keys" => context.new_rustfunc(PyDictRef::keys), "get" => context.new_rustfunc(PyDictRef::get), + "setdefault" => context.new_rustfunc(PyDictRef::setdefault), "copy" => context.new_rustfunc(PyDictRef::copy), "update" => context.new_rustfunc(PyDictRef::update), "pop" => context.new_rustfunc(PyDictRef::pop), From cad4a13cf7fe86c5f0a9f904a54a6ab51e304160 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 25 Apr 2019 08:29:16 +0100 Subject: [PATCH 406/884] Add utility methods for default optional args. --- vm/src/function.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/vm/src/function.rs b/vm/src/function.rs index a72ead8f4e..d3f26950f0 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -361,6 +361,23 @@ impl OptionalArg { Missing => None, } } + + pub fn unwrap_or(self, default: T) -> T { + match self { + Present(value) => value, + Missing => default, + } + } + + pub fn unwrap_or_else(self, f: F) -> T + where + F: FnOnce() -> T, + { + match self { + Present(value) => value, + Missing => f(), + } + } } impl FromArgs for OptionalArg From 1a48fb17df7ba18e87c0371d32e225072051f177 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 25 Apr 2019 08:29:41 +0100 Subject: [PATCH 407/884] Add PyContext.new_classmethod --- vm/src/pyobject.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index d172961a35..89e5c8c394 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -21,7 +21,7 @@ use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objbytearray; use crate::obj::objbytes; -use crate::obj::objclassmethod; +use crate::obj::objclassmethod::{self, PyClassMethod}; use crate::obj::objcode; use crate::obj::objcode::PyCodeRef; use crate::obj::objcomplex::{self, PyComplex}; @@ -668,6 +668,19 @@ impl PyContext { ) } + pub fn new_classmethod(&self, f: F) -> PyObjectRef + where + F: IntoPyNativeFunc, + { + PyObject::new( + PyClassMethod { + callable: self.new_rustfunc(f), + }, + self.classmethod_type(), + None, + ) + } + pub fn new_property(&self, f: F) -> PyObjectRef where F: IntoPyNativeFunc, From 4f0b17e84fbb56afca5d92e064c2a587270476bb Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 25 Apr 2019 08:30:32 +0100 Subject: [PATCH 408/884] Add dict.fromkeys. --- tests/snippets/dict.py | 3 +++ vm/src/obj/objdict.rs | 21 +++++++++++++++++++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 2b52c5f6d6..7d67668c93 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -188,3 +188,6 @@ def __eq__(self, other): assert x['b'] == 0 assert None == x.setdefault('c') assert x['c'] is None + +assert {1: None, "b": None} == dict.fromkeys([1, "b"]) +assert {1: 0, "b": 0} == dict.fromkeys([1, "b"], 0) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index a9cbcc41f3..d36285c271 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -3,8 +3,8 @@ use std::fmt; use crate::function::{KwArgs, OptionalArg}; use crate::pyobject::{ - IdProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyObjectRef, PyRef, PyResult, - PyValue, + IdProtocol, IntoPyObject, ItemProtocol, PyAttributes, PyContext, PyIterable, PyObjectRef, + PyRef, PyResult, PyValue, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -93,6 +93,22 @@ impl PyDictRef { Ok(()) } + fn fromkeys( + class: PyClassRef, + iterable: PyIterable, + value: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let mut dict = DictContentType::default(); + let value = value.unwrap_or_else(|| vm.ctx.none()); + for elem in iterable.iter(vm)? { + let elem = elem?; + dict.insert(vm, &elem, value.clone())?; + } + let entries = RefCell::new(dict); + PyDict { entries }.into_ref_with_type(vm, class) + } + fn bool(self, _vm: &VirtualMachine) -> bool { !self.entries.borrow().is_empty() } @@ -492,6 +508,7 @@ pub fn init(context: &PyContext) { "values" => context.new_rustfunc(PyDictRef::values), "items" => context.new_rustfunc(PyDictRef::items), "keys" => context.new_rustfunc(PyDictRef::keys), + "fromkeys" => context.new_classmethod(PyDictRef::fromkeys), "get" => context.new_rustfunc(PyDictRef::get), "setdefault" => context.new_rustfunc(PyDictRef::setdefault), "copy" => context.new_rustfunc(PyDictRef::copy), From d3f4c8f6e1d52b461849d95a791a49bf221fc1da Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 25 Apr 2019 08:36:38 +0100 Subject: [PATCH 409/884] Use unwrap_or_else for OptionalArg in dictionary. --- vm/src/obj/objdict.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index d36285c271..87cf3010b6 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -222,10 +222,7 @@ impl PyDictRef { ) -> PyResult { match self.entries.borrow().get(vm, &key)? { Some(value) => Ok(value), - None => match default { - OptionalArg::Present(value) => Ok(value), - OptionalArg::Missing => Ok(vm.ctx.none()), - }, + None => Ok(default.unwrap_or_else(|| vm.ctx.none())), } } @@ -239,10 +236,7 @@ impl PyDictRef { match entries.get(vm, &key)? { Some(value) => Ok(value), None => { - let set_value = match default { - OptionalArg::Present(value) => value, - OptionalArg::Missing => vm.ctx.none(), - }; + let set_value = default.unwrap_or_else(|| vm.ctx.none()); entries.insert(vm, &key, set_value.clone())?; Ok(set_value) } From f38f263d939c555bb6197726f1459acd8320e6d1 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Thu, 25 Apr 2019 18:29:25 +0900 Subject: [PATCH 410/884] Implement stripping with arguments --- tests/snippets/strings.py | 5 +++++ vm/src/obj/objstr.rs | 28 ++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 86fbe69f3e..3364a293c5 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -52,6 +52,11 @@ assert b.lstrip() == 'hallo ' assert b.rstrip() == ' hallo' +s = '^*RustPython*^' +assert s.strip('^*') == 'RustPython' +assert s.lstrip('^*') == 'RustPython*^' +assert s.rstrip('^*') == '^*RustPython' + c = 'hallo' assert c.capitalize() == 'Hallo' assert c.center(11, '-') == '---hallo---' diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index dd610f4c83..56fc373c39 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -296,18 +296,34 @@ impl PyString { } #[pymethod] - fn strip(&self, _vm: &VirtualMachine) -> String { - self.value.trim().to_string() + fn strip(&self, chars: OptionalArg, _vm: &VirtualMachine) -> String { + let chars = match chars { + OptionalArg::Present(ref chars) => &chars.value, + OptionalArg::Missing => return self.value.trim().to_string(), + }; + self.value.trim_matches(|c| chars.contains(c)).to_string() } #[pymethod] - fn lstrip(&self, _vm: &VirtualMachine) -> String { - self.value.trim_start().to_string() + fn lstrip(&self, chars: OptionalArg, _vm: &VirtualMachine) -> String { + let chars = match chars { + OptionalArg::Present(ref chars) => &chars.value, + OptionalArg::Missing => return self.value.trim_start().to_string(), + }; + self.value + .trim_start_matches(|c| chars.contains(c)) + .to_string() } #[pymethod] - fn rstrip(&self, _vm: &VirtualMachine) -> String { - self.value.trim_end().to_string() + fn rstrip(&self, chars: OptionalArg, _vm: &VirtualMachine) -> String { + let chars = match chars { + OptionalArg::Present(ref chars) => &chars.value, + OptionalArg::Missing => return self.value.trim_end().to_string(), + }; + self.value + .trim_end_matches(|c| chars.contains(c)) + .to_string() } #[pymethod] From d48b59fd74c4078d3688cc0a2f7ad3f4db5239b9 Mon Sep 17 00:00:00 2001 From: Yan Feng Date: Thu, 25 Apr 2019 20:13:04 +0800 Subject: [PATCH 411/884] Fix os_stat for MacOS --- vm/src/stdlib/os.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 796578bb18..db5ba3bcb3 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -321,7 +321,7 @@ impl StatResultRef { } } -#[cfg(unix)] +#[cfg(target_os = "linux")] fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { use std::os::linux::fs::MetadataExt; match fs::metadata(&path.value) { @@ -340,6 +340,25 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } } +#[cfg(target_os = "macos")] +fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + use std::os::macos::fs::MetadataExt; + match fs::metadata(&path.value) { + Ok(meta) => Ok(StatResult { + st_mode: meta.st_mode(), + st_ino: meta.st_ino(), + st_dev: meta.st_dev(), + st_nlink: meta.st_nlink(), + st_uid: meta.st_uid(), + st_gid: meta.st_gid(), + st_size: meta.st_size(), + } + .into_ref(vm) + .into_object()), + Err(s) => Err(vm.new_os_error(s.to_string())), + } +} + #[cfg(windows)] fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { use std::os::windows::fs::MetadataExt; @@ -359,7 +378,7 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } } -#[cfg(all(not(unix), not(windows)))] +#[cfg(all(not(target_os = "linux"), not(target_os = "macos"), not(windows)))] fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { unimplemented!(); } From e6f08cf93edd2bb1f04bd754dc001e7f51edf4f9 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Fri, 26 Apr 2019 06:29:01 +0900 Subject: [PATCH 412/884] Enable dict round trip test for json --- tests/snippets/json_snippet.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/snippets/json_snippet.py b/tests/snippets/json_snippet.py index 917556a478..224fde8ca4 100644 --- a/tests/snippets/json_snippet.py +++ b/tests/snippets/json_snippet.py @@ -25,8 +25,7 @@ def round_trip_test(obj): assert [1, "string", 1.0, True] == json.loads(json.dumps((1, "string", 1.0, True))) assert '{}' == json.dumps({}) -# TODO: uncomment once dict comparison is implemented -# round_trip_test({'a': 'b'}) +round_trip_test({'a': 'b'}) assert 1 == json.loads("1") assert -1 == json.loads("-1") From e4698987bba0ced53c1d5e557fdfe493cd58e2bc Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 25 Apr 2019 15:14:43 -0500 Subject: [PATCH 413/884] Add a cfg for os.stat for android --- vm/src/stdlib/os.rs | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index db5ba3bcb3..6d81ca9c13 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -359,6 +359,25 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } } +#[cfg(target_os = "android")] +fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { + use std::os::android::fs::MetadataExt; + match fs::metadata(&path.value) { + Ok(meta) => Ok(StatResult { + st_mode: meta.st_mode(), + st_ino: meta.st_ino(), + st_dev: meta.st_dev(), + st_nlink: meta.st_nlink(), + st_uid: meta.st_uid(), + st_gid: meta.st_gid(), + st_size: meta.st_size(), + } + .into_ref(vm) + .into_object()), + Err(s) => Err(vm.new_os_error(s.to_string())), + } +} + #[cfg(windows)] fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { use std::os::windows::fs::MetadataExt; @@ -378,7 +397,12 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } } -#[cfg(all(not(target_os = "linux"), not(target_os = "macos"), not(windows)))] +#[cfg(not(any( + target_os = "linux", + target_os = "macos", + target_os = "android", + windows +)))] fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { unimplemented!(); } From 6e867edb6db3f73ffc6679e910b976e08dc9f5d6 Mon Sep 17 00:00:00 2001 From: Agent Date: Fri, 26 Apr 2019 12:17:16 +0800 Subject: [PATCH 414/884] Fix json.loads (#876) * Fix json.loads --- vm/src/stdlib/json.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index dceec8a7df..d57dd14a99 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -199,13 +199,12 @@ pub fn de_pyobject(vm: &VirtualMachine, s: &str) -> PyResult { // TODO: Support deserializing string sub-classes de.deserialize(&mut serde_json::Deserializer::from_str(s)) .map_err(|err| { - let json_decode_error = vm + let module = vm .get_attribute(vm.sys_module.clone(), "modules") .unwrap() .get_item("json", vm) - .unwrap() - .get_item("JSONDecodeError", vm) .unwrap(); + let json_decode_error = vm.get_attribute(module, "JSONDecodeError").unwrap(); let json_decode_error = json_decode_error.downcast().unwrap(); let exc = vm.new_exception(json_decode_error, format!("{}", err)); vm.set_attr(&exc, "lineno", vm.ctx.new_int(err.line())) From 0e9acdc12b5a0ed98f85210451a596331304c909 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Fri, 26 Apr 2019 15:33:37 +0900 Subject: [PATCH 415/884] Fix justification --- tests/snippets/strings.py | 11 +++++++++++ vm/src/obj/objstr.rs | 12 ++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 3364a293c5..0baa35d23c 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -57,6 +57,17 @@ assert s.lstrip('^*') == 'RustPython*^' assert s.rstrip('^*') == '^*RustPython' +s = 'RustPython' +assert s.ljust(8) == 'RustPython' +assert s.rjust(8) == 'RustPython' +assert s.ljust(12) == 'RustPython ' +assert s.rjust(12) == ' RustPython' +assert s.ljust(12, '_') == 'RustPython__' +assert s.rjust(12, '_') == '__RustPython' +# The fill character must be exactly one character long +assert_raises(TypeError, lambda: s.ljust(12, '__')) +assert_raises(TypeError, lambda: s.rjust(12, '__')) + c = 'hallo' assert c.capitalize() == 'Hallo' assert c.center(11, '-') == '---hallo---' diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 56fc373c39..8ea438e35d 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -740,7 +740,11 @@ impl PyString { ) -> PyResult { let value = &self.value; let rep_char = Self::get_fill_char(&rep, vm)?; - Ok(format!("{}{}", value, rep_char.repeat(len))) + if len <= value.len() { + Ok(value.to_string()) + } else { + Ok(format!("{}{}", value, rep_char.repeat(len - value.len()))) + } } #[pymethod] @@ -752,7 +756,11 @@ impl PyString { ) -> PyResult { let value = &self.value; let rep_char = Self::get_fill_char(&rep, vm)?; - Ok(format!("{}{}", rep_char.repeat(len), value)) + if len <= value.len() { + Ok(value.to_string()) + } else { + Ok(format!("{}{}", rep_char.repeat(len - value.len()), value)) + } } #[pymethod] From 16152a1d3290be1fe8cd2773010483bf39628c68 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 26 Apr 2019 23:49:51 +0900 Subject: [PATCH 416/884] Fix range() to support negative index --- tests/snippets/builtin_range.py | 2 ++ vm/src/obj/objrange.rs | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/tests/snippets/builtin_range.py b/tests/snippets/builtin_range.py index 2ea71f7a7f..1dd5e7028d 100644 --- a/tests/snippets/builtin_range.py +++ b/tests/snippets/builtin_range.py @@ -54,3 +54,5 @@ # range retains the original int refs i = 2**64 assert range(i).stop is i + +assert range(10)[-1] == 9 diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 1a21a49ad3..56b7c03960 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -1,5 +1,4 @@ use std::cell::Cell; -use std::ops::Mul; use num_bigint::{BigInt, Sign}; use num_integer::Integer; @@ -65,15 +64,18 @@ impl PyRange { } #[inline] - pub fn get<'a, T>(&'a self, index: T) -> Option - where - &'a BigInt: Mul, - { + pub fn get(&self, index: &BigInt) -> Option { let start = self.start.as_bigint(); let stop = self.stop.as_bigint(); let step = self.step.as_bigint(); - let result = start + step * index; + let index = if index < &BigInt::zero() { + stop + index + } else { + index.clone() + }; + + let result = start + step * &index; if (self.forward() && !self.is_empty() && &result < stop) || (!self.forward() && !self.is_empty() && &result > stop) @@ -274,7 +276,7 @@ impl PyRange { } RangeIndex::Slice(slice) => { let new_start = if let Some(int) = slice.start_index(vm)? { - if let Some(i) = self.get(int) { + if let Some(i) = self.get(&int) { PyInt::new(i).into_ref(vm) } else { self.start.clone() @@ -284,7 +286,7 @@ impl PyRange { }; let new_end = if let Some(int) = slice.stop_index(vm)? { - if let Some(i) = self.get(int) { + if let Some(i) = self.get(&int) { PyInt::new(i).into_ref(vm) } else { self.stop.clone() @@ -339,7 +341,8 @@ type PyRangeIteratorRef = PyRef; impl PyRangeIteratorRef { fn next(self, vm: &VirtualMachine) -> PyResult { - if let Some(int) = self.range.get(self.position.get()) { + let position = BigInt::from(self.position.get()); + if let Some(int) = self.range.get(&position) { self.position.set(self.position.get() + 1); Ok(int) } else { From 16353f4f546a8b6a5bb80cf85db759b8188055b4 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 27 Apr 2019 00:06:07 +0900 Subject: [PATCH 417/884] range negative side bound error --- tests/snippets/builtin_range.py | 4 ++++ vm/src/obj/objrange.rs | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/snippets/builtin_range.py b/tests/snippets/builtin_range.py index 1dd5e7028d..b087f7add0 100644 --- a/tests/snippets/builtin_range.py +++ b/tests/snippets/builtin_range.py @@ -55,4 +55,8 @@ i = 2**64 assert range(i).stop is i +# negative index assert range(10)[-1] == 9 +assert_raises(IndexError, lambda: range(10)[-11], 'out of bound') +assert str(range(10)[-2:4]) == str(range(8, 4)) +assert str(range(10)[-6:-2]) == str(range(4, 8)) diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 56b7c03960..abe82ec3b0 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -70,7 +70,11 @@ impl PyRange { let step = self.step.as_bigint(); let index = if index < &BigInt::zero() { - stop + index + let index = stop + index; + if index < BigInt::zero() { + return None; + } + index } else { index.clone() }; From a7091ca88da3f9ee4fcd38ea033c2210c1e1dfb3 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 27 Apr 2019 00:27:47 +0900 Subject: [PATCH 418/884] Add range.__eq__ --- tests/snippets/builtin_range.py | 4 ++-- vm/src/obj/objrange.rs | 14 +++++++++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/snippets/builtin_range.py b/tests/snippets/builtin_range.py index b087f7add0..a8797f75a1 100644 --- a/tests/snippets/builtin_range.py +++ b/tests/snippets/builtin_range.py @@ -58,5 +58,5 @@ # negative index assert range(10)[-1] == 9 assert_raises(IndexError, lambda: range(10)[-11], 'out of bound') -assert str(range(10)[-2:4]) == str(range(8, 4)) -assert str(range(10)[-6:-2]) == str(range(4, 8)) +assert range(10)[-2:4] == range(8, 4) +assert range(10)[-6:-2] == range(4, 8) diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index abe82ec3b0..d98e7491fe 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -13,7 +13,7 @@ use crate::vm::VirtualMachine; use super::objint::{PyInt, PyIntRef}; use super::objiter; use super::objslice::{PySlice, PySliceRef}; -use super::objtype::PyClassRef; +use super::objtype::{self, PyClassRef}; #[derive(Debug, Clone)] pub struct PyRange { @@ -110,6 +110,7 @@ pub fn init(context: &PyContext) { "__bool__" => context.new_rustfunc(PyRange::bool), "__contains__" => context.new_rustfunc(PyRange::contains), "__doc__" => context.new_str(range_doc.to_string()), + "__eq__" => context.new_rustfunc(PyRange::eq), "__getitem__" => context.new_rustfunc(PyRange::getitem), "__iter__" => context.new_rustfunc(PyRange::iter), "__len__" => context.new_rustfunc(PyRange::len), @@ -246,6 +247,17 @@ impl PyRange { } } + fn eq(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { + if objtype::isinstance(&rhs, &vm.ctx.range_type()) { + let rhs = get_value(&rhs); + self.start.as_bigint() == rhs.start.as_bigint() + && self.stop.as_bigint() == rhs.stop.as_bigint() + && self.step.as_bigint() == rhs.step.as_bigint() + } else { + false + } + } + fn index(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { if let Ok(int) = needle.downcast::() { match self.index_of(int.as_bigint()) { From 462c8233f3f8d3a94f102b9bd13f70a43ea86343 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 27 Apr 2019 13:49:54 +0300 Subject: [PATCH 419/884] Add exceptions stack to VirtualMachine --- vm/src/bytecode.rs | 2 ++ vm/src/compile.rs | 12 ++++++++---- vm/src/frame.rs | 19 ++++++++++++------- vm/src/vm.rs | 10 ++++++++++ 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 73d69dc180..a6288cf50f 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -183,6 +183,7 @@ pub enum Instruction { conversion: Option, spec: String, }, + PopException, } use self::Instruction::*; @@ -381,6 +382,7 @@ impl Instruction { UnpackEx { before, after } => w!(UnpackEx, before, after), Unpack => w!(Unpack), FormatValue { spec, .. } => w!(FormatValue, spec), // TODO: write conversion + PopException => w!(PopException), } } } diff --git a/vm/src/compile.rs b/vm/src/compile.rs index b7df813134..f16544006f 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -627,16 +627,20 @@ impl Compiler { // We have a match, store in name (except x as y) if let Some(alias) = &handler.name { - // Duplicate exception for context: - self.emit(Instruction::Duplicate); self.store_name(alias); + } else { + // Drop exception from top of stack: + self.emit(Instruction::Pop); } + } else { + // Catch all! + // Drop exception from top of stack: + self.emit(Instruction::Pop); } // Handler code: self.compile_statements(&handler.body)?; - // Drop exception from top of stack: - self.emit(Instruction::Pop); + self.emit(Instruction::PopException); self.emit(Instruction::Jump { target: finally_label, }); diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 06c970de1f..56d8c0d7b9 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -724,9 +724,9 @@ impl Frame { _ => vm.get_none(), }; let exception = match argc { - 0 => match in_exc { - true => self.get_exception(vm, false)?, - false => { + 0 => match vm.pop_exception() { + Some(exc) => exc, + None => { return Err(vm.new_exception( vm.ctx.exceptions.runtime_error.clone(), "No active exception to reraise".to_string(), @@ -739,9 +739,9 @@ impl Frame { }; let context = match argc { 0 => vm.get_none(), // We have already got the exception, - _ => match in_exc { - true => self.get_exception(vm, false)?, - false => vm.get_none(), + _ => match vm.pop_exception() { + Some(exc) => exc, + None => vm.get_none(), }, }; info!( @@ -860,6 +860,10 @@ impl Frame { self.push_value(formatted); Ok(None) } + bytecode::Instruction::PopException {} => { + assert!(!vm.pop_exception().is_none()); + Ok(None) + } } } @@ -966,7 +970,8 @@ impl Frame { while let Some(block) = self.pop_block() { match block.typ { BlockType::TryExcept { handler } => { - self.push_value(exc); + self.push_value(exc.clone()); + vm.push_exception(exc); self.jump(handler); return None; } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index ab15e14e95..a677d73410 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -50,6 +50,7 @@ pub struct VirtualMachine { pub ctx: PyContext, pub frames: RefCell>, pub wasm_id: Option, + pub exceptions: RefCell>, } impl VirtualMachine { @@ -69,6 +70,7 @@ impl VirtualMachine { ctx, frames: RefCell::new(vec![]), wasm_id: None, + exceptions: RefCell::new(vec![]), }; builtins::make_module(&vm, builtins.clone()); @@ -921,6 +923,14 @@ impl VirtualMachine { self._membership_iter_search(haystack, needle) } } + + pub fn push_exception(&self, exc: PyObjectRef) -> () { + self.exceptions.borrow_mut().push(exc) + } + + pub fn pop_exception(&self) -> Option { + self.exceptions.borrow_mut().pop() + } } impl Default for VirtualMachine { From 4b9bafc824cfd4430ae27dfb9aa7e6001abe4961 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 27 Apr 2019 14:33:24 +0300 Subject: [PATCH 420/884] Remove in_exc_handler for Raise --- vm/src/bytecode.rs | 3 +-- vm/src/compile.rs | 31 +++++-------------------------- vm/src/frame.rs | 2 +- 3 files changed, 7 insertions(+), 29 deletions(-) diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index a6288cf50f..09bb7e70a3 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -136,7 +136,6 @@ pub enum Instruction { PopBlock, Raise { argc: usize, - in_exc: bool, }, BuildString { size: usize, @@ -366,7 +365,7 @@ impl Instruction { SetupWith { end } => w!(SetupWith, end), CleanupWith { end } => w!(CleanupWith, end), PopBlock => w!(PopBlock), - Raise { argc, in_exc } => w!(Raise, argc, in_exc), + Raise { argc } => w!(Raise, argc), BuildString { size } => w!(BuildString, size), BuildTuple { size, unpack } => w!(BuildTuple, size, unpack), BuildList { size, unpack } => w!(BuildList, size, unpack), diff --git a/vm/src/compile.rs b/vm/src/compile.rs index f16544006f..44ea521f3c 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -24,7 +24,6 @@ struct Compiler { current_qualified_path: Option, in_loop: bool, in_function_def: bool, - in_exc_handler: bool, } /// Compile a given sourcecode into a bytecode object. @@ -86,7 +85,6 @@ impl Compiler { current_qualified_path: None, in_loop: false, in_function_def: false, - in_exc_handler: false, } } @@ -330,24 +328,15 @@ impl Compiler { match cause { Some(cause) => { self.compile_expression(cause)?; - self.emit(Instruction::Raise { - argc: 2, - in_exc: self.in_exc_handler, - }); + self.emit(Instruction::Raise { argc: 2 }); } None => { - self.emit(Instruction::Raise { - argc: 1, - in_exc: self.in_exc_handler, - }); + self.emit(Instruction::Raise { argc: 1 }); } } } None => { - self.emit(Instruction::Raise { - argc: 0, - in_exc: self.in_exc_handler, - }); + self.emit(Instruction::Raise { argc: 0 }); } }, ast::Statement::Try { @@ -392,10 +381,7 @@ impl Compiler { }); } } - self.emit(Instruction::Raise { - argc: 1, - in_exc: self.in_exc_handler, - }); + self.emit(Instruction::Raise { argc: 1 }); self.set_label(end_label); } ast::Statement::Break => { @@ -597,8 +583,6 @@ impl Compiler { self.emit(Instruction::Jump { target: else_label }); // except handlers: - let was_in_exc_handler = self.in_exc_handler; - self.in_exc_handler = true; self.set_label(handler_label); // Exception is on top of stack now handler_label = self.new_label(); @@ -661,12 +645,7 @@ impl Compiler { if let Some(statements) = finalbody { self.compile_statements(statements)?; } - self.emit(Instruction::Raise { - argc: 0, - in_exc: true, - }); - - self.in_exc_handler = was_in_exc_handler; + self.emit(Instruction::Raise { argc: 0 }); // We successfully ran the try block: // else: diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 56d8c0d7b9..00d843d2ac 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -718,7 +718,7 @@ impl Frame { Ok(None) } - bytecode::Instruction::Raise { argc, in_exc } => { + bytecode::Instruction::Raise { argc } => { let cause = match argc { 2 => self.get_exception(vm, true)?, _ => vm.get_none(), From 180ec22d4b67f26b3c0ef31bd0d80cf162158880 Mon Sep 17 00:00:00 2001 From: Agent Date: Sat, 27 Apr 2019 22:41:19 +0800 Subject: [PATCH 421/884] Add constants to os module (#883) --- Lib/os.py | 10 ++++++++++ tests/snippets/stdlib_os.py | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/Lib/os.py b/Lib/os.py index 3cadb5f616..a14702d5e4 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -1,9 +1,19 @@ from _os import * +curdir = '.' +pardir = '..' +extsep = '.' + if name == 'nt': sep = '\\' + linesep = '\r\n' + altsep = '/' + pathsep = ';' else: sep = '/' + linesep = '\n' + altsep = None + pathsep = ':' # Change environ to automatically call putenv(), unsetenv if they exist. from _collections_abc import MutableMapping diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 0fb44cbb59..4b0344f12d 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -33,11 +33,20 @@ os.unsetenv(ENV_KEY) assert os.getenv(ENV_KEY) == None +assert os.curdir == "." +assert os.pardir == ".." +assert os.extsep == "." if os.name == "nt": assert os.sep == "\\" + assert os.linesep == "\r\n" + assert os.altsep == "/" + assert os.pathsep == ";" else: assert os.sep == "/" + assert os.linesep == "\n" + assert os.altsep == None + assert os.pathsep == ":" class TestWithTempDir(): def __enter__(self): From 9d40a4c9d8f2ab22c8f05c929321f4f26a141375 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Sat, 27 Apr 2019 17:44:21 +0200 Subject: [PATCH 422/884] Support list.__imul__ --- tests/snippets/list.py | 4 ++++ vm/src/obj/objlist.rs | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/tests/snippets/list.py b/tests/snippets/list.py index 12d961b619..e8e731de97 100644 --- a/tests/snippets/list.py +++ b/tests/snippets/list.py @@ -14,6 +14,10 @@ assert x * 0 == [], "list __mul__ by 0 failed" assert x * -1 == [], "list __mul__ by -1 failed" assert x * 2 == [1, 2, 3, 1, 2, 3], "list __mul__ by 2 failed" +y = x +x *= 2 +assert y is x +assert x == [1, 2, 3] * 2 # index() assert ['a', 'b', 'c'].index('b') == 1 diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index bfc763d8cd..5c1bad5af8 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -395,6 +395,12 @@ impl PyListRef { vm.ctx.new_list(new_elements) } + fn imul(self, counter: isize, _vm: &VirtualMachine) -> Self { + let new_elements = seq_mul(&self.elements.borrow(), counter); + self.elements.replace(new_elements); + self + } + fn count(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { let mut count: usize = 0; for element in self.elements.borrow().iter() { @@ -823,6 +829,7 @@ pub fn init(context: &PyContext) { "__iter__" => context.new_rustfunc(PyListRef::iter), "__setitem__" => context.new_rustfunc(PyListRef::setitem), "__mul__" => context.new_rustfunc(PyListRef::mul), + "__imul__" => context.new_rustfunc(PyListRef::imul), "__len__" => context.new_rustfunc(PyListRef::len), "__new__" => context.new_rustfunc(list_new), "__repr__" => context.new_rustfunc(PyListRef::repr), From d87468a1cbe7c65c87f193032485d4cc0ba6ee43 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 27 Apr 2019 18:48:41 +0900 Subject: [PATCH 423/884] Add type.__name__ --- tests/snippets/types_snippet.py | 1 + vm/src/obj/objtype.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/tests/snippets/types_snippet.py b/tests/snippets/types_snippet.py index f441552d11..0759574181 100644 --- a/tests/snippets/types_snippet.py +++ b/tests/snippets/types_snippet.py @@ -49,3 +49,4 @@ class D(B, C): pass assert type in object.__subclasses__() +assert cls.__name__ == 'Cls' diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 819f850881..c14e512301 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -99,6 +99,10 @@ impl PyClassRef { issubclass(&subclass, &self) } + fn name(self, _vm: &VirtualMachine) -> String { + self.name.clone() + } + fn repr(self, _vm: &VirtualMachine) -> String { format!("", self.name) } @@ -198,6 +202,7 @@ pub fn init(ctx: &PyContext) { .add_getter(PyClassRef::mro) .add_setter(PyClassRef::set_mro) .create(), + "__name__" => ctx.new_property(PyClassRef::name), "__repr__" => ctx.new_rustfunc(PyClassRef::repr), "__prepare__" => ctx.new_rustfunc(PyClassRef::prepare), "__getattribute__" => ctx.new_rustfunc(PyClassRef::getattribute), From 60afbb4bf5dd9c9b8a3f32e60f2be9bcbe2ff6f2 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 27 Apr 2019 21:53:08 +0300 Subject: [PATCH 424/884] Don't pop exeption for context --- vm/src/frame.rs | 4 ++-- vm/src/vm.rs | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 00d843d2ac..69b1d510ac 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -739,8 +739,8 @@ impl Frame { }; let context = match argc { 0 => vm.get_none(), // We have already got the exception, - _ => match vm.pop_exception() { - Some(exc) => exc, + _ => match vm.last_exception() { + Some(exc) => exc.clone(), None => vm.get_none(), }, }; diff --git a/vm/src/vm.rs b/vm/src/vm.rs index a677d73410..9308c0caa2 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -931,6 +931,17 @@ impl VirtualMachine { pub fn pop_exception(&self) -> Option { self.exceptions.borrow_mut().pop() } + + pub fn last_exception(&self) -> Option> { + let exceptions = self.exceptions.borrow(); + if exceptions.is_empty() { + None + } else { + Some(Ref::map(self.exceptions.borrow(), |exceptions| { + exceptions.last().unwrap() + })) + } + } } impl Default for VirtualMachine { From 7fb98c91d2ab4de624eb1622e46ecbf13ceb8703 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 05:05:33 +0900 Subject: [PATCH 425/884] PyFloat uses #[pyclass] #[pyimpl] #[pymethod] --- vm/src/obj/objfloat.rs | 66 +++++++++++++++++++++++------------------- 1 file changed, 37 insertions(+), 29 deletions(-) diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index e16edf5ce9..5438d31c1e 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -4,13 +4,14 @@ use super::objstr; use super::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - IntoPyObject, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; use num_bigint::ToBigInt; use num_rational::Ratio; use num_traits::ToPrimitive; +#[pyclass(name = "float")] #[derive(Debug, Copy, Clone, PartialEq)] pub struct PyFloat { value: f64, @@ -40,7 +41,9 @@ impl From for PyFloat { } } +#[pyimpl] impl PyFloat { + #[pymethod(name = "__eq__")] fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let value = self.value; let result = if objtype::isinstance(&other, &vm.ctx.float_type()) { @@ -60,6 +63,7 @@ impl PyFloat { vm.ctx.new_bool(result) } + #[pymethod(name = "__lt__")] fn lt(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&i2, &vm.ctx.float_type()) { @@ -72,6 +76,7 @@ impl PyFloat { } } + #[pymethod(name = "__le__")] fn le(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&i2, &vm.ctx.float_type()) { @@ -84,6 +89,7 @@ impl PyFloat { } } + #[pymethod(name = "__gt__")] fn gt(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&i2, &vm.ctx.float_type()) { @@ -96,6 +102,7 @@ impl PyFloat { } } + #[pymethod(name = "__ge__")] fn ge(&self, i2: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&i2, &vm.ctx.float_type()) { @@ -108,10 +115,12 @@ impl PyFloat { } } + #[pymethod(name = "__abs__")] fn abs(&self, _vm: &VirtualMachine) -> f64 { self.value.abs() } + #[pymethod(name = "__add__")] fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&other, &vm.ctx.float_type()) { @@ -124,10 +133,17 @@ impl PyFloat { } } + #[pymethod(name = "__radd__")] + fn radd(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + self.add(other, vm) + } + + #[pymethod(name = "__bool__")] fn bool(&self, _vm: &VirtualMachine) -> bool { self.value != 0.0 } + #[pymethod(name = "__divmod__")] fn divmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.float_type()) || objtype::isinstance(&other, &vm.ctx.int_type()) @@ -140,6 +156,7 @@ impl PyFloat { } } + #[pymethod(name = "__floordiv__")] fn floordiv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { @@ -199,6 +216,7 @@ impl PyFloat { PyFloat { value }.into_ref_with_type(vm, cls) } + #[pymethod(name = "__mod__")] fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { @@ -218,10 +236,12 @@ impl PyFloat { } } + #[pymethod(name = "__neg__")] fn neg(&self, _vm: &VirtualMachine) -> f64 { -self.value } + #[pymethod(name = "__pow__")] fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let v1 = self.value; if objtype::isinstance(&other, &vm.ctx.float_type()) { @@ -234,6 +254,7 @@ impl PyFloat { } } + #[pymethod(name = "__sub__")] fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; if objtype::isinstance(&other, &vm.ctx.float_type()) { @@ -247,6 +268,7 @@ impl PyFloat { } } + #[pymethod(name = "__rsub__")] fn rsub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; if objtype::isinstance(&other, &vm.ctx.float_type()) { @@ -260,10 +282,12 @@ impl PyFloat { } } + #[pymethod(name = "__repr__")] fn repr(&self, _vm: &VirtualMachine) -> String { self.value.to_string() } + #[pymethod(name = "__truediv__")] fn truediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { @@ -283,6 +307,7 @@ impl PyFloat { } } + #[pymethod(name = "__rtruediv__")] fn rtruediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { @@ -302,6 +327,7 @@ impl PyFloat { } } + #[pymethod(name = "__mul__")] fn mul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { let v1 = self.value; if objtype::isinstance(&other, &vm.ctx.float_type) { @@ -315,15 +341,23 @@ impl PyFloat { } } + #[pymethod(name = "__rmul__")] + fn rmul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.mul(other, vm) + } + + #[pyproperty(name = "real")] fn real(zelf: PyRef, _vm: &VirtualMachine) -> PyFloatRef { zelf } + #[pymethod(name = "is_integer")] fn is_integer(&self, _vm: &VirtualMachine) -> bool { let v = self.value; (v - v.round()).abs() < std::f64::EPSILON } + #[pymethod(name = "as_integer_ratio")] fn as_integer_ratio(&self, vm: &VirtualMachine) -> PyResult { let value = self.value; if value.is_infinite() { @@ -362,36 +396,10 @@ pub fn make_float(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { #[rustfmt::skip] // to avoid line splitting pub fn init(context: &PyContext) { - let float_type = &context.float_type; - + PyFloat::extend_class(context, &context.float_type); let float_doc = "Convert a string or number to a floating point number, if possible."; - - extend_class!(context, float_type, { - "__eq__" => context.new_rustfunc(PyFloat::eq), - "__lt__" => context.new_rustfunc(PyFloat::lt), - "__le__" => context.new_rustfunc(PyFloat::le), - "__gt__" => context.new_rustfunc(PyFloat::gt), - "__ge__" => context.new_rustfunc(PyFloat::ge), - "__abs__" => context.new_rustfunc(PyFloat::abs), - "__add__" => context.new_rustfunc(PyFloat::add), - "__radd__" => context.new_rustfunc(PyFloat::add), - "__bool__" => context.new_rustfunc(PyFloat::bool), - "__divmod__" => context.new_rustfunc(PyFloat::divmod), - "__floordiv__" => context.new_rustfunc(PyFloat::floordiv), + extend_class!(context, &context.float_type, { "__new__" => context.new_rustfunc(PyFloat::new_float), - "__mod__" => context.new_rustfunc(PyFloat::mod_), - "__neg__" => context.new_rustfunc(PyFloat::neg), - "__pow__" => context.new_rustfunc(PyFloat::pow), - "__sub__" => context.new_rustfunc(PyFloat::sub), - "__rsub__" => context.new_rustfunc(PyFloat::rsub), - "__repr__" => context.new_rustfunc(PyFloat::repr), "__doc__" => context.new_str(float_doc.to_string()), - "__truediv__" => context.new_rustfunc(PyFloat::truediv), - "__rtruediv__" => context.new_rustfunc(PyFloat::rtruediv), - "__mul__" => context.new_rustfunc(PyFloat::mul), - "__rmul__" => context.new_rustfunc(PyFloat::mul), - "real" => context.new_property(PyFloat::real), - "is_integer" => context.new_rustfunc(PyFloat::is_integer), - "as_integer_ratio" => context.new_rustfunc(PyFloat::as_integer_ratio) }); } From ed79ccc6b1e2c530c27f975cd9069d2be703d973 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 06:04:21 +0900 Subject: [PATCH 426/884] PyComplex uses PyClassImpl --- vm/src/obj/objcomplex.rs | 47 +++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 9f88dd6a41..5992c32d62 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -2,13 +2,14 @@ use num_complex::Complex64; use num_traits::ToPrimitive; use crate::function::OptionalArg; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objfloat::{self, PyFloat}; use super::objint; use super::objtype::{self, PyClassRef}; +#[pyclass(name = "complex")] #[derive(Debug, Copy, Clone, PartialEq)] pub struct PyComplex { value: Complex64, @@ -28,24 +29,14 @@ impl From for PyComplex { } pub fn init(context: &PyContext) { - let complex_type = &context.complex_type; - + PyComplex::extend_class(context, &context.complex_type); let complex_doc = "Create a complex number from a real part and an optional imaginary part.\n\n\ This is equivalent to (real + imag*1j) where imag defaults to 0."; - extend_class!(context, complex_type, { + extend_class!(context, &context.complex_type, { "__doc__" => context.new_str(complex_doc.to_string()), - "__abs__" => context.new_rustfunc(PyComplexRef::abs), - "__add__" => context.new_rustfunc(PyComplexRef::add), - "__eq__" => context.new_rustfunc(PyComplexRef::eq), - "__neg__" => context.new_rustfunc(PyComplexRef::neg), "__new__" => context.new_rustfunc(PyComplexRef::new), - "__radd__" => context.new_rustfunc(PyComplexRef::radd), - "__repr__" => context.new_rustfunc(PyComplexRef::repr), - "conjugate" => context.new_rustfunc(PyComplexRef::conjugate), - "imag" => context.new_property(PyComplexRef::imag), - "real" => context.new_property(PyComplexRef::real) }); } @@ -73,21 +64,28 @@ impl PyComplexRef { let value = Complex64::new(real, imag); PyComplex { value }.into_ref_with_type(vm, cls) } +} - fn real(self, _vm: &VirtualMachine) -> PyFloat { +#[pyimpl] +impl PyComplex { + #[pyproperty(name = "real")] + fn real(&self, _vm: &VirtualMachine) -> PyFloat { self.value.re.into() } - fn imag(self, _vm: &VirtualMachine) -> PyFloat { + #[pyproperty(name = "imag")] + fn imag(&self, _vm: &VirtualMachine) -> PyFloat { self.value.im.into() } - fn abs(self, _vm: &VirtualMachine) -> PyFloat { + #[pymethod(name = "__abs__")] + fn abs(&self, _vm: &VirtualMachine) -> PyFloat { let Complex64 { im, re } = self.value; re.hypot(im).into() } - fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + #[pymethod(name = "__add__")] + fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.complex_type()) { vm.ctx.new_complex(self.value + get_value(&other)) } else if objtype::isinstance(&other, &vm.ctx.int_type()) { @@ -100,7 +98,8 @@ impl PyComplexRef { } } - fn radd(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + #[pymethod(name = "__radd__")] + fn radd(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_complex(Complex64::new( self.value.re + objint::get_value(&other).to_f64().unwrap(), @@ -111,11 +110,13 @@ impl PyComplexRef { } } - fn conjugate(self, _vm: &VirtualMachine) -> PyComplex { + #[pymethod(name = "conjugate")] + fn conjugate(&self, _vm: &VirtualMachine) -> PyComplex { self.value.conj().into() } - fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + #[pymethod(name = "__eq__")] + fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let result = if objtype::isinstance(&other, &vm.ctx.complex_type()) { self.value == get_value(&other) } else if objtype::isinstance(&other, &vm.ctx.int_type()) { @@ -132,11 +133,13 @@ impl PyComplexRef { vm.ctx.new_bool(result) } - fn neg(self, _vm: &VirtualMachine) -> PyComplex { + #[pymethod(name = "__neg__")] + fn neg(&self, _vm: &VirtualMachine) -> PyComplex { PyComplex::from(-self.value) } - fn repr(self, _vm: &VirtualMachine) -> String { + #[pymethod(name = "__repr__")] + fn repr(&self, _vm: &VirtualMachine) -> String { let Complex64 { re, im } = self.value; if re == 0.0 { format!("{}j", im) From 15e4e678cac0d0bf32bcaf0cf89a07012f0c4126 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 06:19:12 +0900 Subject: [PATCH 427/884] Add complex.__bool__ --- tests/snippets/builtin_complex.py | 6 ++++++ vm/src/obj/objcomplex.rs | 7 ++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index c073f74337..1712533f04 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -27,6 +27,12 @@ assert -complex(1, -1) == complex(-1, 1) assert -complex(0, 0) == complex(0, 0) +# __bool__ + +assert bool(complex(0, 0)) is False +assert bool(complex(0, 1)) is True +assert bool(complex(1, 0)) is True + # real a = complex(3, 4) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 5992c32d62..df3a6f0156 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -1,5 +1,5 @@ use num_complex::Complex64; -use num_traits::ToPrimitive; +use num_traits::{ToPrimitive, Zero}; use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; @@ -147,4 +147,9 @@ impl PyComplex { format!("({}+{}j)", re, im) } } + + #[pymethod(name = "__bool__")] + fn bool(&self, _vm: &VirtualMachine) -> bool { + self.value != Complex64::zero() + } } From 2eae737f3d050bb06bec971d1b59e443a59f29e6 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 06:52:54 +0900 Subject: [PATCH 428/884] complex.__add__ can raises OverflowError --- tests/snippets/builtin_complex.py | 10 +++++++++ vm/src/obj/objcomplex.rs | 35 +++++++++++++++++-------------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index 1712533f04..bfe43059e7 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -1,3 +1,5 @@ +from testutils import assertRaises + # __abs__ assert abs(complex(3, 4)) == 5 @@ -50,3 +52,11 @@ assert 1j + 1 == complex(1, 1) assert (1j + 1) + 3 == complex(4, 1) assert 3 + (1j + 1) == complex(4, 1) + +# overflow +with assertRaises(OverflowError): + complex(10 ** 1000, 0) +with assertRaises(OverflowError): + complex(0, 10 ** 1000) +with assertRaises(OverflowError): + complex(0, 0) + 10 ** 1000 diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index df3a6f0156..3be4e8239f 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -66,6 +66,17 @@ impl PyComplexRef { } } +fn to_complex(value: PyObjectRef, vm: &VirtualMachine) -> PyResult> { + if objtype::isinstance(&value, &vm.ctx.int_type()) { + match objint::get_value(&value).to_f64() { + Some(v) => Ok(Some(Complex64::new(v, 0.0))), + None => Err(vm.new_overflow_error("int too large to convert to float".to_string())), + } + } else { + Ok(None) + } +} + #[pyimpl] impl PyComplex { #[pyproperty(name = "real")] @@ -85,28 +96,20 @@ impl PyComplex { } #[pymethod(name = "__add__")] - fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.complex_type()) { - vm.ctx.new_complex(self.value + get_value(&other)) - } else if objtype::isinstance(&other, &vm.ctx.int_type()) { - vm.ctx.new_complex(Complex64::new( - self.value.re + objint::get_value(&other).to_f64().unwrap(), - self.value.im, - )) + Ok(vm.ctx.new_complex(self.value + get_value(&other))) } else { - vm.ctx.not_implemented() + self.radd(other, vm) } } #[pymethod(name = "__radd__")] - fn radd(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - if objtype::isinstance(&other, &vm.ctx.int_type()) { - vm.ctx.new_complex(Complex64::new( - self.value.re + objint::get_value(&other).to_f64().unwrap(), - self.value.im, - )) - } else { - vm.ctx.not_implemented() + fn radd(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match to_complex(other, vm) { + Ok(Some(other)) => Ok(vm.ctx.new_complex(self.value + other)), + Ok(None) => Ok(vm.ctx.not_implemented()), + Err(err) => Err(err), } } From a2625916f5fea651b6290980d9427a35f505153b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 12:01:54 +0900 Subject: [PATCH 429/884] Fix complex.__add__ to support float --- tests/snippets/builtin_complex.py | 6 ++++++ vm/src/obj/objcomplex.rs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index bfe43059e7..be688cc634 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -53,6 +53,12 @@ assert (1j + 1) + 3 == complex(4, 1) assert 3 + (1j + 1) == complex(4, 1) +# float and complex addition +assert 1.1 + 1.2j == complex(1.1, 1.2) +assert 1.3j + 1.4 == complex(1.4, 1.3) +assert (1.5j + 1.6) + 3 == complex(4.6, 1.5) +assert 3.5 + (1.1j + 1.2) == complex(4.7, 1.1) + # overflow with assertRaises(OverflowError): complex(10 ** 1000, 0) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 3be4e8239f..af4880e066 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -72,6 +72,9 @@ fn to_complex(value: PyObjectRef, vm: &VirtualMachine) -> PyResult Ok(Some(Complex64::new(v, 0.0))), None => Err(vm.new_overflow_error("int too large to convert to float".to_string())), } + } else if objtype::isinstance(&value, &vm.ctx.float_type()) { + let v = objfloat::get_value(&value); + Ok(Some(Complex64::new(v, 0.0))) } else { Ok(None) } From dc05459459cd25a223399bb689c7a26939909ea9 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 12:09:49 +0900 Subject: [PATCH 430/884] Add float.__sub__, float.__rsub__ --- tests/snippets/builtin_complex.py | 15 +++++++++++++++ vm/src/obj/objcomplex.rs | 22 ++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index be688cc634..2df8e4df72 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -59,6 +59,21 @@ assert (1.5j + 1.6) + 3 == complex(4.6, 1.5) assert 3.5 + (1.1j + 1.2) == complex(4.7, 1.1) +# subtraction +assert 1 - 1j == complex(1, -1) +assert 1j - 1 == complex(-1, 1) +assert 2j - 1j == complex(0, 1) + +# type error addition +with assertRaises(TypeError): + assert 1j + 'str' +with assertRaises(TypeError): + assert 1j - 'str' +with assertRaises(TypeError): + assert 'str' + 1j +with assertRaises(TypeError): + assert 'str' - 1j + # overflow with assertRaises(OverflowError): complex(10 ** 1000, 0) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index af4880e066..93a0559aaf 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -116,6 +116,28 @@ impl PyComplex { } } + #[pymethod(name = "__sub__")] + fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.complex_type()) { + Ok(vm.ctx.new_complex(self.value - get_value(&other))) + } else { + match to_complex(other, vm) { + Ok(Some(other)) => Ok(vm.ctx.new_complex(self.value - other)), + Ok(None) => Ok(vm.ctx.not_implemented()), + Err(err) => Err(err), + } + } + } + + #[pymethod(name = "__rsub__")] + fn rsub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match to_complex(other, vm) { + Ok(Some(other)) => Ok(vm.ctx.new_complex(other - self.value)), + Ok(None) => Ok(vm.ctx.not_implemented()), + Err(err) => Err(err), + } + } + #[pymethod(name = "conjugate")] fn conjugate(&self, _vm: &VirtualMachine) -> PyComplex { self.value.conj().into() From 9dda776e2541618ef6bd11f96f88e4da0d78c6d9 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 05:24:33 +0900 Subject: [PATCH 431/884] Add float.__int__ / float.__float__ --- tests/snippets/floats.py | 3 +++ vm/src/obj/objfloat.rs | 12 +++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 08e7d4b2b9..801a4ccd72 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -84,6 +84,9 @@ assert 2.0.__mul__(1) == 2.0 assert 2.0.__rsub__(1) == -1.0 +assert 1.2.__int__() == 1 +assert 1.2.__float__() == 1.2 + assert (1.7).real == 1.7 assert (1.3).is_integer() == False assert (1.0).is_integer() == True diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 5438d31c1e..4b5f02d4e6 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -7,7 +7,7 @@ use crate::pyobject::{ IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, }; use crate::vm::VirtualMachine; -use num_bigint::ToBigInt; +use num_bigint::{BigInt, ToBigInt}; use num_rational::Ratio; use num_traits::ToPrimitive; @@ -346,6 +346,16 @@ impl PyFloat { self.mul(other, vm) } + #[pymethod(name = "__int__")] + fn int(&self, _vm: &VirtualMachine) -> BigInt { + self.value.to_bigint().unwrap() + } + + #[pymethod(name = "__float__")] + fn float(zelf: PyRef, _vm: &VirtualMachine) -> PyFloatRef { + zelf + } + #[pyproperty(name = "real")] fn real(zelf: PyRef, _vm: &VirtualMachine) -> PyFloatRef { zelf From 4810a55c82dbb845df11b9bcf094863b69ee23fc Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 12:44:25 +0900 Subject: [PATCH 432/884] Add float.__trunc__ --- tests/snippets/floats.py | 4 ++++ vm/src/obj/objfloat.rs | 9 +++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 801a4ccd72..f30aeddafb 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -86,6 +86,10 @@ assert 1.2.__int__() == 1 assert 1.2.__float__() == 1.2 +assert 1.2.__trunc__() == 1 +assert int(1.2) == 1 +assert float(1.2) == 1.2 +# assert math.trunc(1.2) == 1 assert (1.7).real == 1.7 assert (1.3).is_integer() == False diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 4b5f02d4e6..0fa06435a1 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -346,11 +346,16 @@ impl PyFloat { self.mul(other, vm) } - #[pymethod(name = "__int__")] - fn int(&self, _vm: &VirtualMachine) -> BigInt { + #[pymethod(name = "__trunc__")] + fn trunc(&self, _vm: &VirtualMachine) -> BigInt { self.value.to_bigint().unwrap() } + #[pymethod(name = "__int__")] + fn int(&self, vm: &VirtualMachine) -> BigInt { + self.trunc(vm) + } + #[pymethod(name = "__float__")] fn float(zelf: PyRef, _vm: &VirtualMachine) -> PyFloatRef { zelf From 8047ab3d3efb5550471826235255d311a1648349 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 20:03:30 +0900 Subject: [PATCH 433/884] Add float tests --- tests/snippets/floats.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index f30aeddafb..f5877604e5 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -7,10 +7,15 @@ a = 1.2 b = 1.3 c = 1.2 +z = 2 + +assert -a == -1.2 + assert a < b assert not b < a assert a <= b assert a <= c +assert a < z assert b > a assert not a > b @@ -18,13 +23,25 @@ assert b >= a assert c >= a assert not a >= b +assert z > a assert a + b == 2.5 assert a - c == 0 assert a / c == 1 +assert a % c == 0 +assert a + z == 3.2 +assert z + a == 3.2 +assert a - z == -0.8 +assert z - a == 0.8 +assert a / z == 0.6 +assert 6 / a == 5.0 +assert 2.0 % z == 0.0 +# assert z % 2.0 == 0.0 assert a < 5 assert a <= 5 +assert a < 5.5 +assert a <= 5.5 try: assert a < 'a' except TypeError: From 304cea89b8336b1f6f6ee53b08ba18e27b6ed14d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 20:31:07 +0900 Subject: [PATCH 434/884] objint::get_float_value --- vm/src/obj/objfloat.rs | 26 ++++++++------------------ vm/src/obj/objint.rs | 6 ++++++ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 0fa06435a1..099d18bb3f 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -162,9 +162,7 @@ impl PyFloat { let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) } else if objtype::isinstance(&other, &vm.ctx.int_type) { - objint::get_value(&other).to_f64().ok_or_else(|| { - vm.new_overflow_error("int too large to convert to float".to_string()) - })? + objint::get_float_value(&other, vm)? } else { return Ok(vm.ctx.not_implemented()); }; @@ -180,12 +178,10 @@ impl PyFloat { let value = if objtype::isinstance(&arg, &vm.ctx.float_type()) { get_value(&arg) } else if objtype::isinstance(&arg, &vm.ctx.int_type()) { - match objint::get_value(&arg).to_f64() { - Some(f) => f, - None => { - return Err( - vm.new_overflow_error("int too large to convert to float".to_string()) - ); + match objint::get_float_value(&arg, vm) { + Ok(f) => f, + Err(e) => { + return Err(e); } } } else if objtype::isinstance(&arg, &vm.ctx.str_type()) { @@ -222,9 +218,7 @@ impl PyFloat { let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) } else if objtype::isinstance(&other, &vm.ctx.int_type) { - objint::get_value(&other).to_f64().ok_or_else(|| { - vm.new_overflow_error("int too large to convert to float".to_string()) - })? + objint::get_float_value(&other, vm)? } else { return Ok(vm.ctx.not_implemented()); }; @@ -293,9 +287,7 @@ impl PyFloat { let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) } else if objtype::isinstance(&other, &vm.ctx.int_type) { - objint::get_value(&other).to_f64().ok_or_else(|| { - vm.new_overflow_error("int too large to convert to float".to_string()) - })? + objint::get_float_value(&other, vm)? } else { return Ok(vm.ctx.not_implemented()); }; @@ -313,9 +305,7 @@ impl PyFloat { let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { get_value(&other) } else if objtype::isinstance(&other, &vm.ctx.int_type) { - objint::get_value(&other).to_f64().ok_or_else(|| { - vm.new_overflow_error("int too large to convert to float".to_string()) - })? + objint::get_float_value(&other, vm)? } else { return Ok(vm.ctx.not_implemented()); }; diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 20f5de4caf..c9a5c9a402 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -535,6 +535,12 @@ pub fn get_value(obj: &PyObjectRef) -> &BigInt { &obj.payload::().unwrap().value } +pub fn get_float_value(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + get_value(obj).to_f64().ok_or_else(|| { + vm.new_overflow_error("OverflowError: int too large to convert to float".to_string()) + }) +} + #[inline] fn div_ints(vm: &VirtualMachine, i1: &BigInt, i2: &BigInt) -> PyResult { if i2.is_zero() { From 20b2f64cef8279fc59c20d6f5825bd2daf3e039b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 20:15:02 +0900 Subject: [PATCH 435/884] Add float.__rmod__ --- tests/snippets/floats.py | 7 ++++++- vm/src/obj/objfloat.rs | 24 ++++++++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index f5877604e5..c4f7bdb649 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -36,7 +36,7 @@ assert a / z == 0.6 assert 6 / a == 5.0 assert 2.0 % z == 0.0 -# assert z % 2.0 == 0.0 +assert z % 2.0 == 0.0 assert a < 5 assert a <= 5 @@ -100,6 +100,11 @@ assert 1.0.__rtruediv__(2) == 2.0 assert 2.0.__mul__(1) == 2.0 assert 2.0.__rsub__(1) == -1.0 +assert 2.0.__mod__(2) == 0.0 +assert 2.0.__rmod__(2) == 0.0 +assert_raises(ZeroDivisionError, lambda: 2.0 / 0) +assert_raises(ZeroDivisionError, lambda: 2.0 // 0) +assert_raises(ZeroDivisionError, lambda: 2.0 % 0) assert 1.2.__int__() == 1 assert 1.2.__float__() == 1.2 diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 099d18bb3f..a588eee6ad 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -41,6 +41,14 @@ impl From for PyFloat { } } +fn mod_(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { + if v2 != 0.0 { + Ok(vm.ctx.new_float(v1 % v2)) + } else { + Err(vm.new_zero_division_error("float mod by zero".to_string())) + } +} + #[pyimpl] impl PyFloat { #[pymethod(name = "__eq__")] @@ -223,11 +231,19 @@ impl PyFloat { return Ok(vm.ctx.not_implemented()); }; - if v2 != 0.0 { - Ok(vm.ctx.new_float(v1 % v2)) + mod_(v1, v2, vm) + } + + #[pymethod(name = "__rmod__")] + fn rmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let v2 = self.value; + let v1 = if objtype::isinstance(&other, &vm.ctx.int_type) { + objint::get_float_value(&other, vm)? } else { - Err(vm.new_zero_division_error("float mod by zero".to_string())) - } + return Ok(vm.ctx.not_implemented()); + }; + + mod_(v1, v2, vm) } #[pymethod(name = "__neg__")] From f1551af0f5b4f31c14240f12d62313541c9fdf91 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 28 Apr 2019 18:52:52 +0300 Subject: [PATCH 436/884] Add tests - not working --- tests/snippets/try_exceptions.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index 83e00c0fe5..5f49a40d0a 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -167,3 +167,22 @@ def __init__(self): raise NameError except NameError as ex2: assert ex2.__context__ == None + +def f(): + raise + +with assertRaises(ZeroDivisionError): + try: + 1/0 + except: + f() + +with assertRaises(ZeroDivisionError): + try: + 1/0 + except ZeroDivisionError: + try: + raise + except NameError: + pass + raise From af7a89aa99cf3b66bf9103f46d4945aeb6c454d7 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 28 Apr 2019 19:06:14 +0300 Subject: [PATCH 437/884] pop exception on context --- vm/src/frame.rs | 4 ++-- vm/src/vm.rs | 11 ----------- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 69b1d510ac..00d843d2ac 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -739,8 +739,8 @@ impl Frame { }; let context = match argc { 0 => vm.get_none(), // We have already got the exception, - _ => match vm.last_exception() { - Some(exc) => exc.clone(), + _ => match vm.pop_exception() { + Some(exc) => exc, None => vm.get_none(), }, }; diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 9308c0caa2..a677d73410 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -931,17 +931,6 @@ impl VirtualMachine { pub fn pop_exception(&self) -> Option { self.exceptions.borrow_mut().pop() } - - pub fn last_exception(&self) -> Option> { - let exceptions = self.exceptions.borrow(); - if exceptions.is_empty() { - None - } else { - Some(Ref::map(self.exceptions.borrow(), |exceptions| { - exceptions.last().unwrap() - })) - } - } } impl Default for VirtualMachine { From b72fc3bc9d57cc23f308c73b113d170a36cf8716 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 28 Apr 2019 19:20:16 +0300 Subject: [PATCH 438/884] Add test for #867 --- tests/snippets/try_exceptions.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index 5f49a40d0a..c6a148e788 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -182,7 +182,14 @@ def f(): 1/0 except ZeroDivisionError: try: - raise + raise except NameError: pass raise + +# Regression https://github.com/RustPython/RustPython/issues/867 +for _ in [1, 2]: + try: + raise ArithmeticError() + except ArithmeticError as e: + continue From c3714c2eca8a5f4416ec181188c43ba29b4fe714 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 19:22:03 +0900 Subject: [PATCH 439/884] Add math.trunc --- tests/snippets/math_basics.py | 25 ++++++++++++++++++++++++- vm/src/stdlib/math.rs | 19 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index 09f3ed3b3a..c707f0458b 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -1,3 +1,6 @@ +import math +from testutils import assertRaises + a = 4 #print(a ** 3) @@ -17,7 +20,6 @@ assert -a == -4 assert +a == 4 -# import math # assert(math.exp(2) == math.exp(2.0)) # assert(math.exp(True) == math.exp(1.0)) # @@ -27,3 +29,24 @@ # return 1.1111 # # assert math.log(1.1111) == math.log(Conversible()) + +# roundings +assert math.trunc(1) == 1 + +class A(object): + def __trunc__(self): + return 2 + +assert math.trunc(A()) == 2 + +class A(object): + def __trunc__(self): + return 2.0 + +assert math.trunc(A()) == 2.0 + +class A(object): + def __trunc__(self): + return 'str' + +assert math.trunc(A()) == 'str' diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index 2ee584a799..82d8ac57d9 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -8,7 +8,7 @@ use statrs::function::gamma::{gamma, ln_gamma}; use crate::function::PyFuncArgs; use crate::obj::objfloat; -use crate::pyobject::{PyObjectRef, PyResult}; +use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; // Helper macro: @@ -172,6 +172,20 @@ fn math_lgamma(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } +fn math_trunc(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(value, None)]); + const MAGIC_NAME: &str = "__trunc__"; + if let Ok(method) = vm.get_method(value.clone(), MAGIC_NAME) { + vm.invoke(method, vec![]) + } else { + Err(vm.new_type_error(format!( + "TypeError: type {} doesn't define {} method", + value.class().name, + MAGIC_NAME, + ))) + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -219,6 +233,9 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "gamma" => ctx.new_rustfunc(math_gamma), "lgamma" => ctx.new_rustfunc(math_lgamma), + // Rounding functions: + "trunc" => ctx.new_rustfunc(math_trunc), + // Constants: "pi" => ctx.new_float(std::f64::consts::PI), // 3.14159... "e" => ctx.new_float(std::f64::consts::E), // 2.71.. From 6afe78c9a673a13e4ae277c98b9c3722e6afa6b8 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 20:43:25 +0900 Subject: [PATCH 440/884] Add math.ceil / math.floor --- tests/snippets/math_basics.py | 32 -------------- tests/snippets/math_module.py | 80 +++++++++++++++++++++++++++++++++++ vm/src/stdlib/math.rs | 37 +++++++++++++--- 3 files changed, 111 insertions(+), 38 deletions(-) create mode 100644 tests/snippets/math_module.py diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index c707f0458b..16d6e011ce 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -1,4 +1,3 @@ -import math from testutils import assertRaises a = 4 @@ -19,34 +18,3 @@ assert a - 3 == 1 assert -a == -4 assert +a == 4 - -# assert(math.exp(2) == math.exp(2.0)) -# assert(math.exp(True) == math.exp(1.0)) -# -# class Conversible(): -# def __float__(self): -# print("Converting to float now!") -# return 1.1111 -# -# assert math.log(1.1111) == math.log(Conversible()) - -# roundings -assert math.trunc(1) == 1 - -class A(object): - def __trunc__(self): - return 2 - -assert math.trunc(A()) == 2 - -class A(object): - def __trunc__(self): - return 2.0 - -assert math.trunc(A()) == 2.0 - -class A(object): - def __trunc__(self): - return 'str' - -assert math.trunc(A()) == 'str' diff --git a/tests/snippets/math_module.py b/tests/snippets/math_module.py new file mode 100644 index 0000000000..1064610f1e --- /dev/null +++ b/tests/snippets/math_module.py @@ -0,0 +1,80 @@ +import math +from testutils import assertRaises + +# assert(math.exp(2) == math.exp(2.0)) +# assert(math.exp(True) == math.exp(1.0)) +# +# class Conversible(): +# def __float__(self): +# print("Converting to float now!") +# return 1.1111 +# +# assert math.log(1.1111) == math.log(Conversible()) + +# roundings +assert int.__trunc__ +assert int.__floor__ +assert int.__ceil__ + +# assert float.__trunc__ +with assertRaises(AttributeError): + assert float.__floor__ +with assertRaises(AttributeError): + assert float.__ceil__ + +assert math.trunc(2) == 2 +assert math.ceil(3) == 3 +assert math.floor(4) == 4 + +assert math.trunc(2.2) == 2 +assert math.ceil(3.3) == 4 +assert math.floor(4.4) == 4 + +class A(object): + def __trunc__(self): + return 2 + + def __ceil__(self): + return 3 + + def __floor__(self): + return 4 + +assert math.trunc(A()) == 2 +assert math.ceil(A()) == 3 +assert math.floor(A()) == 4 + +class A(object): + def __trunc__(self): + return 2.2 + + def __ceil__(self): + return 3.3 + + def __floor__(self): + return 4.4 + +assert math.trunc(A()) == 2.2 +assert math.ceil(A()) == 3.3 +assert math.floor(A()) == 4.4 + +class A(object): + def __trunc__(self): + return 'trunc' + + def __ceil__(self): + return 'ceil' + + def __floor__(self): + return 'floor' + +assert math.trunc(A()) == 'trunc' +assert math.ceil(A()) == 'ceil' +assert math.floor(A()) == 'floor' + +with assertRaises(TypeError): + math.trunc(object()) +with assertRaises(TypeError): + math.ceil(object()) +with assertRaises(TypeError): + math.floor(object()) diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index 82d8ac57d9..58b3ee953a 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -7,7 +7,7 @@ use statrs::function::erf::{erf, erfc}; use statrs::function::gamma::{gamma, ln_gamma}; use crate::function::PyFuncArgs; -use crate::obj::objfloat; +use crate::obj::{objfloat, objtype}; use crate::pyobject::{PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; @@ -172,20 +172,43 @@ fn math_lgamma(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } -fn math_trunc(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(value, None)]); - const MAGIC_NAME: &str = "__trunc__"; - if let Ok(method) = vm.get_method(value.clone(), MAGIC_NAME) { +fn try_magic_method(func_name: &str, vm: &VirtualMachine, value: &PyObjectRef) -> PyResult { + if let Ok(method) = vm.get_method(value.clone(), func_name) { vm.invoke(method, vec![]) } else { Err(vm.new_type_error(format!( "TypeError: type {} doesn't define {} method", value.class().name, - MAGIC_NAME, + func_name, ))) } } +fn math_trunc(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(value, None)]); + try_magic_method("__trunc__", vm, value) +} + +fn math_ceil(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(value, None)]); + if objtype::isinstance(value, &vm.ctx.float_type) { + let v = objfloat::get_value(value); + Ok(vm.ctx.new_float(v.ceil())) + } else { + try_magic_method("__ceil__", vm, value) + } +} + +fn math_floor(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(value, None)]); + if objtype::isinstance(value, &vm.ctx.float_type) { + let v = objfloat::get_value(value); + Ok(vm.ctx.new_float(v.floor())) + } else { + try_magic_method("__floor__", vm, value) + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -235,6 +258,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { // Rounding functions: "trunc" => ctx.new_rustfunc(math_trunc), + "ceil" => ctx.new_rustfunc(math_ceil), + "floor" => ctx.new_rustfunc(math_floor), // Constants: "pi" => ctx.new_float(std::f64::consts::PI), // 3.14159... From 388b62aae20f378248c74057efa4490867df8133 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sun, 28 Apr 2019 13:44:06 +0900 Subject: [PATCH 441/884] objint::to_int to call __int__ --- tests/snippets/ints.py | 34 ++++++++++++++++++++++++++++++++++ vm/src/obj/objint.rs | 26 +++++++++++++++++--------- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index 1fbbc2bef7..a072cc5427 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -74,3 +74,37 @@ with assertRaises(TypeError): # check that first parameter is truly positional only int(val_options=1) + +class A(object): + def __int__(self): + return 10 + +assert int(A()) == 10 + +class B(object): + pass + +b = B() +b.__int__ = lambda: 20 + +with assertRaises(TypeError): + assert int(b) == 20 + +class C(object): + def __int__(self): + return 'str' + +with assertRaises(TypeError): + int(C()) + +class I(int): + def __int__(self): + return 3 + +assert int(I(1)) == 3 + +class F(float): + def __int__(self): + return 3 + +assert int(F(1.2)) == 3 diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index c9a5c9a402..3687ed2b04 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -1,12 +1,12 @@ use std::fmt; use std::hash::{Hash, Hasher}; -use num_bigint::{BigInt, ToBigInt}; +use num_bigint::BigInt; use num_integer::Integer; use num_traits::{Pow, Signed, ToPrimitive, Zero}; use crate::format::FormatSpec; -use crate::function::OptionalArg; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, @@ -510,11 +510,8 @@ fn int_new(cls: PyClassRef, options: IntOptions, vm: &VirtualMachine) -> PyResul } // Casting function: -// TODO: this should just call `__int__` on the object pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult { match_class!(obj.clone(), - i @ PyInt => Ok(i.as_bigint().clone()), - f @ PyFloat => Ok(f.to_f64().to_bigint().unwrap()), s @ PyString => { i32::from_str_radix(s.as_str(), base) .map(BigInt::from) @@ -523,10 +520,21 @@ pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult Err(vm.new_type_error(format!( - "int() argument must be a string or a number, not '{}'", - obj.class().name - ))) + obj => { + if let Ok(f) = vm.get_method(obj.clone(), "__int__") { + let int_res = vm.invoke(f, PyFuncArgs::default())?; + match int_res.payload::() { + Some(i) => Ok(i.as_bigint().clone()), + None => Err(vm.new_type_error(format!( + "TypeError: __int__ returned non-int (type '{}')", int_res.class().name))), + } + } else { + Err(vm.new_type_error(format!( + "int() argument must be a string or a number, not '{}'", + obj.class().name + ))) + } + } ) } From 81dd5faa81bc65b10cdd0b540b939dc2318c436a Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 25 Apr 2019 11:13:51 +1200 Subject: [PATCH 442/884] Accept a tuple for the second argument of isinstance and issubclass --- tests/snippets/types_snippet.py | 6 +++++ vm/src/builtins.rs | 48 ++++++++++++++++++++++++--------- vm/src/vm.rs | 8 ++++-- 3 files changed, 48 insertions(+), 14 deletions(-) diff --git a/tests/snippets/types_snippet.py b/tests/snippets/types_snippet.py index 0759574181..13395cdbee 100644 --- a/tests/snippets/types_snippet.py +++ b/tests/snippets/types_snippet.py @@ -27,6 +27,12 @@ assert isinstance(type, type) assert issubclass(type, type) +assert not isinstance(type, (int, float)) +assert isinstance(type, (int, object)) + +assert not issubclass(type, (int, float)) +assert issubclass(type, (int, type)) + class A: pass class B(A): pass class C(A): pass diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 9f393d333e..360b37e9ab 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -20,12 +20,13 @@ use crate::obj::objtype::{self, PyClassRef}; use crate::frame::Scope; use crate::function::{Args, KwArgs, OptionalArg, PyFuncArgs}; use crate::pyobject::{ - IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, + Either, IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; use crate::obj::objcode::PyCodeRef; +use crate::obj::objtuple::PyTupleRef; #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::io::io_open; @@ -312,19 +313,42 @@ fn builtin_id(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // builtin_input -fn builtin_isinstance(obj: PyObjectRef, typ: PyClassRef, vm: &VirtualMachine) -> PyResult { - vm.isinstance(&obj, &typ) +fn builtin_isinstance( + obj: PyObjectRef, + typ: Either, + vm: &VirtualMachine, +) -> PyResult { + match typ { + Either::A(ref cls) => vm.isinstance(&obj, cls), + Either::B(ref tuple) => { + for cls_obj in tuple.elements.borrow().iter() { + let cls = PyClassRef::try_from_object(vm, cls_obj.clone())?; + if vm.isinstance(&obj, &cls)? { + return Ok(true); + } + } + Ok(false) + } + } } -fn builtin_issubclass(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(subclass, Some(vm.get_type())), (cls, Some(vm.get_type()))] - ); - - let issubclass = vm.issubclass(subclass, cls)?; - Ok(vm.context().new_bool(issubclass)) +fn builtin_issubclass( + subclass: PyClassRef, + typ: Either, + vm: &VirtualMachine, +) -> PyResult { + match typ { + Either::A(ref cls) => vm.issubclass(&subclass, cls), + Either::B(ref tuple) => { + for cls_obj in tuple.elements.borrow().iter() { + let cls = PyClassRef::try_from_object(vm, cls_obj.clone())?; + if vm.issubclass(&subclass, &cls)? { + return Ok(true); + } + } + Ok(false) + } + } } fn builtin_iter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index ab15e14e95..068d7df191 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -304,8 +304,12 @@ impl VirtualMachine { /// Determines if `subclass` is a subclass of `cls`, either directly, indirectly or virtually /// via the __subclasscheck__ magic method. - pub fn issubclass(&self, subclass: &PyObjectRef, cls: &PyObjectRef) -> PyResult { - let ret = self.call_method(cls, "__subclasscheck__", vec![subclass.clone()])?; + pub fn issubclass(&self, subclass: &PyClassRef, cls: &PyClassRef) -> PyResult { + let ret = self.call_method( + cls.as_object(), + "__subclasscheck__", + vec![subclass.clone().into_object()], + )?; objbool::boolval(self, ret) } From 4a4631a622a3c73b15eb5f3c2bfbbdf7969350fa Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 28 Apr 2019 16:58:42 +1200 Subject: [PATCH 443/884] Use match_class instead of Either in isinstance/issubclass --- vm/src/builtins.rs | 47 +++++++++++++++++++++------------------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 360b37e9ab..41082707ea 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -11,22 +11,22 @@ use num_traits::Signed; use crate::compile; use crate::import::import_module; use crate::obj::objbool; +use crate::obj::objcode::PyCodeRef; use crate::obj::objdict::PyDictRef; use crate::obj::objint::{self, PyIntRef}; use crate::obj::objiter; use crate::obj::objstr::{self, PyString, PyStringRef}; -use crate::obj::objtype::{self, PyClassRef}; +use crate::obj::objtuple::PyTuple; +use crate::obj::objtype::{self, PyClass, PyClassRef}; use crate::frame::Scope; use crate::function::{Args, KwArgs, OptionalArg, PyFuncArgs}; use crate::pyobject::{ - Either, IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, + IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; -use crate::obj::objcode::PyCodeRef; -use crate::obj::objtuple::PyTupleRef; #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::io::io_open; @@ -313,42 +313,37 @@ fn builtin_id(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // builtin_input -fn builtin_isinstance( - obj: PyObjectRef, - typ: Either, +fn type_test( vm: &VirtualMachine, + typ: PyObjectRef, + test: impl Fn(&PyClassRef) -> PyResult, + test_name: &str, ) -> PyResult { - match typ { - Either::A(ref cls) => vm.isinstance(&obj, cls), - Either::B(ref tuple) => { + match_class!(typ, + cls @ PyClass => test(&cls), + tuple @ PyTuple => { for cls_obj in tuple.elements.borrow().iter() { let cls = PyClassRef::try_from_object(vm, cls_obj.clone())?; - if vm.isinstance(&obj, &cls)? { + if test(&cls)? { return Ok(true); } } Ok(false) - } - } + }, + _ => Err(vm.new_type_error(format!("{}() arg 2 must be a type or tuple of types", test_name))) + ) +} + +fn builtin_isinstance(obj: PyObjectRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult { + type_test(vm, typ, |cls| vm.isinstance(&obj, cls), "isinstance") } fn builtin_issubclass( subclass: PyClassRef, - typ: Either, + typ: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - match typ { - Either::A(ref cls) => vm.issubclass(&subclass, cls), - Either::B(ref tuple) => { - for cls_obj in tuple.elements.borrow().iter() { - let cls = PyClassRef::try_from_object(vm, cls_obj.clone())?; - if vm.issubclass(&subclass, &cls)? { - return Ok(true); - } - } - Ok(false) - } - } + type_test(vm, typ, |cls| vm.issubclass(&subclass, cls), "issubclass") } fn builtin_iter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { From f8373aa38a4da14d1cadebd5e5b068f8340357b6 Mon Sep 17 00:00:00 2001 From: Nicolas Trinquier Date: Sun, 28 Apr 2019 22:57:34 +0200 Subject: [PATCH 444/884] Add mul method for the complex type --- tests/snippets/builtin_complex.py | 5 +++++ vm/src/obj/objcomplex.rs | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index 2df8e4df72..9c13e2d85a 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -24,6 +24,11 @@ assert complex(1, 2) != 'foo' assert complex(1, 2).__eq__('foo') == NotImplemented +# __mul__ + +assert complex(2, -3) * complex(-5, 7) == complex(-21, 29) +assert complex(2, -3) * 5 == complex(10, -15) + # __neg__ assert -complex(1, -1) == complex(-1, 1) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 93a0559aaf..3dd48c3f97 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -161,6 +161,18 @@ impl PyComplex { vm.ctx.new_bool(result) } + #[pymethod(name = "__mul__")] + fn mul(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + match to_complex(other, vm) { + Ok(Some(other)) => Ok(vm.ctx.new_complex(Complex64::new( + self.value.re * other.re - self.value.im * other.im, + self.value.re * other.im + self.value.re * other.im, + ))), + Ok(None) => Ok(vm.ctx.not_implemented()), + Err(err) => Err(err), + } + } + #[pymethod(name = "__neg__")] fn neg(&self, _vm: &VirtualMachine) -> PyComplex { PyComplex::from(-self.value) From eebdbfe679237a3efb0ec1605d9e41e875dbd5c1 Mon Sep 17 00:00:00 2001 From: Nicolas Trinquier Date: Sun, 28 Apr 2019 22:57:56 +0200 Subject: [PATCH 445/884] Add int and float methods for the complex type --- tests/snippets/builtin_complex.py | 2 +- vm/src/obj/objcomplex.rs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index 9c13e2d85a..4d0fa12029 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -26,7 +26,7 @@ # __mul__ -assert complex(2, -3) * complex(-5, 7) == complex(-21, 29) +assert complex(2, -3) * complex(-5, 7) == complex(11, 29) assert complex(2, -3) * 5 == complex(10, -15) # __neg__ diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 3dd48c3f97..cd70c61c75 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -161,12 +161,22 @@ impl PyComplex { vm.ctx.new_bool(result) } + #[pymethod(name = "__float__")] + fn float(&self, vm: &VirtualMachine) -> PyResult { + return Err(vm.new_type_error(String::from("Can't convert complex to float"))); + } + + #[pymethod(name = "__int__")] + fn int(&self, vm: &VirtualMachine) -> PyResult { + return Err(vm.new_type_error(String::from("Can't convert complex to int"))); + } + #[pymethod(name = "__mul__")] - fn mul(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn mul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match to_complex(other, vm) { Ok(Some(other)) => Ok(vm.ctx.new_complex(Complex64::new( self.value.re * other.re - self.value.im * other.im, - self.value.re * other.im + self.value.re * other.im, + self.value.re * other.im + self.value.im * other.re, ))), Ok(None) => Ok(vm.ctx.not_implemented()), Err(err) => Err(err), From 56ef6ec5f811cd3fc269bf083d58657c313fc45a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 29 Apr 2019 20:08:15 +0300 Subject: [PATCH 446/884] Change to is_some --- vm/src/frame.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 00d843d2ac..a9e1559692 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -861,7 +861,7 @@ impl Frame { Ok(None) } bytecode::Instruction::PopException {} => { - assert!(!vm.pop_exception().is_none()); + assert!(vm.pop_exception().is_some()); Ok(None) } } From 68e15a2bd88b4fc55f92647ef3cdeff455a71f9d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 29 Apr 2019 14:46:04 +0900 Subject: [PATCH 447/884] Add basic float.__round__ without ndigits support --- tests/snippets/math_basics.py | 7 +++++++ vm/src/obj/objfloat.rs | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index 16d6e011ce..a9092d58d8 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -18,3 +18,10 @@ assert a - 3 == 1 assert -a == -4 assert +a == 4 + +assert round(1.2) == 1 +assert round(1.8) == 2 +assert round(0.5) == 0 +assert round(1.5) == 2 +assert round(-0.5) == 0 +assert round(-1.5) == -2 diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index a588eee6ad..f40c403a8b 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -2,9 +2,11 @@ use super::objbytes; use super::objint; use super::objstr; use super::objtype; +use crate::function::OptionalArg; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + IdProtocol, IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, + TypeProtocol, }; use crate::vm::VirtualMachine; use num_bigint::{BigInt, ToBigInt}; @@ -357,6 +359,36 @@ impl PyFloat { self.value.to_bigint().unwrap() } + #[pymethod(name = "__round__")] + fn round(&self, ndigits: OptionalArg, vm: &VirtualMachine) -> PyObjectRef { + let ndigits = match ndigits { + OptionalArg::Missing => None, + OptionalArg::Present(ref value) => { + if !vm.get_none().is(value) { + // retrive int to implement it + Some(vm.ctx.not_implemented()) + } else { + None + } + } + }; + if ndigits.is_none() { + let fract = self.value.fract(); + let value = if fract.abs() == 0.5 { + if self.value.trunc() % 2.0 == 0.0 { + self.value - fract + } else { + self.value + fract + } + } else { + self.value.round() + }; + vm.ctx.new_int(value.to_bigint().unwrap()) + } else { + vm.ctx.not_implemented() + } + } + #[pymethod(name = "__int__")] fn int(&self, vm: &VirtualMachine) -> BigInt { self.trunc(vm) From 490db0a84f8b6207392617adeadf7e42869a008e Mon Sep 17 00:00:00 2001 From: Nicolas Trinquier Date: Mon, 29 Apr 2019 20:13:01 +0200 Subject: [PATCH 448/884] Add support for complex type for the to_complex function --- vm/src/obj/objcomplex.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index cd70c61c75..f9ff9d9b02 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -67,7 +67,9 @@ impl PyComplexRef { } fn to_complex(value: PyObjectRef, vm: &VirtualMachine) -> PyResult> { - if objtype::isinstance(&value, &vm.ctx.int_type()) { + if objtype::isinstance(&value, &vm.ctx.complex_type()) { + Ok(Some(get_value(&value))) + } else if objtype::isinstance(&value, &vm.ctx.int_type()) { match objint::get_value(&value).to_f64() { Some(v) => Ok(Some(Complex64::new(v, 0.0))), None => Err(vm.new_overflow_error("int too large to convert to float".to_string())), From 4c8f655d4ee6d0aef5ae65334a9cdc2666d9a04e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 30 Apr 2019 01:37:47 +0900 Subject: [PATCH 449/884] float.__round__ inf/nan support --- tests/snippets/math_basics.py | 15 ++++++++++++++- vm/src/obj/objfloat.rs | 29 ++++++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index a9092d58d8..51ea5c7262 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -1,4 +1,4 @@ -from testutils import assertRaises +from testutils import assert_raises a = 4 @@ -25,3 +25,16 @@ assert round(1.5) == 2 assert round(-0.5) == 0 assert round(-1.5) == -2 + +assert_raises( + ValueError, + lambda: round(float('nan')), + 'ValueError: cannot convert float NaN to integer') +assert_raises( + OverflowError, + lambda: round(float('inf')), + 'OverflowError: cannot convert float NaN to integer') +assert_raises( + OverflowError, + lambda: round(-float('inf')), + 'OverflowError: cannot convert float NaN to integer') diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index f40c403a8b..bb7a65975d 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -51,6 +51,28 @@ fn mod_(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { } } +fn try_to_bigint(value: f64, vm: &VirtualMachine) -> PyResult { + match value.to_bigint() { + Some(int) => Ok(int), + None => { + if value.is_infinite() { + Err(vm.new_overflow_error( + "OverflowError: cannot convert float NaN to integer".to_string(), + )) + } else if value.is_nan() { + Err(vm + .new_value_error("ValueError: cannot convert float NaN to integer".to_string())) + } else { + // unreachable unless BigInt has a bug + unreachable!( + "A finite float value failed to be converted to bigint: {}", + value + ) + } + } + } +} + #[pyimpl] impl PyFloat { #[pymethod(name = "__eq__")] @@ -360,7 +382,7 @@ impl PyFloat { } #[pymethod(name = "__round__")] - fn round(&self, ndigits: OptionalArg, vm: &VirtualMachine) -> PyObjectRef { + fn round(&self, ndigits: OptionalArg, vm: &VirtualMachine) -> PyResult { let ndigits = match ndigits { OptionalArg::Missing => None, OptionalArg::Present(ref value) => { @@ -383,9 +405,10 @@ impl PyFloat { } else { self.value.round() }; - vm.ctx.new_int(value.to_bigint().unwrap()) + let int = try_to_bigint(value, vm)?; + Ok(vm.ctx.new_int(int)) } else { - vm.ctx.not_implemented() + Ok(vm.ctx.not_implemented()) } } From b7c7591639617e845d0d7e8c141887a1e2bf91e2 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 30 Apr 2019 01:48:03 +0900 Subject: [PATCH 450/884] Fix float.__trunc__ to handle abnormal values --- tests/snippets/floats.py | 4 +++- vm/src/obj/objfloat.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index c4f7bdb649..8820813f1f 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -111,7 +111,9 @@ assert 1.2.__trunc__() == 1 assert int(1.2) == 1 assert float(1.2) == 1.2 -# assert math.trunc(1.2) == 1 +assert math.trunc(1.2) == 1 +assert_raises(OverflowError, float('inf').__trunc__) +assert_raises(ValueError, float('nan').__trunc__) assert (1.7).real == 1.7 assert (1.3).is_integer() == False diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index bb7a65975d..80c325a330 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -377,8 +377,8 @@ impl PyFloat { } #[pymethod(name = "__trunc__")] - fn trunc(&self, _vm: &VirtualMachine) -> BigInt { - self.value.to_bigint().unwrap() + fn trunc(&self, vm: &VirtualMachine) -> PyResult { + try_to_bigint(self.value, vm) } #[pymethod(name = "__round__")] @@ -413,7 +413,7 @@ impl PyFloat { } #[pymethod(name = "__int__")] - fn int(&self, vm: &VirtualMachine) -> BigInt { + fn int(&self, vm: &VirtualMachine) -> PyResult { self.trunc(vm) } From 132d34fc67c908356c81435666d2ac805566cec6 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 27 Apr 2019 00:27:47 +0900 Subject: [PATCH 451/884] Add range.__eq__ --- tests/snippets/builtin_range.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/snippets/builtin_range.py b/tests/snippets/builtin_range.py index a8797f75a1..55d4f51943 100644 --- a/tests/snippets/builtin_range.py +++ b/tests/snippets/builtin_range.py @@ -27,6 +27,11 @@ assert range(4, 10, 2).count(7) == 0 assert range(10).count("foo") == 0 +# __eq__ +assert range(1, 2, 3) == range(1, 2, 3) +assert range(1, 2, 1) == range(1, 2) +assert range(2) == range(0, 2) + # __bool__ assert bool(range(1)) assert bool(range(1, 2)) From c951cb3cfd693536968dee2c675862b01761210e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 30 Apr 2019 03:27:06 +0900 Subject: [PATCH 452/884] float.__round__ for ndigits == 0 --- tests/snippets/floats.py | 8 ++++++++ vm/src/obj/objfloat.rs | 17 ++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 8820813f1f..176ce927e7 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -114,6 +114,14 @@ assert math.trunc(1.2) == 1 assert_raises(OverflowError, float('inf').__trunc__) assert_raises(ValueError, float('nan').__trunc__) +assert 0.5.__round__() == 0.0 +assert 1.5.__round__() == 2.0 +assert 0.5.__round__(0) == 0.0 +assert 1.5.__round__(0) == 2.0 +assert 0.5.__round__(None) == 0.0 +assert 1.5.__round__(None) == 2.0 +assert_raises(OverflowError, float('inf').__round__) +assert_raises(ValueError, float('nan').__round__) assert (1.7).real == 1.7 assert (1.3).is_integer() == False diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 80c325a330..42810da8e9 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -11,7 +11,7 @@ use crate::pyobject::{ use crate::vm::VirtualMachine; use num_bigint::{BigInt, ToBigInt}; use num_rational::Ratio; -use num_traits::ToPrimitive; +use num_traits::{ToPrimitive, Zero}; #[pyclass(name = "float")] #[derive(Debug, Copy, Clone, PartialEq)] @@ -387,8 +387,19 @@ impl PyFloat { OptionalArg::Missing => None, OptionalArg::Present(ref value) => { if !vm.get_none().is(value) { - // retrive int to implement it - Some(vm.ctx.not_implemented()) + let ndigits = if objtype::isinstance(value, &vm.ctx.int_type()) { + objint::get_value(value) + } else { + return Err(vm.new_type_error(format!( + "TypeError: '{}' object cannot be interpreted as an integer", + value.class().name + ))); + }; + if ndigits.is_zero() { + None + } else { + Some(ndigits) + } } else { None } From ece66018512e47f766646614f0ec53fd9b764327 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Tue, 30 Apr 2019 15:47:41 +0200 Subject: [PATCH 453/884] add PyByteInner as fn argument / use Either --- vm/src/obj/objbyteinner.rs | 273 +++++++++++++++++-------------------- vm/src/obj/objbytes.rs | 30 ++-- 2 files changed, 139 insertions(+), 164 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 46f55ec8db..2f0e6b9039 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -1,5 +1,11 @@ use crate::obj::objint::PyIntRef; -use crate::obj::objslice::PySlice; +use crate::obj::objnone::PyNoneRef; +use crate::obj::objslice::PySliceRef; +use crate::obj::objtuple::PyTupleRef; +use crate::pyobject::Either; +use crate::pyobject::PyRef; +use crate::pyobject::PyValue; +use crate::pyobject::TryFromObject; use crate::pyobject::{PyIterable, PyObjectRef}; use core::ops::Range; use num_bigint::BigInt; @@ -16,7 +22,7 @@ use std::hash::{Hash, Hasher}; use super::objint; use super::objsequence::{is_valid_slice_arg, PySliceableSequence}; -use super::objtype; + use crate::obj::objint::PyInt; use num_integer::Integer; use num_traits::ToPrimitive; @@ -24,7 +30,7 @@ use num_traits::ToPrimitive; use super::objbytearray::{get_value as get_value_bytearray, PyByteArray}; use super::objbytes::PyBytes; use super::objmemory::PyMemoryView; -use super::objnone::PyNone; + use super::objsequence; #[derive(Debug, Default, Clone)] @@ -32,6 +38,37 @@ pub struct PyByteInner { pub elements: Vec, } +impl TryFromObject for PyByteInner { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + match_class!(obj, + + i @ PyBytes => Ok(PyByteInner{elements: i.get_value().to_vec()}), + j @ PyByteArray => Ok(PyByteInner{elements: get_value_bytearray(&j.as_object()).to_vec()}), + k @ PyMemoryView => Ok(PyByteInner{elements: k.get_obj_value().unwrap()}), + obj => Err(vm.new_type_error(format!( + "a bytes-like object is required, not {}", + obj.class() + ))) + ) + } +} + +impl TryFromObject for Either> { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + match PyByteInner::try_from_object(vm, obj.clone()) { + Ok(a) => Ok(Either::A(a)), + Err(_) => match obj.clone().downcast::() { + Ok(b) => Ok(Either::B(b)), + Err(_) => Err(vm.new_type_error(format!( + "a bytes-like object or {} is required, not {}", + B::class(vm), + obj.class() + ))), + }, + } + } +} + #[derive(FromArgs)] pub struct ByteInnerNewOptions { #[pyarg(positional_only, optional = true)] @@ -103,11 +140,11 @@ impl ByteInnerNewOptions { #[derive(FromArgs)] pub struct ByteInnerFindOptions { #[pyarg(positional_only, optional = false)] - sub: PyObjectRef, + sub: Either, #[pyarg(positional_only, optional = true)] - start: OptionalArg, + start: OptionalArg>, #[pyarg(positional_only, optional = true)] - end: OptionalArg, + end: OptionalArg>, } impl ByteInnerFindOptions { @@ -116,29 +153,19 @@ impl ByteInnerFindOptions { elements: &[u8], vm: &VirtualMachine, ) -> PyResult<(Vec, Range)> { - let sub = match try_as_bytes_like(&self.sub) { - Some(value) => value, - None => match_class!(self.sub, - i @ PyInt => vec![i.as_bigint().byte_or(vm)?], - obj => {return Err(vm.new_type_error(format!("argument should be integer or bytes-like object, not {}", obj)));}), + let sub = match self.sub { + Either::A(v) => v.elements.to_vec(), + Either::B(int) => vec![int.as_bigint().byte_or(vm)?], }; - let start = if let OptionalArg::Present(st) = self.start { - match_class!(st, - i @ PyInt => {Some(i.as_bigint().clone())}, - _obj @ PyNone => None, - _=> {return Err(vm.new_type_error("slice indices must be integers or None or have an __index__ method".to_string()));} - ) - } else { - None + + let start = match self.start { + OptionalArg::Present(Some(int)) => Some(int.as_bigint().clone()), + _ => None, }; - let end = if let OptionalArg::Present(e) = self.end { - match_class!(e, - i @ PyInt => {Some(i.as_bigint().clone())}, - _obj @ PyNone => None, - _=> {return Err(vm.new_type_error("slice indices must be integers or None or have an __index__ method".to_string()));} - ) - } else { - None + + let end = match self.end { + OptionalArg::Present(Some(int)) => Some(int.as_bigint().clone()), + _ => None, }; let range = elements.to_vec().get_slice_range(&start, &end); @@ -199,20 +226,16 @@ impl ByteInnerPaddingOptions { #[derive(FromArgs)] pub struct ByteInnerTranslateOptions { #[pyarg(positional_only, optional = false)] - table: PyObjectRef, + table: Either, #[pyarg(positional_or_keyword, optional = true)] - delete: OptionalArg, + delete: OptionalArg, } impl ByteInnerTranslateOptions { pub fn get_value(self, vm: &VirtualMachine) -> PyResult<(Vec, Vec)> { - let table = match try_as_bytes_like(&self.table) { - Some(value) => value, - None => match_class!(self.table, - - _n @ PyNone => (0..=255).collect::>(), - obj => {return Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)));}, - ), + let table = match self.table { + Either::A(v) => v.elements.to_vec(), + Either::B(_) => (0..=255).collect::>(), }; if table.len() != 256 { @@ -221,18 +244,9 @@ impl ByteInnerTranslateOptions { ); } - let delete = if let OptionalArg::Present(value) = &self.delete { - match try_as_bytes_like(&value) { - Some(value) => value, - None => { - return Err(vm.new_type_error(format!( - "a bytes-like object is required, not {}", - value - ))); - } - } - } else { - vec![] + let delete = match self.delete { + OptionalArg::Present(byte) => byte.elements, + _ => vec![], }; Ok((table, delete)) @@ -319,52 +333,43 @@ impl PyByteInner { elements } - pub fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match try_as_bytes_like(&needle) { - Some(value) => self.contains_bytes(&value, vm), - None => match_class!(needle, - i @ PyInt => self.contains_int(&i, vm), - obj => {Err(vm.new_type_error(format!("a bytes-like object is required, not {}", obj)))}), - } - } - - fn contains_bytes(&self, other: &[u8], vm: &VirtualMachine) -> PyResult { - for (n, i) in self.elements.iter().enumerate() { - if n + other.len() <= self.len() - && *i == other[0] - && &self.elements[n..n + other.len()] == other - { - return Ok(vm.new_bool(true)); + pub fn contains(&self, needle: Either, vm: &VirtualMachine) -> PyResult { + match needle { + Either::A(byte) => { + let other = &byte.elements[..]; + for (n, i) in self.elements.iter().enumerate() { + if n + other.len() <= self.len() + && *i == other[0] + && &self.elements[n..n + other.len()] == other + { + return Ok(vm.new_bool(true)); + } + } + Ok(vm.new_bool(false)) + } + Either::B(int) => { + if self.elements.contains(&int.as_bigint().byte_or(vm)?) { + Ok(vm.new_bool(true)) + } else { + Ok(vm.new_bool(false)) + } } - } - Ok(vm.new_bool(false)) - } - - fn contains_int(&self, int: &PyInt, vm: &VirtualMachine) -> PyResult { - if self.elements.contains(&int.as_bigint().byte_or(vm)?) { - Ok(vm.new_bool(true)) - } else { - Ok(vm.new_bool(false)) } } - pub fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(needle, - int @ PyInt => //self.inner.getitem_int(&int, vm), - { - if let Some(idx) = self.elements.get_pos(int.as_bigint().to_i32().unwrap()) { - Ok(vm.new_int(self.elements[idx])) - } else { - Err(vm.new_index_error("index out of range".to_string())) + pub fn getitem(&self, needle: Either, vm: &VirtualMachine) -> PyResult { + match needle { + Either::A(int) => { + if let Some(idx) = self.elements.get_pos(int.as_bigint().to_i32().unwrap()) { + Ok(vm.new_int(self.elements[idx])) + } else { + Err(vm.new_index_error("index out of range".to_string())) + } + } + Either::B(slice) => Ok(vm + .ctx + .new_bytes(self.elements.get_slice_items(vm, slice.as_object())?)), } - }, - slice @ PySlice => //self.inner.getitem_slice(slice.as_object(), vm), - { - Ok(vm - .ctx - .new_bytes(self.elements.get_slice_items(vm, slice.as_object())?)) - }, - obj => Err(vm.new_type_error(format!("byte indices must be integers or slices, not {}", obj)))) } pub fn isalnum(&self, vm: &VirtualMachine) -> PyResult { @@ -475,7 +480,7 @@ impl PyByteInner { } pub fn swapcase(&self, _vm: &VirtualMachine) -> Vec { - let mut new: Vec = Vec::new(); + let mut new: Vec = Vec::with_capacity(self.elements.len()); for w in &self.elements { match w { 65..=90 => new.push(w.to_ascii_lowercase()), @@ -532,7 +537,6 @@ impl PyByteInner { options: ByteInnerPaddingOptions, vm: &VirtualMachine, ) -> PyResult> { - // let fn_name = "center".to_string(); let (fillbyte, diff) = options.get_value("center", self.len(), vm)?; let mut ln: usize = diff / 2; @@ -559,7 +563,6 @@ impl PyByteInner { options: ByteInnerPaddingOptions, vm: &VirtualMachine, ) -> PyResult> { - // let fn_name = "ljust".to_string(); let (fillbyte, diff) = options.get_value("ljust", self.len(), vm)?; // merge all @@ -575,7 +578,6 @@ impl PyByteInner { options: ByteInnerPaddingOptions, vm: &VirtualMachine, ) -> PyResult> { - // let fn_name = "rjust".to_string(); let (fillbyte, diff) = options.get_value("rjust", self.len(), vm)?; // merge all @@ -629,35 +631,28 @@ impl PyByteInner { pub fn startsendswith( &self, - arg: PyObjectRef, + arg: Either, start: OptionalArg, end: OptionalArg, endswith: bool, // true for endswith, false for startswith vm: &VirtualMachine, ) -> PyResult { - let suff = if objtype::isinstance(&arg, &vm.ctx.tuple_type()) { - let mut flatten = vec![]; - for v in objsequence::get_elements(&arg).to_vec() { - match try_as_bytes_like(&v) { - None => { - return Err(vm.new_type_error(format!( - "a bytes-like object is required, not {}", - &v.class().name, - ))); + let suff = match arg { + Either::A(byte) => byte.elements, + Either::B(tuple) => { + let mut flatten = vec![]; + for v in objsequence::get_elements(tuple.as_object()).to_vec() { + match try_as_bytes_like(&v) { + None => { + return Err(vm.new_type_error(format!( + "a bytes-like object is required, not {}", + &v.class().name, + ))); + } + Some(value) => flatten.extend(value), } - Some(value) => flatten.extend(value), - } - } - flatten - } else { - match try_as_bytes_like(&arg) { - Some(value) => value, - None => { - return Err(vm.new_type_error(format!( - "endswith first arg must be bytes or a tuple of bytes, not {}", - arg - ))); } + flatten } }; @@ -715,33 +710,17 @@ impl PyByteInner { Ok(-1isize) } - pub fn maketrans(from: PyObjectRef, to: PyObjectRef, vm: &VirtualMachine) -> PyResult { + pub fn maketrans(from: PyByteInner, to: PyByteInner, vm: &VirtualMachine) -> PyResult { let mut res = vec![]; - let from = match try_as_bytes_like(&from) { - Some(value) => value, - None => { - return Err( - vm.new_type_error(format!("a bytes-like object is required, not {}", from)) - ); - } - }; - - let to = match try_as_bytes_like(&to) { - Some(value) => value, - None => { - return Err( - vm.new_type_error(format!("a bytes-like object is required, not {}", to)) - ); - } - }; - for i in 0..=255 { - res.push(if let Some(position) = from.iter().position(|&x| x == i) { - to[position] - } else { - i - }); + res.push( + if let Some(position) = from.elements.iter().position(|&x| x == i) { + to.elements[position] + } else { + i + }, + ); } Ok(vm.ctx.new_bytes(res)) @@ -763,20 +742,12 @@ impl PyByteInner { pub fn strip( &self, - chars: OptionalArg, + chars: OptionalArg, position: ByteInnerPosition, - vm: &VirtualMachine, + _vm: &VirtualMachine, ) -> PyResult> { - let chars = if let OptionalArg::Present(content) = chars { - match try_as_bytes_like(&content) { - Some(value) => value, - None => { - return Err(vm.new_type_error(format!( - "a bytes-like object is required, not {}", - content - ))); - } - } + let chars = if let OptionalArg::Present(bytes) = chars { + bytes.elements } else { vec![b' '] }; diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index bd483be18a..c16fd123fe 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,4 +1,9 @@ +use crate::obj::objint::PyIntRef; +use crate::obj::objslice::PySliceRef; use crate::obj::objstr::PyStringRef; +use crate::obj::objtuple::PyTupleRef; + +use crate::pyobject::Either; use crate::vm::VirtualMachine; use core::cell::Cell; use std::ops::Deref; @@ -35,7 +40,6 @@ impl PyBytes { inner: PyByteInner { elements }, } } - pub fn get_value(&self) -> &[u8] { &self.inner.elements } @@ -150,12 +154,12 @@ impl PyBytesRef { } #[pymethod(name = "__contains__")] - fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn contains(self, needle: Either, vm: &VirtualMachine) -> PyResult { self.inner.contains(needle, vm) } #[pymethod(name = "__getitem__")] - fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn getitem(self, needle: Either, vm: &VirtualMachine) -> PyResult { self.inner.getitem(needle, vm) } @@ -256,7 +260,7 @@ impl PyBytesRef { #[pymethod(name = "endswith")] fn endswith( self, - suffix: PyObjectRef, + suffix: Either, start: OptionalArg, end: OptionalArg, vm: &VirtualMachine, @@ -267,12 +271,12 @@ impl PyBytesRef { #[pymethod(name = "startswith")] fn startswith( self, - suffix: PyObjectRef, + prefix: Either, start: OptionalArg, end: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - self.inner.startsendswith(suffix, start, end, false, vm) + self.inner.startsendswith(prefix, start, end, false, vm) } #[pymethod(name = "find")] @@ -281,12 +285,12 @@ impl PyBytesRef { } #[pymethod(name = "index")] - fn index(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + fn index(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { let res = self.inner.find(options, false, vm)?; if res == -1 { return Err(vm.new_value_error("substring not found".to_string())); } - Ok(vm.new_int(res)) + Ok(res) } #[pymethod(name = "rfind")] @@ -295,12 +299,12 @@ impl PyBytesRef { } #[pymethod(name = "rindex")] - fn rindex(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + fn rindex(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { let res = self.inner.find(options, true, vm)?; if res == -1 { return Err(vm.new_value_error("substring not found".to_string())); } - Ok(vm.new_int(res)) + Ok(res) } #[pymethod(name = "translate")] @@ -309,21 +313,21 @@ impl PyBytesRef { } #[pymethod(name = "strip")] - fn strip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { + fn strip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { Ok(vm .ctx .new_bytes(self.inner.strip(chars, ByteInnerPosition::All, vm)?)) } #[pymethod(name = "lstrip")] - fn lstrip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { + fn lstrip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { Ok(vm .ctx .new_bytes(self.inner.strip(chars, ByteInnerPosition::Left, vm)?)) } #[pymethod(name = "rstrip")] - fn rstrip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { + fn rstrip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { Ok(vm .ctx .new_bytes(self.inner.strip(chars, ByteInnerPosition::Right, vm)?)) From 599f1f678f8b4318ab35a87d91d91132e3ee77f3 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 30 Apr 2019 06:03:41 +0900 Subject: [PATCH 454/884] extend_class for PyRange --- vm/src/obj/objrange.rs | 74 ++++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index d98e7491fe..a22788119f 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -6,7 +6,7 @@ use num_traits::{One, Signed, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, + PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -15,6 +15,15 @@ use super::objiter; use super::objslice::{PySlice, PySliceRef}; use super::objtype::{self, PyClassRef}; +/// range(stop) -> range object +/// range(start, stop[, step]) -> range object +/// +/// Return an object that produces a sequence of integers from start (inclusive) +/// to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1. +/// start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3. +/// These are exactly the valid indices for a list of 4 elements. +/// When step is given, it specifies the increment (or decrement). +#[pyclass] #[derive(Debug, Clone)] pub struct PyRange { pub start: PyIntRef, @@ -96,33 +105,7 @@ pub fn get_value(obj: &PyObjectRef) -> PyRange { } pub fn init(context: &PyContext) { - let range_type = &context.range_type; - - let range_doc = "range(stop) -> range object\n\ - range(start, stop[, step]) -> range object\n\n\ - Return an object that produces a sequence of integers from start (inclusive)\n\ - to stop (exclusive) by step. range(i, j) produces i, i+1, i+2, ..., j-1.\n\ - start defaults to 0, and stop is omitted! range(4) produces 0, 1, 2, 3.\n\ - These are exactly the valid indices for a list of 4 elements.\n\ - When step is given, it specifies the increment (or decrement)."; - - extend_class!(context, range_type, { - "__bool__" => context.new_rustfunc(PyRange::bool), - "__contains__" => context.new_rustfunc(PyRange::contains), - "__doc__" => context.new_str(range_doc.to_string()), - "__eq__" => context.new_rustfunc(PyRange::eq), - "__getitem__" => context.new_rustfunc(PyRange::getitem), - "__iter__" => context.new_rustfunc(PyRange::iter), - "__len__" => context.new_rustfunc(PyRange::len), - "__new__" => context.new_rustfunc(range_new), - "__repr__" => context.new_rustfunc(PyRange::repr), - "__reversed__" => context.new_rustfunc(PyRange::reversed), - "count" => context.new_rustfunc(PyRange::count), - "index" => context.new_rustfunc(PyRange::index), - "start" => context.new_property(PyRange::start), - "stop" => context.new_property(PyRange::stop), - "step" => context.new_property(PyRange::step), - }); + PyRange::extend_class(context, &context.range_type); let rangeiterator_type = &context.rangeiterator_type; extend_class!(context, rangeiterator_type, { @@ -133,6 +116,7 @@ pub fn init(context: &PyContext) { type PyRangeRef = PyRef; +#[pyimpl] impl PyRange { fn new(cls: PyClassRef, stop: PyIntRef, vm: &VirtualMachine) -> PyResult { PyRange { @@ -160,18 +144,22 @@ impl PyRange { .into_ref_with_type(vm, cls) } + #[pyproperty(name = "start")] fn start(&self, _vm: &VirtualMachine) -> PyIntRef { self.start.clone() } + #[pyproperty(name = "stop")] fn stop(&self, _vm: &VirtualMachine) -> PyIntRef { self.stop.clone() } + #[pyproperty(name = "step")] fn step(&self, _vm: &VirtualMachine) -> PyIntRef { self.step.clone() } + #[pymethod(name = "__iter__")] fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRangeIterator { PyRangeIterator { position: Cell::new(0), @@ -179,6 +167,7 @@ impl PyRange { } } + #[pymethod(name = "__reversed__")] fn reversed(&self, vm: &VirtualMachine) -> PyRangeIterator { let start = self.start.as_bigint(); let stop = self.stop.as_bigint(); @@ -211,6 +200,7 @@ impl PyRange { } } + #[pymethod(name = "__len__")] fn len(&self, _vm: &VirtualMachine) -> PyInt { let start = self.start.as_bigint(); let stop = self.stop.as_bigint(); @@ -224,6 +214,7 @@ impl PyRange { } } + #[pymethod(name = "__repr__")] fn repr(&self, _vm: &VirtualMachine) -> String { if self.step.as_bigint().is_one() { format!("range({}, {})", self.start, self.stop) @@ -232,10 +223,12 @@ impl PyRange { } } + #[pymethod(name = "__bool__")] fn bool(&self, _vm: &VirtualMachine) -> bool { !self.is_empty() } + #[pymethod(name = "__contains__")] fn contains(&self, needle: PyObjectRef, _vm: &VirtualMachine) -> bool { if let Ok(int) = needle.downcast::() { match self.offset(int.as_bigint()) { @@ -247,6 +240,7 @@ impl PyRange { } } + #[pymethod(name = "__eq__")] fn eq(&self, rhs: PyObjectRef, vm: &VirtualMachine) -> bool { if objtype::isinstance(&rhs, &vm.ctx.range_type()) { let rhs = get_value(&rhs); @@ -258,6 +252,7 @@ impl PyRange { } } + #[pymethod(name = "index")] fn index(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { if let Ok(int) = needle.downcast::() { match self.index_of(int.as_bigint()) { @@ -269,6 +264,7 @@ impl PyRange { } } + #[pymethod(name = "count")] fn count(&self, item: PyObjectRef, _vm: &VirtualMachine) -> PyInt { if let Ok(int) = item.downcast::() { if self.index_of(int.as_bigint()).is_some() { @@ -281,6 +277,7 @@ impl PyRange { } } + #[pymethod(name = "__getitem__")] fn getitem(&self, subscript: RangeIndex, vm: &VirtualMachine) -> PyResult { match subscript { RangeIndex::Int(index) => { @@ -327,18 +324,19 @@ impl PyRange { } } } -} -fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let range = if args.args.len() <= 2 { - let (cls, stop) = args.bind(vm)?; - PyRange::new(cls, stop, vm) - } else { - let (cls, start, stop, step) = args.bind(vm)?; - PyRange::new_from(cls, start, stop, step, vm) - }?; + #[pymethod(name = "__new__")] + fn range_new(args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { + let range = if args.args.len() <= 2 { + let (cls, stop) = args.bind(vm)?; + PyRange::new(cls, stop, vm) + } else { + let (cls, start, stop, step) = args.bind(vm)?; + PyRange::new_from(cls, start, stop, step, vm) + }?; - Ok(range.into_object()) + Ok(range.into_object()) + } } #[derive(Debug)] From 5d13daef207852ecde3feef346981bd1ab8d1f5d Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Tue, 30 Apr 2019 18:01:40 +0200 Subject: [PATCH 455/884] remove all try_as_bytes_like --- vm/src/obj/objbyteinner.rs | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 2f0e6b9039..b75f8be697 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -612,18 +612,9 @@ impl PyByteInner { pub fn join(&self, iter: PyIterable, vm: &VirtualMachine) -> PyResult { let mut refs = vec![]; - for (index, v) in iter.iter(vm)?.enumerate() { + for v in iter.iter(vm)? { let v = v?; - match try_as_bytes_like(&v) { - None => { - return Err(vm.new_type_error(format!( - "sequence item {}: expected a bytes-like object, {} found", - index, - &v.class().name, - ))); - } - Some(value) => refs.extend(value), - } + refs.extend(PyByteInner::try_from_object(vm, v)?.elements) } Ok(vm.ctx.new_bytes(refs)) @@ -642,15 +633,7 @@ impl PyByteInner { Either::B(tuple) => { let mut flatten = vec![]; for v in objsequence::get_elements(tuple.as_object()).to_vec() { - match try_as_bytes_like(&v) { - None => { - return Err(vm.new_type_error(format!( - "a bytes-like object is required, not {}", - &v.class().name, - ))); - } - Some(value) => flatten.extend(value), - } + flatten.extend(PyByteInner::try_from_object(vm, v)?.elements) } flatten } @@ -784,15 +767,6 @@ pub fn try_as_byte(obj: &PyObjectRef) -> Option> { _ => None) } -pub fn try_as_bytes_like(obj: &PyObjectRef) -> Option> { - match_class!(obj.clone(), - - i @ PyBytes => Some(i.get_value().to_vec()), - j @ PyByteArray => Some(get_value_bytearray(&j.as_object()).to_vec()), - k @ PyMemoryView => Some(k.get_obj_value().unwrap()), - _ => None) -} - pub trait ByteOr: ToPrimitive { fn byte_or(&self, vm: &VirtualMachine) -> Result { match self.to_u8() { From a318e4e09e062affcf97f12540a87724b849a1b4 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 01:19:53 +0900 Subject: [PATCH 456/884] objfloat.rs try_float for common operand handling --- vm/src/obj/objfloat.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index a588eee6ad..87ac3199d4 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -41,6 +41,16 @@ impl From for PyFloat { } } +fn try_float(value: &PyObjectRef, vm: &VirtualMachine) -> PyResult> { + Ok(if objtype::isinstance(&value, &vm.ctx.float_type()) { + Some(get_value(&value)) + } else if objtype::isinstance(&value, &vm.ctx.int_type()) { + Some(objint::get_float_value(&value, vm)?) + } else { + None + }) +} + fn mod_(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { if v2 != 0.0 { Ok(vm.ctx.new_float(v1 % v2)) From a0ad4368c618cd2f49a4425897b1b7a4181261f7 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 30 Apr 2019 01:59:38 +0900 Subject: [PATCH 457/884] Add float.pow OverflowError handling --- tests/snippets/floats.py | 3 +++ vm/src/obj/objfloat.rs | 15 +++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index c4f7bdb649..58855acc97 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -113,6 +113,9 @@ assert float(1.2) == 1.2 # assert math.trunc(1.2) == 1 +assert 1.2 ** 2 == 1.44 +assert_raises(OverflowError, lambda: 1.2 ** (10 ** 1000)) + assert (1.7).real == 1.7 assert (1.3).is_integer() == False assert (1.0).is_integer() == True diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 87ac3199d4..23636cef83 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -262,16 +262,11 @@ impl PyFloat { } #[pymethod(name = "__pow__")] - fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - let v1 = self.value; - if objtype::isinstance(&other, &vm.ctx.float_type()) { - vm.ctx.new_float(v1.powf(get_value(&other))) - } else if objtype::isinstance(&other, &vm.ctx.int_type()) { - let result = v1.powf(objint::get_value(&other).to_f64().unwrap()); - vm.ctx.new_float(result) - } else { - vm.ctx.not_implemented() - } + fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| self.value.powf(other).into_pyobject(vm), + ) } #[pymethod(name = "__sub__")] From 813307f8f94b89cd78165b34a469a2f247ea3417 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 30 Apr 2019 02:58:50 +0900 Subject: [PATCH 458/884] Refactor PyFloat using try_float() --- tests/snippets/floats.py | 15 +++ vm/src/obj/objfloat.rs | 191 +++++++++++++++------------------------ 2 files changed, 89 insertions(+), 117 deletions(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 58855acc97..580c5920fd 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -8,6 +8,7 @@ b = 1.3 c = 1.2 z = 2 +ov = 10 ** 1000 assert -a == -1.2 @@ -37,6 +38,20 @@ assert 6 / a == 5.0 assert 2.0 % z == 0.0 assert z % 2.0 == 0.0 +assert_raises(OverflowError, lambda: a + ov) +assert_raises(OverflowError, lambda: a - ov) +assert_raises(OverflowError, lambda: a * ov) +assert_raises(OverflowError, lambda: a / ov) +assert_raises(OverflowError, lambda: a // ov) +assert_raises(OverflowError, lambda: a % ov) +assert_raises(OverflowError, lambda: a ** ov) +assert_raises(OverflowError, lambda: ov + a) +assert_raises(OverflowError, lambda: ov - a) +assert_raises(OverflowError, lambda: ov * a) +assert_raises(OverflowError, lambda: ov / a) +assert_raises(OverflowError, lambda: ov // a) +assert_raises(OverflowError, lambda: ov % a) +# assert_raises(OverflowError, lambda: ov ** a) assert a < 5 assert a <= 5 diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 23636cef83..426483f4c1 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -51,14 +51,30 @@ fn try_float(value: &PyObjectRef, vm: &VirtualMachine) -> PyResult> }) } -fn mod_(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { +fn inner_div(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { if v2 != 0.0 { - Ok(vm.ctx.new_float(v1 % v2)) + Ok(v1 / v2) + } else { + Err(vm.new_zero_division_error("float division by zero".to_string())) + } +} + +fn inner_mod(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { + if v2 != 0.0 { + Ok(v1 % v2) } else { Err(vm.new_zero_division_error("float mod by zero".to_string())) } } +fn inner_floordiv(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { + if v2 != 0.0 { + Ok((v1 / v2).floor()) + } else { + Err(vm.new_zero_division_error("float floordiv by zero".to_string())) + } +} + #[pyimpl] impl PyFloat { #[pymethod(name = "__eq__")] @@ -139,20 +155,15 @@ impl PyFloat { } #[pymethod(name = "__add__")] - fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - let v1 = self.value; - if objtype::isinstance(&other, &vm.ctx.float_type()) { - vm.ctx.new_float(v1 + get_value(&other)) - } else if objtype::isinstance(&other, &vm.ctx.int_type()) { - vm.ctx - .new_float(v1 + objint::get_value(&other).to_f64().unwrap()) - } else { - vm.ctx.not_implemented() - } + fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (self.value + other).into_pyobject(vm), + ) } #[pymethod(name = "__radd__")] - fn radd(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + fn radd(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.add(other, vm) } @@ -163,45 +174,39 @@ impl PyFloat { #[pymethod(name = "__divmod__")] fn divmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - if objtype::isinstance(&other, &vm.ctx.float_type()) - || objtype::isinstance(&other, &vm.ctx.int_type()) - { - let r1 = self.floordiv(other.clone(), vm)?; - let r2 = self.mod_(other, vm)?; - Ok(vm.ctx.new_tuple(vec![r1, r2])) - } else { - Ok(vm.ctx.not_implemented()) - } + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| { + let r1 = inner_floordiv(self.value, other, vm)?; + let r2 = inner_mod(self.value, other, vm)?; + Ok(vm + .ctx + .new_tuple(vec![vm.ctx.new_float(r1), vm.ctx.new_float(r2)])) + }, + ) } #[pymethod(name = "__floordiv__")] fn floordiv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let v1 = self.value; - let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { - get_value(&other) - } else if objtype::isinstance(&other, &vm.ctx.int_type) { - objint::get_float_value(&other, vm)? - } else { - return Ok(vm.ctx.not_implemented()); - }; + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| inner_floordiv(self.value, other, vm)?.into_pyobject(vm), + ) + } - if v2 != 0.0 { - Ok(vm.ctx.new_float((v1 / v2).floor())) - } else { - Err(vm.new_zero_division_error("float floordiv by zero".to_string())) - } + #[pymethod(name = "__rfloordiv__")] + fn rfloordiv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| inner_floordiv(other, self.value, vm)?.into_pyobject(vm), + ) } fn new_float(cls: PyClassRef, arg: PyObjectRef, vm: &VirtualMachine) -> PyResult { let value = if objtype::isinstance(&arg, &vm.ctx.float_type()) { get_value(&arg) } else if objtype::isinstance(&arg, &vm.ctx.int_type()) { - match objint::get_float_value(&arg, vm) { - Ok(f) => f, - Err(e) => { - return Err(e); - } - } + objint::get_float_value(&arg, vm)? } else if objtype::isinstance(&arg, &vm.ctx.str_type()) { match lexical::try_parse(objstr::get_value(&arg)) { Ok(f) => f, @@ -232,28 +237,18 @@ impl PyFloat { #[pymethod(name = "__mod__")] fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let v1 = self.value; - let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { - get_value(&other) - } else if objtype::isinstance(&other, &vm.ctx.int_type) { - objint::get_float_value(&other, vm)? - } else { - return Ok(vm.ctx.not_implemented()); - }; - - mod_(v1, v2, vm) + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| inner_mod(self.value, other, vm)?.into_pyobject(vm), + ) } #[pymethod(name = "__rmod__")] fn rmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let v2 = self.value; - let v1 = if objtype::isinstance(&other, &vm.ctx.int_type) { - objint::get_float_value(&other, vm)? - } else { - return Ok(vm.ctx.not_implemented()); - }; - - mod_(v1, v2, vm) + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| inner_mod(other, self.value, vm)?.into_pyobject(vm), + ) } #[pymethod(name = "__neg__")] @@ -271,30 +266,18 @@ impl PyFloat { #[pymethod(name = "__sub__")] fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let v1 = self.value; - if objtype::isinstance(&other, &vm.ctx.float_type()) { - Ok(vm.ctx.new_float(v1 - get_value(&other))) - } else if objtype::isinstance(&other, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_float(v1 - objint::get_value(&other).to_f64().unwrap())) - } else { - Ok(vm.ctx.not_implemented()) - } + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (self.value - other).into_pyobject(vm), + ) } #[pymethod(name = "__rsub__")] fn rsub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let v1 = self.value; - if objtype::isinstance(&other, &vm.ctx.float_type()) { - Ok(vm.ctx.new_float(get_value(&other) - v1)) - } else if objtype::isinstance(&other, &vm.ctx.int_type()) { - Ok(vm - .ctx - .new_float(objint::get_value(&other).to_f64().unwrap() - v1)) - } else { - Ok(vm.ctx.not_implemented()) - } + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (other - self.value).into_pyobject(vm), + ) } #[pymethod(name = "__repr__")] @@ -304,52 +287,26 @@ impl PyFloat { #[pymethod(name = "__truediv__")] fn truediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let v1 = self.value; - let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { - get_value(&other) - } else if objtype::isinstance(&other, &vm.ctx.int_type) { - objint::get_float_value(&other, vm)? - } else { - return Ok(vm.ctx.not_implemented()); - }; - - if v2 != 0.0 { - Ok(vm.ctx.new_float(v1 / v2)) - } else { - Err(vm.new_zero_division_error("float division by zero".to_string())) - } + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| inner_div(self.value, other, vm)?.into_pyobject(vm), + ) } #[pymethod(name = "__rtruediv__")] fn rtruediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let v1 = self.value; - let v2 = if objtype::isinstance(&other, &vm.ctx.float_type) { - get_value(&other) - } else if objtype::isinstance(&other, &vm.ctx.int_type) { - objint::get_float_value(&other, vm)? - } else { - return Ok(vm.ctx.not_implemented()); - }; - - if v1 != 0.0 { - Ok(vm.ctx.new_float(v2 / v1)) - } else { - Err(vm.new_zero_division_error("float division by zero".to_string())) - } + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| inner_div(other, self.value, vm)?.into_pyobject(vm), + ) } #[pymethod(name = "__mul__")] fn mul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let v1 = self.value; - if objtype::isinstance(&other, &vm.ctx.float_type) { - Ok(vm.ctx.new_float(v1 * get_value(&other))) - } else if objtype::isinstance(&other, &vm.ctx.int_type) { - Ok(vm - .ctx - .new_float(v1 * objint::get_value(&other).to_f64().unwrap())) - } else { - Ok(vm.ctx.not_implemented()) - } + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (self.value * other).into_pyobject(vm), + ) } #[pymethod(name = "__rmul__")] From cfff7cbd63d00d14ca4ce93d56164a65656985b9 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Fri, 19 Apr 2019 08:17:16 +0200 Subject: [PATCH 459/884] add split and rsplit --- tests/snippets/bytes.py | 178 ++++++++++++ vm/src/obj/objbyteinner.rs | 539 +++++++++++++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 24 +- 3 files changed, 740 insertions(+), 1 deletion(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 2733533d8f..2b4af2dacf 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -345,3 +345,181 @@ assert b"www.example.com".lstrip(b"cmowz.") == b"example.com" assert b" spacious ".rstrip() == b" spacious" assert b"mississippi".rstrip(b"ipz") == b"mississ" + + +# split +assert b"1,2,3".split(b",") == [b"1", b"2", b"3"] +assert b"1,2,3".split(b",", maxsplit=1) == [b"1", b"2,3"] +assert b"1,2,,3,".split(b",") == [b"1", b"2", b"", b"3", b""] +assert b"1 2 3".split() == [b"1", b"2", b"3"] +assert b"1 2 3".split(maxsplit=1) == [b"1", b"2 3"] +assert b" 1 2 3 ".split() == [b"1", b"2", b"3"] +assert b"k\ruh\nfz e f".split() == [b"k", b"uh", b"fz", b"e", b"f"] + +SPLIT_FIXTURES = [ + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1, 2, 3], [1, 2, 3], [1, 2, 3]], + [[1, 2, 3], [1, 2, 3], [1, 2, 3]], + -1, + ], + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5], + [4, 5], + [[1, 2, 3], [1, 2, 3], [1, 2, 3], []], + [[1, 2, 3], [1, 2, 3], [1, 2, 3], []], + -1, + ], + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3], + [4, 5], + [[1, 2, 3], [1, 2, 3], [1, 2, 3], [3]], + [[1, 2, 3], [1, 2, 3], [1, 2, 3], [3]], + -1, + ], + [ + [4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[], [2, 3], [1, 2, 3], [1, 2, 3]], + [[], [2, 3], [1, 2, 3], [1, 2, 3]], + -1, + ], + [ + [1, 4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1], [2, 3], [1, 2, 3], [1, 2, 3]], + [[1], [2, 3], [1, 2, 3], [1, 2, 3]], + -1, + ], + [ + [1, 2, 3, 4, 5, 4, 5, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1, 2, 3], [], [], [1, 2, 3], [1, 2, 3]], + [[1, 2, 3], [], [], [1, 2, 3], [1, 2, 3]], + -1, + ], + # maxsplit + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1, 2, 3], [1, 2, 3, 4, 5, 1, 2, 3]], + [[1, 2, 3, 4, 5, 1, 2, 3], [1, 2, 3]], + 1, + ], + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5], + [4, 5], + [[1, 2, 3], [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]], + [[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], []], + 1, + ], + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3], + [4, 5], + [[1, 2, 3], [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3]], + [[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], [3]], + 1, + ], + [ + [4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[], [2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]], + [[4, 5, 2, 3, 4, 5, 1, 2, 3], [1, 2, 3]], + 1, + ], + [ + [1, 4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1], [2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]], + [[1, 4, 5, 2, 3, 4, 5, 1, 2, 3], [1, 2, 3]], + 1, + ], + [ + [1, 2, 3, 4, 5, 4, 5, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1, 2, 3], [], [4, 5, 1, 2, 3, 4, 5, 1, 2, 3]], + [[1, 2, 3, 4, 5, 4, 5], [1, 2, 3], [1, 2, 3]], + 2, + ], + [ + [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9], + None, + [[117, 104], [102, 122], [101, 102]], + [[117, 104], [102, 122], [101, 102]], + -1, + ], + [ + [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9], + None, + [[117, 104, 10, 102, 122, 32, 101, 102, 9, 9]], + [[13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102]], + 0, + ], + [ + [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9], + None, + [[117, 104], [102, 122, 32, 101, 102, 9, 9]], + [[13, 13, 13, 117, 104, 10, 102, 122], [101, 102]], + 1, + ], + [ + [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9], + None, + [[117, 104], [102, 122], [101, 102, 9, 9]], + [[13, 13, 13, 117, 104], [102, 122], [101, 102]], + 2, + ], + [ + [13, 13, 13, 117, 104, 10, 10, 10, 102, 122, 32, 32, 101, 102, 9, 9], + None, + [[117, 104], [102, 122], [101, 102]], + [[117, 104], [102, 122], [101, 102]], + -1, + ], + [[49, 44, 50, 44, 51], [44], [[49], [50], [51]], [[49], [50], [51]], -1], + [[49, 44, 50, 44, 51], [44], [[49], [50, 44, 51]], [[49, 44, 50], [51]], 1], + [ + [49, 44, 50, 44, 44, 51, 44], + [44], + [[49], [50], [], [51], []], + [[49], [50], [], [51], []], + -1, + ], + [[49, 32, 50, 32, 51], None, [[49], [50], [51]], [[49], [50], [51]], -1], + [[49, 32, 50, 32, 51], None, [[49], [50, 32, 51]], [[49, 32, 50], [51]], 1], + [ + [32, 32, 32, 49, 32, 32, 32, 50, 32, 32, 32, 51, 32, 32, 32], + None, + [[49], [50], [51]], + [[49], [50], [51]], + -1, + ], +] + + +# for i in SPLIT_FIXTURES: # for not yet implemented : TypeError: Unsupported method: __next__ +n_sp = 0 +while n_sp < len(SPLIT_FIXTURES): + i = SPLIT_FIXTURES[n_sp] + sep = None if i[1] == None else bytes(i[1]) + try: + assert bytes(i[0]).split(sep=sep, maxsplit=i[4]) == [bytes(j) for j in i[2]] + except AssertionError: + print(i[0], i[1], i[2]) + print( + "Expected : ", [list(x) for x in bytes(i[0]).split(sep=sep, maxsplit=i[4])] + ) + break + + try: + assert bytes(i[0]).rsplit(sep=sep, maxsplit=i[4]) == [bytes(j) for j in i[3]] + except AssertionError: + print(i[0], i[1], i[2]) + print( + "Expected Rev : ", + [list(x) for x in bytes(i[0]).rsplit(sep=sep, maxsplit=i[4])], + ) + break + + n_sp += 1 diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index b75f8be697..7b4037b579 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -253,6 +253,41 @@ impl ByteInnerTranslateOptions { } } +#[derive(FromArgs)] +pub struct ByteInnerSplitOptions { + #[pyarg(positional_or_keyword, optional = true)] + sep: OptionalArg, + #[pyarg(positional_or_keyword, optional = true)] + maxsplit: OptionalArg, +} + +impl ByteInnerSplitOptions { + pub fn get_value(self, vm: &VirtualMachine) -> PyResult<(Vec, i32)> { + let sep = if let OptionalArg::Present(value) = self.sep { + match try_as_bytes_like(&value) { + Some(value) => value, + None => match_class!(value, + _n @ PyNone=> vec![], + obj => {return Err(vm.new_type_error(format!( + "a bytes-like object is required, not {}", + obj + )));} + ), + } + } else { + vec![] + }; + + let maxsplit = if let OptionalArg::Present(value) = self.maxsplit { + value.as_bigint().to_i32().unwrap() + } else { + -1 + }; + + Ok((sep.clone(), maxsplit)) + } +} + impl PyByteInner { pub fn repr(&self) -> PyResult { let mut res = String::with_capacity(self.elements.len()); @@ -757,6 +792,30 @@ impl PyByteInner { } Ok(self.elements[start..end].to_vec()) } + + pub fn split( + &self, + options: ByteInnerSplitOptions, + vm: &VirtualMachine, + ) -> PyResult> { + let (sep, maxsplit) = options.get_value(vm)?; + + let splitted = split_slice(&self.elements, &sep, maxsplit); + + Ok(splitted) + } + + pub fn rsplit( + &self, + options: ByteInnerSplitOptions, + vm: &VirtualMachine, + ) -> PyResult> { + let (sep, maxsplit) = options.get_value(vm)?; + + let splitted = split_slice_reverse(&self.elements, &sep, maxsplit); + + Ok(splitted) + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { @@ -783,3 +842,483 @@ pub enum ByteInnerPosition { Right, All, } + +fn split_slice<'a>(slice: &'a [u8], sep: &[u8], maxsplit: i32) -> Vec<&'a [u8]> { + let mut splitted: Vec<&[u8]> = vec![]; + let mut prev_index = 0; + let mut index = 0; + let mut count = 0; + let mut in_string = false; + + // No sep given, will split for any \t \n \r and space = [9, 10, 13, 32] + if sep.is_empty() { + // split wihtout sep always trim left spaces for any maxsplit + // so we have to ignore left spaces. + loop { + if [9, 10, 13, 32].contains(&slice[index]) { + index += 1 + } else { + prev_index = index; + break; + } + } + + // most simple case + if maxsplit == 0 { + splitted.push(&slice[index..slice.len()]); + return splitted; + } + + // main loop. in_string means previous char is ascii char(true) or space(false) + // loop from left to right + loop { + if [9, 10, 13, 32].contains(&slice[index]) { + if in_string { + splitted.push(&slice[prev_index..index]); + in_string = false; + count += 1; + if count == maxsplit { + // while index < slice.len() + splitted.push(&slice[index + 1..slice.len()]); + break; + } + } + } else if !in_string { + prev_index = index; + in_string = true; + } + + index += 1; + + // handle last item in slice + if index == slice.len() { + if in_string { + if [9, 10, 13, 32].contains(&slice[index - 1]) { + splitted.push(&slice[prev_index..index - 1]); + } else { + splitted.push(&slice[prev_index..index]); + } + } + break; + } + } + } else { + // sep is given, we match exact slice + while index != slice.len() { + if index + sep.len() >= slice.len() { + if &slice[index..slice.len()] == sep { + splitted.push(&slice[prev_index..index]); + splitted.push(&[]); + break; + } + splitted.push(&slice[prev_index..slice.len()]); + break; + } + + if &slice[index..index + sep.len()] == sep { + splitted.push(&slice[prev_index..index]); + index += sep.len(); + prev_index = index; + count += 1; + if count == maxsplit { + // maxsplit reached, append, the remaing + splitted.push(&slice[prev_index..slice.len()]); + break; + } + continue; + } + + index += 1; + } + } + splitted +} + +fn split_slice_reverse<'a>(slice: &'a [u8], sep: &[u8], maxsplit: i32) -> Vec<&'a [u8]> { + let mut splitted: Vec<&[u8]> = vec![]; + let mut prev_index = slice.len(); + let mut index = slice.len(); + let mut count = 0; + + // No sep given, will split for any \t \n \r and space = [9, 10, 13, 32] + if sep.is_empty() { + //adjust index + index -= 1; + + // rsplit without sep always trim right spaces for any maxsplit + // so we have to ignore right spaces. + loop { + if [9, 10, 13, 32].contains(&slice[index]) { + index -= 1 + } else { + break; + } + } + prev_index = index + 1; + + // most simple case + if maxsplit == 0 { + splitted.push(&slice[0..=index]); + return splitted; + } + + // main loop. in_string means previous char is ascii char(true) or space(false) + // loop from right to left and reverse result the end + let mut in_string = true; + loop { + if [9, 10, 13, 32].contains(&slice[index]) { + if in_string { + splitted.push(&slice[index + 1..prev_index]); + count += 1; + if count == maxsplit { + // maxsplit reached, append, the remaing + splitted.push(&slice[0..index]); + break; + } + in_string = false; + index -= 1; + continue; + } + } else if !in_string { + in_string = true; + if index == 0 { + splitted.push(&slice[0..1]); + break; + } + prev_index = index + 1; + } + if index == 0 { + break; + } + index -= 1; + } + } else { + // sep is give, we match exact slice going backwards + while index != 0 { + if index <= sep.len() { + if &slice[0..index] == sep { + splitted.push(&slice[index..prev_index]); + splitted.push(&[]); + break; + } + splitted.push(&slice[0..prev_index]); + break; + } + if &slice[(index - sep.len())..index] == sep { + splitted.push(&slice[index..prev_index]); + index -= sep.len(); + prev_index = index; + count += 1; + if count == maxsplit { + // maxsplit reached, append, the remaing + splitted.push(&slice[0..prev_index]); + break; + } + continue; + } + + index -= 1; + } + } + splitted.reverse(); + splitted +} + +#[cfg(test)] + +// needed for dev. Same as python tests in bytes.py. should it be kept ? + +mod tests { + use super::*; + + #[test] + fn no_end() { + assert_eq!( + split_slice(&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], &[4, 5], -1), + vec![[1, 2, 3], [1, 2, 3], [1, 2, 3]] + ); + assert_eq!( + split_slice_reverse(&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], &[4, 5], -1), + vec![[1, 2, 3], [1, 2, 3], [1, 2, 3]] + ) + } + + #[test] + fn needle_end() { + let v: Vec<&[u8]> = vec![&[1, 2, 3], &[1, 2, 3], &[1, 2, 3], &[]]; + assert_eq!( + split_slice(&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5], &[4, 5], -1), + v + ); + assert_eq!( + split_slice_reverse(&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5], &[4, 5], -1), + v + ) + } + + #[test] + fn needle_end_minus_one() { + let v = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3]; + let n = [4, 5]; + let res: Vec<&[u8]> = vec![&[1, 2, 3], &[1, 2, 3], &[1, 2, 3], &[3]]; + + assert_eq!(split_slice(&v, &n, -1), res); + assert_eq!(split_slice_reverse(&v, &n, -1), res) + } + + #[test] + fn needle_start() { + let v = [4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; + let n = [4, 5]; + let res: Vec<&[u8]> = vec![&[], &[2, 3], &[1, 2, 3], &[1, 2, 3]]; + + assert_eq!(split_slice(&v, &n, -1), res); + assert_eq!(split_slice_reverse(&v, &n, -1), res) + } + + #[test] + fn needle_start_plus_one() { + let v = [1, 4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; + let n = [4, 5]; + let res: Vec<&[u8]> = vec![&[1], &[2, 3], &[1, 2, 3], &[1, 2, 3]]; + + assert_eq!(split_slice(&v, &n, -1), res); + assert_eq!(split_slice_reverse(&v, &n, -1), res) + } + #[test] + fn needles_next_to() { + let v = [1, 2, 3, 4, 5, 4, 5, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; + let n = [4, 5]; + let res: Vec<&[u8]> = vec![&[1, 2, 3], &[], &[], &[1, 2, 3], &[1, 2, 3]]; + + assert_eq!(split_slice(&v, &n, -1), res); + assert_eq!(split_slice_reverse(&v, &n, -1), res) + } + #[test] + fn no_end_max() { + let v = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; + let n = [4, 5]; + let res: Vec<&[u8]> = vec![&[1, 2, 3], &[1, 2, 3, 4, 5, 1, 2, 3]]; + let res_rev: Vec<&[u8]> = vec![&[1, 2, 3, 4, 5, 1, 2, 3], &[1, 2, 3]]; + let max = 1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn needle_end_max() { + let v = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]; + let n = [4, 5]; + let res: Vec<&[u8]> = vec![&[1, 2, 3], &[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]]; + let res_rev: Vec<&[u8]> = vec![&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], &[]]; + let max = 1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + #[test] + fn needle_end_minus_one_max() { + let v = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3]; + let n = [4, 5]; + let res: Vec<&[u8]> = vec![&[1, 2, 3], &[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3]]; + let res_rev: Vec<&[u8]> = vec![&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], &[3]]; + let max = 1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn needle_start_max() { + let v = [4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; + let n = [4, 5]; + let res: Vec<&[u8]> = vec![&[], &[2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]]; + let res_rev: Vec<&[u8]> = vec![&[4, 5, 2, 3, 4, 5, 1, 2, 3], &[1, 2, 3]]; + let max = 1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + #[test] + fn needle_start_minus_one_max() { + let v = [1, 4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; + let n = [4, 5]; + let res: Vec<&[u8]> = vec![&[1], &[2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]]; + let res_rev: Vec<&[u8]> = vec![&[1, 4, 5, 2, 3, 4, 5, 1, 2, 3], &[1, 2, 3]]; + let max = 1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn needle_next_to() { + let v = [1, 2, 3, 4, 5, 4, 5, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; + let n = [4, 5]; + let res: Vec<&[u8]> = vec![&[1, 2, 3], &[], &[4, 5, 1, 2, 3, 4, 5, 1, 2, 3]]; + let res_rev: Vec<&[u8]> = vec![&[1, 2, 3, 4, 5, 4, 5], &[1, 2, 3], &[1, 2, 3]]; + let max = 2; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn no_needle() { + let v = [107, 13, 117, 104, 10, 102, 122, 32, 101, 9, 102]; + let n = []; + let res: Vec<&[u8]> = vec![&[107], &[117, 104], &[102, 122], &[101], &[102]]; + let res_rev: Vec<&[u8]> = vec![&[107], &[117, 104], &[102, 122], &[101], &[102]]; + let max = -1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn no_needle_end_nostring() { + let v = [107, 13, 117, 104, 10, 102, 122, 32, 101, 9, 102, 9]; + let n = []; + let res: Vec<&[u8]> = vec![&[107], &[117, 104], &[102, 122], &[101], &[102]]; + let res_rev: Vec<&[u8]> = vec![&[107], &[117, 104], &[102, 122], &[101], &[102]]; + let max = -1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn no_needle_sides_no_max() { + let v = [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9]; + let n = []; + let res: Vec<&[u8]> = vec![&[117, 104], &[102, 122], &[101, 102]]; + let res_rev: Vec<&[u8]> = vec![&[117, 104], &[102, 122], &[101, 102]]; + let max = -1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn no_needle_sides_max_zero() { + let v = [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9]; + let n = []; + let res: Vec<&[u8]> = vec![&[117, 104, 10, 102, 122, 32, 101, 102, 9, 9]]; + let res_rev: Vec<&[u8]> = vec![&[13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102]]; + let max = 0; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn no_needle_sides_max_one() { + let v = [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9]; + let n = []; + let res: Vec<&[u8]> = vec![&[117, 104], &[102, 122, 32, 101, 102, 9, 9]]; + let res_rev: Vec<&[u8]> = vec![&[13, 13, 13, 117, 104, 10, 102, 122], &[101, 102]]; + let max = 1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn no_needle_sides_max_two() { + let v = [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9]; + let n = []; + let res: Vec<&[u8]> = vec![&[117, 104], &[102, 122], &[101, 102, 9, 9]]; + let res_rev: Vec<&[u8]> = vec![&[13, 13, 13, 117, 104], &[102, 122], &[101, 102]]; + let max = 2; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + #[test] + fn no_needle_no_max_big_spaces() { + let v = [ + 13, 13, 13, 117, 104, 10, 10, 10, 102, 122, 32, 32, 101, 102, 9, 9, + ]; + let n = []; + let res: Vec<&[u8]> = vec![&[117, 104], &[102, 122], &[101, 102]]; + let res_rev: Vec<&[u8]> = vec![&[117, 104], &[102, 122], &[101, 102]]; + let max = -1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn cpython_needle() { + let v = [49, 44, 50, 44, 51]; + let n = [44]; + let res: Vec<&[u8]> = vec![&[49], &[50], &[51]]; + let res_rev: Vec<&[u8]> = vec![&[49], &[50], &[51]]; + let max = -1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn cpython_needle_max_one() { + let v = [49, 44, 50, 44, 51]; + let n = [44]; + let res: Vec<&[u8]> = vec![&[49], &[50, 44, 51]]; + let res_rev: Vec<&[u8]> = vec![&[49, 44, 50], &[51]]; + let max = 1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn cpython_nearneedle() { + let v = [49, 44, 50, 44, 44, 51, 44]; + let n = [44]; + let res: Vec<&[u8]> = vec![&[49], &[50], &[], &[51], &[]]; + let res_rev: Vec<&[u8]> = vec![&[49], &[50], &[], &[51], &[]]; + let max = -1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn cpython_space_no_sep() { + let v = [49, 32, 50, 32, 51]; + let n = []; + let res: Vec<&[u8]> = vec![&[49], &[50], &[51]]; + let res_rev: Vec<&[u8]> = vec![&[49], &[50], &[51]]; + let max = -1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn cpython_space_no_sep_max_one() { + let v = [49, 32, 50, 32, 51]; + let n = []; + let res: Vec<&[u8]> = vec![&[49], &[50, 32, 51]]; + let res_rev: Vec<&[u8]> = vec![&[49, 32, 50], &[51]]; + let max = 1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + + #[test] + fn cpython_bigspace() { + let v = [32, 32, 32, 49, 32, 32, 32, 50, 32, 32, 32, 51, 32, 32, 32]; + let n = []; + let res: Vec<&[u8]> = vec![&[49], &[50], &[51]]; + let res_rev: Vec<&[u8]> = vec![&[49], &[50], &[51]]; + let max = -1; + + assert_eq!(split_slice(&v, &n, max), res); + assert_eq!(split_slice_reverse(&v, &n, max), res_rev) + } + +} diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index c16fd123fe..c3b80a5c9e 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -13,7 +13,7 @@ use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, Py use super::objbyteinner::{ ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerPosition, - ByteInnerTranslateOptions, PyByteInner, + ByteInnerSplitOptions, ByteInnerTranslateOptions, PyByteInner, }; use super::objiter; @@ -332,6 +332,28 @@ impl PyBytesRef { .ctx .new_bytes(self.inner.strip(chars, ByteInnerPosition::Right, vm)?)) } + + #[pymethod(name = "split")] + fn split(self, options: ByteInnerSplitOptions, vm: &VirtualMachine) -> PyResult { + let as_bytes = self + .inner + .split(options, vm)? + .iter() + .map(|x| vm.ctx.new_bytes(x.to_vec())) + .collect::>(); + Ok(vm.ctx.new_list(as_bytes)) + } + + #[pymethod(name = "rsplit")] + fn rsplit(self, options: ByteInnerSplitOptions, vm: &VirtualMachine) -> PyResult { + let as_bytes = self + .inner + .rsplit(options, vm)? + .iter() + .map(|x| vm.ctx.new_bytes(x.to_vec())) + .collect::>(); + Ok(vm.ctx.new_list(as_bytes)) + } } #[derive(Debug)] From 0af8ad05eb36694cb11224cd72d9b8fd1f0682f3 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sat, 20 Apr 2019 17:03:12 +0200 Subject: [PATCH 460/884] add expandstab --- tests/snippets/bytes.py | 12 +++++++++ vm/src/obj/objbyteinner.rs | 52 ++++++++++++++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 9 +++++-- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 2b4af2dacf..a3ecc6a946 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -523,3 +523,15 @@ break n_sp += 1 + + +# expandtabs +a = b"\x01\x03\r\x05\t8CYZ\t\x06CYZ\t\x17cba`\n\x12\x13\x14" +assert ( + a.expandtabs() == b"\x01\x03\r\x05 8CYZ \x06CYZ \x17cba`\n\x12\x13\x14" +) +assert a.expandtabs(5) == b"\x01\x03\r\x05 8CYZ \x06CYZ \x17cba`\n\x12\x13\x14" +assert b"01\t012\t0123\t01234".expandtabs() == b"01 012 0123 01234" +assert b"01\t012\t0123\t01234".expandtabs(4) == b"01 012 0123 01234" +assert b"123\t123".expandtabs(-5) == b"123123" +assert b"123\t123".expandtabs(0) == b"123123" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 7b4037b579..228c972489 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -288,6 +288,26 @@ impl ByteInnerSplitOptions { } } +#[derive(FromArgs)] +pub struct ByteInnerExpandtabsOptions { + #[pyarg(positional_or_keyword, optional = true)] + tabsize: OptionalArg, +} + +impl ByteInnerExpandtabsOptions { + pub fn get_value(self) -> usize { + if let OptionalArg::Present(value) = self.tabsize { + if let Some(v) = objint::get_value(&value).to_usize() { + v + } else { + 0 + } + } else { + 8 + } + } +} + impl PyByteInner { pub fn repr(&self) -> PyResult { let mut res = String::with_capacity(self.elements.len()); @@ -816,6 +836,38 @@ impl PyByteInner { Ok(splitted) } + + pub fn expandtabs(&self, options: ByteInnerExpandtabsOptions) -> Vec { + let tabsize = options.get_value(); + let mut counter: usize = 0; + let mut res = vec![]; + + if tabsize == 0 { + return self + .elements + .iter() + .cloned() + .filter(|x| *x != b'\t') + .collect::>(); + } + + for i in &self.elements { + if *i == b'\t' { + let len = tabsize - counter % tabsize; + res.extend_from_slice(&vec![b' '; len]); + counter += len; + } else { + res.push(*i); + if *i == b'\r' || *i == b'\n' { + counter = 0; + } else { + counter += 1; + } + } + } + + res + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index c3b80a5c9e..28218e597f 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -12,8 +12,8 @@ use crate::function::OptionalArg; use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; use super::objbyteinner::{ - ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerPosition, - ByteInnerSplitOptions, ByteInnerTranslateOptions, PyByteInner, + ByteInnerExpandtabsOptions, ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, + ByteInnerPosition, ByteInnerSplitOptions, ByteInnerTranslateOptions, PyByteInner, }; use super::objiter; @@ -354,6 +354,11 @@ impl PyBytesRef { .collect::>(); Ok(vm.ctx.new_list(as_bytes)) } + + #[pymethod(name = "expandtabs")] + fn expandtabs(self, options: ByteInnerExpandtabsOptions, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.expandtabs(options))) + } } #[derive(Debug)] From 434985d6d07689ad84595774f11208cdc45b6387 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sat, 20 Apr 2019 21:04:13 +0200 Subject: [PATCH 461/884] add partition/reparition refactor split/rsplit --- tests/snippets/bytes.py | 17 +++++++++++++++++ vm/src/obj/objbyteinner.rs | 35 ++++++++++++++++++++++++----------- vm/src/obj/objbytes.rs | 24 ++++++++++++++++++++++-- 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index a3ecc6a946..76efed6c2c 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -535,3 +535,20 @@ assert b"01\t012\t0123\t01234".expandtabs(4) == b"01 012 0123 01234" assert b"123\t123".expandtabs(-5) == b"123123" assert b"123\t123".expandtabs(0) == b"123123" + + +# partition +assert b"123456789".partition(b"45") == (b"123", b"45", b"6789") +assert b"14523456789".partition(b"45") == (b"1", b"45", b"23456789") +a = b"14523456789".partition(bytearray(b"45")) +assert isinstance(a[1], bytearray) +a = b"14523456789".partition(memoryview(b"45")) +assert isinstance(a[1], memoryview) + +# partition +assert b"123456789".rpartition(b"45") == (b"123", b"45", b"6789") +assert b"14523456789".rpartition(b"45") == (b"14523", b"45", b"6789") +a = b"14523456789".rpartition(bytearray(b"45")) +assert isinstance(a[1], bytearray) +a = b"14523456789".rpartition(memoryview(b"45")) +assert isinstance(a[1], memoryview) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 228c972489..3bc7cba69a 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -816,25 +816,38 @@ impl PyByteInner { pub fn split( &self, options: ByteInnerSplitOptions, + reverse: bool, vm: &VirtualMachine, ) -> PyResult> { let (sep, maxsplit) = options.get_value(vm)?; - let splitted = split_slice(&self.elements, &sep, maxsplit); - - Ok(splitted) + if reverse { + Ok(split_slice_reverse(&self.elements, &sep, maxsplit)) + } else { + Ok(split_slice(&self.elements, &sep, maxsplit)) + } } - pub fn rsplit( + pub fn partition( &self, - options: ByteInnerSplitOptions, + sep: &PyObjectRef, + reverse: bool, vm: &VirtualMachine, - ) -> PyResult> { - let (sep, maxsplit) = options.get_value(vm)?; - - let splitted = split_slice_reverse(&self.elements, &sep, maxsplit); - - Ok(splitted) + ) -> PyResult<(Vec, Vec)> { + let sep = match try_as_bytes_like(&sep) { + Some(value) => value, + None => { + return Err( + vm.new_type_error(format!("a bytes-like object is required, not {}", sep)) + ); + } + }; + let splitted = if reverse { + split_slice_reverse(&self.elements, &sep, 1) + } else { + split_slice(&self.elements, &sep, 1) + }; + Ok((splitted[0].to_vec(), splitted[1].to_vec())) } pub fn expandtabs(&self, options: ByteInnerExpandtabsOptions) -> Vec { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 28218e597f..d84d971f41 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -18,6 +18,7 @@ use super::objbyteinner::{ use super::objiter; use super::objtype::PyClassRef; + /// "bytes(iterable_of_ints) -> bytes\n\ /// bytes(string, encoding[, errors]) -> bytes\n\ /// bytes(bytes_or_buffer) -> immutable copy of bytes_or_buffer\n\ @@ -337,7 +338,7 @@ impl PyBytesRef { fn split(self, options: ByteInnerSplitOptions, vm: &VirtualMachine) -> PyResult { let as_bytes = self .inner - .split(options, vm)? + .split(options, false, vm)? .iter() .map(|x| vm.ctx.new_bytes(x.to_vec())) .collect::>(); @@ -348,13 +349,32 @@ impl PyBytesRef { fn rsplit(self, options: ByteInnerSplitOptions, vm: &VirtualMachine) -> PyResult { let as_bytes = self .inner - .rsplit(options, vm)? + .split(options, true, vm)? .iter() .map(|x| vm.ctx.new_bytes(x.to_vec())) .collect::>(); Ok(vm.ctx.new_list(as_bytes)) } + #[pymethod(name = "partition")] + fn partition(self, sep: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // TODO: when implementing bytearray,remember sep ALWAYS converted to bytearray + // even it's bytes or memoryview + let (left, right) = self.inner.partition(&sep, false, vm)?; + Ok(vm + .ctx + .new_tuple(vec![vm.ctx.new_bytes(left), sep, vm.ctx.new_bytes(right)])) + } + #[pymethod(name = "rpartition")] + fn rpartition(self, sep: PyObjectRef, vm: &VirtualMachine) -> PyResult { + // TODO: when implementing bytearray,remember sep ALWAYS converted to bytearray + // even it's bytes or memoryview + let (left, right) = self.inner.partition(&sep, true, vm)?; + Ok(vm + .ctx + .new_tuple(vec![vm.ctx.new_bytes(left), sep, vm.ctx.new_bytes(right)])) + } + #[pymethod(name = "expandtabs")] fn expandtabs(self, options: ByteInnerExpandtabsOptions, vm: &VirtualMachine) -> PyResult { Ok(vm.ctx.new_bytes(self.inner.expandtabs(options))) From d89ca3c3e49d80feaeb53d68a9c6364d2904174f Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 21 Apr 2019 00:38:27 +0200 Subject: [PATCH 462/884] add spitlines --- tests/snippets/bytes.py | 15 +++++++-- vm/src/obj/objbyteinner.rs | 64 ++++++++++++++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 14 ++++++++- 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 76efed6c2c..e7dce5f4e7 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -541,7 +541,7 @@ assert b"123456789".partition(b"45") == (b"123", b"45", b"6789") assert b"14523456789".partition(b"45") == (b"1", b"45", b"23456789") a = b"14523456789".partition(bytearray(b"45")) -assert isinstance(a[1], bytearray) +assert isinstance(a[1], bytearray) a = b"14523456789".partition(memoryview(b"45")) assert isinstance(a[1], memoryview) @@ -549,6 +549,17 @@ assert b"123456789".rpartition(b"45") == (b"123", b"45", b"6789") assert b"14523456789".rpartition(b"45") == (b"14523", b"45", b"6789") a = b"14523456789".rpartition(bytearray(b"45")) -assert isinstance(a[1], bytearray) +assert isinstance(a[1], bytearray) a = b"14523456789".rpartition(memoryview(b"45")) assert isinstance(a[1], memoryview) + +# splitlines +assert b"ab c\n\nde fg\rkl\r\n".splitlines() == [b"ab c", b"", b"de fg", b"kl"] +assert b"ab c\n\nde fg\rkl\r\n".splitlines(keepends=True) == [ + b"ab c\n", + b"\n", + b"de fg\r", + b"kl\r\n", +] +assert b"".splitlines() == [] +assert b"One line\n".splitlines() == [b"One line"] diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 3bc7cba69a..d32c99d2c2 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -308,6 +308,22 @@ impl ByteInnerExpandtabsOptions { } } +#[derive(FromArgs)] +pub struct ByteInnerSplitlinesOptions { + #[pyarg(positional_or_keyword, optional = true)] + keepends: OptionalArg, +} + +impl ByteInnerSplitlinesOptions { + pub fn get_value(self, vm: &VirtualMachine) -> PyResult { + if let OptionalArg::Present(value) = self.keepends { + Ok(bool::try_from_object(vm, value)?) + } else { + Ok(false) + } + } +} + impl PyByteInner { pub fn repr(&self) -> PyResult { let mut res = String::with_capacity(self.elements.len()); @@ -881,6 +897,54 @@ impl PyByteInner { res } + + pub fn splitlines( + &self, + options: ByteInnerSplitlinesOptions, + vm: &VirtualMachine, + ) -> PyResult> { + let keepends = options.get_value(vm)?; + + let mut res = vec![]; + + if self.elements.is_empty() { + return Ok(vec![]); + } + + let mut prev_index = 0; + let mut index = 0; + let keep = if keepends { 1 } else { 0 }; + let slice = &self.elements; + + while index < slice.len() { + match slice[index] { + b'\n' => { + res.push(&slice[prev_index..index + keep]); + index += 1; + prev_index = index; + } + b'\r' => { + if index + 2 <= slice.len() && slice[index + 1] == b'\n' { + res.push(&slice[prev_index..index + keep + keep]); + index += 2; + } else { + res.push(&slice[prev_index..index + keep]); + index += 1; + } + prev_index = index; + } + _x => { + if index == slice.len() - 1 { + res.push(&slice[prev_index..=index]); + break; + } + index += 1 + } + } + } + + Ok(res) + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index d84d971f41..31396c11cd 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -13,7 +13,8 @@ use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, Py use super::objbyteinner::{ ByteInnerExpandtabsOptions, ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, - ByteInnerPosition, ByteInnerSplitOptions, ByteInnerTranslateOptions, PyByteInner, + ByteInnerPosition, ByteInnerSplitOptions, ByteInnerSplitlinesOptions, + ByteInnerTranslateOptions, PyByteInner, }; use super::objiter; @@ -379,6 +380,17 @@ impl PyBytesRef { fn expandtabs(self, options: ByteInnerExpandtabsOptions, vm: &VirtualMachine) -> PyResult { Ok(vm.ctx.new_bytes(self.inner.expandtabs(options))) } + + #[pymethod(name = "splitlines")] + fn splitlines(self, options: ByteInnerSplitlinesOptions, vm: &VirtualMachine) -> PyResult { + let as_bytes = self + .inner + .splitlines(options, vm)? + .iter() + .map(|x| vm.ctx.new_bytes(x.to_vec())) + .collect::>(); + Ok(vm.ctx.new_list(as_bytes)) + } } #[derive(Debug)] From 29f674fa1dbb7650524e2e736489b15e0e54f037 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 21 Apr 2019 01:01:51 +0200 Subject: [PATCH 463/884] fix split with empty bytes --- tests/snippets/bytes.py | 4 ++++ vm/src/obj/objbyteinner.rs | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index e7dce5f4e7..944467349b 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -355,6 +355,10 @@ assert b"1 2 3".split(maxsplit=1) == [b"1", b"2 3"] assert b" 1 2 3 ".split() == [b"1", b"2", b"3"] assert b"k\ruh\nfz e f".split() == [b"k", b"uh", b"fz", b"e", b"f"] +assert b"Two lines\n".split(b'\n') == [b'Two lines', b''] +assert b"".split() == [] +assert b"".split(b"\n") == [b''] +assert b"\n".split(b"\n") == [b'', b''] SPLIT_FIXTURES = [ [ diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index d32c99d2c2..bb0f0118a5 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -837,6 +837,13 @@ impl PyByteInner { ) -> PyResult> { let (sep, maxsplit) = options.get_value(vm)?; + if self.elements.is_empty() { + if !sep.is_empty() { + return Ok(vec![&[]]); + } + return Ok(vec![]); + } + if reverse { Ok(split_slice_reverse(&self.elements, &sep, maxsplit)) } else { From b60d7413b9a2b4476329548e499715b007224fb4 Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Sun, 21 Apr 2019 11:26:12 +0200 Subject: [PATCH 464/884] add zfill --- tests/snippets/bytes.py | 13 ++++++++++--- vm/src/obj/objbyteinner.rs | 20 ++++++++++++++++++++ vm/src/obj/objbytes.rs | 5 +++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 944467349b..17f9520fab 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -355,10 +355,10 @@ assert b"1 2 3".split(maxsplit=1) == [b"1", b"2 3"] assert b" 1 2 3 ".split() == [b"1", b"2", b"3"] assert b"k\ruh\nfz e f".split() == [b"k", b"uh", b"fz", b"e", b"f"] -assert b"Two lines\n".split(b'\n') == [b'Two lines', b''] +assert b"Two lines\n".split(b"\n") == [b"Two lines", b""] assert b"".split() == [] -assert b"".split(b"\n") == [b''] -assert b"\n".split(b"\n") == [b'', b''] +assert b"".split(b"\n") == [b""] +assert b"\n".split(b"\n") == [b"", b""] SPLIT_FIXTURES = [ [ @@ -567,3 +567,10 @@ ] assert b"".splitlines() == [] assert b"One line\n".splitlines() == [b"One line"] + +# zfill + +assert b"42".zfill(5) == b"00042" +assert b"-42".zfill(5) == b"-0042" +assert b"42".zfill(1) == b"42" +assert b"42".zfill(-1) == b"42" diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index bb0f0118a5..45333f3b64 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -952,6 +952,26 @@ impl PyByteInner { Ok(res) } + + pub fn zfill(&self, width: PyIntRef) -> Vec { + if let Some(value) = width.as_bigint().to_usize() { + if value < self.elements.len() { + return self.elements.to_vec(); + } + let mut res = vec![]; + if self.elements.starts_with(&[b'-']) { + res.push(b'-'); + res.extend_from_slice(&vec![b'0'; value - self.elements.len()]); + res.extend_from_slice(&self.elements[1..]); + } else { + res.extend_from_slice(&vec![b'0'; value - self.elements.len()]); + res.extend_from_slice(&self.elements[0..]); + } + res + } else { + self.elements.to_vec() + } + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 31396c11cd..2f9a7d86ee 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -391,6 +391,11 @@ impl PyBytesRef { .collect::>(); Ok(vm.ctx.new_list(as_bytes)) } + + #[pymethod(name = "zfill")] + fn zfill(self, width: PyIntRef, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.zfill(width))) + } } #[derive(Debug)] From cf7032347545afd1874b6ff6faa0726d71df881b Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Sun, 21 Apr 2019 12:40:21 +0200 Subject: [PATCH 465/884] add replace --- tests/snippets/bytes.py | 8 ++++++ vm/src/obj/objbyteinner.rs | 58 ++++++++++++++++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 11 ++++++++ 3 files changed, 77 insertions(+) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 17f9520fab..9c1eb63f48 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -574,3 +574,11 @@ assert b"-42".zfill(5) == b"-0042" assert b"42".zfill(1) == b"42" assert b"42".zfill(-1) == b"42" + +# replace +assert b"123456789123".replace(b"23", b"XX") == b'1XX4567891XX' +assert b"123456789123".replace(b"23", b"XX", 1) == b'1XX456789123' +assert b"123456789123".replace(b"23", b"XX", 0) == b"123456789123" +assert b"123456789123".replace(b"23", b"XX", -1) == b'1XX4567891XX' +assert b"123456789123".replace(b"23", b"") == b"14567891" + diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 45333f3b64..bb6b0380d4 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -972,6 +972,64 @@ impl PyByteInner { self.elements.to_vec() } } + + pub fn replace( + &self, + old: PyObjectRef, + new: PyObjectRef, + count: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult> { + let old = match try_as_bytes_like(&old) { + Some(value) => value, + None => { + return Err( + vm.new_type_error(format!("a bytes-like object is required, not {}", old)) + ); + } + }; + + let new = match try_as_bytes_like(&new) { + Some(value) => value, + None => { + return Err( + vm.new_type_error(format!("a bytes-like object is required, not {}", new)) + ); + } + }; + + let count = if let OptionalArg::Present(int) = count { + if let Some(value) = int.as_bigint().to_u32() { + value + } else { + self.elements.len() as u32 + } + } else { + self.elements.len() as u32 + }; + + let mut res = vec![]; + let mut index = 0; + let mut done = 0; + + let slice = &self.elements; + while index <= slice.len() - old.len() { + if done == count { + res.extend_from_slice(&slice[index..]); + break; + } + if &slice[index..index + old.len()] == old.as_slice() { + res.extend_from_slice(&new); + index += old.len(); + done += 1; + } else { + res.push(slice[index]); + index += 1 + } + } + + Ok(res) + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 2f9a7d86ee..77c8f7ed85 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -396,6 +396,17 @@ impl PyBytesRef { fn zfill(self, width: PyIntRef, vm: &VirtualMachine) -> PyResult { Ok(vm.ctx.new_bytes(self.inner.zfill(width))) } + + #[pymethod(name = "replace")] + fn replace( + self, + old: PyObjectRef, + new: PyObjectRef, + count: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.replace(old, new, count, vm)?)) + } } #[derive(Debug)] From fd614b7a757a11679d0431caeca3cafbb00ab70f Mon Sep 17 00:00:00 2001 From: Jimmy Girardet Date: Tue, 30 Apr 2019 17:12:36 +0200 Subject: [PATCH 466/884] refactor split options --- vm/src/obj/objbyteinner.rs | 29 +++++++---------------------- vm/src/obj/objbytes.rs | 4 ++-- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index bb6b0380d4..31a04e0e1c 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -256,26 +256,16 @@ impl ByteInnerTranslateOptions { #[derive(FromArgs)] pub struct ByteInnerSplitOptions { #[pyarg(positional_or_keyword, optional = true)] - sep: OptionalArg, + sep: OptionalArg>, #[pyarg(positional_or_keyword, optional = true)] maxsplit: OptionalArg, } impl ByteInnerSplitOptions { - pub fn get_value(self, vm: &VirtualMachine) -> PyResult<(Vec, i32)> { - let sep = if let OptionalArg::Present(value) = self.sep { - match try_as_bytes_like(&value) { - Some(value) => value, - None => match_class!(value, - _n @ PyNone=> vec![], - obj => {return Err(vm.new_type_error(format!( - "a bytes-like object is required, not {}", - obj - )));} - ), - } - } else { - vec![] + pub fn get_value(self) -> PyResult<(Vec, i32)> { + let sep = match self.sep.into_option() { + Some(Some(bytes)) => bytes.elements, + _ => vec![], }; let maxsplit = if let OptionalArg::Present(value) = self.maxsplit { @@ -829,13 +819,8 @@ impl PyByteInner { Ok(self.elements[start..end].to_vec()) } - pub fn split( - &self, - options: ByteInnerSplitOptions, - reverse: bool, - vm: &VirtualMachine, - ) -> PyResult> { - let (sep, maxsplit) = options.get_value(vm)?; + pub fn split(&self, options: ByteInnerSplitOptions, reverse: bool) -> PyResult> { + let (sep, maxsplit) = options.get_value()?; if self.elements.is_empty() { if !sep.is_empty() { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 77c8f7ed85..0eb228a41f 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -339,7 +339,7 @@ impl PyBytesRef { fn split(self, options: ByteInnerSplitOptions, vm: &VirtualMachine) -> PyResult { let as_bytes = self .inner - .split(options, false, vm)? + .split(options, false)? .iter() .map(|x| vm.ctx.new_bytes(x.to_vec())) .collect::>(); @@ -350,7 +350,7 @@ impl PyBytesRef { fn rsplit(self, options: ByteInnerSplitOptions, vm: &VirtualMachine) -> PyResult { let as_bytes = self .inner - .split(options, true, vm)? + .split(options, true)? .iter() .map(|x| vm.ctx.new_bytes(x.to_vec())) .collect::>(); From 09710b52226a9f8cfc5ee3b70cc88fb2ada06723 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 03:15:28 +0900 Subject: [PATCH 467/884] Add float.__rdivmod__ and div/mod tests --- tests/snippets/floats.py | 7 +++++++ vm/src/obj/objfloat.rs | 24 ++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 580c5920fd..45f25c1001 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -106,6 +106,8 @@ assert 2.0.__rmul__(1.0) == 2.0 assert 1.0.__truediv__(2.0) == 0.5 assert 1.0.__rtruediv__(2.0) == 2.0 +assert 2.5.__divmod__(2.0) == (1.0, 0.5) +assert 2.0.__rdivmod__(2.5) == (1.0, 0.5) assert 1.0.__add__(1) == 2.0 assert 1.0.__radd__(1) == 2.0 @@ -120,6 +122,11 @@ assert_raises(ZeroDivisionError, lambda: 2.0 / 0) assert_raises(ZeroDivisionError, lambda: 2.0 // 0) assert_raises(ZeroDivisionError, lambda: 2.0 % 0) +assert_raises(ZeroDivisionError, lambda: divmod(2.0, 0)) +assert_raises(ZeroDivisionError, lambda: 2 / 0.0) +assert_raises(ZeroDivisionError, lambda: 2 // 0.0) +assert_raises(ZeroDivisionError, lambda: 2 % 0.0) +# assert_raises(ZeroDivisionError, lambda: divmod(2, 0.0)) assert 1.2.__int__() == 1 assert 1.2.__float__() == 1.2 diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 426483f4c1..2845e5ef4b 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -75,6 +75,14 @@ fn inner_floordiv(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult { } } +fn inner_divmod(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<(f64, f64)> { + if v2 != 0.0 { + Ok(((v1 / v2).floor(), v1 % v2)) + } else { + Err(vm.new_zero_division_error("float divmod()".to_string())) + } +} + #[pyimpl] impl PyFloat { #[pymethod(name = "__eq__")] @@ -177,8 +185,20 @@ impl PyFloat { try_float(&other, vm)?.map_or_else( || Ok(vm.ctx.not_implemented()), |other| { - let r1 = inner_floordiv(self.value, other, vm)?; - let r2 = inner_mod(self.value, other, vm)?; + let (r1, r2) = inner_divmod(self.value, other, vm)?; + Ok(vm + .ctx + .new_tuple(vec![vm.ctx.new_float(r1), vm.ctx.new_float(r2)])) + }, + ) + } + + #[pymethod(name = "__rdivmod__")] + fn rdivmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| { + let (r1, r2) = inner_divmod(other, self.value, vm)?; Ok(vm .ctx .new_tuple(vec![vm.ctx.new_float(r1), vm.ctx.new_float(r2)])) From 1c814ff2b5b9a53ebef4d09f7f0d96b63e544d60 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 04:02:34 +0900 Subject: [PATCH 468/884] Add float.__rpow__ --- tests/snippets/floats.py | 1 + vm/src/obj/objfloat.rs | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 45f25c1001..d409463eef 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -137,6 +137,7 @@ assert 1.2 ** 2 == 1.44 assert_raises(OverflowError, lambda: 1.2 ** (10 ** 1000)) +assert 3 ** 2.0 == 9.0 assert (1.7).real == 1.7 assert (1.3).is_integer() == False diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 2845e5ef4b..baf043d6cf 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -284,6 +284,14 @@ impl PyFloat { ) } + #[pymethod(name = "__rpow__")] + fn rpow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + try_float(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| other.powf(self.value).into_pyobject(vm), + ) + } + #[pymethod(name = "__sub__")] fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { try_float(&other, vm)?.map_or_else( From 0d505adbb5135676d03357b40d88861fd06bafa8 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Tue, 30 Apr 2019 22:01:45 +0200 Subject: [PATCH 469/884] use PyBytinner as arg --- vm/src/obj/objbyteinner.rs | 101 ++++++++++++------------------------- vm/src/obj/objbytes.rs | 25 +++++---- 2 files changed, 47 insertions(+), 79 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 31a04e0e1c..d6c47c2f04 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -281,19 +281,14 @@ impl ByteInnerSplitOptions { #[derive(FromArgs)] pub struct ByteInnerExpandtabsOptions { #[pyarg(positional_or_keyword, optional = true)] - tabsize: OptionalArg, + tabsize: OptionalArg, } impl ByteInnerExpandtabsOptions { pub fn get_value(self) -> usize { - if let OptionalArg::Present(value) = self.tabsize { - if let Some(v) = objint::get_value(&value).to_usize() { - v - } else { - 0 - } - } else { - 8 + match self.tabsize.into_option() { + Some(int) => int.as_bigint().to_usize().unwrap_or(0), + None => 8, } } } @@ -301,16 +296,20 @@ impl ByteInnerExpandtabsOptions { #[derive(FromArgs)] pub struct ByteInnerSplitlinesOptions { #[pyarg(positional_or_keyword, optional = true)] - keepends: OptionalArg, + keepends: OptionalArg, } impl ByteInnerSplitlinesOptions { - pub fn get_value(self, vm: &VirtualMachine) -> PyResult { - if let OptionalArg::Present(value) = self.keepends { - Ok(bool::try_from_object(vm, value)?) - } else { - Ok(false) + pub fn get_value(self) -> bool { + match self.keepends.into_option() { + Some(x) => x, + None => false, } + // if let OptionalArg::Present(value) = self.keepends { + // Ok(bool::try_from_object(vm, value)?) + // } else { + // Ok(false) + // } } } @@ -836,24 +835,11 @@ impl PyByteInner { } } - pub fn partition( - &self, - sep: &PyObjectRef, - reverse: bool, - vm: &VirtualMachine, - ) -> PyResult<(Vec, Vec)> { - let sep = match try_as_bytes_like(&sep) { - Some(value) => value, - None => { - return Err( - vm.new_type_error(format!("a bytes-like object is required, not {}", sep)) - ); - } - }; + pub fn partition(&self, sep: &PyByteInner, reverse: bool) -> PyResult<(Vec, Vec)> { let splitted = if reverse { - split_slice_reverse(&self.elements, &sep, 1) + split_slice_reverse(&self.elements, &sep.elements, 1) } else { - split_slice(&self.elements, &sep, 1) + split_slice(&self.elements, &sep.elements, 1) }; Ok((splitted[0].to_vec(), splitted[1].to_vec())) } @@ -890,17 +876,13 @@ impl PyByteInner { res } - pub fn splitlines( - &self, - options: ByteInnerSplitlinesOptions, - vm: &VirtualMachine, - ) -> PyResult> { - let keepends = options.get_value(vm)?; + pub fn splitlines(&self, options: ByteInnerSplitlinesOptions) -> Vec<&[u8]> { + let keepends = options.get_value(); let mut res = vec![]; if self.elements.is_empty() { - return Ok(vec![]); + return vec![]; } let mut prev_index = 0; @@ -935,7 +917,7 @@ impl PyByteInner { } } - Ok(res) + res } pub fn zfill(&self, width: PyIntRef) -> Vec { @@ -960,37 +942,16 @@ impl PyByteInner { pub fn replace( &self, - old: PyObjectRef, - new: PyObjectRef, + old: PyByteInner, + new: PyByteInner, count: OptionalArg, - vm: &VirtualMachine, ) -> PyResult> { - let old = match try_as_bytes_like(&old) { - Some(value) => value, - None => { - return Err( - vm.new_type_error(format!("a bytes-like object is required, not {}", old)) - ); - } - }; - - let new = match try_as_bytes_like(&new) { - Some(value) => value, - None => { - return Err( - vm.new_type_error(format!("a bytes-like object is required, not {}", new)) - ); - } - }; - - let count = if let OptionalArg::Present(int) = count { - if let Some(value) = int.as_bigint().to_u32() { - value - } else { - self.elements.len() as u32 - } - } else { - self.elements.len() as u32 + let count = match count.into_option() { + Some(int) => int + .as_bigint() + .to_u32() + .unwrap_or(self.elements.len() as u32), + None => self.elements.len() as u32, }; let mut res = vec![]; @@ -1003,8 +964,8 @@ impl PyByteInner { res.extend_from_slice(&slice[index..]); break; } - if &slice[index..index + old.len()] == old.as_slice() { - res.extend_from_slice(&new); + if &slice[index..index + old.len()] == old.elements.as_slice() { + res.extend_from_slice(&new.elements); index += old.len(); done += 1; } else { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 0eb228a41f..54bccf6f0f 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,4 +1,5 @@ use crate::obj::objint::PyIntRef; + use crate::obj::objslice::PySliceRef; use crate::obj::objstr::PyStringRef; use crate::obj::objtuple::PyTupleRef; @@ -9,7 +10,9 @@ use core::cell::Cell; use std::ops::Deref; use crate::function::OptionalArg; -use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{ + PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, +}; use super::objbyteinner::{ ByteInnerExpandtabsOptions, ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, @@ -360,8 +363,10 @@ impl PyBytesRef { #[pymethod(name = "partition")] fn partition(self, sep: PyObjectRef, vm: &VirtualMachine) -> PyResult { // TODO: when implementing bytearray,remember sep ALWAYS converted to bytearray - // even it's bytes or memoryview - let (left, right) = self.inner.partition(&sep, false, vm)?; + // even it's bytes or memoryview so PyByteInner wiil be ok for args + let sepa = PyByteInner::try_from_object(vm, sep.clone())?; + + let (left, right) = self.inner.partition(&sepa, false)?; Ok(vm .ctx .new_tuple(vec![vm.ctx.new_bytes(left), sep, vm.ctx.new_bytes(right)])) @@ -369,8 +374,10 @@ impl PyBytesRef { #[pymethod(name = "rpartition")] fn rpartition(self, sep: PyObjectRef, vm: &VirtualMachine) -> PyResult { // TODO: when implementing bytearray,remember sep ALWAYS converted to bytearray - // even it's bytes or memoryview - let (left, right) = self.inner.partition(&sep, true, vm)?; + // even it's bytes or memoryview so PyByteInner wiil be ok for args + let sepa = PyByteInner::try_from_object(vm, sep.clone())?; + + let (left, right) = self.inner.partition(&sepa, true)?; Ok(vm .ctx .new_tuple(vec![vm.ctx.new_bytes(left), sep, vm.ctx.new_bytes(right)])) @@ -385,7 +392,7 @@ impl PyBytesRef { fn splitlines(self, options: ByteInnerSplitlinesOptions, vm: &VirtualMachine) -> PyResult { let as_bytes = self .inner - .splitlines(options, vm)? + .splitlines(options) .iter() .map(|x| vm.ctx.new_bytes(x.to_vec())) .collect::>(); @@ -400,12 +407,12 @@ impl PyBytesRef { #[pymethod(name = "replace")] fn replace( self, - old: PyObjectRef, - new: PyObjectRef, + old: PyByteInner, + new: PyByteInner, count: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - Ok(vm.ctx.new_bytes(self.inner.replace(old, new, count, vm)?)) + Ok(vm.ctx.new_bytes(self.inner.replace(old, new, count)?)) } } From 22a35c9b90c6796fc8de97f08932257224c1c56e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 01:00:25 +0900 Subject: [PATCH 470/884] extend_class for PyRangeIterator --- vm/src/obj/objrange.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index a22788119f..ac129f7543 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -106,12 +106,7 @@ pub fn get_value(obj: &PyObjectRef) -> PyRange { pub fn init(context: &PyContext) { PyRange::extend_class(context, &context.range_type); - - let rangeiterator_type = &context.rangeiterator_type; - extend_class!(context, rangeiterator_type, { - "__next__" => context.new_rustfunc(PyRangeIteratorRef::next), - "__iter__" => context.new_rustfunc(PyRangeIteratorRef::iter), - }); + PyRangeIterator::extend_class(context, &context.rangeiterator_type); } type PyRangeRef = PyRef; @@ -339,6 +334,7 @@ impl PyRange { } } +#[pyclass] #[derive(Debug)] pub struct PyRangeIterator { position: Cell, @@ -353,8 +349,10 @@ impl PyValue for PyRangeIterator { type PyRangeIteratorRef = PyRef; -impl PyRangeIteratorRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +impl PyRangeIterator { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { let position = BigInt::from(self.position.get()); if let Some(int) = self.range.get(&position) { self.position.set(self.position.get() + 1); @@ -364,8 +362,9 @@ impl PyRangeIteratorRef { } } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRangeIteratorRef { + zelf } } From 14dd85a596f3b5e5e002f4899f5fd737230b363d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 03:00:54 +0900 Subject: [PATCH 471/884] Fix __builtins__.divmod to reflect __rdivmod__ --- vm/src/builtins.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 41082707ea..300d11b315 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -123,11 +123,14 @@ fn builtin_dir(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn builtin_divmod(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(x, None), (y, None)]); - match vm.get_method(x.clone(), "__divmod__") { - Ok(attrib) => vm.invoke(attrib, vec![y.clone()]), - Err(..) => Err(vm.new_type_error("unsupported operand type(s) for divmod".to_string())), - } + arg_check!(vm, args, required = [(a, None), (b, None)]); + vm.call_or_reflection( + a.clone(), + b.clone(), + "__divmod__", + "__rdivmod__", + |vm, a, b| Err(vm.new_unsupported_operand_error(a, b, "divmod")), + ) } /// Implements `eval`. From 0d891cba2c90b56cb11ee1b20ef3458acf86686c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 02:59:43 +0900 Subject: [PATCH 472/884] Temporary fix to assertRaises and assert_raises prints errors --- tests/snippets/testutils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/snippets/testutils.py b/tests/snippets/testutils.py index 7dda57b17a..49b7fedc17 100644 --- a/tests/snippets/testutils.py +++ b/tests/snippets/testutils.py @@ -14,9 +14,9 @@ def assert_raises(exc_type, expr, msg=None): except exc_type: pass else: - failmsg = '{!s} was not raised'.format(exc_type.__name__) + failmsg = '{} was not raised'.format(exc_type.__name__) if msg is not None: - failmsg += ': {!s}'.format(msg) + failmsg += ': {}'.format(msg) assert False, failmsg @@ -29,8 +29,8 @@ def __enter__(self): def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is None: - failmsg = '{!s} was not raised'.format(self.expected.__name__) - assert False, failmsg + failmsg = '{} was not raised'.format(self.expected.__name__) + assert False, failmsg if not issubclass(exc_type, self.expected): return False return True From 4ef65d8ac303a3574f5be78482afc0cfa942ae4d Mon Sep 17 00:00:00 2001 From: Tzu-Yin Hong Date: Wed, 1 May 2019 15:53:28 +0800 Subject: [PATCH 473/884] Add bytearray.append --- tests/snippets/bytearray.py | 6 ++++++ vm/src/obj/objbytearray.rs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index 563da2c53b..4286f81710 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -65,3 +65,9 @@ pass else: assert False + +a = bytearray(b'appen') +assert len(a) == 5 +a.append(100) +assert len(a) == 6 +assert a.pop() == 100 diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index f6244399b2..16758a438b 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -79,6 +79,7 @@ pub fn init(context: &PyContext) { "istitle" =>context.new_rustfunc(PyByteArrayRef::istitle), "isupper" => context.new_rustfunc(PyByteArrayRef::isupper), "lower" => context.new_rustfunc(PyByteArrayRef::lower), + "append" => context.new_rustfunc(PyByteArrayRef::append), "pop" => context.new_rustfunc(PyByteArrayRef::pop), "upper" => context.new_rustfunc(PyByteArrayRef::upper) }); @@ -213,6 +214,10 @@ impl PyByteArrayRef { self.value.borrow_mut().clear(); } + fn append(self, x: u8, _vm: &VirtualMachine) { + self.value.borrow_mut().push(x); + } + fn pop(self, vm: &VirtualMachine) -> PyResult { let mut bytes = self.value.borrow_mut(); bytes From bb878a34027112e19c06189662ae756ff8afe264 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 01:25:04 +0900 Subject: [PATCH 474/884] PyFloat to use extend_class for __new__ and __doc__ --- vm/src/obj/objfloat.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index baf043d6cf..fabe992460 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -11,6 +11,7 @@ use num_bigint::{BigInt, ToBigInt}; use num_rational::Ratio; use num_traits::ToPrimitive; +/// Convert a string or number to a floating point number, if possible. #[pyclass(name = "float")] #[derive(Debug, Copy, Clone, PartialEq)] pub struct PyFloat { @@ -222,7 +223,8 @@ impl PyFloat { ) } - fn new_float(cls: PyClassRef, arg: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__new__")] + fn float_new(cls: PyClassRef, arg: PyObjectRef, vm: &VirtualMachine) -> PyResult { let value = if objtype::isinstance(&arg, &vm.ctx.float_type()) { get_value(&arg) } else if objtype::isinstance(&arg, &vm.ctx.int_type()) { @@ -408,9 +410,4 @@ pub fn make_float(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { #[rustfmt::skip] // to avoid line splitting pub fn init(context: &PyContext) { PyFloat::extend_class(context, &context.float_type); - let float_doc = "Convert a string or number to a floating point number, if possible."; - extend_class!(context, &context.float_type, { - "__new__" => context.new_rustfunc(PyFloat::new_float), - "__doc__" => context.new_str(float_doc.to_string()), - }); } From 4a598d03e28536fb40c6dbd87be03c9b85bdf8ff Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 03:37:01 +0900 Subject: [PATCH 475/884] Add float.imag, float.conjugate --- tests/snippets/floats.py | 2 ++ vm/src/obj/objfloat.rs | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index d409463eef..0ba3460a90 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -140,6 +140,8 @@ assert 3 ** 2.0 == 9.0 assert (1.7).real == 1.7 +assert (1.7).imag == 0.0 +assert (1.7).conjugate() == 1.7 assert (1.3).is_integer() == False assert (1.0).is_integer() == True diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index fabe992460..ea58fed58c 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -364,6 +364,16 @@ impl PyFloat { zelf } + #[pyproperty(name = "imag")] + fn imag(&self, _vm: &VirtualMachine) -> f64 { + 0.0f64 + } + + #[pymethod(name = "conjugate")] + fn conjugate(zelf: PyRef, _vm: &VirtualMachine) -> PyFloatRef { + zelf + } + #[pymethod(name = "is_integer")] fn is_integer(&self, _vm: &VirtualMachine) -> bool { let v = self.value; From 1247a35be94ee11cbee909bf753bb5b5bfc913a9 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 1 May 2019 21:05:21 +0200 Subject: [PATCH 476/884] Improve handling of lexical errors. --- parser/src/fstring.rs | 7 +- parser/src/lexer.rs | 167 +++++++++++++++++++++++-------- tests/snippets/invalid_syntax.py | 4 +- 3 files changed, 133 insertions(+), 45 deletions(-) diff --git a/parser/src/fstring.rs b/parser/src/fstring.rs index cad885531e..e1e758907e 100644 --- a/parser/src/fstring.rs +++ b/parser/src/fstring.rs @@ -5,7 +5,7 @@ use std::str; use lalrpop_util::ParseError as LalrpopError; use crate::ast::{ConversionFlag, StringGroup}; -use crate::lexer::{LexicalError, Location, Tok}; +use crate::lexer::{LexicalError, LexicalErrorType, Location, Tok}; use crate::parser::parse_expression; use self::FStringError::*; @@ -25,7 +25,10 @@ pub enum FStringError { impl From for LalrpopError { fn from(_err: FStringError) -> Self { lalrpop_util::ParseError::User { - error: LexicalError::StringError, + error: LexicalError { + error: LexicalErrorType::StringError, + location: Default::default(), + }, } } } diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 11d30cbee7..6ea29ea282 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -50,17 +50,25 @@ pub struct Lexer> { at_begin_of_line: bool, nesting: usize, // Amount of parenthesis indentation_stack: Vec, - pending: Vec>, + pending: Vec, chr0: Option, chr1: Option, location: Location, + keywords: HashMap, } #[derive(Debug)] -pub enum LexicalError { +pub struct LexicalError { + pub error: LexicalErrorType, + pub location: Location, +} + +#[derive(Debug)] +pub enum LexicalErrorType { StringError, NestingError, UnrecognizedToken { tok: char }, + OtherError(String), } #[derive(Clone, Debug, Default, PartialEq)] @@ -125,9 +133,10 @@ pub fn get_keywords() -> HashMap { keywords } -pub type Spanned = Result<(Location, Tok, Location), LexicalError>; +pub type Spanned = (Location, Tok, Location); +pub type LexResult = Result; -pub fn make_tokenizer<'a>(source: &'a str) -> impl Iterator> + 'a { +pub fn make_tokenizer<'a>(source: &'a str) -> impl Iterator + 'a { let nlh = NewlineHandler::new(source.chars()); let lch = LineContinationHandler::new(nlh); Lexer::new(lch) @@ -259,6 +268,7 @@ where chr0: None, location: Location::new(0, 0), chr1: None, + keywords: get_keywords(), }; lxr.next_char(); lxr.next_char(); @@ -269,7 +279,7 @@ where } // Lexer helper functions: - fn lex_identifier(&mut self) -> Spanned { + fn lex_identifier(&mut self) -> LexResult { let mut name = String::new(); let start_pos = self.get_pos(); @@ -310,16 +320,14 @@ where } let end_pos = self.get_pos(); - let mut keywords = get_keywords(); - - if keywords.contains_key(&name) { - Ok((start_pos, keywords.remove(&name).unwrap(), end_pos)) + if self.keywords.contains_key(&name) { + Ok((start_pos, self.keywords[&name].clone(), end_pos)) } else { Ok((start_pos, Tok::Name { name }, end_pos)) } } - fn lex_number(&mut self) -> Spanned { + fn lex_number(&mut self) -> LexResult { let start_pos = self.get_pos(); if self.chr0 == Some('0') { if self.chr1 == Some('x') || self.chr1 == Some('X') { @@ -345,12 +353,12 @@ where } } - fn lex_number_radix(&mut self, start_pos: Location, radix: u32) -> Spanned { + fn lex_number_radix(&mut self, start_pos: Location, radix: u32) -> LexResult { let mut value_text = String::new(); loop { - if self.is_number(radix) { - value_text.push(self.next_char().unwrap()); + if let Some(c) = self.take_number(radix) { + value_text.push(c); } else if self.chr0 == Some('_') { self.next_char(); } else { @@ -359,18 +367,21 @@ where } let end_pos = self.get_pos(); - let value = BigInt::from_str_radix(&value_text, radix).unwrap(); + let value = BigInt::from_str_radix(&value_text, radix).map_err(|e| LexicalError { + error: LexicalErrorType::OtherError(format!("{:?}", e)), + location: start_pos.clone(), + })?; Ok((start_pos, Tok::Int { value }, end_pos)) } - fn lex_normal_number(&mut self) -> Spanned { + fn lex_normal_number(&mut self) -> LexResult { let start_pos = self.get_pos(); let mut value_text = String::new(); // Normal number: - while self.is_number(10) { - value_text.push(self.next_char().unwrap()); + while let Some(c) = self.take_number(10) { + value_text.push(c); } // If float: @@ -378,8 +389,8 @@ where // Take '.': if self.chr0 == Some('.') { value_text.push(self.next_char().unwrap()); - while self.is_number(10) { - value_text.push(self.next_char().unwrap()); + while let Some(c) = self.take_number(10) { + value_text.push(c); } } @@ -392,8 +403,8 @@ where value_text.push(self.next_char().unwrap()); } - while self.is_number(10) { - value_text.push(self.next_char().unwrap()); + while let Some(c) = self.take_number(10) { + value_text.push(c); } } @@ -448,7 +459,7 @@ where is_raw: bool, _is_unicode: bool, is_fstring: bool, - ) -> Spanned { + ) -> LexResult { let quote_char = self.next_char().unwrap(); let mut string_content = String::new(); let start_pos = self.get_pos(); @@ -474,7 +485,10 @@ where if let Some(c) = self.next_char() { string_content.push(c) } else { - return Err(LexicalError::StringError); + return Err(LexicalError { + error: LexicalErrorType::StringError, + location: self.get_pos(), + }); } } else { match self.next_char() { @@ -502,7 +516,10 @@ where string_content.push(c); } None => { - return Err(LexicalError::StringError); + return Err(LexicalError { + error: LexicalErrorType::StringError, + location: self.get_pos(), + }); } } } @@ -525,7 +542,10 @@ where } else { if c == '\n' { if !triple_quoted { - return Err(LexicalError::StringError); + return Err(LexicalError { + error: LexicalErrorType::StringError, + location: self.get_pos(), + }); } self.new_line(); } @@ -533,7 +553,10 @@ where } } None => { - return Err(LexicalError::StringError); + return Err(LexicalError { + error: LexicalErrorType::StringError, + location: self.get_pos(), + }); } } } @@ -545,7 +568,10 @@ where value: lex_byte(string_content)?, } } else { - return Err(LexicalError::StringError); + return Err(LexicalError { + error: LexicalErrorType::StringError, + location: self.get_pos(), + }); } } else { Tok::String { @@ -575,8 +601,8 @@ where } } - fn is_number(&self, radix: u32) -> bool { - match radix { + fn take_number(&mut self, radix: u32) -> Option { + let take_char = match radix { 2 => match self.chr0 { Some('0'..='1') => true, _ => false, @@ -594,6 +620,12 @@ where _ => false, }, x => unimplemented!("Radix not implemented: {}", x), + }; + + if take_char { + Some(self.next_char().unwrap()) + } else { + None } } @@ -615,7 +647,7 @@ where self.location.column = 1; } - fn inner_next(&mut self) -> Option> { + fn inner_next(&mut self) -> Option { if !self.pending.is_empty() { return Some(self.pending.remove(0)); } @@ -631,6 +663,17 @@ where loop { match self.chr0 { Some(' ') => { + /* + if tabs != 0 { + // Don't allow spaces after tabs as part of indentation. + // This is technically stricter than python3 but spaces after + // tabs is even more insane than mixing spaces and tabs. + return Some(Err(LexicalError { + error: LexicalErrorType::OtherError("Spaces not allowed as part of indentation after tabs".to_string()), + location: self.get_pos(), + })); + } + */ self.next_char(); spaces += 1; } @@ -639,7 +682,13 @@ where // Don't allow tabs after spaces as part of indentation. // This is technically stricter than python3 but spaces before // tabs is even more insane than mixing spaces and tabs. - panic!("Tabs not allowed as part of indentation after spaces"); + return Some(Err(LexicalError { + error: LexicalErrorType::OtherError( + "Tabs not allowed as part of indentation after spaces" + .to_string(), + ), + location: self.get_pos(), + })); } self.next_char(); tabs += 1; @@ -694,7 +743,10 @@ where self.pending.push(Ok((tok_start, Tok::Dedent, tok_end))); } None => { - panic!("inconsistent use of tabs and spaces in indentation") + return Some(Err(LexicalError { + error: LexicalErrorType::OtherError("inconsistent use of tabs and spaces in indentation".to_string()), + location: self.get_pos(), + })); } _ => { break; @@ -704,12 +756,25 @@ where if indentation_level != *self.indentation_stack.last().unwrap() { // TODO: handle wrong indentations - panic!("Non matching indentation levels!"); + return Some(Err(LexicalError { + error: LexicalErrorType::OtherError( + "Non matching indentation levels!".to_string(), + ), + location: self.get_pos(), + })); } return Some(self.pending.remove(0)); } - None => panic!("inconsistent use of tabs and spaces in indentation"), + None => { + return Some(Err(LexicalError { + error: LexicalErrorType::OtherError( + "inconsistent use of tabs and spaces in indentation" + .to_string(), + ), + location: self.get_pos(), + })); + } } } } @@ -928,7 +993,10 @@ where let tok_end = self.get_pos(); return Some(Ok((tok_start, Tok::NotEqual, tok_end))); } else { - return Some(Err(LexicalError::UnrecognizedToken { tok: '!' })); + return Some(Err(LexicalError { + error: LexicalErrorType::UnrecognizedToken { tok: '!' }, + location: tok_start, + })); } } '~' => { @@ -942,7 +1010,10 @@ where ')' => { let result = self.eat_single_char(Tok::Rpar); if self.nesting == 0 { - return Some(Err(LexicalError::NestingError)); + return Some(Err(LexicalError { + error: LexicalErrorType::NestingError, + location: self.get_pos(), + })); } self.nesting -= 1; return Some(result); @@ -955,7 +1026,10 @@ where ']' => { let result = self.eat_single_char(Tok::Rsqb); if self.nesting == 0 { - return Some(Err(LexicalError::NestingError)); + return Some(Err(LexicalError { + error: LexicalErrorType::NestingError, + location: self.get_pos(), + })); } self.nesting -= 1; return Some(result); @@ -968,7 +1042,10 @@ where '}' => { let result = self.eat_single_char(Tok::Rbrace); if self.nesting == 0 { - return Some(Err(LexicalError::NestingError)); + return Some(Err(LexicalError { + error: LexicalErrorType::NestingError, + location: self.get_pos(), + })); } self.nesting -= 1; return Some(result); @@ -1089,7 +1166,10 @@ where } _ => { let c = self.next_char(); - return Some(Err(LexicalError::UnrecognizedToken { tok: c.unwrap() })); + return Some(Err(LexicalError { + error: LexicalErrorType::UnrecognizedToken { tok: c.unwrap() }, + location: self.get_pos(), + })); } // Ignore all the rest.. } } @@ -1099,7 +1179,7 @@ where } } - fn eat_single_char(&mut self, ty: Tok) -> Spanned { + fn eat_single_char(&mut self, ty: Tok) -> LexResult { let tok_start = self.get_pos(); self.next_char(); let tok_end = self.get_pos(); @@ -1116,7 +1196,7 @@ impl Iterator for Lexer where T: Iterator, { - type Item = Spanned; + type Item = LexResult; fn next(&mut self) -> Option { // Idea: create some sort of hash map for single char tokens: @@ -1152,7 +1232,10 @@ fn lex_byte(s: String) -> Result, LexicalError> { hex_value.clear(); } } else { - return Err(LexicalError::StringError); + return Err(LexicalError { + error: LexicalErrorType::StringError, + location: Default::default(), + }); } } else { match (c, escape) { diff --git a/tests/snippets/invalid_syntax.py b/tests/snippets/invalid_syntax.py index bbbd08a41e..3c9c6d46c8 100644 --- a/tests/snippets/invalid_syntax.py +++ b/tests/snippets/invalid_syntax.py @@ -1,4 +1,4 @@ - +from testutils import assertRaises src = """ def valid_func(): @@ -14,3 +14,5 @@ def valid_func(): else: raise AssertionError("Must throw syntax error") +with assertRaises(SyntaxError): + compile('0xX', 'test.py', 'exec') From c7fd54e809d542320c97977ee7734ef36f153068 Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Sun, 28 Apr 2019 13:25:57 +0200 Subject: [PATCH 477/884] Convert iterators to pyclass macros --- vm/src/obj/objbytearray.rs | 22 ++++++++++------------ vm/src/obj/objbytes.rs | 20 +++++++++----------- vm/src/obj/objenumerate.rs | 20 +++++++++++--------- vm/src/obj/objfilter.rs | 31 +++++++++++++++---------------- vm/src/obj/objiter.rs | 25 ++++++++++++------------- vm/src/obj/objlist.rs | 23 +++++++++++------------ vm/src/obj/objmap.rs | 30 +++++++++++++++--------------- vm/src/obj/objrange.rs | 22 ++++++++++------------ vm/src/obj/objtuple.rs | 22 ++++++++++------------ vm/src/obj/objzip.rs | 20 +++++++++++--------- vm/src/stdlib/os.rs | 22 +++++++++++----------- 11 files changed, 125 insertions(+), 132 deletions(-) diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 16758a438b..6f78ecf481 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -7,7 +7,7 @@ use std::ops::{Deref, DerefMut}; use num_traits::ToPrimitive; use crate::function::OptionalArg; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint; @@ -84,11 +84,7 @@ pub fn init(context: &PyContext) { "upper" => context.new_rustfunc(PyByteArrayRef::upper) }); - let bytearrayiterator_type = &context.bytearrayiterator_type; - extend_class!(context, bytearrayiterator_type, { - "__next__" => context.new_rustfunc(PyByteArrayIteratorRef::next), - "__iter__" => context.new_rustfunc(PyByteArrayIteratorRef::iter), - }); + PyByteArrayIterator::extend_class(context, &context.bytearrayiterator_type); } fn bytearray_new( @@ -287,6 +283,7 @@ mod tests { } } +#[pyclass] #[derive(Debug)] pub struct PyByteArrayIterator { position: Cell, @@ -299,10 +296,10 @@ impl PyValue for PyByteArrayIterator { } } -type PyByteArrayIteratorRef = PyRef; - -impl PyByteArrayIteratorRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +impl PyByteArrayIterator { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { if self.position.get() < self.bytearray.value.borrow().len() { let ret = self.bytearray.value.borrow()[self.position.get()]; self.position.set(self.position.get() + 1); @@ -312,7 +309,8 @@ impl PyByteArrayIteratorRef { } } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 0556de0535..df97959029 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -64,11 +64,7 @@ pub fn init(context: &PyContext) { extend_class!(context, bytes_type, { "fromhex" => context.new_rustfunc(PyBytesRef::fromhex), }); - let bytesiterator_type = &context.bytesiterator_type; - extend_class!(context, bytesiterator_type, { - "__next__" => context.new_rustfunc(PyBytesIteratorRef::next), - "__iter__" => context.new_rustfunc(PyBytesIteratorRef::iter), - }); + PyBytesIterator::extend_class(context, &context.bytesiterator_type); } #[pyimpl] @@ -271,6 +267,7 @@ impl PyBytesRef { } } +#[pyclass] #[derive(Debug)] pub struct PyBytesIterator { position: Cell, @@ -283,10 +280,10 @@ impl PyValue for PyBytesIterator { } } -type PyBytesIteratorRef = PyRef; - -impl PyBytesIteratorRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +impl PyBytesIterator { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { if self.position.get() < self.bytes.inner.len() { let ret = self.bytes[self.position.get()]; self.position.set(self.position.get() + 1); @@ -296,7 +293,8 @@ impl PyBytesIteratorRef { } } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } diff --git a/vm/src/obj/objenumerate.rs b/vm/src/obj/objenumerate.rs index c94aeabbdb..56d232272b 100644 --- a/vm/src/obj/objenumerate.rs +++ b/vm/src/obj/objenumerate.rs @@ -5,13 +5,14 @@ use num_bigint::BigInt; use num_traits::Zero; use crate::function::OptionalArg; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objint::PyIntRef; use super::objiter; use super::objtype::PyClassRef; +#[pyclass] #[derive(Debug)] pub struct PyEnumerate { counter: RefCell, @@ -44,8 +45,10 @@ fn enumerate_new( .into_ref_with_type(vm, cls) } -impl PyEnumerateRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +impl PyEnumerate { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { let iterator = &self.iterator; let counter = &self.counter; let next_obj = objiter::call_next(vm, iterator)?; @@ -58,16 +61,15 @@ impl PyEnumerateRef { Ok(result) } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } pub fn init(context: &PyContext) { - let enumerate_type = &context.enumerate_type; - extend_class!(context, enumerate_type, { + PyEnumerate::extend_class(context, &context.enumerate_type); + extend_class!(context, &context.enumerate_type, { "__new__" => context.new_rustfunc(enumerate_new), - "__next__" => context.new_rustfunc(PyEnumerateRef::next), - "__iter__" => context.new_rustfunc(PyEnumerateRef::iter), }); } diff --git a/vm/src/obj/objfilter.rs b/vm/src/obj/objfilter.rs index 858a0531a0..24800d6aaf 100644 --- a/vm/src/obj/objfilter.rs +++ b/vm/src/obj/objfilter.rs @@ -1,4 +1,4 @@ -use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{IdProtocol, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; // Required for arg_check! to use isinstance use super::objbool; @@ -7,6 +7,11 @@ use crate::obj::objtype::PyClassRef; pub type PyFilterRef = PyRef; +/// filter(function or None, iterable) --> filter object +/// +/// Return an iterator yielding those items of iterable for which function(item) +/// is true. If function is None, return the items that are true. +#[pyclass] #[derive(Debug)] pub struct PyFilter { predicate: PyObjectRef, @@ -34,8 +39,10 @@ fn filter_new( .into_ref_with_type(vm, cls) } -impl PyFilterRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +impl PyFilter { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { let predicate = &self.predicate; let iterator = &self.iterator; loop { @@ -53,23 +60,15 @@ impl PyFilterRef { } } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } pub fn init(context: &PyContext) { - let filter_type = &context.filter_type; - - let filter_doc = - "filter(function or None, iterable) --> filter object\n\n\ - Return an iterator yielding those items of iterable for which function(item)\n\ - is true. If function is None, return the items that are true."; - - extend_class!(context, filter_type, { + PyFilter::extend_class(context, &context.filter_type); + extend_class!(context, &context.filter_type, { "__new__" => context.new_rustfunc(filter_new), - "__doc__" => context.new_str(filter_doc.to_string()), - "__next__" => context.new_rustfunc(PyFilterRef::next), - "__iter__" => context.new_rustfunc(PyFilterRef::iter), }); } diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 6e1dc1d064..efe672d62d 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -4,7 +4,9 @@ use std::cell::Cell; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol}; +use crate::pyobject::{ + PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, +}; use crate::vm::VirtualMachine; use super::objtype; @@ -75,6 +77,7 @@ pub fn new_stop_iteration(vm: &VirtualMachine) -> PyObjectRef { vm.new_exception(stop_iteration_type, "End of iterator".to_string()) } +#[pyclass] #[derive(Debug)] pub struct PySequenceIterator { pub position: Cell, @@ -87,10 +90,10 @@ impl PyValue for PySequenceIterator { } } -type PySequenceIteratorRef = PyRef; - -impl PySequenceIteratorRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +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) => { @@ -105,16 +108,12 @@ impl PySequenceIteratorRef { } } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } pub fn init(context: &PyContext) { - let iter_type = &context.iter_type; - - extend_class!(context, iter_type, { - "__next__" => context.new_rustfunc(PySequenceIteratorRef::next), - "__iter__" => context.new_rustfunc(PySequenceIteratorRef::iter), - }); + PySequenceIterator::extend_class(context, &context.iter_type); } diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 5c1bad5af8..6b00b6828c 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -8,7 +8,8 @@ use num_traits::{One, Signed, ToPrimitive, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - IdProtocol, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, + IdProtocol, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, + TryFromObject, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -776,6 +777,7 @@ fn list_sort(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } +#[pyclass] #[derive(Debug)] pub struct PyListIterator { pub position: Cell, @@ -788,10 +790,10 @@ impl PyValue for PyListIterator { } } -type PyListIteratorRef = PyRef; - -impl PyListIteratorRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +impl PyListIterator { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { if self.position.get() < self.list.elements.borrow().len() { let ret = self.list.elements.borrow()[self.position.get()].clone(); self.position.set(self.position.get() + 1); @@ -801,8 +803,9 @@ impl PyListIteratorRef { } } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } @@ -848,9 +851,5 @@ pub fn init(context: &PyContext) { "remove" => context.new_rustfunc(PyListRef::remove) }); - let listiterator_type = &context.listiterator_type; - extend_class!(context, listiterator_type, { - "__next__" => context.new_rustfunc(PyListIteratorRef::next), - "__iter__" => context.new_rustfunc(PyListIteratorRef::iter), - }); + PyListIterator::extend_class(context, &context.listiterator_type); } diff --git a/vm/src/obj/objmap.rs b/vm/src/obj/objmap.rs index 061f4179f4..61fe67c0d1 100644 --- a/vm/src/obj/objmap.rs +++ b/vm/src/obj/objmap.rs @@ -1,10 +1,15 @@ use crate::function::Args; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objiter; use super::objtype::PyClassRef; +/// map(func, *iterables) --> map object +/// +/// Make an iterator that computes the function using arguments from +/// each of the iterables. Stops when the shortest iterable is exhausted. +#[pyclass] #[derive(Debug)] pub struct PyMap { mapper: PyObjectRef, @@ -35,8 +40,10 @@ fn map_new( .into_ref_with_type(vm, cls.clone()) } -impl PyMapRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +impl PyMap { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { let next_objs = self .iterators .iter() @@ -47,22 +54,15 @@ impl PyMapRef { vm.invoke(self.mapper.clone(), next_objs) } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } pub fn init(context: &PyContext) { - let map_type = &context.map_type; - - let map_doc = "map(func, *iterables) --> map object\n\n\ - Make an iterator that computes the function using arguments from\n\ - each of the iterables. Stops when the shortest iterable is exhausted."; - - extend_class!(context, map_type, { + PyMap::extend_class(context, &context.map_type); + extend_class!(context, &context.map_type, { "__new__" => context.new_rustfunc(map_new), - "__next__" => context.new_rustfunc(PyMapRef::next), - "__iter__" => context.new_rustfunc(PyMapRef::iter), - "__doc__" => context.new_str(map_doc.to_string()) }); } diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index d98e7491fe..2a43579c58 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -6,7 +6,7 @@ use num_traits::{One, Signed, Zero}; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, + PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -124,11 +124,7 @@ pub fn init(context: &PyContext) { "step" => context.new_property(PyRange::step), }); - let rangeiterator_type = &context.rangeiterator_type; - extend_class!(context, rangeiterator_type, { - "__next__" => context.new_rustfunc(PyRangeIteratorRef::next), - "__iter__" => context.new_rustfunc(PyRangeIteratorRef::iter), - }); + PyRangeIterator::extend_class(context, &context.rangeiterator_type); } type PyRangeRef = PyRef; @@ -341,6 +337,7 @@ fn range_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(range.into_object()) } +#[pyclass] #[derive(Debug)] pub struct PyRangeIterator { position: Cell, @@ -353,10 +350,10 @@ impl PyValue for PyRangeIterator { } } -type PyRangeIteratorRef = PyRef; - -impl PyRangeIteratorRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +impl PyRangeIterator { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { let position = BigInt::from(self.position.get()); if let Some(int) = self.range.get(&position) { self.position.set(self.position.get() + 1); @@ -366,8 +363,9 @@ impl PyRangeIteratorRef { } } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 2169452845..42999189f1 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -3,7 +3,7 @@ use std::fmt; use std::hash::{Hash, Hasher}; use crate::function::OptionalArg; -use crate::pyobject::{IdProtocol, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{IdProtocol, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; @@ -224,6 +224,7 @@ fn tuple_new( PyTuple::from(elements).into_ref_with_type(vm, cls) } +#[pyclass] #[derive(Debug)] pub struct PyTupleIterator { position: Cell, @@ -236,10 +237,10 @@ impl PyValue for PyTupleIterator { } } -type PyTupleIteratorRef = PyRef; - -impl PyTupleIteratorRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +impl PyTupleIterator { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { if self.position.get() < self.tuple.elements.borrow().len() { let ret = self.tuple.elements.borrow()[self.position.get()].clone(); self.position.set(self.position.get() + 1); @@ -249,8 +250,9 @@ impl PyTupleIteratorRef { } } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } @@ -282,9 +284,5 @@ If the argument is a tuple, the return value is the same object."; "index" => context.new_rustfunc(PyTupleRef::index) }); - let tupleiterator_type = &context.tupleiterator_type; - extend_class!(context, tupleiterator_type, { - "__next__" => context.new_rustfunc(PyTupleIteratorRef::next), - "__iter__" => context.new_rustfunc(PyTupleIteratorRef::iter), - }); + PyTupleIterator::extend_class(context, &context.tupleiterator_type); } diff --git a/vm/src/obj/objzip.rs b/vm/src/obj/objzip.rs index 81640a724c..d9c577aa03 100644 --- a/vm/src/obj/objzip.rs +++ b/vm/src/obj/objzip.rs @@ -1,5 +1,5 @@ use crate::function::Args; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use super::objiter; @@ -7,6 +7,7 @@ use crate::obj::objtype::PyClassRef; pub type PyZipRef = PyRef; +#[pyclass] #[derive(Debug)] pub struct PyZip { iterators: Vec, @@ -26,8 +27,10 @@ fn zip_new(cls: PyClassRef, iterables: Args, vm: &VirtualMachine) -> PyResult PyResult { +#[pyimpl] +impl PyZip { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { if self.iterators.is_empty() { Err(objiter::new_stop_iteration(vm)) } else { @@ -41,16 +44,15 @@ impl PyZipRef { } } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } pub fn init(context: &PyContext) { - let zip_type = &context.zip_type; - extend_class!(context, zip_type, { + PyZip::extend_class(context, &context.zip_type); + extend_class!(context, &context.zip_type, { "__new__" => context.new_rustfunc(zip_new), - "__next__" => context.new_rustfunc(PyZipRef::next), - "__iter__" => context.new_rustfunc(PyZipRef::iter), }); } diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 6d81ca9c13..2b6c859eb4 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -15,7 +15,7 @@ use crate::obj::objiter; use crate::obj::objstr; use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{ItemProtocol, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{ItemProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; #[cfg(unix)] @@ -232,6 +232,7 @@ impl DirEntryRef { } } +#[pyclass] #[derive(Debug)] struct ScandirIterator { entries: RefCell, @@ -243,10 +244,10 @@ impl PyValue for ScandirIterator { } } -type ScandirIteratorRef = PyRef; - -impl ScandirIteratorRef { - fn next(self, vm: &VirtualMachine) -> PyResult { +#[pyimpl] +impl ScandirIterator { + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { match self.entries.borrow_mut().next() { Some(entry) => match entry { Ok(entry) => Ok(DirEntry { entry }.into_ref(vm).into_object()), @@ -256,8 +257,9 @@ impl ScandirIteratorRef { } } - fn iter(self, _vm: &VirtualMachine) -> Self { - self + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf } } @@ -418,10 +420,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let environ = _os_environ(vm); - let scandir_iter = py_class!(ctx, "ScandirIter", ctx.object(), { - "__iter__" => ctx.new_rustfunc(ScandirIteratorRef::iter), - "__next__" => ctx.new_rustfunc(ScandirIteratorRef::next), - }); + let scandir_iter = ctx.new_class("ScandirIter", ctx.object()); + ScandirIterator::extend_class(ctx, &scandir_iter); let dir_entry = py_class!(ctx, "DirEntry", ctx.object(), { "name" => ctx.new_property(DirEntryRef::name), From c138dea9926fe5fe4148e33485ffb14bb02c7e08 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 04:03:18 +0900 Subject: [PATCH 478/884] Remove int.__pow__ float handling --- vm/src/obj/objint.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 3687ed2b04..98fc39a1b0 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -13,7 +13,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objfloat::{self, PyFloat}; +use super::objfloat::PyFloat; use super::objstr::{PyString, PyStringRef}; use super::objtype; use crate::obj::objtype::PyClassRef; @@ -327,9 +327,6 @@ impl PyInt { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other).to_u32().unwrap(); vm.ctx.new_int(self.value.pow(v2)) - } else if objtype::isinstance(&other, &vm.ctx.float_type()) { - let v2 = objfloat::get_value(&other); - vm.ctx.new_float((self.value.to_f64().unwrap()).powf(v2)) } else { vm.ctx.not_implemented() } From 614378d7202821c070563ac5a773ecd39ab036d9 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 04:56:51 +0900 Subject: [PATCH 479/884] Add int.__pow__ negative exp impl --- tests/snippets/ints.py | 3 +++ vm/src/obj/objint.rs | 25 +++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index a072cc5427..3aed1144d3 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -37,6 +37,8 @@ assert (2).__rmul__(1) == 2 assert (2).__truediv__(1) == 2.0 assert (2).__rtruediv__(1) == 0.5 +assert (2).__pow__(3) == 8 +assert (10).__pow__(-1) == 0.1 # real/imag attributes assert (1).real == 1 @@ -58,6 +60,7 @@ assert (2).__rmul__(1.0) == NotImplemented assert (2).__truediv__(1.0) == NotImplemented assert (2).__rtruediv__(1.0) == NotImplemented +assert (2).__pow__(3.0) == NotImplemented assert int() == 0 diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 98fc39a1b0..12caa060ae 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -323,13 +323,21 @@ impl PyInt { } #[pymethod(name = "__pow__")] - fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - if objtype::isinstance(&other, &vm.ctx.int_type()) { - let v2 = get_value(&other).to_u32().unwrap(); - vm.ctx.new_int(self.value.pow(v2)) + fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Ok(if objtype::isinstance(&other, &vm.ctx.int_type()) { + let other = other.payload::().unwrap(); + if other.value.is_negative() { + let v1 = self.float(vm)?; + let v2 = other.float(vm)?; + vm.ctx.new_float(v1.pow(v2.to_f64().unwrap())) + } else if let Some(v2) = other.value.to_u32() { + vm.ctx.new_int(self.value.pow(v2)) + } else { + vm.ctx.not_implemented() + } } else { vm.ctx.not_implemented() - } + }) } #[pymethod(name = "__mod__")] @@ -400,10 +408,9 @@ impl PyInt { } #[pymethod(name = "__float__")] - fn float(&self, vm: &VirtualMachine) -> PyResult { + fn float(&self, vm: &VirtualMachine) -> PyResult { self.value .to_f64() - .map(PyFloat::from) .ok_or_else(|| vm.new_overflow_error("int too large to convert to float".to_string())) } @@ -541,9 +548,7 @@ pub fn get_value(obj: &PyObjectRef) -> &BigInt { } pub fn get_float_value(obj: &PyObjectRef, vm: &VirtualMachine) -> PyResult { - get_value(obj).to_f64().ok_or_else(|| { - vm.new_overflow_error("OverflowError: int too large to convert to float".to_string()) - }) + obj.payload::().unwrap().float(vm) } #[inline] From eb9ca06ee721eebd991cde1b7d42bdf49837819d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 05:09:11 +0900 Subject: [PATCH 480/884] Add int.__rpow__ --- tests/snippets/ints.py | 2 ++ vm/src/obj/objint.rs | 39 +++++++++++++++++++++++++++------------ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index 3aed1144d3..40b4c2c6d1 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -39,6 +39,7 @@ assert (2).__rtruediv__(1) == 0.5 assert (2).__pow__(3) == 8 assert (10).__pow__(-1) == 0.1 +assert (2).__rpow__(3) == 9 # real/imag attributes assert (1).real == 1 @@ -61,6 +62,7 @@ assert (2).__truediv__(1.0) == NotImplemented assert (2).__rtruediv__(1.0) == NotImplemented assert (2).__pow__(3.0) == NotImplemented +assert (2).__rpow__(3.0) == NotImplemented assert int() == 0 diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 12caa060ae..fe977ca0be 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -111,6 +111,19 @@ impl_try_from_object_int!( (u64, to_u64), ); +fn inner_pow(int1: &PyInt, int2: &PyInt, vm: &VirtualMachine) -> PyResult { + Ok(if int2.value.is_negative() { + let v1 = int1.float(vm)?; + let v2 = int2.float(vm)?; + vm.ctx.new_float(v1.pow(v2)) + } else if let Some(v2) = int2.value.to_u64() { + vm.ctx.new_int(int1.value.pow(v2)) + } else { + // missing feature: BigInt exp + vm.ctx.not_implemented() + }) +} + #[pyimpl] impl PyInt { #[pymethod(name = "__eq__")] @@ -324,20 +337,22 @@ impl PyInt { #[pymethod(name = "__pow__")] fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - Ok(if objtype::isinstance(&other, &vm.ctx.int_type()) { + if objtype::isinstance(&other, &vm.ctx.int_type()) { let other = other.payload::().unwrap(); - if other.value.is_negative() { - let v1 = self.float(vm)?; - let v2 = other.float(vm)?; - vm.ctx.new_float(v1.pow(v2.to_f64().unwrap())) - } else if let Some(v2) = other.value.to_u32() { - vm.ctx.new_int(self.value.pow(v2)) - } else { - vm.ctx.not_implemented() - } + inner_pow(self, &other, vm) } else { - vm.ctx.not_implemented() - }) + Ok(vm.ctx.not_implemented()) + } + } + + #[pymethod(name = "__rpow__")] + fn rpow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + let other = other.payload::().unwrap(); + inner_pow(&other, self, vm) + } else { + Ok(vm.ctx.not_implemented()) + } } #[pymethod(name = "__mod__")] From 8d4562b0bbb08179c5218f7bfcc48e8ec9a28e8c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 22:32:45 +0900 Subject: [PATCH 481/884] Fix __builtins__.pow to reflect __rpow__ --- tests/snippets/math_basics.py | 6 +++++- vm/src/builtins.rs | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index 51ea5c7262..28c145e3f5 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -14,7 +14,7 @@ assert a * 3 == 12 assert a / 2 == 2 assert 2 == a / 2 -# assert a % 3 == 1 +assert a % 3 == 1 assert a - 3 == 1 assert -a == -4 assert +a == 4 @@ -38,3 +38,7 @@ OverflowError, lambda: round(-float('inf')), 'OverflowError: cannot convert float NaN to integer') + +assert pow(2, 2) == 4 +assert pow(1, 2.0) == 1.0 +assert pow(2.0, 1) == 2.0 diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 300d11b315..27a53dc92d 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -529,11 +529,11 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(x, None), (y, None)], optional = [(mod_value, Some(vm.ctx.int_type()))] ); - let pow_method_name = "__pow__"; - let result = match vm.get_method(x.clone(), pow_method_name) { - Ok(attrib) => vm.invoke(attrib, vec![y.clone()]), - Err(..) => Err(vm.new_type_error("unsupported operand type(s) for pow".to_string())), - }; + + let result = vm.call_or_reflection(x.clone(), y.clone(), "__pow__", "__rpow__", |vm, x, y| { + Err(vm.new_unsupported_operand_error(x, y, "pow")) + }); + //Check if the 3rd argument is defined and perform modulus on the result //this should be optimized in the future to perform a "power-mod" algorithm in //order to improve performance From c18615b6e5d5011a2330790d169dcbffede06369 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 23:34:11 +0900 Subject: [PATCH 482/884] Fix __builtins__.pow to perform modpow for 3rd argument --- tests/snippets/math_basics.py | 22 +++++++++++++++++++ vm/src/builtins.rs | 41 +++++++++++++++++++++-------------- 2 files changed, 47 insertions(+), 16 deletions(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index 28c145e3f5..2cef18b745 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -42,3 +42,25 @@ assert pow(2, 2) == 4 assert pow(1, 2.0) == 1.0 assert pow(2.0, 1) == 2.0 + +assert pow(2, 4, 5) == 1 +assert_raises( + TypeError, + lambda: pow(2, 4, 5.0), + 'pow() 3rd argument not allowed unless all arguments are integers') +assert_raises( + TypeError, + lambda: pow(2, 4.0, 5), + 'pow() 3rd argument not allowed unless all arguments are integers') +assert_raises( + TypeError, + lambda: pow(2.0, 4, 5), + 'pow() 3rd argument not allowed unless all arguments are integers') +assert_raises( + ValueError, + lambda: pow(2, -1, 5), + 'pow() 2nd argument cannot be negative when 3rd argument specified') +assert_raises( + ValueError, + lambda: pow(2, 2, 0), + 'pow() 3rd argument cannot be 0') diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 27a53dc92d..79c2768881 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -6,7 +6,8 @@ use std::char; use std::io::{self, Write}; use std::path::PathBuf; -use num_traits::Signed; +use num_bigint::Sign; +use num_traits::{Signed, Zero}; use crate::compile; use crate::import::import_module; @@ -530,24 +531,32 @@ fn builtin_pow(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(mod_value, Some(vm.ctx.int_type()))] ); - let result = vm.call_or_reflection(x.clone(), y.clone(), "__pow__", "__rpow__", |vm, x, y| { - Err(vm.new_unsupported_operand_error(x, y, "pow")) - }); - - //Check if the 3rd argument is defined and perform modulus on the result - //this should be optimized in the future to perform a "power-mod" algorithm in - //order to improve performance match mod_value { - Some(mod_value) => { - let mod_method_name = "__mod__"; - match vm.get_method(result.expect("result not defined").clone(), mod_method_name) { - Ok(value) => vm.invoke(value, vec![mod_value.clone()]), - Err(..) => { - Err(vm.new_type_error("unsupported operand type(s) for mod".to_string())) - } + None => vm.call_or_reflection(x.clone(), y.clone(), "__pow__", "__rpow__", |vm, x, y| { + Err(vm.new_unsupported_operand_error(x, y, "pow")) + }), + Some(m) => { + // Check if the 3rd argument is defined and perform modulus on the result + if !(objtype::isinstance(x, &vm.ctx.int_type()) + && objtype::isinstance(y, &vm.ctx.int_type())) + { + return Err(vm.new_type_error( + "pow() 3rd argument not allowed unless all arguments are integers".to_string(), + )); + } + let y = objint::get_value(y); + if y.sign() == Sign::Minus { + return Err(vm.new_value_error( + "pow() 2nd argument cannot be negative when 3rd argument specified".to_string(), + )); + } + let m = objint::get_value(m); + if m.is_zero() { + return Err(vm.new_value_error("pow() 3rd argument cannot be 0".to_string())); } + let x = objint::get_value(x); + Ok(vm.new_int(x.modpow(&y, &m))) } - None => result, } } From 0bf7ff96b804eb454f6bab2800a2644c19a63124 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 2 May 2019 22:42:28 +0900 Subject: [PATCH 483/884] Special handling for base -1, 0, 1 for big exp --- tests/snippets/math_basics.py | 5 +++++ vm/src/obj/objint.rs | 22 ++++++++++++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index 2cef18b745..d950b13a93 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -39,9 +39,14 @@ lambda: round(-float('inf')), 'OverflowError: cannot convert float NaN to integer') +assert pow(0, 0) == 1 assert pow(2, 2) == 4 assert pow(1, 2.0) == 1.0 assert pow(2.0, 1) == 2.0 +assert pow(0, 10**1000) == 0 +assert pow(1, 10**1000) == 1 +assert pow(-1, 10**1000+1) == -1 +assert pow(-1, 10**1000) == 1 assert pow(2, 4, 5) == 1 assert_raises( diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index fe977ca0be..88c91f89f7 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -3,7 +3,7 @@ use std::hash::{Hash, Hasher}; use num_bigint::BigInt; use num_integer::Integer; -use num_traits::{Pow, Signed, ToPrimitive, Zero}; +use num_traits::{One, Pow, Signed, ToPrimitive, Zero}; use crate::format::FormatSpec; use crate::function::{OptionalArg, PyFuncArgs}; @@ -13,7 +13,6 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objfloat::PyFloat; use super::objstr::{PyString, PyStringRef}; use super::objtype; use crate::obj::objtype::PyClassRef; @@ -116,11 +115,22 @@ fn inner_pow(int1: &PyInt, int2: &PyInt, vm: &VirtualMachine) -> PyResult { let v1 = int1.float(vm)?; let v2 = int2.float(vm)?; vm.ctx.new_float(v1.pow(v2)) - } else if let Some(v2) = int2.value.to_u64() { - vm.ctx.new_int(int1.value.pow(v2)) } else { - // missing feature: BigInt exp - vm.ctx.not_implemented() + if let Some(v2) = int2.value.to_u64() { + vm.ctx.new_int(int1.value.pow(v2)) + } else if int1.value.is_one() || int1.value.is_zero() { + vm.ctx.new_int(int1.value.clone()) + } else if int1.value == BigInt::from(-1) { + if int2.value.is_odd() { + vm.ctx.new_int(-1) + } else { + vm.ctx.new_int(1) + } + } else { + // missing feature: BigInt exp + // practically, exp over u64 is not possible to calculate anyway + vm.ctx.not_implemented() + } }) } From 5bc1aa08dc0f676df1490860a7b352953085d9f5 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Thu, 2 May 2019 16:55:33 +0200 Subject: [PATCH 484/884] Add async def and async for parsing. --- parser/src/ast.rs | 13 ++++++++++ parser/src/lexer.rs | 2 ++ parser/src/python.lalrpop | 39 ++++++++++++++++++++---------- parser/src/token.rs | 2 ++ tests/snippets/test_async_stuff.py | 13 ++++++++++ vm/src/compile.rs | 6 +++++ vm/src/stdlib/ast.rs | 37 ++++++++++++++++++++++++++++ vm/src/symboltable.rs | 13 ++++++++++ 8 files changed, 112 insertions(+), 13 deletions(-) create mode 100644 tests/snippets/test_async_stuff.py diff --git a/parser/src/ast.rs b/parser/src/ast.rs index 9fbe8153e9..5d3fcb9213 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -98,6 +98,12 @@ pub enum Statement { body: Vec, orelse: Option>, }, + AsyncFor { + target: Expression, + iter: Expression, + body: Vec, + orelse: Option>, + }, Raise { exception: Option, cause: Option, @@ -122,6 +128,13 @@ pub enum Statement { decorator_list: Vec, returns: Option, }, + AsyncFunctionDef { + name: String, + args: Parameters, + body: Vec, + decorator_list: Vec, + returns: Option, + }, } #[derive(Debug, PartialEq)] diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 11d30cbee7..ac0fc89fcb 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -95,6 +95,8 @@ pub fn get_keywords() -> HashMap { keywords.insert(String::from("and"), Tok::And); keywords.insert(String::from("as"), Tok::As); keywords.insert(String::from("assert"), Tok::Assert); + keywords.insert(String::from("async"), Tok::Async); + keywords.insert(String::from("await"), Tok::Await); keywords.insert(String::from("break"), Tok::Break); keywords.insert(String::from("class"), Tok::Class); keywords.insert(String::from("continue"), Tok::Continue); diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index a9663943fb..89f3fd38a0 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -348,13 +348,14 @@ WhileStatement: ast::LocatedStatement = { }; ForStatement: ast::LocatedStatement = { - "for" "in" ":" => { - let or_else = s2.map(|s| s.2); + "for" "in" ":" => { + let orelse = s2.map(|s| s.2); ast::LocatedStatement { location: loc, - node: ast::Statement::For { - target: e, - iter: t, body: s, orelse: or_else + node: if is_async.is_some() { + ast::Statement::AsyncFor { target, iter, body, orelse } + } else { + ast::Statement::For { target, iter, body, orelse } }, } }, @@ -410,16 +411,26 @@ WithItem: ast::WithItem = { }; FuncDef: ast::LocatedStatement = { - "def" " Test)?> ":" => { + "def" " Test)?> ":" => { ast::LocatedStatement { location: loc, - node: ast::Statement::FunctionDef { - name: i, - args: a, - body: s, - decorator_list: d, - returns: r.map(|x| x.1), - } + node: if is_async.is_some() { + ast::Statement::AsyncFunctionDef { + name: i, + args: a, + body: s, + decorator_list: d, + returns: r.map(|x| x.1), + } + } else { + ast::Statement::FunctionDef { + name: i, + args: a, + body: s, + decorator_list: d, + returns: r.map(|x| x.1), + } + } } }, }; @@ -1045,6 +1056,8 @@ extern { "and" => lexer::Tok::And, "as" => lexer::Tok::As, "assert" => lexer::Tok::Assert, + "async" => lexer::Tok::Async, + "await" => lexer::Tok::Await, "break" => lexer::Tok::Break, "class" => lexer::Tok::Class, "continue" => lexer::Tok::Continue, diff --git a/parser/src/token.rs b/parser/src/token.rs index d2494a1d95..f9bd2a5684 100644 --- a/parser/src/token.rs +++ b/parser/src/token.rs @@ -72,6 +72,8 @@ pub enum Tok { And, As, Assert, + Async, + Await, Break, Class, Continue, diff --git a/tests/snippets/test_async_stuff.py b/tests/snippets/test_async_stuff.py new file mode 100644 index 0000000000..d19f5c382a --- /dev/null +++ b/tests/snippets/test_async_stuff.py @@ -0,0 +1,13 @@ + +import sys +import ast + +src = """ +async def x(): + async for x in [1,2,3]: + pass +""" + +mod = ast.parse(src) +# print(mod) +# print(ast.dump(mod)) diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 76744f112e..5a25be4797 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -322,6 +322,9 @@ impl Compiler { body, orelse, } => self.compile_for(target, iter, body, orelse)?, + ast::Statement::AsyncFor { .. } => { + unimplemented!("async for"); + } ast::Statement::Raise { exception, cause } => match exception { Some(value) => { self.compile_expression(value)?; @@ -352,6 +355,9 @@ impl Compiler { decorator_list, returns, } => self.compile_function_def(name, args, body, decorator_list, returns)?, + ast::Statement::AsyncFunctionDef { .. } => { + unimplemented!("async def"); + } ast::Statement::ClassDef { name, body, diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 816882f31e..16485e85d3 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -96,6 +96,26 @@ fn statement_to_ast( returns => py_returns }) } + ast::Statement::AsyncFunctionDef { + name, + args, + body, + decorator_list, + returns, + } => { + let py_returns = if let Some(hint) = returns { + expression_to_ast(vm, hint)?.into_object() + } else { + vm.ctx.none() + }; + node!(vm, AsyncFunctionDef, { + name => vm.ctx.new_str(name.to_string()), + args => parameters_to_ast(vm, args)?, + body => statements_to_ast(vm, body)?, + decorator_list => expressions_to_ast(vm, decorator_list)?, + returns => py_returns + }) + } ast::Statement::Continue => node!(vm, Continue), ast::Statement::Break => node!(vm, Break), ast::Statement::Pass => node!(vm, Pass), @@ -152,6 +172,21 @@ fn statement_to_ast( vm.ctx.none() } }), + ast::Statement::AsyncFor { + target, + iter, + body, + orelse, + } => node!(vm, AsyncFor, { + target => expression_to_ast(vm, target)?, + iter => expression_to_ast(vm, iter)?, + body => statements_to_ast(vm, body)?, + or_else => if let Some(orelse) = orelse { + statements_to_ast(vm, orelse)?.into_object() + } else { + vm.ctx.none() + } + }), ast::Statement::While { test, body, orelse } => node!(vm, While, { test => expression_to_ast(vm, test)?, body => statements_to_ast(vm, body)?, @@ -462,6 +497,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { // TODO: There's got to be a better way! "arg" => py_class!(ctx, "_ast.arg", ast_base.clone(), {}), "arguments" => py_class!(ctx, "_ast.arguments", ast_base.clone(), {}), + "AsyncFor" => py_class!(ctx, "_ast.AsyncFor", ast_base.clone(), {}), + "AsyncFunctionDef" => py_class!(ctx, "_ast.AsyncFunctionDef", ast_base.clone(), {}), "Assert" => py_class!(ctx, "_ast.Assert", ast_base.clone(), {}), "Attribute" => py_class!(ctx, "_ast.Attribute", ast_base.clone(), {}), "BinOp" => py_class!(ctx, "_ast.BinOp", ast_base.clone(), {}), diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs index 6bdec1e96c..2b975cdf1c 100644 --- a/vm/src/symboltable.rs +++ b/vm/src/symboltable.rs @@ -223,6 +223,13 @@ impl SymbolTableBuilder { args, decorator_list, returns, + } + | ast::Statement::AsyncFunctionDef { + name, + body, + args, + decorator_list, + returns, } => { self.scan_expressions(decorator_list)?; self.register_name(name, SymbolRole::Assigned)?; @@ -265,6 +272,12 @@ impl SymbolTableBuilder { iter, body, orelse, + } + | ast::Statement::AsyncFor { + target, + iter, + body, + orelse, } => { self.scan_expression(target)?; self.scan_expression(iter)?; From b3b523ffa9b58ea82943cb3afc0008e161c570a8 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Thu, 2 May 2019 17:11:51 +0200 Subject: [PATCH 485/884] Add await syntax. --- parser/src/ast.rs | 4 ++++ parser/src/python.lalrpop | 16 +++++++++++++--- tests/snippets/test_async_stuff.py | 2 +- vm/src/compile.rs | 3 +++ vm/src/stdlib/ast.rs | 7 +++++++ vm/src/symboltable.rs | 3 +++ 6 files changed, 31 insertions(+), 4 deletions(-) diff --git a/parser/src/ast.rs b/parser/src/ast.rs index 5d3fcb9213..e0a0501383 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -163,6 +163,9 @@ pub enum Expression { op: UnaryOperator, a: Box, }, + Await { + value: Box, + }, Yield { value: Option>, }, @@ -240,6 +243,7 @@ impl Expression { match self { BoolOp { .. } | Binop { .. } | Unop { .. } => "operator", Subscript { .. } => "subscript", + Await { .. } => "await expression", Yield { .. } | YieldFrom { .. } => "yield expression", Compare { .. } => "comparison", Attribute { .. } => "attribute", diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 89f3fd38a0..02fd9e0b92 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -747,10 +747,20 @@ Power: ast::Expression = { }; AtomExpr: ast::Expression = { + => { + if is_await.is_some() { + ast::Expression::Await { value: Box::new(atom) } + } else { + atom + } + } +} + +AtomExpr2: ast::Expression = { Atom, - "(" ")" => ast::Expression::Call { function: Box::new(f), args: a.0, keywords: a.1 }, - "[" "]" => ast::Expression::Subscript { a: Box::new(e), b: Box::new(s) }, - "." => ast::Expression::Attribute { value: Box::new(e), name: n }, + "(" ")" => ast::Expression::Call { function: Box::new(f), args: a.0, keywords: a.1 }, + "[" "]" => ast::Expression::Subscript { a: Box::new(e), b: Box::new(s) }, + "." => ast::Expression::Attribute { value: Box::new(e), name: n }, }; SubscriptList: ast::Expression = { diff --git a/tests/snippets/test_async_stuff.py b/tests/snippets/test_async_stuff.py index d19f5c382a..a585692962 100644 --- a/tests/snippets/test_async_stuff.py +++ b/tests/snippets/test_async_stuff.py @@ -5,7 +5,7 @@ src = """ async def x(): async for x in [1,2,3]: - pass + await y() """ mod = ast.parse(src) diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 5a25be4797..ce8f66996e 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -1271,6 +1271,9 @@ impl Compiler { }; self.emit(Instruction::YieldValue); } + ast::Expression::Await { .. } => { + unimplemented!("await"); + } ast::Expression::YieldFrom { value } => { self.mark_generator(); self.compile_expression(value)?; diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 16485e85d3..f27b5eb408 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -385,6 +385,12 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyRes } } } + ast::Expression::Await { value } => { + let py_value = expression_to_ast(vm, value)?; + node!(vm, Await, { + value => py_value + }) + } ast::Expression::Yield { value } => { let py_value = match value { Some(value) => expression_to_ast(vm, value)?.into_object(), @@ -501,6 +507,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "AsyncFunctionDef" => py_class!(ctx, "_ast.AsyncFunctionDef", ast_base.clone(), {}), "Assert" => py_class!(ctx, "_ast.Assert", ast_base.clone(), {}), "Attribute" => py_class!(ctx, "_ast.Attribute", ast_base.clone(), {}), + "Await" => py_class!(ctx, "_ast.Await", ast_base.clone(), {}), "BinOp" => py_class!(ctx, "_ast.BinOp", ast_base.clone(), {}), "BoolOp" => py_class!(ctx, "_ast.BoolOp", ast_base.clone(), {}), "Break" => py_class!(ctx, "_ast.Break", ast_base.clone(), {}), diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs index 2b975cdf1c..1d4a72ed90 100644 --- a/vm/src/symboltable.rs +++ b/vm/src/symboltable.rs @@ -408,6 +408,9 @@ impl SymbolTableBuilder { self.scan_expression(value)?; } } + ast::Expression::Await { value } => { + self.scan_expression(value)?; + } ast::Expression::Yield { value } => { if let Some(expression) = value { self.scan_expression(expression)?; From dd0c70f289cb338d107dd39c68296790ee2d8742 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 2 May 2019 16:36:35 +0100 Subject: [PATCH 486/884] Fix typo in objobject. --- vm/src/obj/objobject.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 47cfa008dd..31e52fc990 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -213,7 +213,7 @@ fn object_dict_setter( vm: &VirtualMachine, ) -> PyResult { Err(vm.new_not_implemented_error( - "Setting __dict__ attribute on am object isn't yet implemented".to_string(), + "Setting __dict__ attribute on an object isn't yet implemented".to_string(), )) } From 65d602571d50f692162520985163188bf14b7e06 Mon Sep 17 00:00:00 2001 From: Adam Kelly Date: Thu, 2 May 2019 16:42:31 +0100 Subject: [PATCH 487/884] Create mappingproxy type with __getitem__ and __contains__. --- tests/snippets/mappingproxy.py | 18 +++++++++++++++ vm/src/obj/mod.rs | 1 + vm/src/obj/objmappingproxy.rs | 42 ++++++++++++++++++++++++++++++++++ vm/src/obj/objtype.rs | 16 +++++++++++++ vm/src/pyobject.rs | 5 ++++ 5 files changed, 82 insertions(+) create mode 100644 tests/snippets/mappingproxy.py create mode 100644 vm/src/obj/objmappingproxy.rs diff --git a/tests/snippets/mappingproxy.py b/tests/snippets/mappingproxy.py new file mode 100644 index 0000000000..624bf6d5f2 --- /dev/null +++ b/tests/snippets/mappingproxy.py @@ -0,0 +1,18 @@ +from testutils import assertRaises + +class A(dict): + def a(): + pass + + def b(): + pass + + +assert A.__dict__['a'] == A.a +with assertRaises(KeyError): + A.__dict__['not_here'] + +assert 'b' in A.__dict__ +assert 'c' not in A.__dict__ + +assert '__dict__' in A.__dict__ diff --git a/vm/src/obj/mod.rs b/vm/src/obj/mod.rs index bfe89fd15a..c9401d8c44 100644 --- a/vm/src/obj/mod.rs +++ b/vm/src/obj/mod.rs @@ -20,6 +20,7 @@ pub mod objint; pub mod objiter; pub mod objlist; pub mod objmap; +pub mod objmappingproxy; pub mod objmemory; pub mod objmodule; pub mod objnone; diff --git a/vm/src/obj/objmappingproxy.rs b/vm/src/obj/objmappingproxy.rs new file mode 100644 index 0000000000..c46591105b --- /dev/null +++ b/vm/src/obj/objmappingproxy.rs @@ -0,0 +1,42 @@ +use super::objstr::PyStringRef; +use super::objtype::{self, PyClassRef}; +use crate::pyobject::{PyClassImpl, PyContext, PyRef, PyResult, PyValue}; +use crate::vm::VirtualMachine; + +#[pyclass] +#[derive(Debug)] +pub struct PyMappingProxy { + class: PyClassRef, +} + +pub type PyMappingProxyRef = PyRef; + +impl PyValue for PyMappingProxy { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.mappingproxy_type.clone() + } +} + +#[pyimpl] +impl PyMappingProxy { + pub fn new(class: PyClassRef) -> PyMappingProxy { + PyMappingProxy { class } + } + + #[pymethod(name = "__getitem__")] + pub fn getitem(&self, key: PyStringRef, vm: &VirtualMachine) -> PyResult { + if let Some(value) = objtype::class_get_attr(&self.class, key.as_str()) { + return Ok(value); + } + Err(vm.new_key_error(format!("Key not found: {}", key))) + } + + #[pymethod(name = "__contains__")] + pub fn contains(&self, attr: PyStringRef, _vm: &VirtualMachine) -> bool { + objtype::class_has_attr(&self.class, attr.as_str()) + } +} + +pub fn init(context: &PyContext) { + PyMappingProxy::extend_class(context, &context.mappingproxy_type) +} diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index c14e512301..c66e57354b 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -11,6 +11,7 @@ use crate::vm::VirtualMachine; use super::objdict::PyDictRef; use super::objlist::PyList; +use super::objmappingproxy::PyMappingProxy; use super::objproperty::PropertyBuilder; use super::objstr::PyStringRef; use super::objtuple::PyTuple; @@ -196,6 +197,11 @@ pub fn init(ctx: &PyContext) { extend_class!(&ctx, &ctx.type_type, { "__call__" => ctx.new_rustfunc(type_call), + "__dict__" => + PropertyBuilder::new(ctx) + .add_getter(type_dict) + .add_setter(type_dict_setter) + .create(), "__new__" => ctx.new_rustfunc(type_new), "__mro__" => PropertyBuilder::new(ctx) @@ -273,6 +279,16 @@ pub fn type_call(class: PyClassRef, args: Args, kwargs: KwArgs, vm: &VirtualMach Ok(obj) } +fn type_dict(class: PyClassRef, _vm: &VirtualMachine) -> PyMappingProxy { + PyMappingProxy::new(class) +} + +fn type_dict_setter(_instance: PyClassRef, _value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_not_implemented_error( + "Setting __dict__ attribute on a type isn't yet implemented".to_string(), + )) +} + // This is the internal get_attr implementation for fast lookup on a class. pub fn class_get_attr(class: &PyClassRef, attr_name: &str) -> Option { if let Some(item) = class.attributes.borrow().get(attr_name).cloned() { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 89e5c8c394..454a1605fa 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -37,6 +37,7 @@ use crate::obj::objint::{self, PyInt, PyIntRef}; use crate::obj::objiter; use crate::obj::objlist::{self, PyList}; use crate::obj::objmap; +use crate::obj::objmappingproxy; use crate::obj::objmemory; use crate::obj::objmodule::{self, PyModule}; use crate::obj::objnone::{self, PyNone, PyNoneRef}; @@ -160,6 +161,7 @@ pub struct PyContext { pub bound_method_type: PyClassRef, pub weakref_type: PyClassRef, pub weakproxy_type: PyClassRef, + pub mappingproxy_type: PyClassRef, pub object: PyClassRef, pub exceptions: exceptions::ExceptionZoo, } @@ -292,6 +294,7 @@ impl PyContext { let range_type = create_type("range", &type_type, &object_type); let rangeiterator_type = create_type("range_iterator", &type_type, &object_type); let slice_type = create_type("slice", &type_type, &object_type); + let mappingproxy_type = create_type("mappingproxy", &type_type, &object_type); let exceptions = exceptions::ExceptionZoo::new(&type_type, &object_type); fn create_object(payload: T, cls: &PyClassRef) -> PyRef { @@ -358,6 +361,7 @@ impl PyContext { function_type, builtin_function_or_method_type, super_type, + mappingproxy_type, property_type, readonly_property_type, generator_type, @@ -403,6 +407,7 @@ impl PyContext { objweakproxy::init(&context); objnone::init(&context); objmodule::init(&context); + objmappingproxy::init(&context); exceptions::init(&context); context } From 620bea7830dc18fffc7d7da57e94eb3dec2f276d Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Fri, 3 May 2019 01:00:49 +0300 Subject: [PATCH 488/884] Preallocate output string of str.title() with required size --- vm/src/obj/objstr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 8ea438e35d..22eda1f34b 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -425,7 +425,7 @@ impl PyString { /// uppercase character and the remaining characters are lowercase. #[pymethod] fn title(&self, _vm: &VirtualMachine) -> String { - let mut title = String::new(); + let mut title = String::with_capacity(self.value.len()); let mut previous_is_cased = false; for c in self.value.chars() { if c.is_lowercase() { From 26306b528f513efc849c2f031c90f7efd99e8cda Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 3 May 2019 01:38:32 +0300 Subject: [PATCH 489/884] Simplify PyInt.rxor to reuse PyInt.xor --- vm/src/obj/objint.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 3687ed2b04..fdb0459afe 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -296,11 +296,7 @@ impl PyInt { #[pymethod(name = "__rxor__")] fn rxor(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - if objtype::isinstance(&other, &vm.ctx.int_type()) { - vm.ctx.new_int(get_value(&other) ^ (&self.value)) - } else { - vm.ctx.not_implemented() - } + self.xor(other, vm) } #[pymethod(name = "__or__")] From ea347dfdeca1c7548513ad8deb89c6b66810cfe1 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 3 May 2019 01:04:52 +0300 Subject: [PATCH 490/884] Implement bool.__or__, __and__ and __xor__ --- tests/snippets/bools.py | 12 ++++++++++++ vm/src/obj/objbool.rs | 39 +++++++++++++++++++++++++++++++++++++++ vm/src/obj/objint.rs | 6 +++--- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/tests/snippets/bools.py b/tests/snippets/bools.py index 0c277143bc..b781668b63 100644 --- a/tests/snippets/bools.py +++ b/tests/snippets/bools.py @@ -51,3 +51,15 @@ def __bool__(self): assert int(True) == 1 assert True.conjugate() == 1 assert isinstance(True.conjugate(), int) + +# Boolean operations on pairs of Bools should return Bools, not ints +assert (False | True) is True +assert (False & True) is False +assert (False ^ True) is True +# But only if both are Bools +assert (False | 1) is not True +assert (0 | True) is not True +assert (False & 1) is not False +assert (0 & True) is not False +assert (False ^ 1) is not True +assert (0 ^ True) is not True diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index 724c8bc5ae..e96324f671 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -42,6 +42,9 @@ The class bool is a subclass of the class int, and cannot be subclassed."; extend_class!(context, bool_type, { "__new__" => context.new_rustfunc(bool_new), "__repr__" => context.new_rustfunc(bool_repr), + "__or__" => context.new_rustfunc(bool_or), + "__and__" => context.new_rustfunc(bool_and), + "__xor__" => context.new_rustfunc(bool_xor), "__doc__" => context.new_str(bool_doc.to_string()) }); } @@ -71,6 +74,42 @@ fn bool_repr(vm: &VirtualMachine, args: PyFuncArgs) -> Result Result { + arg_check!(vm, args, required = [(lhs, None), (rhs, None)]); + + if objtype::isinstance(lhs, &vm.ctx.bool_type()) && objtype::isinstance(rhs, &vm.ctx.bool_type()) { + let lhs = get_value(lhs); + let rhs = get_value(rhs); + (lhs || rhs).into_pyobject(vm) + } else { + Ok(lhs.payload::().unwrap().or(rhs.clone(), vm)) + } +} + +fn bool_and(vm: &VirtualMachine, args: PyFuncArgs) -> Result { + arg_check!(vm, args, required = [(lhs, None), (rhs, None)]); + + if objtype::isinstance(lhs, &vm.ctx.bool_type()) && objtype::isinstance(rhs, &vm.ctx.bool_type()) { + let lhs = get_value(lhs); + let rhs = get_value(rhs); + (lhs && rhs).into_pyobject(vm) + } else { + Ok(lhs.payload::().unwrap().and(rhs.clone(), vm)) + } +} + +fn bool_xor(vm: &VirtualMachine, args: PyFuncArgs) -> Result { + arg_check!(vm, args, required = [(lhs, None), (rhs, None)]); + + if objtype::isinstance(lhs, &vm.ctx.bool_type()) && objtype::isinstance(rhs, &vm.ctx.bool_type()) { + let lhs = get_value(lhs); + let rhs = get_value(rhs); + (lhs ^ rhs).into_pyobject(vm) + } else { + Ok(lhs.payload::().unwrap().xor(rhs.clone(), vm)) + } +} + fn bool_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index fdb0459afe..1b728a53fb 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -286,7 +286,7 @@ impl PyInt { } #[pymethod(name = "__xor__")] - fn xor(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + pub fn xor(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) ^ get_value(&other)) } else { @@ -300,7 +300,7 @@ impl PyInt { } #[pymethod(name = "__or__")] - fn or(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + pub fn or(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { vm.ctx.new_int((&self.value) | get_value(&other)) } else { @@ -309,7 +309,7 @@ impl PyInt { } #[pymethod(name = "__and__")] - fn and(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + pub fn and(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other); vm.ctx.new_int((&self.value) & v2) From 6f66e8440dca8f96c1fb45723e8c4342b209276a Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 3 May 2019 01:41:52 +0300 Subject: [PATCH 491/884] Add tests for return type of bool.__or__ etc. --- tests/snippets/bools.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/snippets/bools.py b/tests/snippets/bools.py index b781668b63..23f22dce79 100644 --- a/tests/snippets/bools.py +++ b/tests/snippets/bools.py @@ -63,3 +63,17 @@ def __bool__(self): assert (0 & True) is not False assert (False ^ 1) is not True assert (0 ^ True) is not True + +# Check that the same works with __XXX__ methods +assert False.__or__(0) is not False +assert False.__or__(False) is False +assert False.__ror__(0) is not False +assert False.__ror__(False) is False +assert False.__and__(0) is not False +assert False.__and__(False) is False +assert False.__rand__(0) is not False +assert False.__rand__(False) is False +assert False.__xor__(0) is not False +assert False.__xor__(False) is False +assert False.__rxor__(0) is not False +assert False.__rxor__(False) is False From a118497a4bede58ab01ced7b3428d9b007ede6f1 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Fri, 3 May 2019 02:55:51 +0300 Subject: [PATCH 492/884] Preallocate cloned strings with the minimum expected length Similar to 620bea78. --- vm/src/obj/objstr.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 22eda1f34b..f1449f6ddf 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -183,7 +183,12 @@ impl PyString { if objtype::isinstance(&val, &vm.ctx.int_type()) { let value = &self.value; let multiplier = objint::get_value(&val).to_i32().unwrap(); - let mut result = String::new(); + let capacity = if multiplier > 0 { + multiplier.to_usize().unwrap() * value.len() + } else { + 0 + }; + let mut result = String::with_capacity(capacity); for _x in 0..multiplier { result.push_str(value.as_str()); } @@ -206,7 +211,7 @@ impl PyString { } else { '\'' }; - let mut formatted = String::new(); + let mut formatted = String::with_capacity(value.len()); formatted.push(quote_char); for c in value.chars() { if c == quote_char || c == '\\' { @@ -799,7 +804,7 @@ impl PyString { #[pymethod] fn expandtabs(&self, tab_stop: OptionalArg, _vm: &VirtualMachine) -> String { let tab_stop = tab_stop.into_option().unwrap_or(8 as usize); - let mut expanded_str = String::new(); + let mut expanded_str = String::with_capacity(self.value.len()); let mut tab_size = tab_stop; let mut col_count = 0 as usize; for ch in self.value.chars() { From f12268f79a7c94b2ee5864c43a07ce723187d690 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Fri, 3 May 2019 02:59:39 +0300 Subject: [PATCH 493/884] Implement str.__rmul__ --- vm/src/obj/objstr.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index f1449f6ddf..e5e2afe370 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -198,6 +198,11 @@ impl PyString { } } + #[pymethod(name = "__rmul__")] + fn rmul(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.mul(val, vm) + } + #[pymethod(name = "__str__")] fn str(zelf: PyRef, _vm: &VirtualMachine) -> PyStringRef { zelf From d1730a53adb4ad44eb507944fe02bea3cda7ef23 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Fri, 3 May 2019 03:04:19 +0300 Subject: [PATCH 494/884] Add basic string multiplication tests --- tests/snippets/strings.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 0baa35d23c..7cb1e8f30d 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -29,6 +29,14 @@ assert str(["a", "b", "can't"]) == "['a', 'b', \"can't\"]" +assert "xy" * 3 == "xyxyxy" +assert "x" * 0 == "" +assert "x" * -1 == "" + +assert 3 * "xy" == "xyxyxy" +assert 0 * "x" == "" +assert -1 * "x" == "" + a = 'Hallo' assert a.lower() == 'hallo' assert a.upper() == 'HALLO' From 29ec26917fb1078eb7314b41295be31cae963e35 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 3 May 2019 03:08:42 +0300 Subject: [PATCH 495/884] Return last expression in compile Mode::Single --- vm/src/compile.rs | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 76744f112e..36b4c3ce73 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -130,18 +130,35 @@ impl Compiler { symbol_scope: SymbolScope, ) -> Result<(), CompileError> { self.scope_stack.push(symbol_scope); - for statement in &program.statements { + + let mut emitted_return = false; + + for (i, statement) in program.statements.iter().enumerate() { + let is_last = i == program.statements.len() - 1; + if let ast::Statement::Expression { ref expression } = statement.node { self.compile_expression(expression)?; - self.emit(Instruction::PrintExpr); + + if is_last { + self.emit(Instruction::Duplicate); + self.emit(Instruction::PrintExpr); + self.emit(Instruction::ReturnValue); + emitted_return = true; + } else { + self.emit(Instruction::PrintExpr); + } } else { self.compile_statement(&statement)?; } } - self.emit(Instruction::LoadConst { - value: bytecode::Constant::None, - }); - self.emit(Instruction::ReturnValue); + + if !emitted_return { + self.emit(Instruction::LoadConst { + value: bytecode::Constant::None, + }); + self.emit(Instruction::ReturnValue); + } + Ok(()) } From 821959b889d81956e4a6f5ad15bef15727a41431 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 3 May 2019 03:09:29 +0300 Subject: [PATCH 496/884] Save REPL return value as _ --- src/main.rs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5620d851e5..3e77ddbc21 100644 --- a/src/main.rs +++ b/src/main.rs @@ -147,9 +147,23 @@ fn test_run_script() { fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), CompileError> { match compile::compile(vm, source, &compile::Mode::Single, "".to_string()) { Ok(code) => { - if let Err(err) = vm.run_code_obj(code, scope) { - print_exception(vm, &err); + match vm.run_code_obj(code, scope.clone()) { + Ok(value) => { + // Save non-None values as "_" + + use rustpython_vm::pyobject::{IdProtocol, IntoPyObject, ItemProtocol}; + + if !value.is(&vm.get_none()) { + let key = objstr::PyString::from("_").into_pyobject(vm); + scope.globals.set_item(key, value, vm).unwrap(); + } + } + + Err(err) => { + print_exception(vm, &err); + } } + Ok(()) } // Don't inject syntax errors for line continuation From 857a7014c2fc341f91b6f86dabecc76056722c25 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Fri, 3 May 2019 03:05:27 +0300 Subject: [PATCH 497/884] Enable and extend strings/membership tests --- tests/snippets/membership.py | 15 ++++++++------- tests/snippets/strings.py | 1 + 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/snippets/membership.py b/tests/snippets/membership.py index a944c45398..2987c3c0fe 100644 --- a/tests/snippets/membership.py +++ b/tests/snippets/membership.py @@ -12,9 +12,8 @@ assert "whatever" not in "foobar" # test bytes -# TODO: uncomment this when bytes are implemented -# assert b"foo" in b"foobar" -# assert b"whatever" not in b"foobar" +assert b"foo" in b"foobar" +assert b"whatever" not in b"foobar" assert b"1" < b"2" assert b"1" <= b"2" assert b"5" <= b"5" @@ -32,18 +31,20 @@ assert 3 not in set([1, 2]) # test dicts -# TODO: test dicts when keys other than strings will be allowed assert "a" in {"a": 0, "b": 0} assert "c" not in {"a": 0, "b": 0} +assert 1 in {1: 5, 7: 12} +assert 5 not in {9: 10, 50: 100} +assert True in {True: 5} +assert False not in {True: 5} # test iter assert 3 in iter([1, 2, 3]) assert 3 not in iter([1, 2]) # test sequence -# TODO: uncomment this when ranges are usable -# assert 1 in range(0, 2) -# assert 3 not in range(0, 2) +assert 1 in range(0, 2) +assert 3 not in range(0, 2) # test __contains__ in user objects class MyNotContainingClass(): diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 7cb1e8f30d..28df3f47f4 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -102,6 +102,7 @@ ] +# requires CPython 3.7, and the CI currently runs with 3.6 # assert c.isascii() assert c.index('a') == 1 assert c.rindex('l') == 3 From d121b422b6c80fbb8f9cf50da4bbe71edf98f64a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 3 May 2019 12:14:47 +0300 Subject: [PATCH 498/884] Add browser.load_module --- vm/src/import.rs | 38 ++++++++++++++++---------- wasm/lib/src/browser_module.rs | 49 ++++++++++++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 16 deletions(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index 45c7cd86a2..f4c52a11e9 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -31,27 +31,37 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s .map_err(|e| vm.new_exception(notfound_error.clone(), e))?; let source = util::read_file(file_path.as_path()) .map_err(|e| vm.new_exception(import_error.clone(), e.to_string()))?; - let code_obj = compile::compile( + + import_file( vm, - &source, - &compile::Mode::Exec, + module_name, file_path.to_str().unwrap().to_string(), + source, ) - .map_err(|err| vm.new_syntax_error(&err))?; - // trace!("Code object: {:?}", code_obj); + } +} - let attrs = vm.ctx.new_dict(); - attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; - let module = vm.ctx.new_module(module_name, attrs.clone()); +pub fn import_file( + vm: &VirtualMachine, + module_name: &str, + file_path: String, + content: String, +) -> PyResult { + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); + let code_obj = compile::compile(vm, &content, &compile::Mode::Exec, file_path) + .map_err(|err| vm.new_syntax_error(&err))?; + // trace!("Code object: {:?}", code_obj); - // Store module in cache to prevent infinite loop with mutual importing libs: - sys_modules.set_item(module_name, module.clone(), vm)?; + let attrs = vm.ctx.new_dict(); + attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; + let module = vm.ctx.new_module(module_name, attrs.clone()); - // Execute main code in module: - vm.run_code_obj(code_obj, Scope::new(None, attrs))?; + // Store module in cache to prevent infinite loop with mutual importing libs: + sys_modules.set_item(module_name, module.clone(), vm)?; - Ok(module) - } + // Execute main code in module: + vm.run_code_obj(code_obj, Scope::new(None, attrs))?; + Ok(module) } fn find_source(vm: &VirtualMachine, current_path: PathBuf, name: &str) -> Result { diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 6fb7c6baa5..b4c3fd193c 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -6,6 +6,7 @@ use wasm_bindgen::JsCast; use wasm_bindgen_futures::{future_to_promise, JsFuture}; use rustpython_vm::function::{OptionalArg, PyFuncArgs}; +use rustpython_vm::import::import_file; use rustpython_vm::obj::{ objdict::PyDictRef, objint::PyIntRef, objstr::PyStringRef, objtype::PyClassRef, }; @@ -206,8 +207,12 @@ impl PyPromise { let vm = &stored_vm.vm; let ret = match res { Ok(val) => { - let val = convert::js_to_py(vm, val); - vm.invoke(on_fulfill.into_object(), PyFuncArgs::new(vec![val], vec![])) + let args = if val.is_null() { + vec![] + } else { + vec![convert::js_to_py(vm, val)] + }; + vm.invoke(on_fulfill.into_object(), PyFuncArgs::new(args, vec![])) } Err(err) => { if let OptionalArg::Present(on_reject) = on_reject { @@ -342,6 +347,45 @@ fn browser_prompt( Ok(result) } +fn browser_load_module(module: PyStringRef, path: PyStringRef, vm: &VirtualMachine) -> PyResult { + let weak_vm = weak_vm(vm); + + let mut opts = web_sys::RequestInit::new(); + opts.method("GET"); + + let request = web_sys::Request::new_with_str_and_init(path.as_str(), &opts) + .map_err(|err| convert::js_py_typeerror(vm, err))?; + + let window = window(); + let request_prom = window.fetch_with_request(&request); + + let future = JsFuture::from(request_prom) + .and_then(move |val| { + let response = val + .dyn_into::() + .expect("val to be of type Response"); + response.text() + }) + .and_then(move |text_value: Promise| { + // Convert this other `Promise` into a rust `Future`. + JsFuture::from(text_value) + }) + .and_then(move |text| { + let stored_vm = &weak_vm + .upgrade() + .expect("that the vm is valid when the promise resolves"); + let vm = &stored_vm.vm; + let resp_text = text.as_string().unwrap(); + let res = import_file(vm, module.as_str(), "WEB".to_string(), resp_text); + match res { + Ok(_) => Ok(JsValue::null()), + Err(err) => Err(convert::py_err_to_js_err(vm, &err)), + } + }); + + Ok(PyPromise::from_future(future).into_ref(vm).into_object()) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -370,6 +414,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "alert" => ctx.new_rustfunc(browser_alert), "confirm" => ctx.new_rustfunc(browser_confirm), "prompt" => ctx.new_rustfunc(browser_prompt), + "load_module" => ctx.new_rustfunc(browser_load_module), }) } From 9448254914d7202c846ac6fa00a300fafdc5aea0 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 01:11:14 +0900 Subject: [PATCH 499/884] PyComplex uses extend_class for __new__ and __doc__ --- vm/src/obj/objcomplex.rs | 54 ++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index f9ff9d9b02..ec7afd1102 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -9,6 +9,9 @@ use super::objfloat::{self, PyFloat}; use super::objint; use super::objtype::{self, PyClassRef}; +/// Create a complex number from a real part and an optional imaginary part. +/// +/// This is equivalent to (real + imag*1j) where imag defaults to 0. #[pyclass(name = "complex")] #[derive(Debug, Copy, Clone, PartialEq)] pub struct PyComplex { @@ -30,42 +33,12 @@ impl From for PyComplex { pub fn init(context: &PyContext) { PyComplex::extend_class(context, &context.complex_type); - let complex_doc = - "Create a complex number from a real part and an optional imaginary part.\n\n\ - This is equivalent to (real + imag*1j) where imag defaults to 0."; - - extend_class!(context, &context.complex_type, { - "__doc__" => context.new_str(complex_doc.to_string()), - "__new__" => context.new_rustfunc(PyComplexRef::new), - }); } pub fn get_value(obj: &PyObjectRef) -> Complex64 { obj.payload::().unwrap().value } -impl PyComplexRef { - fn new( - cls: PyClassRef, - real: OptionalArg, - imag: OptionalArg, - vm: &VirtualMachine, - ) -> PyResult { - let real = match real { - OptionalArg::Missing => 0.0, - OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?, - }; - - let imag = match imag { - OptionalArg::Missing => 0.0, - OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?, - }; - - let value = Complex64::new(real, imag); - PyComplex { value }.into_ref_with_type(vm, cls) - } -} - fn to_complex(value: PyObjectRef, vm: &VirtualMachine) -> PyResult> { if objtype::isinstance(&value, &vm.ctx.complex_type()) { Ok(Some(get_value(&value))) @@ -204,4 +177,25 @@ impl PyComplex { fn bool(&self, _vm: &VirtualMachine) -> bool { self.value != Complex64::zero() } + + #[pymethod(name = "__new__")] + fn complex_new( + cls: PyClassRef, + real: OptionalArg, + imag: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let real = match real { + OptionalArg::Missing => 0.0, + OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?, + }; + + let imag = match imag { + OptionalArg::Missing => 0.0, + OptionalArg::Present(ref value) => objfloat::make_float(vm, value)?, + }; + + let value = Complex64::new(real, imag); + PyComplex { value }.into_ref_with_type(vm, cls) + } } From 7b438d9be8e5b4a821e80e0d1494e678233c892c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 01:51:01 +0900 Subject: [PATCH 500/884] impl IntoPyObject for Complex64 --- vm/src/obj/objcomplex.rs | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index ec7afd1102..ab258064ac 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -2,7 +2,9 @@ use num_complex::Complex64; use num_traits::{ToPrimitive, Zero}; use crate::function::OptionalArg; -use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{ + IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, +}; use crate::vm::VirtualMachine; use super::objfloat::{self, PyFloat}; @@ -25,6 +27,12 @@ impl PyValue for PyComplex { } } +impl IntoPyObject for Complex64 { + fn into_pyobject(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_complex(self)) + } +} + impl From for PyComplex { fn from(value: Complex64) -> Self { PyComplex { value } @@ -114,8 +122,8 @@ impl PyComplex { } #[pymethod(name = "conjugate")] - fn conjugate(&self, _vm: &VirtualMachine) -> PyComplex { - self.value.conj().into() + fn conjugate(&self, _vm: &VirtualMachine) -> Complex64 { + self.value.conj() } #[pymethod(name = "__eq__")] @@ -148,19 +156,15 @@ impl PyComplex { #[pymethod(name = "__mul__")] fn mul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match to_complex(other, vm) { - Ok(Some(other)) => Ok(vm.ctx.new_complex(Complex64::new( - self.value.re * other.re - self.value.im * other.im, - self.value.re * other.im + self.value.im * other.re, - ))), - Ok(None) => Ok(vm.ctx.not_implemented()), - Err(err) => Err(err), - } + try_complex(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (self.value * other).into_pyobject(vm), + ) } #[pymethod(name = "__neg__")] - fn neg(&self, _vm: &VirtualMachine) -> PyComplex { - PyComplex::from(-self.value) + fn neg(&self, _vm: &VirtualMachine) -> Complex64 { + -self.value } #[pymethod(name = "__repr__")] From 9a7fadcb6c457b1704f1e357991f485b0b7eb6d8 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 01:52:42 +0900 Subject: [PATCH 501/884] Refactor PyComplex using try_complex --- vm/src/obj/objcomplex.rs | 57 ++++++++++++++-------------------------- vm/src/obj/objfloat.rs | 2 +- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index ab258064ac..eaee632593 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -47,20 +47,14 @@ pub fn get_value(obj: &PyObjectRef) -> Complex64 { obj.payload::().unwrap().value } -fn to_complex(value: PyObjectRef, vm: &VirtualMachine) -> PyResult> { - if objtype::isinstance(&value, &vm.ctx.complex_type()) { - Ok(Some(get_value(&value))) - } else if objtype::isinstance(&value, &vm.ctx.int_type()) { - match objint::get_value(&value).to_f64() { - Some(v) => Ok(Some(Complex64::new(v, 0.0))), - None => Err(vm.new_overflow_error("int too large to convert to float".to_string())), - } - } else if objtype::isinstance(&value, &vm.ctx.float_type()) { - let v = objfloat::get_value(&value); - Ok(Some(Complex64::new(v, 0.0))) +fn try_complex(value: &PyObjectRef, vm: &VirtualMachine) -> PyResult> { + Ok(if objtype::isinstance(&value, &vm.ctx.complex_type()) { + Some(get_value(&value)) + } else if let Some(float) = objfloat::try_float(value, vm)? { + Some(Complex64::new(float, 0.0)) } else { - Ok(None) - } + None + }) } #[pyimpl] @@ -83,42 +77,31 @@ impl PyComplex { #[pymethod(name = "__add__")] fn add(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - if objtype::isinstance(&other, &vm.ctx.complex_type()) { - Ok(vm.ctx.new_complex(self.value + get_value(&other))) - } else { - self.radd(other, vm) - } + try_complex(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (self.value + other).into_pyobject(vm), + ) } #[pymethod(name = "__radd__")] fn radd(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match to_complex(other, vm) { - Ok(Some(other)) => Ok(vm.ctx.new_complex(self.value + other)), - Ok(None) => Ok(vm.ctx.not_implemented()), - Err(err) => Err(err), - } + self.add(other, vm) } #[pymethod(name = "__sub__")] fn sub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - if objtype::isinstance(&other, &vm.ctx.complex_type()) { - Ok(vm.ctx.new_complex(self.value - get_value(&other))) - } else { - match to_complex(other, vm) { - Ok(Some(other)) => Ok(vm.ctx.new_complex(self.value - other)), - Ok(None) => Ok(vm.ctx.not_implemented()), - Err(err) => Err(err), - } - } + try_complex(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (self.value - other).into_pyobject(vm), + ) } #[pymethod(name = "__rsub__")] fn rsub(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match to_complex(other, vm) { - Ok(Some(other)) => Ok(vm.ctx.new_complex(other - self.value)), - Ok(None) => Ok(vm.ctx.not_implemented()), - Err(err) => Err(err), - } + try_complex(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (other - self.value).into_pyobject(vm), + ) } #[pymethod(name = "conjugate")] diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index de4ee9ff5a..b52a469e14 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -44,7 +44,7 @@ impl From for PyFloat { } } -fn try_float(value: &PyObjectRef, vm: &VirtualMachine) -> PyResult> { +pub fn try_float(value: &PyObjectRef, vm: &VirtualMachine) -> PyResult> { Ok(if objtype::isinstance(&value, &vm.ctx.float_type()) { Some(get_value(&value)) } else if objtype::isinstance(&value, &vm.ctx.int_type()) { From 61de5f2efc7f30d2f968638ab6848943f061d831 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 02:03:28 +0900 Subject: [PATCH 502/884] complex.__eq__ using try_float --- tests/snippets/builtin_complex.py | 8 +++++--- vm/src/obj/objcomplex.rs | 16 ++++++---------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index 4d0fa12029..1c0e78b4ee 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -23,6 +23,7 @@ assert complex(1, 2) != complex(1, 1) assert complex(1, 2) != 'foo' assert complex(1, 2).__eq__('foo') == NotImplemented +assert 1j != 10 ** 1000 # __mul__ @@ -40,18 +41,19 @@ assert bool(complex(0, 1)) is True assert bool(complex(1, 0)) is True -# real +# numbers.Complex a = complex(3, 4) b = 4j assert a.real == 3 assert b.real == 0 -# imag - assert a.imag == 4 assert b.imag == 4 +assert a.conjugate() == 3 - 4j +assert b.conjugate() == -4j + # int and complex addition assert 1 + 1j == complex(1, 1) assert 1j + 1 == complex(1, 1) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index eaee632593..6f13e76fa7 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -1,5 +1,5 @@ use num_complex::Complex64; -use num_traits::{ToPrimitive, Zero}; +use num_traits::Zero; use crate::function::OptionalArg; use crate::pyobject::{ @@ -8,7 +8,6 @@ use crate::pyobject::{ use crate::vm::VirtualMachine; use super::objfloat::{self, PyFloat}; -use super::objint; use super::objtype::{self, PyClassRef}; /// Create a complex number from a real part and an optional imaginary part. @@ -113,15 +112,12 @@ impl PyComplex { fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { let result = if objtype::isinstance(&other, &vm.ctx.complex_type()) { self.value == get_value(&other) - } else if objtype::isinstance(&other, &vm.ctx.int_type()) { - match objint::get_value(&other).to_f64() { - Some(f) => self.value.im == 0.0f64 && self.value.re == f, - None => false, - } - } else if objtype::isinstance(&other, &vm.ctx.float_type()) { - self.value.im == 0.0 && self.value.re == objfloat::get_value(&other) } else { - return vm.ctx.not_implemented(); + match objfloat::try_float(&other, vm) { + Ok(Some(other)) => self.value.im == 0.0f64 && self.value.re == other, + Err(_) => false, + Ok(None) => return vm.ctx.not_implemented(), + } }; vm.ctx.new_bool(result) From 982bbd69d803b288dc052c1b0e872d11d2628294 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 02:07:50 +0900 Subject: [PATCH 503/884] complex.__bool__ uses Zero::is_zero instead of zero() --- vm/src/obj/objcomplex.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 6f13e76fa7..9c38650b07 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -158,7 +158,7 @@ impl PyComplex { #[pymethod(name = "__bool__")] fn bool(&self, _vm: &VirtualMachine) -> bool { - self.value != Complex64::zero() + !Complex64::is_zero(&self.value) } #[pymethod(name = "__new__")] From 2a2d0e47648fef006303a4a629a41861bd6c465d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 02:11:13 +0900 Subject: [PATCH 504/884] Add complex.__rmul__ --- tests/snippets/builtin_complex.py | 1 + vm/src/obj/objcomplex.rs | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index 1c0e78b4ee..20300c4e70 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -29,6 +29,7 @@ assert complex(2, -3) * complex(-5, 7) == complex(11, 29) assert complex(2, -3) * 5 == complex(10, -15) +assert 5 * complex(2, -3) == complex(2, -3) * 5 # __neg__ diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 9c38650b07..83dd4ebf11 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -141,6 +141,11 @@ impl PyComplex { ) } + #[pymethod(name = "__rmul__")] + fn rmul(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.mul(other, vm) + } + #[pymethod(name = "__neg__")] fn neg(&self, _vm: &VirtualMachine) -> Complex64 { -self.value From 7c8880fb4ad5d30e42bca3272f1eb04414353ec1 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 02:27:50 +0900 Subject: [PATCH 505/884] complex overflow message test --- tests/snippets/builtin_complex.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index 20300c4e70..a3a911fa70 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -1,4 +1,4 @@ -from testutils import assertRaises +from testutils import assert_raises # __abs__ @@ -73,19 +73,13 @@ assert 2j - 1j == complex(0, 1) # type error addition -with assertRaises(TypeError): - assert 1j + 'str' -with assertRaises(TypeError): - assert 1j - 'str' -with assertRaises(TypeError): - assert 'str' + 1j -with assertRaises(TypeError): - assert 'str' - 1j +assert_raises(TypeError, lambda: 1j + 'str') +assert_raises(TypeError, lambda: 1j - 'str') +assert_raises(TypeError, lambda: 'str' + 1j) +assert_raises(TypeError, lambda: 'str' - 1j) # overflow -with assertRaises(OverflowError): - complex(10 ** 1000, 0) -with assertRaises(OverflowError): - complex(0, 10 ** 1000) -with assertRaises(OverflowError): - complex(0, 0) + 10 ** 1000 +msg = 'int too large to convert to float' +assert_raises(OverflowError, lambda: complex(10 ** 1000, 0), msg) +assert_raises(OverflowError, lambda: complex(0, 10 ** 1000), msg) +assert_raises(OverflowError, lambda: 0j + 10 ** 1000, msg) From 9523baf5ac4c6f8e242aba9b624440082989de18 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 02:35:16 +0900 Subject: [PATCH 506/884] complex [r]truediv, [r]floordiv --- tests/snippets/builtin_complex.py | 18 +++++++++++++++++- vm/src/obj/objcomplex.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index a3a911fa70..5c28a6b114 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -25,12 +25,28 @@ assert complex(1, 2).__eq__('foo') == NotImplemented assert 1j != 10 ** 1000 -# __mul__ +# __mul__, __rmul__ assert complex(2, -3) * complex(-5, 7) == complex(11, 29) assert complex(2, -3) * 5 == complex(10, -15) assert 5 * complex(2, -3) == complex(2, -3) * 5 +# __truediv__, __rtruediv__ + +assert complex(2, -3) / 2 == complex(1, -1.5) +assert 5 / complex(3, -4) == complex(0.6, 0.8) + +# __floordiv__, __rfloordiv__ + +assert_raises( + TypeError, + lambda: complex(2, -3) // 2, + "can't take floor of complex number.") +assert_raises( + TypeError, + lambda: 2 // complex(2, -3), + "can't take floor of complex number.") + # __neg__ assert -complex(1, -1) == complex(-1, 1) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 83dd4ebf11..c0392a3ab5 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -146,6 +146,32 @@ impl PyComplex { self.mul(other, vm) } + #[pymethod(name = "__truediv__")] + fn truediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + try_complex(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (self.value / other).into_pyobject(vm), + ) + } + + #[pymethod(name = "__rtruediv__")] + fn rtruediv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + try_complex(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (other / self.value).into_pyobject(vm), + ) + } + + #[pymethod(name = "__floordiv__")] + fn floordiv(&self, _other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("can't take floor of complex number.".to_string())) + } + + #[pymethod(name = "__rfloordiv__")] + fn rfloordiv(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.floordiv(other, vm) + } + #[pymethod(name = "__neg__")] fn neg(&self, _vm: &VirtualMachine) -> Complex64 { -self.value From 930c8eef503a4b7254759c19a09a2982e944a96d Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 02:57:23 +0900 Subject: [PATCH 507/884] Add complex.{__mod__, __rmod__, __divmod__, __rdivmod__} --- tests/snippets/builtin_complex.py | 22 ++++++++++++++++++++++ vm/src/obj/objcomplex.rs | 20 ++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index 5c28a6b114..81cc9186ef 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -36,6 +36,17 @@ assert complex(2, -3) / 2 == complex(1, -1.5) assert 5 / complex(3, -4) == complex(0.6, 0.8) +# __mod__, __rmod__ + +assert_raises( + TypeError, + lambda: complex(2, -3) % 2, + "can't mod complex numbers.") +assert_raises( + TypeError, + lambda: 2 % complex(2, -3), + "can't mod complex numbers.") + # __floordiv__, __rfloordiv__ assert_raises( @@ -47,6 +58,17 @@ lambda: 2 // complex(2, -3), "can't take floor of complex number.") +# __divmod__, __rdivmod__ + +assert_raises( + TypeError, + lambda: divmod(complex(2, -3), 2), + "can't take floor or mod of complex number.") +assert_raises( + TypeError, + lambda: divmod(2, complex(2, -3)), + "can't take floor or mod of complex number.") + # __neg__ assert -complex(1, -1) == complex(-1, 1) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index c0392a3ab5..3343b5dc72 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -162,6 +162,16 @@ impl PyComplex { ) } + #[pymethod(name = "__mod__")] + fn mod_(&self, _other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("can't mod complex numbers.".to_string())) + } + + #[pymethod(name = "__rmod__")] + fn rmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.mod_(other, vm) + } + #[pymethod(name = "__floordiv__")] fn floordiv(&self, _other: PyObjectRef, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error("can't take floor of complex number.".to_string())) @@ -172,6 +182,16 @@ impl PyComplex { self.floordiv(other, vm) } + #[pymethod(name = "__divmod__")] + fn divmod(&self, _other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("can't take floor or mod of complex number.".to_string())) + } + + #[pymethod(name = "__rdivmod__")] + fn rdivmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.divmod(other, vm) + } + #[pymethod(name = "__neg__")] fn neg(&self, _vm: &VirtualMachine) -> Complex64 { -self.value From 88e64adc561b4bac6b34f07b01f2c26252f25f9e Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 05:43:31 +0900 Subject: [PATCH 508/884] Add complex __pow__ and __rpow__ --- tests/snippets/builtin_complex.py | 6 ++++++ vm/src/obj/objcomplex.rs | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index 81cc9186ef..8d883a9dd4 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -69,6 +69,12 @@ lambda: divmod(2, complex(2, -3)), "can't take floor or mod of complex number.") +# __pow__, __rpow__ + +# assert 1j ** 2 == -1 +assert complex(1) ** 2 == 1 +assert 2 ** complex(2) == 4 + # __neg__ assert -complex(1, -1) == complex(-1, 1) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 3343b5dc72..677763213a 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -207,6 +207,22 @@ impl PyComplex { } } + #[pymethod(name = "__pow__")] + fn pow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + try_complex(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (self.value.powc(other)).into_pyobject(vm), + ) + } + + #[pymethod(name = "__rpow__")] + fn rpow(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + try_complex(&other, vm)?.map_or_else( + || Ok(vm.ctx.not_implemented()), + |other| (other.powc(self.value)).into_pyobject(vm), + ) + } + #[pymethod(name = "__bool__")] fn bool(&self, _vm: &VirtualMachine) -> bool { !Complex64::is_zero(&self.value) From c57fd1b96e218c7dae2cfa6134ecdb53c509d549 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 3 May 2019 23:52:02 +0300 Subject: [PATCH 509/884] Remove closure --- wasm/lib/src/browser_module.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index b4c3fd193c..b3d689d3f3 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -366,10 +366,7 @@ fn browser_load_module(module: PyStringRef, path: PyStringRef, vm: &VirtualMachi .expect("val to be of type Response"); response.text() }) - .and_then(move |text_value: Promise| { - // Convert this other `Promise` into a rust `Future`. - JsFuture::from(text_value) - }) + .and_then(JsFuture::from) .and_then(move |text| { let stored_vm = &weak_vm .upgrade() From 5864c6e063ab2d2f8f5378029def87a5c242e496 Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sat, 4 May 2019 01:14:08 +0200 Subject: [PATCH 510/884] rewrite pybytearray with pybyteinner --- tests/snippets/bytearray.py | 384 ++++++++++++++++++++++++--- tests/snippets/bytes.py | 3 +- vm/src/obj/objbytearray.rs | 500 +++++++++++++++++++++--------------- vm/src/obj/objbyteinner.rs | 55 ++-- vm/src/obj/objbytes.rs | 34 +-- vm/src/stdlib/io.rs | 8 +- 6 files changed, 695 insertions(+), 289 deletions(-) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index 4286f81710..4f29fa1877 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -1,53 +1,368 @@ -#__getitem__ not implemented yet -#a = bytearray(b'abc') -#assert a[0] == b'a' -#assert a[1] == b'b' +from testutils import assertRaises -assert len(bytearray([1,2,3])) == 3 +# new +assert bytearray([1, 2, 3]) +assert bytearray((1, 2, 3)) +assert bytearray(range(4)) +assert bytearray(3) +assert b"bla" +assert ( + bytearray("bla", "utf8") == bytearray("bla", encoding="utf-8") == bytearray(b"bla") +) +with assertRaises(TypeError): + bytearray("bla") +with assertRaises(TypeError): + bytearray("bla", encoding=b"jilj") -assert bytearray(b'1a23').isalnum() -assert not bytearray(b'1%a23').isalnum() +assert bytearray( + b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" +) == bytearray(range(0, 256)) +assert bytearray( + b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" +) == bytearray(range(0, 256)) +assert bytearray(b"omkmok\Xaa") == bytearray( + [111, 109, 107, 109, 111, 107, 92, 88, 97, 97] +) -assert bytearray(b'abc').isalpha() -assert not bytearray(b'abc1').isalpha() + +a = bytearray(b"abcd") +b = bytearray(b"ab") +c = bytearray(b"abcd") + + +# repr +assert repr(bytearray([0, 1, 2])) == repr(bytearray(b"\x00\x01\x02")) +assert ( + repr(bytearray([0, 1, 9, 10, 11, 13, 31, 32, 33, 89, 120, 255])) + == "bytearray(b'\\x00\\x01\\t\\n\\x0b\\r\\x1f !Yx\\xff')" +) +assert repr(bytearray(b"abcd")) == "bytearray(b'abcd')" + +# len +assert len(bytearray("abcdé", "utf8")) == 6 + +# comp +assert a == b"abcd" +assert a > b +assert a >= b +assert b < a +assert b <= a + +assert bytearray(b"foobar").__eq__(2) == NotImplemented +assert bytearray(b"foobar").__ne__(2) == NotImplemented +assert bytearray(b"foobar").__gt__(2) == NotImplemented +assert bytearray(b"foobar").__ge__(2) == NotImplemented +assert bytearray(b"foobar").__lt__(2) == NotImplemented +assert bytearray(b"foobar").__le__(2) == NotImplemented + +# # hash +with assertRaises(TypeError): + hash(bytearray(b"abcd")) # unashable + +# # iter +[i for i in bytearray(b"abcd")] == ["a", "b", "c", "d"] +assert list(bytearray(3)) == [0, 0, 0] + +# add +assert a + b == bytearray(b"abcdab") + +# contains +assert bytearray(b"ab") in bytearray(b"abcd") +assert bytearray(b"cd") in bytearray(b"abcd") +assert bytearray(b"abcd") in bytearray(b"abcd") +assert bytearray(b"a") in bytearray(b"abcd") +assert bytearray(b"d") in bytearray(b"abcd") +assert bytearray(b"dc") not in bytearray(b"abcd") +assert 97 in bytearray(b"abcd") +assert 150 not in bytearray(b"abcd") +with assertRaises(ValueError): + 350 in bytearray(b"abcd") + + +# getitem +d = bytearray(b"abcdefghij") + +assert d[1] == 98 +assert d[-1] == 106 +assert d[2:6] == bytearray(b"cdef") +assert d[-6:] == bytearray(b"efghij") +assert d[1:8:2] == bytearray(b"bdfh") +assert d[8:1:-2] == bytearray(b"igec") + + +# # is_xx methods + +assert bytearray(b"1a23").isalnum() +assert not bytearray(b"1%a23").isalnum() + +assert bytearray(b"abc").isalpha() +assert not bytearray(b"abc1").isalpha() # travis doesn't like this -#assert bytearray(b'xyz').isascii() -#assert not bytearray([128, 157, 32]).isascii() +# assert bytearray(b'xyz').isascii() +# assert not bytearray([128, 157, 32]).isascii() + +assert bytearray(b"1234567890").isdigit() +assert not bytearray(b"12ab").isdigit() -assert bytearray(b'1234567890').isdigit() -assert not bytearray(b'12ab').isdigit() +l = bytearray(b"lower") +b = bytearray(b"UPPER") -l = bytearray(b'lower') assert l.islower() assert not l.isupper() -assert l.upper().isupper() -assert not bytearray(b'Super Friends').islower() +assert b.isupper() +assert not bytearray(b"Super Friends").islower() -assert bytearray(b' \n\t').isspace() -assert not bytearray(b'\td\n').isspace() +assert bytearray(b" \n\t").isspace() +assert not bytearray(b"\td\n").isspace() -b = bytearray(b'UPPER') assert b.isupper() assert not b.islower() -assert b.lower().islower() -assert not bytearray(b'tuPpEr').isupper() +assert l.islower() +assert not bytearray(b"tuPpEr").isupper() -assert bytearray(b'Is Title Case').istitle() -assert not bytearray(b'is Not title casE').istitle() +assert bytearray(b"Is Title Case").istitle() +assert not bytearray(b"is Not title casE").istitle() -a = bytearray(b'abcd') -a.clear() -assert len(a) == 0 +# upper lower, capitalize, swapcase +l = bytearray(b"lower") +b = bytearray(b"UPPER") +assert l.lower().islower() +assert b.upper().isupper() +assert l.capitalize() == b"Lower" +assert b.capitalize() == b"Upper" +assert bytearray().capitalize() == bytearray() +assert b"AaBbCc123'@/".swapcase().swapcase() == b"AaBbCc123'@/" +assert b"AaBbCc123'@/".swapcase() == b"aAbBcC123'@/" + +# # hex from hex +assert bytearray([0, 1, 9, 23, 90, 234]).hex() == "000109175aea" +bytearray.fromhex("62 6c7a 34350a ") == b"blz45\n" try: - bytearray([400]) -except ValueError: - pass -else: - assert False + bytearray.fromhex("62 a 21") +except ValueError as e: + str(e) == "non-hexadecimal number found in fromhex() arg at position 4" +try: + bytearray.fromhex("6Z2") +except ValueError as e: + str(e) == "non-hexadecimal number found in fromhex() arg at position 1" +with assertRaises(TypeError): + bytearray.fromhex(b"hhjjk") +# center +assert [bytearray(b"koki").center(i, b"|") for i in range(3, 10)] == [ + b"koki", + b"koki", + b"|koki", + b"|koki|", + b"||koki|", + b"||koki||", + b"|||koki||", +] + +assert [bytearray(b"kok").center(i, b"|") for i in range(2, 10)] == [ + b"kok", + b"kok", + b"kok|", + b"|kok|", + b"|kok||", + b"||kok||", + b"||kok|||", + b"|||kok|||", +] +bytearray(b"kok").center(4) == b" kok" # " test no arg" +with assertRaises(TypeError): + bytearray(b"b").center(2, "a") +with assertRaises(TypeError): + bytearray(b"b").center(2, b"ba") +with assertRaises(TypeError): + bytearray(b"b").center(b"ba") +assert bytearray(b"kok").center(5, bytearray(b"x")) == b"xkokx" +bytearray(b"kok").center(-5) == b"kok" -b = bytearray(b'test') + +# ljust +assert [bytearray(b"koki").ljust(i, b"|") for i in range(3, 10)] == [ + b"koki", + b"koki", + b"koki|", + b"koki||", + b"koki|||", + b"koki||||", + b"koki|||||", +] +assert [bytearray(b"kok").ljust(i, b"|") for i in range(2, 10)] == [ + b"kok", + b"kok", + b"kok|", + b"kok||", + b"kok|||", + b"kok||||", + b"kok|||||", + b"kok||||||", +] + +bytearray(b"kok").ljust(4) == b"kok " # " test no arg" +with assertRaises(TypeError): + bytearray(b"b").ljust(2, "a") +with assertRaises(TypeError): + bytearray(b"b").ljust(2, b"ba") +with assertRaises(TypeError): + bytearray(b"b").ljust(b"ba") +assert bytearray(b"kok").ljust(5, bytearray(b"x")) == b"kokxx" +assert bytearray(b"kok").ljust(-5) == b"kok" + +# rjust +assert [bytearray(b"koki").rjust(i, b"|") for i in range(3, 10)] == [ + b"koki", + b"koki", + b"|koki", + b"||koki", + b"|||koki", + b"||||koki", + b"|||||koki", +] +assert [bytearray(b"kok").rjust(i, b"|") for i in range(2, 10)] == [ + b"kok", + b"kok", + b"|kok", + b"||kok", + b"|||kok", + b"||||kok", + b"|||||kok", + b"||||||kok", +] + + +bytearray(b"kok").rjust(4) == b" kok" # " test no arg" +with assertRaises(TypeError): + bytearray(b"b").rjust(2, "a") +with assertRaises(TypeError): + bytearray(b"b").rjust(2, b"ba") +with assertRaises(TypeError): + bytearray(b"b").rjust(b"ba") +assert bytearray(b"kok").rjust(5, bytearray(b"x")) == b"xxkok" +assert bytearray(b"kok").rjust(-5) == b"kok" + + +# count +assert bytearray(b"azeazerazeazopia").count(b"aze") == 3 +assert bytearray(b"azeazerazeazopia").count(b"az") == 4 +assert bytearray(b"azeazerazeazopia").count(b"a") == 5 +assert bytearray(b"123456789").count(b"") == 10 +assert bytearray(b"azeazerazeazopia").count(bytearray(b"aze")) == 3 +assert bytearray(b"azeazerazeazopia").count(memoryview(b"aze")) == 3 +assert bytearray(b"azeazerazeazopia").count(memoryview(b"aze"), 1, 9) == 1 +assert bytearray(b"azeazerazeazopia").count(b"aze", None, None) == 3 +assert bytearray(b"azeazerazeazopia").count(b"aze", 2, None) == 2 +assert bytearray(b"azeazerazeazopia").count(b"aze", 2) == 2 +assert bytearray(b"azeazerazeazopia").count(b"aze", None, 7) == 2 +assert bytearray(b"azeazerazeazopia").count(b"aze", None, 7) == 2 +assert bytearray(b"azeazerazeazopia").count(b"aze", 2, 7) == 1 +assert bytearray(b"azeazerazeazopia").count(b"aze", -13, -10) == 1 +assert bytearray(b"azeazerazeazopia").count(b"aze", 1, 10000) == 2 +with assertRaises(ValueError): + bytearray(b"ilj").count(3550) +assert bytearray(b"azeazerazeazopia").count(97) == 5 + +# join +assert bytearray(b"").join( + (b"jiljl", bytearray(b"kmoomk"), memoryview(b"aaaa")) +) == bytearray(b"jiljlkmoomkaaaa") +with assertRaises(TypeError): + bytearray(b"").join((b"km", "kl")) + + +# endswith startswith +assert bytearray(b"abcde").endswith(b"de") +assert bytearray(b"abcde").endswith(b"") +assert not bytearray(b"abcde").endswith(b"zx") +assert bytearray(b"abcde").endswith(b"bc", 0, 3) +assert not bytearray(b"abcde").endswith(b"bc", 2, 3) +assert bytearray(b"abcde").endswith((b"c", bytearray(b"de"))) + +assert bytearray(b"abcde").startswith(b"ab") +assert bytearray(b"abcde").startswith(b"") +assert not bytearray(b"abcde").startswith(b"zx") +assert bytearray(b"abcde").startswith(b"cd", 2) +assert not bytearray(b"abcde").startswith(b"cd", 1, 4) +assert bytearray(b"abcde").startswith((b"a", bytearray(b"bc"))) + + +# index find +assert bytearray(b"abcd").index(b"cd") == 2 +assert bytearray(b"abcd").index(b"cd", 0) == 2 +assert bytearray(b"abcd").index(b"cd", 1) == 2 +assert bytearray(b"abcd").index(99) == 2 +with assertRaises(ValueError): + bytearray(b"abcde").index(b"c", 3, 1) +with assertRaises(ValueError): + bytearray(b"abcd").index(b"cdaaaaa") +with assertRaises(ValueError): + bytearray(b"abcd").index(b"b", 3, 4) +with assertRaises(ValueError): + bytearray(b"abcd").index(1) + + +assert bytearray(b"abcd").find(b"cd") == 2 +assert bytearray(b"abcd").find(b"cd", 0) == 2 +assert bytearray(b"abcd").find(b"cd", 1) == 2 +assert bytearray(b"abcde").find(b"c", 3, 1) == -1 +assert bytearray(b"abcd").find(b"cdaaaaa") == -1 +assert bytearray(b"abcd").find(b"b", 3, 4) == -1 +assert bytearray(b"abcd").find(1) == -1 +assert bytearray(b"abcd").find(99) == 2 + +assert bytearray(b"abcdabcda").find(b"a") == 0 +assert bytearray(b"abcdabcda").rfind(b"a") == 8 +assert bytearray(b"abcdabcda").rfind(b"a", 2, 6) == 4 +assert bytearray(b"abcdabcda").rfind(b"a", None, 6) == 4 +assert bytearray(b"abcdabcda").rfind(b"a", 2, None) == 8 +assert bytearray(b"abcdabcda").index(b"a") == 0 +assert bytearray(b"abcdabcda").rindex(b"a") == 8 + + +# make trans +# fmt: off +assert ( + bytearray.maketrans(memoryview(b"abc"), bytearray(b"zzz")) + == bytes([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 122, 122, 122, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255]) +) +# fmt: on + +# translate +assert bytearray(b"hjhtuyjyujuyj").translate( + bytearray.maketrans(b"hj", bytearray(b"ab")), bytearray(b"h") +) == bytearray(b"btuybyubuyb") +assert bytearray(b"hjhtuyjyujuyj").translate( + bytearray.maketrans(b"hj", bytearray(b"ab")), bytearray(b"a") +) == bytearray(b"abatuybyubuyb") +assert bytearray(b"hjhtuyjyujuyj").translate( + bytearray.maketrans(b"hj", bytearray(b"ab")) +) == bytearray(b"abatuybyubuyb") +assert bytearray(b"hjhtuyfjtyhuhjuyj").translate(None, bytearray(b"ht")) == bytearray( + b"juyfjyujuyj" +) +assert bytearray(b"hjhtuyfjtyhuhjuyj").translate(None, delete=b"ht") == bytearray( + b"juyfjyujuyj" +) + + +# strip lstrip rstrip +assert bytearray(b" spacious ").strip() == bytearray(b"spacious") +assert bytearray(b"www.example.com").strip(b"cmowz.") == bytearray(b"example") +assert bytearray(b" spacious ").lstrip() == bytearray(b"spacious ") +assert bytearray(b"www.example.com").lstrip(b"cmowz.") == bytearray(b"example.com") +assert bytearray(b" spacious ").rstrip() == bytearray(b" spacious") +assert bytearray(b"mississippi").rstrip(b"ipz") == bytearray(b"mississ") + + +# clear +a = bytearray(b"abcd") +a.clear() +assert len(a) == 0 + +b = bytearray(b"test") assert len(b) == 4 b.pop() assert len(b) == 3 @@ -66,8 +381,11 @@ else: assert False -a = bytearray(b'appen') +a = bytearray(b"appen") assert len(a) == 5 a.append(100) +assert a == bytearray(b"append") assert len(a) == 6 assert a.pop() == 100 + +import bytes as bbytes diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 2733533d8f..7086a1423e 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -175,7 +175,8 @@ with assertRaises(TypeError): b"b".center(b"ba") assert b"kok".center(5, bytearray(b"x")) == b"xkokx" -b"kok".center(-5) +b"kok".center(-5) == b"kok" + # ljust diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 6f78ecf481..4336243399 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -1,32 +1,62 @@ //! Implementation of the python bytearray object. -use std::cell::{Cell, RefCell}; -use std::fmt::Write; -use std::ops::{Deref, DerefMut}; - -use num_traits::ToPrimitive; - use crate::function::OptionalArg; -use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::obj::objbyteinner::{ + ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerPosition, + ByteInnerTranslateOptions, ByteOr, PyByteInner, +}; +use crate::obj::objint::PyIntRef; +use crate::obj::objslice::PySliceRef; +use crate::obj::objstr::PyStringRef; +use crate::obj::objtuple::PyTupleRef; +use crate::pyobject::{ + Either, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, + TryFromObject, +}; use crate::vm::VirtualMachine; +use std::cell::{Cell, RefCell}; -use super::objint; use super::objiter; use super::objtype::PyClassRef; -#[derive(Debug)] +/// "bytearray(iterable_of_ints) -> bytearray\n\ +/// bytearray(string, encoding[, errors]) -> bytearray\n\ +/// bytearray(bytes_or_buffer) -> mutable copy of bytes_or_buffer\n\ +/// bytearray(int) -> bytes array of size given by the parameter initialized with null bytes\n\ +/// bytearray() -> empty bytes array\n\n\ +/// Construct a mutable bytearray object from:\n \ +/// - an iterable yielding integers in range(256)\n \ +/// - a text string encoded using the specified encoding\n \ +/// - a bytes or a buffer object\n \ +/// - any object implementing the buffer API.\n \ +/// - an integer"; +#[pyclass(name = "bytearray")] +#[derive(Clone, Debug)] pub struct PyByteArray { - // TODO: shouldn't be public - pub value: RefCell>, + pub inner: RefCell, } type PyByteArrayRef = PyRef; impl PyByteArray { pub fn new(data: Vec) -> Self { PyByteArray { - value: RefCell::new(data), + inner: RefCell::new(PyByteInner { elements: data }), } } + + pub fn from_inner(inner: PyByteInner) -> Self { + PyByteArray { + inner: RefCell::new(inner), + } + } + + // pub fn get_value(&self) -> Vec { + // self.inner.borrow().clone().elements + // } + + // pub fn get_value_mut(&self) -> Vec { + // self.inner.borrow_mut().clone().elements + // } } impl PyValue for PyByteArray { @@ -35,254 +65,318 @@ impl PyValue for PyByteArray { } } -pub fn get_value<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { - obj.payload::().unwrap().value.borrow() -} +// pub fn get_value(obj: &PyObjectRef) -> Vec { +// obj.payload::().unwrap().get_value() +// } -pub fn get_mut_value<'a>(obj: &'a PyObjectRef) -> impl DerefMut> + 'a { - obj.payload::().unwrap().value.borrow_mut() -} - -// Binary data support +// pub fn get_value_mut(obj: &PyObjectRef) -> Vec { +// obj.payload::().unwrap().get_value_mut() +// } /// Fill bytearray class methods dictionary. pub fn init(context: &PyContext) { + PyByteArrayRef::extend_class(context, &context.bytearray_type); let bytearray_type = &context.bytearray_type; - - let bytearray_doc = - "bytearray(iterable_of_ints) -> bytearray\n\ - bytearray(string, encoding[, errors]) -> bytearray\n\ - bytearray(bytes_or_buffer) -> mutable copy of bytes_or_buffer\n\ - bytearray(int) -> bytes array of size given by the parameter initialized with null bytes\n\ - bytearray() -> empty bytes array\n\n\ - Construct a mutable bytearray object from:\n \ - - an iterable yielding integers in range(256)\n \ - - a text string encoded using the specified encoding\n \ - - a bytes or a buffer object\n \ - - any object implementing the buffer API.\n \ - - an integer"; - extend_class!(context, bytearray_type, { - "__doc__" => context.new_str(bytearray_doc.to_string()), - "__new__" => context.new_rustfunc(bytearray_new), - "__eq__" => context.new_rustfunc(PyByteArrayRef::eq), - "__len__" => context.new_rustfunc(PyByteArrayRef::len), - "__repr__" => context.new_rustfunc(PyByteArrayRef::repr), - "__iter__" => context.new_rustfunc(PyByteArrayRef::iter), - "clear" => context.new_rustfunc(PyByteArrayRef::clear), - "isalnum" => context.new_rustfunc(PyByteArrayRef::isalnum), - "isalpha" => context.new_rustfunc(PyByteArrayRef::isalpha), - "isascii" => context.new_rustfunc(PyByteArrayRef::isascii), - "isdigit" => context.new_rustfunc(PyByteArrayRef::isdigit), - "islower" => context.new_rustfunc(PyByteArrayRef::islower), - "isspace" => context.new_rustfunc(PyByteArrayRef::isspace), - "istitle" =>context.new_rustfunc(PyByteArrayRef::istitle), - "isupper" => context.new_rustfunc(PyByteArrayRef::isupper), - "lower" => context.new_rustfunc(PyByteArrayRef::lower), - "append" => context.new_rustfunc(PyByteArrayRef::append), - "pop" => context.new_rustfunc(PyByteArrayRef::pop), - "upper" => context.new_rustfunc(PyByteArrayRef::upper) + "fromhex" => context.new_rustfunc(PyByteArrayRef::fromhex), + "maketrans" => context.new_rustfunc(PyByteInner::maketrans), }); PyByteArrayIterator::extend_class(context, &context.bytearrayiterator_type); } -fn bytearray_new( - cls: PyClassRef, - val_option: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { - // Create bytes data: - let value = if let OptionalArg::Present(ival) = val_option { - let elements = vm.extract_elements(&ival)?; - let mut data_bytes = vec![]; - for elem in elements.iter() { - let v = objint::to_int(vm, elem, 10)?; - if let Some(i) = v.to_u8() { - data_bytes.push(i); - } else { - return Err(vm.new_value_error("byte must be in range(0, 256)".to_string())); - } - } - data_bytes - // return Err(vm.new_type_error("Cannot construct bytes".to_string())); - } else { - vec![] - }; - PyByteArray::new(value).into_ref_with_type(vm, cls.clone()) -} - +#[pyimpl] impl PyByteArrayRef { + #[pymethod(name = "__new__")] + fn bytearray_new( + cls: PyClassRef, + options: ByteInnerNewOptions, + vm: &VirtualMachine, + ) -> PyResult { + PyByteArray::from_inner(options.get_value(vm)?).into_ref_with_type(vm, cls) + } + + #[pymethod(name = "__repr__")] + fn repr(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_str(format!("bytearray(b'{}')", self.inner.borrow().repr()?))) + } + + #[pymethod(name = "__len__")] fn len(self, _vm: &VirtualMachine) -> usize { - self.value.borrow().len() + self.inner.borrow().len() + } + + #[pymethod(name = "__eq__")] + fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().eq(other, vm) + } + + #[pymethod(name = "__ge__")] + fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().ge(other, vm) + } + + #[pymethod(name = "__le__")] + fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().le(other, vm) + } + + #[pymethod(name = "__gt__")] + fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().gt(other, vm) } - fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { - if let Ok(other) = other.downcast::() { - vm.ctx - .new_bool(self.value.borrow().as_slice() == other.value.borrow().as_slice()) + #[pymethod(name = "__lt__")] + fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().lt(other, vm) + } + + #[pymethod(name = "__hash__")] + fn hash(self, vm: &VirtualMachine) -> PyResult { + Err(vm.new_type_error("unhashable type: bytearray".to_string())) + } + + #[pymethod(name = "__iter__")] + fn iter(self, _vm: &VirtualMachine) -> PyByteArrayIterator { + PyByteArrayIterator { + position: Cell::new(0), + bytearray: self, + } + } + + #[pymethod(name = "__add__")] + fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Ok(other) = PyByteInner::try_from_object(vm, other) { + Ok(vm.ctx.new_bytearray(self.inner.borrow().add(other))) } else { - vm.ctx.not_implemented() + Ok(vm.ctx.not_implemented()) } } - fn isalnum(self, _vm: &VirtualMachine) -> bool { - let bytes = self.value.borrow(); - !bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_alphanumeric()) + #[pymethod(name = "__contains__")] + fn contains(self, needle: Either, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().contains(needle, vm) } - fn isalpha(self, _vm: &VirtualMachine) -> bool { - let bytes = self.value.borrow(); - !bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_alphabetic()) + #[pymethod(name = "__getitem__")] + fn getitem(self, needle: Either, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().getitem(needle, vm) } - fn isascii(self, _vm: &VirtualMachine) -> bool { - let bytes = self.value.borrow(); - !bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_ascii()) + #[pymethod(name = "isalnum")] + fn isalnum(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().isalnum(vm) } - fn isdigit(self, _vm: &VirtualMachine) -> bool { - let bytes = self.value.borrow(); - !bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_digit(10)) + #[pymethod(name = "isalpha")] + fn isalpha(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().isalpha(vm) } - fn islower(self, _vm: &VirtualMachine) -> bool { - let bytes = self.value.borrow(); - !bytes.is_empty() - && bytes - .iter() - .filter(|x| !char::from(**x).is_whitespace()) - .all(|x| char::from(*x).is_lowercase()) + #[pymethod(name = "isascii")] + fn isascii(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().isascii(vm) } - fn isspace(self, _vm: &VirtualMachine) -> bool { - let bytes = self.value.borrow(); - !bytes.is_empty() && bytes.iter().all(|x| char::from(*x).is_whitespace()) + #[pymethod(name = "isdigit")] + fn isdigit(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().isdigit(vm) } - fn isupper(self, _vm: &VirtualMachine) -> bool { - let bytes = self.value.borrow(); - !bytes.is_empty() - && bytes - .iter() - .filter(|x| !char::from(**x).is_whitespace()) - .all(|x| char::from(*x).is_uppercase()) + #[pymethod(name = "islower")] + fn islower(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().islower(vm) } - fn istitle(self, _vm: &VirtualMachine) -> bool { - let bytes = self.value.borrow(); - if bytes.is_empty() { - return false; - } + #[pymethod(name = "isspace")] + fn isspace(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().isspace(vm) + } - let mut iter = bytes.iter().peekable(); - let mut prev_cased = false; - - while let Some(c) = iter.next() { - let current = char::from(*c); - let next = if let Some(k) = iter.peek() { - char::from(**k) - } else if current.is_uppercase() { - return !prev_cased; - } else { - return prev_cased; - }; - - if (is_cased(current) && next.is_uppercase() && !prev_cased) - || (!is_cased(current) && next.is_lowercase()) - { - return false; - } - - prev_cased = is_cased(current); - } + #[pymethod(name = "isupper")] + fn isupper(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().isupper(vm) + } - true + #[pymethod(name = "istitle")] + fn istitle(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().istitle(vm) } - fn repr(self, _vm: &VirtualMachine) -> String { - let bytes = self.value.borrow(); - let data = String::from_utf8(bytes.to_vec()).unwrap_or_else(|_| to_hex(&bytes.to_vec())); - format!("bytearray(b'{}')", data) + #[pymethod(name = "lower")] + fn lower(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytearray(self.inner.borrow().lower(vm))) } - fn clear(self, _vm: &VirtualMachine) { - self.value.borrow_mut().clear(); + #[pymethod(name = "upper")] + fn upper(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytearray(self.inner.borrow().upper(vm))) } - fn append(self, x: u8, _vm: &VirtualMachine) { - self.value.borrow_mut().push(x); + #[pymethod(name = "capitalize")] + fn capitalize(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytearray(self.inner.borrow().capitalize(vm))) } - fn pop(self, vm: &VirtualMachine) -> PyResult { - let mut bytes = self.value.borrow_mut(); - bytes - .pop() - .ok_or_else(|| vm.new_index_error("pop from empty bytearray".to_string())) + #[pymethod(name = "swapcase")] + fn swapcase(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytearray(self.inner.borrow().swapcase(vm))) } - fn lower(self, _vm: &VirtualMachine) -> PyByteArray { - let bytes = self.value.borrow().clone().to_ascii_lowercase(); - PyByteArray { - value: RefCell::new(bytes), - } + #[pymethod(name = "hex")] + fn hex(self, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().hex(vm) } - fn upper(self, _vm: &VirtualMachine) -> PyByteArray { - let bytes = self.value.borrow().clone().to_ascii_uppercase(); - PyByteArray { - value: RefCell::new(bytes), + fn fromhex(string: PyStringRef, vm: &VirtualMachine) -> PyResult { + Ok(vm + .ctx + .new_bytearray(PyByteInner::fromhex(string.as_str(), vm)?)) + } + + #[pymethod(name = "center")] + fn center(self, options: ByteInnerPaddingOptions, vm: &VirtualMachine) -> PyResult { + Ok(vm + .ctx + .new_bytearray(self.inner.borrow().center(options, vm)?)) + } + + #[pymethod(name = "ljust")] + fn ljust(self, options: ByteInnerPaddingOptions, vm: &VirtualMachine) -> PyResult { + Ok(vm + .ctx + .new_bytearray(self.inner.borrow().ljust(options, vm)?)) + } + + #[pymethod(name = "rjust")] + fn rjust(self, options: ByteInnerPaddingOptions, vm: &VirtualMachine) -> PyResult { + Ok(vm + .ctx + .new_bytearray(self.inner.borrow().rjust(options, vm)?)) + } + + #[pymethod(name = "count")] + fn count(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().count(options, vm) + } + + #[pymethod(name = "join")] + fn join(self, iter: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().join(iter, vm) + } + + #[pymethod(name = "endswith")] + fn endswith( + self, + suffix: Either, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + self.inner + .borrow() + .startsendswith(suffix, start, end, true, vm) + } + + #[pymethod(name = "startswith")] + fn startswith( + self, + prefix: Either, + start: OptionalArg, + end: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + self.inner + .borrow() + .startsendswith(prefix, start, end, false, vm) + } + + #[pymethod(name = "find")] + fn find(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().find(options, false, vm) + } + + #[pymethod(name = "index")] + fn index(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + let res = self.inner.borrow().find(options, false, vm)?; + if res == -1 { + return Err(vm.new_value_error("substring not found".to_string())); } + Ok(res) } - fn iter(self, _vm: &VirtualMachine) -> PyByteArrayIterator { - PyByteArrayIterator { - position: Cell::new(0), - bytearray: self, + #[pymethod(name = "rfind")] + fn rfind(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().find(options, true, vm) + } + + #[pymethod(name = "rindex")] + fn rindex(self, options: ByteInnerFindOptions, vm: &VirtualMachine) -> PyResult { + let res = self.inner.borrow().find(options, true, vm)?; + if res == -1 { + return Err(vm.new_value_error("substring not found".to_string())); } + Ok(res) } -} -// helper function for istitle -fn is_cased(c: char) -> bool { - c.to_uppercase().next().unwrap() != c || c.to_lowercase().next().unwrap() != c -} + #[pymethod(name = "translate")] + fn translate(self, options: ByteInnerTranslateOptions, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().translate(options, vm) + } -/* -fn getitem(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(obj, Some(vm.ctx.bytearray_type())), (needle, None)] - ); - let elements = get_elements(obj); - get_item(vm, list, &, needle.clone()) -} -*/ -/* -fn set_value(obj: &PyObjectRef, value: Vec) { - obj.borrow_mut().kind = PyObjectPayload::Bytes { value }; -} -*/ - -/// Return a lowercase hex representation of a bytearray -fn to_hex(bytearray: &[u8]) -> String { - bytearray.iter().fold(String::new(), |mut s, b| { - let _ = write!(s, "\\x{:02x}", b); - s - }) -} + #[pymethod(name = "strip")] + fn strip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes( + self.inner + .borrow() + .strip(chars, ByteInnerPosition::All, vm)?, + )) + } -#[cfg(test)] -mod tests { - use super::*; + #[pymethod(name = "lstrip")] + fn lstrip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes( + self.inner + .borrow() + .strip(chars, ByteInnerPosition::Left, vm)?, + )) + } + + #[pymethod(name = "rstrip")] + fn rstrip(self, chars: OptionalArg, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes( + self.inner + .borrow() + .strip(chars, ByteInnerPosition::Right, vm)?, + )) + } + + #[pymethod(name = "clear")] + fn clear(self, _vm: &VirtualMachine) { + self.inner.borrow_mut().elements.clear(); + } - #[test] - fn bytearray_to_hex_formatting() { - assert_eq!(&to_hex(&[11u8, 222u8]), "\\x0b\\xde"); + #[pymethod(name = "append")] + fn append(self, x: PyIntRef, vm: &VirtualMachine) -> Result<(), PyObjectRef> { + self.inner + .borrow_mut() + .elements + .push(x.as_bigint().byte_or(vm)?); + Ok(()) + } + #[pymethod(name = "pop")] + fn pop(self, vm: &VirtualMachine) -> PyResult { + let bytes = &mut self.inner.borrow_mut().elements; + bytes + .pop() + .ok_or_else(|| vm.new_index_error("pop from empty bytearray".to_string())) } } +// fn set_value(obj: &PyObjectRef, value: Vec) { +// obj.borrow_mut().kind = PyObjectPayload::Bytes { value }; +// } + #[pyclass] #[derive(Debug)] pub struct PyByteArrayIterator { @@ -300,8 +394,8 @@ impl PyValue for PyByteArrayIterator { impl PyByteArrayIterator { #[pymethod(name = "__next__")] fn next(&self, vm: &VirtualMachine) -> PyResult { - if self.position.get() < self.bytearray.value.borrow().len() { - let ret = self.bytearray.value.borrow()[self.position.get()]; + if self.position.get() < self.bytearray.inner.borrow().len() { + let ret = self.bytearray.inner.borrow().elements[self.position.get()]; self.position.set(self.position.get() + 1); Ok(ret) } else { diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index b75f8be697..9e3473b939 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -27,7 +27,7 @@ use crate::obj::objint::PyInt; use num_integer::Integer; use num_traits::ToPrimitive; -use super::objbytearray::{get_value as get_value_bytearray, PyByteArray}; +use super::objbytearray::PyByteArray; use super::objbytes::PyBytes; use super::objmemory::PyMemoryView; @@ -43,7 +43,7 @@ impl TryFromObject for PyByteInner { match_class!(obj, i @ PyBytes => Ok(PyByteInner{elements: i.get_value().to_vec()}), - j @ PyByteArray => Ok(PyByteInner{elements: get_value_bytearray(&j.as_object()).to_vec()}), + j @ PyByteArray => Ok(PyByteInner{elements: j.inner.borrow().elements.to_vec()}), k @ PyMemoryView => Ok(PyByteInner{elements: k.get_obj_value().unwrap()}), obj => Err(vm.new_type_error(format!( "a bytes-like object is required, not {}", @@ -261,6 +261,7 @@ impl PyByteInner { 0..=8 => res.push_str(&format!("\\x0{}", i)), 9 => res.push_str("\\t"), 10 => res.push_str("\\n"), + 11 => res.push_str(&format!("\\x0{:x}", i)), 13 => res.push_str("\\r"), 32..=126 => res.push(*(i) as char), _ => res.push_str(&format!("\\x{:x}", i)), @@ -277,43 +278,43 @@ impl PyByteInner { self.elements.len() == 0 } - pub fn eq(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { - if self.elements == other.elements { - Ok(vm.new_bool(true)) + pub fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Ok(other) = PyByteInner::try_from_object(vm, other) { + Ok(vm.new_bool(self.elements == other.elements)) } else { - Ok(vm.new_bool(false)) + Ok(vm.ctx.not_implemented()) } } - pub fn ge(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { - if self.elements >= other.elements { - Ok(vm.new_bool(true)) + pub fn ge(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Ok(other) = PyByteInner::try_from_object(vm, other) { + Ok(vm.new_bool(self.elements >= other.elements)) } else { - Ok(vm.new_bool(false)) + Ok(vm.ctx.not_implemented()) } } - pub fn le(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { - if self.elements <= other.elements { - Ok(vm.new_bool(true)) + pub fn le(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Ok(other) = PyByteInner::try_from_object(vm, other) { + Ok(vm.new_bool(self.elements <= other.elements)) } else { - Ok(vm.new_bool(false)) + Ok(vm.ctx.not_implemented()) } } - pub fn gt(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { - if self.elements > other.elements { - Ok(vm.new_bool(true)) + pub fn gt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Ok(other) = PyByteInner::try_from_object(vm, other) { + Ok(vm.new_bool(self.elements > other.elements)) } else { - Ok(vm.new_bool(false)) + Ok(vm.ctx.not_implemented()) } } - pub fn lt(&self, other: &PyByteInner, vm: &VirtualMachine) -> PyResult { - if self.elements < other.elements { - Ok(vm.new_bool(true)) + pub fn lt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Ok(other) = PyByteInner::try_from_object(vm, other) { + Ok(vm.new_bool(self.elements < other.elements)) } else { - Ok(vm.new_bool(false)) + Ok(vm.ctx.not_implemented()) } } @@ -323,14 +324,12 @@ impl PyByteInner { hasher.finish() as usize } - pub fn add(&self, other: &PyByteInner, _vm: &VirtualMachine) -> Vec { - let elements: Vec = self - .elements + pub fn add(&self, other: PyByteInner) -> Vec { + self.elements .iter() .chain(other.elements.iter()) .cloned() - .collect(); - elements + .collect::>() } pub fn contains(&self, needle: Either, vm: &VirtualMachine) -> PyResult { @@ -763,7 +762,7 @@ pub fn try_as_byte(obj: &PyObjectRef) -> Option> { match_class!(obj.clone(), i @ PyBytes => Some(i.get_value().to_vec()), - j @ PyByteArray => Some(get_value_bytearray(&j.as_object()).to_vec()), + j @ PyByteArray => Some(j.inner.borrow().elements.to_vec()), _ => None) } diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 59b0731f4f..30ad6623e0 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -9,7 +9,9 @@ use core::cell::Cell; use std::ops::Deref; use crate::function::OptionalArg; -use crate::pyobject::{PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{ + PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, +}; use super::objbyteinner::{ ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerPosition, @@ -100,35 +102,25 @@ impl PyBytesRef { #[pymethod(name = "__eq__")] fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - bytes @ PyBytes => self.inner.eq(&bytes.inner, vm), - _ => Ok(vm.ctx.not_implemented())) + self.inner.eq(other, vm) } - #[pymethod(name = "__ge__")] fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - bytes @ PyBytes => self.inner.ge(&bytes.inner, vm), - _ => Ok(vm.ctx.not_implemented())) + self.inner.ge(other, vm) } #[pymethod(name = "__le__")] fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - bytes @ PyBytes => self.inner.le(&bytes.inner, vm), - _ => Ok(vm.ctx.not_implemented())) + self.inner.le(other, vm) } #[pymethod(name = "__gt__")] fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - bytes @ PyBytes => self.inner.gt(&bytes.inner, vm), - _ => Ok(vm.ctx.not_implemented())) + self.inner.gt(other, vm) } #[pymethod(name = "__lt__")] fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - bytes @ PyBytes => self.inner.lt(&bytes.inner, vm), - _ => Ok(vm.ctx.not_implemented())) + self.inner.lt(other, vm) } + #[pymethod(name = "__hash__")] fn hash(self, _vm: &VirtualMachine) -> usize { self.inner.hash() @@ -144,9 +136,11 @@ impl PyBytesRef { #[pymethod(name = "__add__")] fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - bytes @ PyBytes => Ok(vm.ctx.new_bytes(self.inner.add(&bytes.inner, vm))), - _ => Ok(vm.ctx.not_implemented())) + if let Ok(other) = PyByteInner::try_from_object(vm, other) { + Ok(vm.ctx.new_bytearray(self.inner.add(other))) + } else { + Ok(vm.ctx.not_implemented()) + } } #[pymethod(name = "__contains__")] diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index b8e72cc15d..e9b49ec442 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -124,7 +124,7 @@ fn buffered_reader_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { //Copy bytes from the buffer vector into the results vector if let Some(bytes) = buffer.payload::() { - result.extend_from_slice(&bytes.value.borrow()); + result.extend_from_slice(&bytes.inner.borrow().elements); }; let py_len = vm.call_method(&buffer, "__len__", PyFuncArgs::default())?; @@ -207,9 +207,9 @@ fn file_io_readinto(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if let Some(bytes) = obj.payload::() { //TODO: Implement for MemoryView - let mut value_mut = bytes.value.borrow_mut(); + let value_mut = &mut bytes.inner.borrow_mut().elements; value_mut.clear(); - match f.read_to_end(&mut value_mut) { + match f.read_to_end(value_mut) { Ok(_) => {} Err(_) => return Err(vm.new_value_error("Error reading from Take".to_string())), } @@ -237,7 +237,7 @@ fn file_io_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { match obj.payload::() { Some(bytes) => { - let value_mut = bytes.value.borrow(); + let value_mut = &mut bytes.inner.borrow_mut().elements; match handle.write(&value_mut[..]) { Ok(len) => { //reset raw fd on the FileIO object From 01b245f5df7cee71eb469545cdeb5406de07e5ab Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 10:59:32 +0300 Subject: [PATCH 511/884] Add stat to DirEntry --- vm/src/stdlib/os.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 2b6c859eb4..fd5893e743 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -15,7 +15,9 @@ use crate::obj::objiter; use crate::obj::objstr; use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{ItemProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{ + ItemProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryIntoRef, +}; use crate::vm::VirtualMachine; #[cfg(unix)] @@ -230,6 +232,10 @@ impl DirEntryRef { .map_err(|s| vm.new_os_error(s.to_string()))? .is_file()) } + + fn stat(self, vm: &VirtualMachine) -> PyResult { + os_stat(self.path(vm).try_into_ref(vm)?, vm) + } } #[pyclass] @@ -428,6 +434,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "path" => ctx.new_property(DirEntryRef::path), "is_dir" => ctx.new_rustfunc(DirEntryRef::is_dir), "is_file" => ctx.new_rustfunc(DirEntryRef::is_file), + "stat" => ctx.new_rustfunc(DirEntryRef::stat), }); let stat_result = py_class!(ctx, "stat_result", ctx.object(), { From b96f4a575809ecaf2bc8c8699b2286ceec7b5498 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 11:00:36 +0300 Subject: [PATCH 512/884] Add CPython stat.py file --- Lib/stat.py | 179 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 Lib/stat.py diff --git a/Lib/stat.py b/Lib/stat.py new file mode 100644 index 0000000000..a9c678ec03 --- /dev/null +++ b/Lib/stat.py @@ -0,0 +1,179 @@ +"""Constants/functions for interpreting results of os.stat() and os.lstat(). + +Suggested usage: from stat import * +""" + +# Indices for stat struct members in the tuple returned by os.stat() + +ST_MODE = 0 +ST_INO = 1 +ST_DEV = 2 +ST_NLINK = 3 +ST_UID = 4 +ST_GID = 5 +ST_SIZE = 6 +ST_ATIME = 7 +ST_MTIME = 8 +ST_CTIME = 9 + +# Extract bits from the mode + +def S_IMODE(mode): + """Return the portion of the file's mode that can be set by + os.chmod(). + """ + return mode & 0o7777 + +def S_IFMT(mode): + """Return the portion of the file's mode that describes the + file type. + """ + return mode & 0o170000 + +# Constants used as S_IFMT() for various file types +# (not all are implemented on all systems) + +S_IFDIR = 0o040000 # directory +S_IFCHR = 0o020000 # character device +S_IFBLK = 0o060000 # block device +S_IFREG = 0o100000 # regular file +S_IFIFO = 0o010000 # fifo (named pipe) +S_IFLNK = 0o120000 # symbolic link +S_IFSOCK = 0o140000 # socket file + +# Functions to test for each file type + +def S_ISDIR(mode): + """Return True if mode is from a directory.""" + return S_IFMT(mode) == S_IFDIR + +def S_ISCHR(mode): + """Return True if mode is from a character special device file.""" + return S_IFMT(mode) == S_IFCHR + +def S_ISBLK(mode): + """Return True if mode is from a block special device file.""" + return S_IFMT(mode) == S_IFBLK + +def S_ISREG(mode): + """Return True if mode is from a regular file.""" + return S_IFMT(mode) == S_IFREG + +def S_ISFIFO(mode): + """Return True if mode is from a FIFO (named pipe).""" + return S_IFMT(mode) == S_IFIFO + +def S_ISLNK(mode): + """Return True if mode is from a symbolic link.""" + return S_IFMT(mode) == S_IFLNK + +def S_ISSOCK(mode): + """Return True if mode is from a socket.""" + return S_IFMT(mode) == S_IFSOCK + +# Names for permission bits + +S_ISUID = 0o4000 # set UID bit +S_ISGID = 0o2000 # set GID bit +S_ENFMT = S_ISGID # file locking enforcement +S_ISVTX = 0o1000 # sticky bit +S_IREAD = 0o0400 # Unix V7 synonym for S_IRUSR +S_IWRITE = 0o0200 # Unix V7 synonym for S_IWUSR +S_IEXEC = 0o0100 # Unix V7 synonym for S_IXUSR +S_IRWXU = 0o0700 # mask for owner permissions +S_IRUSR = 0o0400 # read by owner +S_IWUSR = 0o0200 # write by owner +S_IXUSR = 0o0100 # execute by owner +S_IRWXG = 0o0070 # mask for group permissions +S_IRGRP = 0o0040 # read by group +S_IWGRP = 0o0020 # write by group +S_IXGRP = 0o0010 # execute by group +S_IRWXO = 0o0007 # mask for others (not in group) permissions +S_IROTH = 0o0004 # read by others +S_IWOTH = 0o0002 # write by others +S_IXOTH = 0o0001 # execute by others + +# Names for file flags + +UF_NODUMP = 0x00000001 # do not dump file +UF_IMMUTABLE = 0x00000002 # file may not be changed +UF_APPEND = 0x00000004 # file may only be appended to +UF_OPAQUE = 0x00000008 # directory is opaque when viewed through a union stack +UF_NOUNLINK = 0x00000010 # file may not be renamed or deleted +UF_COMPRESSED = 0x00000020 # OS X: file is hfs-compressed +UF_HIDDEN = 0x00008000 # OS X: file should not be displayed +SF_ARCHIVED = 0x00010000 # file may be archived +SF_IMMUTABLE = 0x00020000 # file may not be changed +SF_APPEND = 0x00040000 # file may only be appended to +SF_NOUNLINK = 0x00100000 # file may not be renamed or deleted +SF_SNAPSHOT = 0x00200000 # file is a snapshot file + + +_filemode_table = ( + ((S_IFLNK, "l"), + (S_IFSOCK, "s"), # Must appear before IFREG and IFDIR as IFSOCK == IFREG | IFDIR + (S_IFREG, "-"), + (S_IFBLK, "b"), + (S_IFDIR, "d"), + (S_IFCHR, "c"), + (S_IFIFO, "p")), + + ((S_IRUSR, "r"),), + ((S_IWUSR, "w"),), + ((S_IXUSR|S_ISUID, "s"), + (S_ISUID, "S"), + (S_IXUSR, "x")), + + ((S_IRGRP, "r"),), + ((S_IWGRP, "w"),), + ((S_IXGRP|S_ISGID, "s"), + (S_ISGID, "S"), + (S_IXGRP, "x")), + + ((S_IROTH, "r"),), + ((S_IWOTH, "w"),), + ((S_IXOTH|S_ISVTX, "t"), + (S_ISVTX, "T"), + (S_IXOTH, "x")) +) + +def filemode(mode): + """Convert a file's mode to a string of the form '-rwxrwxrwx'.""" + perm = [] + for table in _filemode_table: + for bit, char in table: + if mode & bit == bit: + perm.append(char) + break + else: + perm.append("-") + return "".join(perm) + + +# Windows FILE_ATTRIBUTE constants for interpreting os.stat()'s +# "st_file_attributes" member + +FILE_ATTRIBUTE_ARCHIVE = 32 +FILE_ATTRIBUTE_COMPRESSED = 2048 +FILE_ATTRIBUTE_DEVICE = 64 +FILE_ATTRIBUTE_DIRECTORY = 16 +FILE_ATTRIBUTE_ENCRYPTED = 16384 +FILE_ATTRIBUTE_HIDDEN = 2 +FILE_ATTRIBUTE_INTEGRITY_STREAM = 32768 +FILE_ATTRIBUTE_NORMAL = 128 +FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 8192 +FILE_ATTRIBUTE_NO_SCRUB_DATA = 131072 +FILE_ATTRIBUTE_OFFLINE = 4096 +FILE_ATTRIBUTE_READONLY = 1 +FILE_ATTRIBUTE_REPARSE_POINT = 1024 +FILE_ATTRIBUTE_SPARSE_FILE = 512 +FILE_ATTRIBUTE_SYSTEM = 4 +FILE_ATTRIBUTE_TEMPORARY = 256 +FILE_ATTRIBUTE_VIRTUAL = 65536 + + +# If available, use C implementation +try: + from _stat import * +except ImportError: + pass From 775be3a36afa421acac122ba11ff1b7ef815b47b Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 11:07:56 +0300 Subject: [PATCH 513/884] Add stat mode tests --- tests/snippets/stdlib_os.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 4b0344f12d..2e6aa0a352 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -1,5 +1,6 @@ import os import time +import stat from testutils import assert_raises @@ -99,9 +100,11 @@ def __exit__(self, exc_type, exc_val, exc_tb): names.add(dir_entry.name) paths.add(dir_entry.path) if dir_entry.is_dir(): + assert stat.S_ISDIR(dir_entry.stat().st_mode) == True dirs.add(dir_entry.name) if dir_entry.is_file(): files.add(dir_entry.name) + assert stat.S_ISREG(dir_entry.stat().st_mode) == True assert names == set([FILE_NAME, FILE_NAME2, FOLDER]) assert paths == set([fname, fname2, folder]) @@ -111,6 +114,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): # Stat stat_res = os.stat(fname) print(stat_res.st_mode) + assert stat.S_ISREG(stat_res.st_mode) == True print(stat_res.st_ino) print(stat_res.st_dev) print(stat_res.st_nlink) From 5270da39344cddcf5ace17b3e8c6c5646e4bc435 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 12:25:17 +0300 Subject: [PATCH 514/884] Add ExceptHandler block --- tests/snippets/try_exceptions.py | 13 +++++++++++++ vm/src/frame.rs | 16 ++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index c6a148e788..5c90a502e1 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -193,3 +193,16 @@ def f(): raise ArithmeticError() except ArithmeticError as e: continue + + +def g(): + try: + 1/0 + except ArithmeticError: + return 5 + +try: + g() + raise NameError +except NameError as ex: + assert ex.__context__ == None diff --git a/vm/src/frame.rs b/vm/src/frame.rs index a9e1559692..23c9b8e2ba 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -203,6 +203,7 @@ enum BlockType { end: bytecode::Label, context_manager: PyObjectRef, }, + ExceptHandler {}, } pub type FrameRef = PyRef; @@ -862,6 +863,12 @@ impl Frame { } bytecode::Instruction::PopException {} => { assert!(vm.pop_exception().is_some()); + let block = self.pop_block(); + assert!(block.is_some()); + match block.unwrap().typ { + BlockType::ExceptHandler {} => (), + _ => assert!(false), + }; Ok(None) } } @@ -937,6 +944,9 @@ impl Frame { } } } + BlockType::ExceptHandler { .. } => { + vm.pop_exception(); + } } } @@ -959,6 +969,9 @@ impl Frame { panic!("Exception in with __exit__ {:?}", exc); } }, + BlockType::ExceptHandler { .. } => { + vm.pop_exception(); + } } self.pop_block(); @@ -970,6 +983,7 @@ impl Frame { while let Some(block) = self.pop_block() { match block.typ { BlockType::TryExcept { handler } => { + self.push_block(BlockType::ExceptHandler {}); self.push_value(exc.clone()); vm.push_exception(exc); self.jump(handler); @@ -1004,6 +1018,8 @@ impl Frame { } } BlockType::Loop { .. } => {} + // Exception was already poped on Raised. + BlockType::ExceptHandler { .. } => {} } } Some(exc) From d803acfdc5e3493471c18e95bb503e52d8541421 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 12:28:19 +0300 Subject: [PATCH 515/884] Add yield in except test --- tests/snippets/try_exceptions.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/snippets/try_exceptions.py b/tests/snippets/try_exceptions.py index 5c90a502e1..f37101b611 100644 --- a/tests/snippets/try_exceptions.py +++ b/tests/snippets/try_exceptions.py @@ -206,3 +206,17 @@ def g(): raise NameError except NameError as ex: assert ex.__context__ == None + + +def y(): + try: + 1/0 + except ArithmeticError: + yield 5 + + +try: + y() + raise NameError +except NameError as ex: + assert ex.__context__ == None From 159063bb4711e1c5864bd4c23fc92960dd9e544a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 12:50:28 +0300 Subject: [PATCH 516/884] Convert windows file attributes to unix mode --- vm/src/stdlib/os.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index fd5893e743..da35d6c69d 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -386,12 +386,33 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } } +// Copied from CPython fileutils.c +#[cfg(windows)] +fn attributes_to_mode(attr: u32) -> u32 { + let file_attribute_directory: u32 = 16; + let file_attribute_readonly: u32 = 1; + let s_ifdir: u32 = 0o040000; + let s_ifreg: u32 = 0o100000; + let mut m: u32 = 0; + if attr & file_attribute_directory == file_attribute_directory { + m |= s_ifdir | 0111; /* IFEXEC for user,group,other */ + } else { + m |= s_ifreg; + } + if attr & file_attribute_readonly == file_attribute_readonly { + m |= 0444; + } else { + m |= 0666; + } + m +} + #[cfg(windows)] fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { use std::os::windows::fs::MetadataExt; match fs::metadata(&path.value) { Ok(meta) => Ok(StatResult { - st_mode: meta.file_attributes(), + st_mode: attributes_to_mode(meta.file_attributes()), st_ino: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. st_dev: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. st_nlink: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. From 32714f889e968a49afdcf29b556a1bf7db5c0493 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 17:18:04 +0300 Subject: [PATCH 517/884] Remove empty params from ExceptHandler --- vm/src/frame.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 23c9b8e2ba..c22ad88973 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -203,7 +203,7 @@ enum BlockType { end: bytecode::Label, context_manager: PyObjectRef, }, - ExceptHandler {}, + ExceptHandler, } pub type FrameRef = PyRef; @@ -866,7 +866,7 @@ impl Frame { let block = self.pop_block(); assert!(block.is_some()); match block.unwrap().typ { - BlockType::ExceptHandler {} => (), + BlockType::ExceptHandler => (), _ => assert!(false), }; Ok(None) @@ -944,7 +944,7 @@ impl Frame { } } } - BlockType::ExceptHandler { .. } => { + BlockType::ExceptHandler => { vm.pop_exception(); } } @@ -969,7 +969,7 @@ impl Frame { panic!("Exception in with __exit__ {:?}", exc); } }, - BlockType::ExceptHandler { .. } => { + BlockType::ExceptHandler => { vm.pop_exception(); } } @@ -1019,7 +1019,7 @@ impl Frame { } BlockType::Loop { .. } => {} // Exception was already poped on Raised. - BlockType::ExceptHandler { .. } => {} + BlockType::ExceptHandler => {} } } Some(exc) From 810b8ee4d0d8a1fa4be05f1fc92b04c717c16235 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 17:19:59 +0300 Subject: [PATCH 518/884] Simplify PopException --- vm/src/frame.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index c22ad88973..ddddad91e2 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -862,14 +862,13 @@ impl Frame { Ok(None) } bytecode::Instruction::PopException {} => { - assert!(vm.pop_exception().is_some()); - let block = self.pop_block(); - assert!(block.is_some()); - match block.unwrap().typ { - BlockType::ExceptHandler => (), - _ => assert!(false), - }; - Ok(None) + let block = self.pop_block().unwrap(); // this asserts that the block is_some. + if let BlockType::ExceptHandler = block.typ { + assert!(vm.pop_exception().is_some()); + Ok(None) + } else { + panic!("Block type must be ExceptHandler here.") + } } } } From 46ad1f5449925c0dc762d8610018b6961e9063b5 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 18:01:23 +0300 Subject: [PATCH 519/884] Add os.symlink --- tests/snippets/stdlib_os.py | 8 ++++++-- vm/src/stdlib/os.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 2e6aa0a352..8a3c4a251a 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -67,6 +67,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): FILE_NAME = "test1" FILE_NAME2 = "test2" +SYMLINK = "symlink" FOLDER = "dir1" CONTENT = b"testing" CONTENT2 = b"rustpython" @@ -92,6 +93,9 @@ def __exit__(self, exc_type, exc_val, exc_tb): folder = tmpdir + os.sep + FOLDER os.mkdir(folder) + symlink = tmpdir + os.sep + SYMLINK + os.symlink(fname, symlink) + names = set() paths = set() dirs = set() @@ -106,8 +110,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): files.add(dir_entry.name) assert stat.S_ISREG(dir_entry.stat().st_mode) == True - assert names == set([FILE_NAME, FILE_NAME2, FOLDER]) - assert paths == set([fname, fname2, folder]) + assert names == set([FILE_NAME, FILE_NAME2, FOLDER, SYMLINK]) + assert paths == set([fname, fname2, folder, symlink]) assert dirs == set([FOLDER]) assert files == set([FILE_NAME, FILE_NAME2]) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index da35d6c69d..c48ec2aa1b 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -436,6 +436,35 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { unimplemented!(); } +#[cfg(unix)] +fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + use std::os::unix::fs; + fs::symlink(&src.value, &dst.value).map_err(|s| vm.new_os_error(s.to_string())) +} + +#[cfg(windows)] +fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + use std::os::windows::fs; + let ret = match fs::metadata(&dst.value) { + Ok(meta) => { + if meta.is_file() { + fs::symlink_file(&src.value, &dst.value) + } else if meta.is_dir() { + fs::symlink_dir(&src.value, &dst.value) + } else { + panic!("Uknown file type"); + } + } + Err(_) => fs::symlink_file(&src.value, &dst.value), + }; + ret.map_err(|s| vm.new_os_error(s.to_string())) +} + +#[cfg(not(any(unix)))] +fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + unimplemented!(); +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -489,6 +518,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "DirEntry" => dir_entry, "stat_result" => stat_result, "stat" => ctx.new_rustfunc(os_stat), + "symlink" => ctx.new_rustfunc(os_symlink), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), "O_RDWR" => ctx.new_int(2), From 6c1dca7cc23456023e0a4f51186a3896b0f56923 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 18:14:21 +0300 Subject: [PATCH 520/884] Add DirEntry.is_symlink --- tests/snippets/stdlib_os.py | 4 ++++ vm/src/stdlib/os.rs | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 8a3c4a251a..5cad2f7ec6 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -100,6 +100,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): paths = set() dirs = set() files = set() + symlinks = set() for dir_entry in os.scandir(tmpdir): names.add(dir_entry.name) paths.add(dir_entry.path) @@ -109,11 +110,14 @@ def __exit__(self, exc_type, exc_val, exc_tb): if dir_entry.is_file(): files.add(dir_entry.name) assert stat.S_ISREG(dir_entry.stat().st_mode) == True + if dir_entry.is_symlink(): + symlinks.add(dir_entry.name) assert names == set([FILE_NAME, FILE_NAME2, FOLDER, SYMLINK]) assert paths == set([fname, fname2, folder, symlink]) assert dirs == set([FOLDER]) assert files == set([FILE_NAME, FILE_NAME2]) + assert symlinks == set([SYMLINK]) # Stat stat_res = os.stat(fname) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index c48ec2aa1b..f7252ef8c3 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -233,6 +233,14 @@ impl DirEntryRef { .is_file()) } + fn is_symlink(self, vm: &VirtualMachine) -> PyResult { + Ok(self + .entry + .file_type() + .map_err(|s| vm.new_os_error(s.to_string()))? + .is_symlink()) + } + fn stat(self, vm: &VirtualMachine) -> PyResult { os_stat(self.path(vm).try_into_ref(vm)?, vm) } @@ -484,6 +492,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "path" => ctx.new_property(DirEntryRef::path), "is_dir" => ctx.new_rustfunc(DirEntryRef::is_dir), "is_file" => ctx.new_rustfunc(DirEntryRef::is_file), + "is_symlink" => ctx.new_rustfunc(DirEntryRef::is_symlink), "stat" => ctx.new_rustfunc(DirEntryRef::stat), }); From 45d7c383cdbffd29d7b42fbed6680b5bee13fd61 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 3 May 2019 01:54:10 +0300 Subject: [PATCH 521/884] Implement bool.__ror__, __rand__ and __rxor__ --- vm/src/obj/objbool.rs | 49 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 41 insertions(+), 8 deletions(-) diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index e96324f671..c1a999e457 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -43,8 +43,11 @@ The class bool is a subclass of the class int, and cannot be subclassed."; "__new__" => context.new_rustfunc(bool_new), "__repr__" => context.new_rustfunc(bool_repr), "__or__" => context.new_rustfunc(bool_or), + "__ror__" => context.new_rustfunc(bool_ror), "__and__" => context.new_rustfunc(bool_and), + "__rand__" => context.new_rustfunc(bool_rand), "__xor__" => context.new_rustfunc(bool_xor), + "__rxor__" => context.new_rustfunc(bool_rxor), "__doc__" => context.new_str(bool_doc.to_string()) }); } @@ -74,10 +77,10 @@ fn bool_repr(vm: &VirtualMachine, args: PyFuncArgs) -> Result Result { - arg_check!(vm, args, required = [(lhs, None), (rhs, None)]); - - if objtype::isinstance(lhs, &vm.ctx.bool_type()) && objtype::isinstance(rhs, &vm.ctx.bool_type()) { +fn do_bool_or(vm: &VirtualMachine, lhs: &PyObjectRef, rhs: &PyObjectRef) -> PyResult { + if objtype::isinstance(lhs, &vm.ctx.bool_type()) + && objtype::isinstance(rhs, &vm.ctx.bool_type()) + { let lhs = get_value(lhs); let rhs = get_value(rhs); (lhs || rhs).into_pyobject(vm) @@ -86,10 +89,20 @@ fn bool_or(vm: &VirtualMachine, args: PyFuncArgs) -> Result Result { +fn bool_or(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(lhs, None), (rhs, None)]); + do_bool_or(vm, lhs, rhs) +} - if objtype::isinstance(lhs, &vm.ctx.bool_type()) && objtype::isinstance(rhs, &vm.ctx.bool_type()) { +fn bool_ror(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(rhs, None), (lhs, None)]); + do_bool_or(vm, lhs, rhs) +} + +fn do_bool_and(vm: &VirtualMachine, lhs: &PyObjectRef, rhs: &PyObjectRef) -> PyResult { + if objtype::isinstance(lhs, &vm.ctx.bool_type()) + && objtype::isinstance(rhs, &vm.ctx.bool_type()) + { let lhs = get_value(lhs); let rhs = get_value(rhs); (lhs && rhs).into_pyobject(vm) @@ -98,10 +111,20 @@ fn bool_and(vm: &VirtualMachine, args: PyFuncArgs) -> Result Result { +fn bool_and(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(lhs, None), (rhs, None)]); + do_bool_and(vm, lhs, rhs) +} - if objtype::isinstance(lhs, &vm.ctx.bool_type()) && objtype::isinstance(rhs, &vm.ctx.bool_type()) { +fn bool_rand(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(rhs, None), (lhs, None)]); + do_bool_and(vm, lhs, rhs) +} + +fn do_bool_xor(vm: &VirtualMachine, lhs: &PyObjectRef, rhs: &PyObjectRef) -> PyResult { + if objtype::isinstance(lhs, &vm.ctx.bool_type()) + && objtype::isinstance(rhs, &vm.ctx.bool_type()) + { let lhs = get_value(lhs); let rhs = get_value(rhs); (lhs ^ rhs).into_pyobject(vm) @@ -110,6 +133,16 @@ fn bool_xor(vm: &VirtualMachine, args: PyFuncArgs) -> Result PyResult { + arg_check!(vm, args, required = [(lhs, None), (rhs, None)]); + do_bool_xor(vm, lhs, rhs) +} + +fn bool_rxor(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(rhs, None), (lhs, None)]); + do_bool_xor(vm, lhs, rhs) +} + fn bool_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, From 4cce44ab31d10e2dd5fc2d96d65ecc5e092f7bf7 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 3 May 2019 02:24:55 +0300 Subject: [PATCH 522/884] Add test for print file parameter --- tests/snippets/printing.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/snippets/printing.py b/tests/snippets/printing.py index e420c6c7fd..b2c9ca55df 100644 --- a/tests/snippets/printing.py +++ b/tests/snippets/printing.py @@ -1,4 +1,5 @@ from testutils import assert_raises +import io print(2 + 3) @@ -9,3 +10,7 @@ print('test', end=None, sep=None, flush=None) except: assert False, 'Expected None passed to end, sep, and flush to not raise errors' + +buf = io.StringIO() +print('hello, world', file=buf) +assert buf.getvalue() == 'hello, world\n', buf.getvalue() From ad2499f432276a79ad1ef50dcd0b86ff26bd8f7d Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 3 May 2019 02:25:32 +0300 Subject: [PATCH 523/884] Implement print file parameter --- vm/src/builtins.rs | 70 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 21 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 300d11b315..bf6ce663ee 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -22,8 +22,8 @@ use crate::obj::objtype::{self, PyClass, PyClassRef}; use crate::frame::Scope; use crate::function::{Args, KwArgs, OptionalArg, PyFuncArgs}; use crate::pyobject::{ - IdProtocol, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, - TypeProtocol, + IdProtocol, IntoPyObject, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, + TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -559,32 +559,60 @@ pub struct PrintOptions { end: Option, #[pyarg(keyword_only, default = "false")] flush: bool, + #[pyarg(keyword_only, default = "None")] + file: Option, } pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> { - let stdout = io::stdout(); - let mut stdout_lock = stdout.lock(); - let mut first = true; - for object in objects { - if first { - first = false; - } else if let Some(ref sep) = options.sep { - write!(stdout_lock, "{}", sep.value).unwrap(); - } else { - write!(stdout_lock, " ").unwrap(); + if let Some(file) = options.file { + let sep = match options.sep { + Some(sep) => sep.into_pyobject(vm).unwrap(), + _ => PyString::from(" ").into_pyobject(vm).unwrap(), + }; + + let mut first = true; + for object in objects { + if first { + first = false; + } else { + vm.call_method(&file, "write", vec![sep.clone()])?; + } + + vm.call_method(&file, "write", vec![object])?; } - let s = &vm.to_str(&object)?.value; - write!(stdout_lock, "{}", s).unwrap(); - } - if let Some(end) = options.end { - write!(stdout_lock, "{}", end.value).unwrap(); + let end = match options.end { + Some(end) => end.into_pyobject(vm).unwrap(), + _ => PyString::from("\n").into_pyobject(vm).unwrap(), + }; + vm.call_method(&file, "write", vec![end])?; + + if options.flush { + vm.call_method(&file, "flush", vec![])?; + } } else { - writeln!(stdout_lock).unwrap(); - } + let sep = options.sep.as_ref().map_or(" ", |sep| &sep.value); + + let stdout = io::stdout(); + let mut stdout_lock = stdout.lock(); + let mut first = true; + for object in objects { + if first { + first = false; + } else { + write!(stdout_lock, "{}", sep).unwrap(); + } - if options.flush { - stdout_lock.flush().unwrap(); + let s = &vm.to_str(&object)?.value; + write!(stdout_lock, "{}", s).unwrap(); + } + + let end = options.end.as_ref().map_or("\n", |end| &end.value); + write!(stdout_lock, "{}", end).unwrap(); + + if options.flush { + stdout_lock.flush().unwrap(); + } } Ok(()) From bbb8df797fee90574053be1efd018f1c54235308 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Sat, 4 May 2019 22:55:14 +0300 Subject: [PATCH 524/884] Refactor builtin_print to avoid duplicate loop code --- vm/src/builtins.rs | 99 +++++++++++++++++++++++++++------------------- 1 file changed, 58 insertions(+), 41 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index bf6ce663ee..e80c35010e 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -563,56 +563,73 @@ pub struct PrintOptions { file: Option, } -pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> { - if let Some(file) = options.file { - let sep = match options.sep { - Some(sep) => sep.into_pyobject(vm).unwrap(), - _ => PyString::from(" ").into_pyobject(vm).unwrap(), - }; +trait Printer { + fn write(&mut self, vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<()>; + fn flush(&mut self, vm: &VirtualMachine) -> PyResult<()>; +} - let mut first = true; - for object in objects { - if first { - first = false; - } else { - vm.call_method(&file, "write", vec![sep.clone()])?; - } +impl Printer for &'_ PyObjectRef { + fn write(&mut self, vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<()> { + vm.call_method(self, "write", vec![obj])?; + Ok(()) + } - vm.call_method(&file, "write", vec![object])?; - } + fn flush(&mut self, vm: &VirtualMachine) -> PyResult<()> { + vm.call_method(self, "flush", vec![])?; + Ok(()) + } +} - let end = match options.end { - Some(end) => end.into_pyobject(vm).unwrap(), - _ => PyString::from("\n").into_pyobject(vm).unwrap(), - }; - vm.call_method(&file, "write", vec![end])?; +impl Printer for std::io::StdoutLock<'_> { + fn write(&mut self, vm: &VirtualMachine, obj: PyObjectRef) -> PyResult<()> { + let s = &vm.to_str(&obj)?.value; + write!(self, "{}", s).unwrap(); + Ok(()) + } - if options.flush { - vm.call_method(&file, "flush", vec![])?; - } + fn flush(&mut self, _vm: &VirtualMachine) -> PyResult<()> { + ::flush(self).unwrap(); + Ok(()) + } +} + +pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> { + let stdout = io::stdout(); + + let mut printer: Box = if let Some(file) = &options.file { + Box::new(file) } else { - let sep = options.sep.as_ref().map_or(" ", |sep| &sep.value); - - let stdout = io::stdout(); - let mut stdout_lock = stdout.lock(); - let mut first = true; - for object in objects { - if first { - first = false; - } else { - write!(stdout_lock, "{}", sep).unwrap(); - } + Box::new(stdout.lock()) + }; - let s = &vm.to_str(&object)?.value; - write!(stdout_lock, "{}", s).unwrap(); + let sep = options + .sep + .as_ref() + .map_or(" ", |sep| &sep.value) + .into_pyobject(vm) + .unwrap(); + + let mut first = true; + for object in objects { + if first { + first = false; + } else { + printer.write(vm, sep.clone())?; } - let end = options.end.as_ref().map_or("\n", |end| &end.value); - write!(stdout_lock, "{}", end).unwrap(); + printer.write(vm, object)?; + } - if options.flush { - stdout_lock.flush().unwrap(); - } + let end = options + .end + .as_ref() + .map_or("\n", |end| &end.value) + .into_pyobject(vm) + .unwrap(); + printer.write(vm, end)?; + + if options.flush { + printer.flush(vm)?; } Ok(()) From 6225de30986f8f6f11b5f88c584d5d9b36e3dab9 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 4 May 2019 23:15:22 +0300 Subject: [PATCH 525/884] Change file attributes to consts --- vm/src/stdlib/os.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index f7252ef8c3..cebdc665a2 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -397,17 +397,17 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { // Copied from CPython fileutils.c #[cfg(windows)] fn attributes_to_mode(attr: u32) -> u32 { - let file_attribute_directory: u32 = 16; - let file_attribute_readonly: u32 = 1; - let s_ifdir: u32 = 0o040000; - let s_ifreg: u32 = 0o100000; + const FILE_ATTRIBUTE_DIRECTORY: u32 = 16; + const FILE_ATTRIBUTE_READONLY: u32 = 1; + const S_IFDIR: u32 = 0o040000; + const S_IFREG: u32 = 0o100000; let mut m: u32 = 0; - if attr & file_attribute_directory == file_attribute_directory { - m |= s_ifdir | 0111; /* IFEXEC for user,group,other */ + if attr & FILE_ATTRIBUTE_DIRECTORY == FILE_ATTRIBUTE_DIRECTORY { + m |= S_IFDIR | 0111; /* IFEXEC for user,group,other */ } else { - m |= s_ifreg; + m |= S_IFREG; } - if attr & file_attribute_readonly == file_attribute_readonly { + if attr & FILE_ATTRIBUTE_READONLY == FILE_ATTRIBUTE_READONLY { m |= 0444; } else { m |= 0666; From 1a5bddc41b42aff37152cc1cfed8940d174ea9de Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 3 May 2019 02:34:13 +0300 Subject: [PATCH 526/884] Fix clippy error about float comparison --- vm/src/obj/objfloat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 6c097b7062..2da23bf4fc 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -397,7 +397,7 @@ impl PyFloat { }; if ndigits.is_none() { let fract = self.value.fract(); - let value = if fract.abs() == 0.5 { + let value = if (fract.abs() - 0.5).abs() < std::f64::EPSILON { if self.value.trunc() % 2.0 == 0.0 { self.value - fract } else { From 88c3852b734c33fcd85f134da11432826411c762 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Sun, 5 May 2019 00:02:10 +0300 Subject: [PATCH 527/884] Don't check for methods on iter - it's a builtin function --- tests/not_impl_gen.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/not_impl_gen.py b/tests/not_impl_gen.py index 2e20f56094..e70cfc51b0 100644 --- a/tests/not_impl_gen.py +++ b/tests/not_impl_gen.py @@ -7,14 +7,13 @@ float, frozenset, int, - iter, list, memoryview, range, set, str, tuple, - object + object, ] header = open("generator/not_impl_header.txt") From c38a3711d0151560b0ac73c01a5e9f4664c6ec01 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Sun, 5 May 2019 00:05:13 +0300 Subject: [PATCH 528/884] Include reimplemented methods in whats_left.sh, and skip inherited methods --- tests/not_impl_gen.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/not_impl_gen.py b/tests/not_impl_gen.py index e70cfc51b0..2f1df44a2f 100644 --- a/tests/not_impl_gen.py +++ b/tests/not_impl_gen.py @@ -16,6 +16,18 @@ object, ] + +def attr_is_not_inherited(type_, attr): + """ + returns True if type_'s attr is not inherited from any of its base classes + """ + + bases = obj.__mro__[1:] + + return getattr(obj, attr) not in ( + getattr(base, attr, None) for base in bases) + + header = open("generator/not_impl_header.txt") footer = open("generator/not_impl_footer.txt") output = open("snippets/whats_left_to_implement.py", "w") @@ -25,7 +37,11 @@ for obj in objects: output.write(f" '{obj.__name__}': ({obj.__name__}, [\n") - output.write("\n".join(f" '{attr}'," for attr in dir(obj))) + output.write("\n".join( + f" {attr!r}," + for attr in dir(obj) + if attr_is_not_inherited(obj, attr) + )) output.write("\n ])," + ("\n" if objects[-1] == obj else "\n\n")) output.write("}\n\n") From 5bd23b4fb63cd5d200f9c62f804c68a6f3a11de1 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 5 May 2019 19:14:34 +0300 Subject: [PATCH 529/884] is_dir and is_file follow_symlink by default --- vm/src/stdlib/os.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index cebdc665a2..a5634de64a 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -218,19 +218,17 @@ impl DirEntryRef { } fn is_dir(self, vm: &VirtualMachine) -> PyResult { - Ok(self - .entry - .file_type() - .map_err(|s| vm.new_os_error(s.to_string()))? - .is_dir()) + match fs::metadata(self.entry.path()) { + Ok(meta) => Ok(meta.is_dir()), + Err(s) => Err(vm.new_os_error(s.to_string())), + } } fn is_file(self, vm: &VirtualMachine) -> PyResult { - Ok(self - .entry - .file_type() - .map_err(|s| vm.new_os_error(s.to_string()))? - .is_file()) + match fs::metadata(self.entry.path()) { + Ok(meta) => Ok(meta.is_file()), + Err(s) => Err(vm.new_os_error(s.to_string())), + } } fn is_symlink(self, vm: &VirtualMachine) -> PyResult { From 914f32afc357c868f99ea840269013ad853e6bb8 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 5 May 2019 19:17:30 +0300 Subject: [PATCH 530/884] Add symlink tests --- tests/snippets/stdlib_os.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 5cad2f7ec6..d4e1edb4e1 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -67,7 +67,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): FILE_NAME = "test1" FILE_NAME2 = "test2" -SYMLINK = "symlink" +SYMLINK_FILE = "symlink" +SYMLINK_FOLDER = "symlink1" FOLDER = "dir1" CONTENT = b"testing" CONTENT2 = b"rustpython" @@ -93,8 +94,10 @@ def __exit__(self, exc_type, exc_val, exc_tb): folder = tmpdir + os.sep + FOLDER os.mkdir(folder) - symlink = tmpdir + os.sep + SYMLINK - os.symlink(fname, symlink) + symlink_file = tmpdir + os.sep + SYMLINK_FILE + os.symlink(fname, symlink_file) + symlink_folder = tmpdir + os.sep + SYMLINK_FOLDER + os.symlink(folder, symlink_folder) names = set() paths = set() @@ -113,11 +116,11 @@ def __exit__(self, exc_type, exc_val, exc_tb): if dir_entry.is_symlink(): symlinks.add(dir_entry.name) - assert names == set([FILE_NAME, FILE_NAME2, FOLDER, SYMLINK]) - assert paths == set([fname, fname2, folder, symlink]) - assert dirs == set([FOLDER]) - assert files == set([FILE_NAME, FILE_NAME2]) - assert symlinks == set([SYMLINK]) + assert names == set([FILE_NAME, FILE_NAME2, FOLDER, SYMLINK_FILE, SYMLINK_FOLDER]) + assert paths == set([fname, fname2, folder, symlink_file, symlink_folder]) + assert dirs == set([FOLDER, SYMLINK_FOLDER]) + assert files == set([FILE_NAME, FILE_NAME2, SYMLINK_FILE]) + assert symlinks == set([SYMLINK_FILE, SYMLINK_FOLDER]) # Stat stat_res = os.stat(fname) @@ -130,3 +133,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): print(stat_res.st_gid) print(stat_res.st_size) assert stat_res.st_size == len(CONTENT2) + len(CONTENT3) + + # stat default is follow_symlink=True + os.stat(fname).st_ino == os.stat(symlink_file).st_ino + os.stat(fname).st_mode == os.stat(symlink_file).st_mode From 260af4d877749e280152f834db08b23ef31b13e5 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 5 May 2019 19:40:01 +0300 Subject: [PATCH 531/884] Add follow_symlinks to DirEntry.{is_file, is_dir} --- tests/snippets/stdlib_os.py | 10 +++++++++ vm/src/stdlib/os.rs | 44 +++++++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index d4e1edb4e1..f50f5c9b2a 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -102,7 +102,9 @@ def __exit__(self, exc_type, exc_val, exc_tb): names = set() paths = set() dirs = set() + dirs_no_symlink = set() files = set() + files_no_symlink = set() symlinks = set() for dir_entry in os.scandir(tmpdir): names.add(dir_entry.name) @@ -110,16 +112,24 @@ def __exit__(self, exc_type, exc_val, exc_tb): if dir_entry.is_dir(): assert stat.S_ISDIR(dir_entry.stat().st_mode) == True dirs.add(dir_entry.name) + if dir_entry.is_dir(follow_symlinks=False): + assert stat.S_ISDIR(dir_entry.stat().st_mode) == True + dirs_no_symlink.add(dir_entry.name) if dir_entry.is_file(): files.add(dir_entry.name) assert stat.S_ISREG(dir_entry.stat().st_mode) == True + if dir_entry.is_file(follow_symlinks=False): + files_no_symlink.add(dir_entry.name) + assert stat.S_ISREG(dir_entry.stat().st_mode) == True if dir_entry.is_symlink(): symlinks.add(dir_entry.name) assert names == set([FILE_NAME, FILE_NAME2, FOLDER, SYMLINK_FILE, SYMLINK_FOLDER]) assert paths == set([fname, fname2, folder, symlink_file, symlink_folder]) assert dirs == set([FOLDER, SYMLINK_FOLDER]) + assert dirs_no_symlink == set([FOLDER]) assert files == set([FILE_NAME, FILE_NAME2, SYMLINK_FILE]) + assert files_no_symlink == set([FILE_NAME, FILE_NAME2]) assert symlinks == set([SYMLINK_FILE, SYMLINK_FOLDER]) # Stat diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index a5634de64a..a8941badcc 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -208,6 +208,12 @@ impl PyValue for DirEntry { } } +#[derive(FromArgs)] +struct FollowSymlinks { + #[pyarg(keyword_only, default = "true")] + follow_symlinks: bool, +} + impl DirEntryRef { fn name(self, _vm: &VirtualMachine) -> String { self.entry.file_name().into_string().unwrap() @@ -217,18 +223,38 @@ impl DirEntryRef { self.entry.path().to_str().unwrap().to_string() } - fn is_dir(self, vm: &VirtualMachine) -> PyResult { - match fs::metadata(self.entry.path()) { - Ok(meta) => Ok(meta.is_dir()), - Err(s) => Err(vm.new_os_error(s.to_string())), + fn perform_on_metadata( + self, + follow_symlinks: FollowSymlinks, + action: &Fn(fs::Metadata) -> bool, + vm: &VirtualMachine, + ) -> PyResult { + match follow_symlinks.follow_symlinks { + true => match fs::metadata(self.entry.path()) { + Ok(meta) => Ok(action(meta)), + Err(s) => Err(vm.new_os_error(s.to_string())), + }, + false => match fs::symlink_metadata(self.entry.path()) { + Ok(meta) => Ok(action(meta)), + Err(s) => Err(vm.new_os_error(s.to_string())), + }, } } - fn is_file(self, vm: &VirtualMachine) -> PyResult { - match fs::metadata(self.entry.path()) { - Ok(meta) => Ok(meta.is_file()), - Err(s) => Err(vm.new_os_error(s.to_string())), - } + fn is_dir(self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { + self.perform_on_metadata( + follow_symlinks, + &|meta: fs::Metadata| -> bool { meta.is_dir() }, + vm, + ) + } + + fn is_file(self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { + self.perform_on_metadata( + follow_symlinks, + &|meta: fs::Metadata| -> bool { meta.is_file() }, + vm, + ) } fn is_symlink(self, vm: &VirtualMachine) -> PyResult { From 2c30e83896ea11234fc6c63350f837aa2f7a69b7 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 5 May 2019 20:04:39 +0300 Subject: [PATCH 532/884] Add follow_symlinks to stat --- tests/snippets/stdlib_os.py | 3 + vm/src/stdlib/os.rs | 111 +++++++++++++++--------------------- 2 files changed, 49 insertions(+), 65 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index f50f5c9b2a..8d111b6f3e 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -147,3 +147,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): # stat default is follow_symlink=True os.stat(fname).st_ino == os.stat(symlink_file).st_ino os.stat(fname).st_mode == os.stat(symlink_file).st_mode + + os.stat(fname, follow_symlinks=False).st_ino == os.stat(symlink_file, follow_symlinks=False).st_ino + os.stat(fname, follow_symlinks=False).st_mode == os.stat(symlink_file, follow_symlinks=False).st_mode diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index a8941badcc..a67114da61 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -229,16 +229,12 @@ impl DirEntryRef { action: &Fn(fs::Metadata) -> bool, vm: &VirtualMachine, ) -> PyResult { - match follow_symlinks.follow_symlinks { - true => match fs::metadata(self.entry.path()) { - Ok(meta) => Ok(action(meta)), - Err(s) => Err(vm.new_os_error(s.to_string())), - }, - false => match fs::symlink_metadata(self.entry.path()) { - Ok(meta) => Ok(action(meta)), - Err(s) => Err(vm.new_os_error(s.to_string())), - }, - } + let metadata = match follow_symlinks.follow_symlinks { + true => fs::metadata(self.entry.path()), + false => fs::symlink_metadata(self.entry.path()), + }; + let meta = metadata.map_err(|s| vm.new_os_error(s.to_string()))?; + Ok(action(meta)) } fn is_dir(self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { @@ -265,8 +261,8 @@ impl DirEntryRef { .is_symlink()) } - fn stat(self, vm: &VirtualMachine) -> PyResult { - os_stat(self.path(vm).try_into_ref(vm)?, vm) + fn stat(self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { + os_stat(self.path(vm).try_into_ref(vm)?, follow_symlinks, vm) } } @@ -361,11 +357,15 @@ impl StatResultRef { } } -#[cfg(target_os = "linux")] -fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { - use std::os::linux::fs::MetadataExt; - match fs::metadata(&path.value) { - Ok(meta) => Ok(StatResult { +macro_rules! os_unix_stat_inner { + ( $path:expr, $follow_symlinks:expr, $vm:expr) => {{ + let metadata = match $follow_symlinks.follow_symlinks { + true => fs::metadata($path), + false => fs::symlink_metadata($path), + }; + let meta = metadata.map_err(|s| $vm.new_os_error(s.to_string()))?; + + Ok(StatResult { st_mode: meta.st_mode(), st_ino: meta.st_ino(), st_dev: meta.st_dev(), @@ -374,48 +374,27 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { st_gid: meta.st_gid(), st_size: meta.st_size(), } - .into_ref(vm) - .into_object()), - Err(s) => Err(vm.new_os_error(s.to_string())), - } + .into_ref($vm) + .into_object()) + }}; +} + +#[cfg(target_os = "linux")] +fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { + use std::os::linux::fs::MetadataExt; + os_unix_stat_inner!(&path.value, follow_symlinks, vm) } #[cfg(target_os = "macos")] -fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { +fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { use std::os::macos::fs::MetadataExt; - match fs::metadata(&path.value) { - Ok(meta) => Ok(StatResult { - st_mode: meta.st_mode(), - st_ino: meta.st_ino(), - st_dev: meta.st_dev(), - st_nlink: meta.st_nlink(), - st_uid: meta.st_uid(), - st_gid: meta.st_gid(), - st_size: meta.st_size(), - } - .into_ref(vm) - .into_object()), - Err(s) => Err(vm.new_os_error(s.to_string())), - } + os_unix_stat_inner!(&path.value, follow_symlinks, vm) } #[cfg(target_os = "android")] -fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { +fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { use std::os::android::fs::MetadataExt; - match fs::metadata(&path.value) { - Ok(meta) => Ok(StatResult { - st_mode: meta.st_mode(), - st_ino: meta.st_ino(), - st_dev: meta.st_dev(), - st_nlink: meta.st_nlink(), - st_uid: meta.st_uid(), - st_gid: meta.st_gid(), - st_size: meta.st_size(), - } - .into_ref(vm) - .into_object()), - Err(s) => Err(vm.new_os_error(s.to_string())), - } + os_unix_stat_inner!(&path.value, follow_symlinks, vm) } // Copied from CPython fileutils.c @@ -440,22 +419,24 @@ fn attributes_to_mode(attr: u32) -> u32 { } #[cfg(windows)] -fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { +fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { use std::os::windows::fs::MetadataExt; - match fs::metadata(&path.value) { - Ok(meta) => Ok(StatResult { - st_mode: attributes_to_mode(meta.file_attributes()), - st_ino: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. - st_dev: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. - st_nlink: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. - st_uid: 0, // 0 on windows - st_gid: 0, // 0 on windows - st_size: meta.file_size(), - } - .into_ref(vm) - .into_object()), - Err(s) => Err(vm.new_os_error(s.to_string())), + let metadata = match follow_symlinks.follow_symlinks { + true => fs::metadata(&path.value), + false => fs::symlink_metadata(&path.value), + }; + let meta = metadata.map_err(|s| vm.new_os_error(s.to_string()))?; + Ok(StatResult { + st_mode: attributes_to_mode(meta.file_attributes()), + st_ino: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_dev: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_nlink: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_uid: 0, // 0 on windows + st_gid: 0, // 0 on windows + st_size: meta.file_size(), } + .into_ref(vm) + .into_object()) } #[cfg(not(any( From 6b495eac1e9c9ed049a18f98d19eeb561b72c00f Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 6 May 2019 02:13:47 +0900 Subject: [PATCH 533/884] str(1.0) == '1.0' --- tests/snippets/floats.py | 4 ++++ vm/src/obj/objfloat.rs | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 2ac10b94fe..82f2b86e31 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -173,6 +173,10 @@ assert_raises(OverflowError, float('-inf').as_integer_ratio) assert_raises(ValueError, float('nan').as_integer_ratio) +assert str(1.0) == '1.0' +assert str(0.0) == '0.0' +assert str(1.123456789) == '1.123456789' + # Test special case for lexer, float starts with a dot: a = .5 assert a == 0.5 diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index b52a469e14..39618c7970 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -335,8 +335,12 @@ impl PyFloat { } #[pymethod(name = "__repr__")] - fn repr(&self, _vm: &VirtualMachine) -> String { - self.value.to_string() + fn repr(&self, vm: &VirtualMachine) -> String { + if self.is_integer(vm) { + format!("{:.1}", self.value) + } else { + self.value.to_string() + } } #[pymethod(name = "__truediv__")] From 717c96837e960e5820610eea9a08680636b61d47 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sun, 5 May 2019 20:24:20 +0300 Subject: [PATCH 534/884] Borrow instead of cloning in `ord()` --- vm/src/builtins.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index a82914ad16..f2b7bee872 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -507,7 +507,7 @@ fn builtin_oct(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_ord(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(string, Some(vm.ctx.str_type()))]); - let string = objstr::get_value(string); + let string = objstr::borrow_value(string); let string_len = string.chars().count(); if string_len > 1 { return Err(vm.new_type_error(format!( From 6a73790b262b9c90f543d97b536d7079e1493878 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sun, 5 May 2019 20:24:54 +0300 Subject: [PATCH 535/884] Raise proper error when string of length 0 is given to `ord()` --- vm/src/builtins.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index f2b7bee872..d3158fe6b4 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -509,7 +509,7 @@ fn builtin_ord(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(string, Some(vm.ctx.str_type()))]); let string = objstr::borrow_value(string); let string_len = string.chars().count(); - if string_len > 1 { + if string_len != 1 { return Err(vm.new_type_error(format!( "ord() expected a character, but string of length {} found", string_len From c06b6ccc9667acc823c5618c6ef7fd5d7f1d65ff Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 5 May 2019 21:19:20 +0300 Subject: [PATCH 536/884] Fix windows build --- vm/src/stdlib/os.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index a67114da61..0794eb99b2 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -420,6 +420,7 @@ fn attributes_to_mode(attr: u32) -> u32 { #[cfg(windows)] fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { + use std::os::windows::fs; use std::os::windows::fs::MetadataExt; let metadata = match follow_symlinks.follow_symlinks { true => fs::metadata(&path.value), @@ -473,7 +474,7 @@ fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResu ret.map_err(|s| vm.new_os_error(s.to_string())) } -#[cfg(not(any(unix)))] +#[cfg(all(not(unix), not(windows)))] fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { unimplemented!(); } From ecd6e85f3c3e3b1d579f45db69d02a98e57ed70d Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 5 May 2019 21:32:49 +0300 Subject: [PATCH 537/884] Fix windows build try 2 --- vm/src/stdlib/os.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 0794eb99b2..b52e3762e8 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -420,7 +420,6 @@ fn attributes_to_mode(attr: u32) -> u32 { #[cfg(windows)] fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { - use std::os::windows::fs; use std::os::windows::fs::MetadataExt; let metadata = match follow_symlinks.follow_symlinks { true => fs::metadata(&path.value), @@ -452,24 +451,24 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { #[cfg(unix)] fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { - use std::os::unix::fs; - fs::symlink(&src.value, &dst.value).map_err(|s| vm.new_os_error(s.to_string())) + use std::os::unix::fs as unix_fs; + unix_fs::symlink(&src.value, &dst.value).map_err(|s| vm.new_os_error(s.to_string())) } #[cfg(windows)] fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { - use std::os::windows::fs; + use std::os::windows::fs as win_fs; let ret = match fs::metadata(&dst.value) { Ok(meta) => { if meta.is_file() { - fs::symlink_file(&src.value, &dst.value) + win_fs::symlink_file(&src.value, &dst.value) } else if meta.is_dir() { - fs::symlink_dir(&src.value, &dst.value) + win_fs::symlink_dir(&src.value, &dst.value) } else { panic!("Uknown file type"); } } - Err(_) => fs::symlink_file(&src.value, &dst.value), + Err(_) => win_fs::symlink_file(&src.value, &dst.value), }; ret.map_err(|s| vm.new_os_error(s.to_string())) } From 5a95c5af22d2c43e04431aba5e36db0396c3e0ab Mon Sep 17 00:00:00 2001 From: jgirardet Date: Sun, 5 May 2019 23:29:49 +0200 Subject: [PATCH 538/884] remove unittests --- vm/src/obj/objbyteinner.rs | 299 ------------------------------------- 1 file changed, 299 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index d6c47c2f04..eebeb4fb52 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -1183,302 +1183,3 @@ fn split_slice_reverse<'a>(slice: &'a [u8], sep: &[u8], maxsplit: i32) -> Vec<&' splitted.reverse(); splitted } - -#[cfg(test)] - -// needed for dev. Same as python tests in bytes.py. should it be kept ? - -mod tests { - use super::*; - - #[test] - fn no_end() { - assert_eq!( - split_slice(&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], &[4, 5], -1), - vec![[1, 2, 3], [1, 2, 3], [1, 2, 3]] - ); - assert_eq!( - split_slice_reverse(&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], &[4, 5], -1), - vec![[1, 2, 3], [1, 2, 3], [1, 2, 3]] - ) - } - - #[test] - fn needle_end() { - let v: Vec<&[u8]> = vec![&[1, 2, 3], &[1, 2, 3], &[1, 2, 3], &[]]; - assert_eq!( - split_slice(&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5], &[4, 5], -1), - v - ); - assert_eq!( - split_slice_reverse(&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5], &[4, 5], -1), - v - ) - } - - #[test] - fn needle_end_minus_one() { - let v = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3]; - let n = [4, 5]; - let res: Vec<&[u8]> = vec![&[1, 2, 3], &[1, 2, 3], &[1, 2, 3], &[3]]; - - assert_eq!(split_slice(&v, &n, -1), res); - assert_eq!(split_slice_reverse(&v, &n, -1), res) - } - - #[test] - fn needle_start() { - let v = [4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; - let n = [4, 5]; - let res: Vec<&[u8]> = vec![&[], &[2, 3], &[1, 2, 3], &[1, 2, 3]]; - - assert_eq!(split_slice(&v, &n, -1), res); - assert_eq!(split_slice_reverse(&v, &n, -1), res) - } - - #[test] - fn needle_start_plus_one() { - let v = [1, 4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; - let n = [4, 5]; - let res: Vec<&[u8]> = vec![&[1], &[2, 3], &[1, 2, 3], &[1, 2, 3]]; - - assert_eq!(split_slice(&v, &n, -1), res); - assert_eq!(split_slice_reverse(&v, &n, -1), res) - } - #[test] - fn needles_next_to() { - let v = [1, 2, 3, 4, 5, 4, 5, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; - let n = [4, 5]; - let res: Vec<&[u8]> = vec![&[1, 2, 3], &[], &[], &[1, 2, 3], &[1, 2, 3]]; - - assert_eq!(split_slice(&v, &n, -1), res); - assert_eq!(split_slice_reverse(&v, &n, -1), res) - } - #[test] - fn no_end_max() { - let v = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; - let n = [4, 5]; - let res: Vec<&[u8]> = vec![&[1, 2, 3], &[1, 2, 3, 4, 5, 1, 2, 3]]; - let res_rev: Vec<&[u8]> = vec![&[1, 2, 3, 4, 5, 1, 2, 3], &[1, 2, 3]]; - let max = 1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn needle_end_max() { - let v = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]; - let n = [4, 5]; - let res: Vec<&[u8]> = vec![&[1, 2, 3], &[1, 2, 3, 4, 5, 1, 2, 3, 4, 5]]; - let res_rev: Vec<&[u8]> = vec![&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], &[]]; - let max = 1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - #[test] - fn needle_end_minus_one_max() { - let v = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3]; - let n = [4, 5]; - let res: Vec<&[u8]> = vec![&[1, 2, 3], &[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3]]; - let res_rev: Vec<&[u8]> = vec![&[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], &[3]]; - let max = 1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn needle_start_max() { - let v = [4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; - let n = [4, 5]; - let res: Vec<&[u8]> = vec![&[], &[2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]]; - let res_rev: Vec<&[u8]> = vec![&[4, 5, 2, 3, 4, 5, 1, 2, 3], &[1, 2, 3]]; - let max = 1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - #[test] - fn needle_start_minus_one_max() { - let v = [1, 4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; - let n = [4, 5]; - let res: Vec<&[u8]> = vec![&[1], &[2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]]; - let res_rev: Vec<&[u8]> = vec![&[1, 4, 5, 2, 3, 4, 5, 1, 2, 3], &[1, 2, 3]]; - let max = 1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn needle_next_to() { - let v = [1, 2, 3, 4, 5, 4, 5, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]; - let n = [4, 5]; - let res: Vec<&[u8]> = vec![&[1, 2, 3], &[], &[4, 5, 1, 2, 3, 4, 5, 1, 2, 3]]; - let res_rev: Vec<&[u8]> = vec![&[1, 2, 3, 4, 5, 4, 5], &[1, 2, 3], &[1, 2, 3]]; - let max = 2; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn no_needle() { - let v = [107, 13, 117, 104, 10, 102, 122, 32, 101, 9, 102]; - let n = []; - let res: Vec<&[u8]> = vec![&[107], &[117, 104], &[102, 122], &[101], &[102]]; - let res_rev: Vec<&[u8]> = vec![&[107], &[117, 104], &[102, 122], &[101], &[102]]; - let max = -1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn no_needle_end_nostring() { - let v = [107, 13, 117, 104, 10, 102, 122, 32, 101, 9, 102, 9]; - let n = []; - let res: Vec<&[u8]> = vec![&[107], &[117, 104], &[102, 122], &[101], &[102]]; - let res_rev: Vec<&[u8]> = vec![&[107], &[117, 104], &[102, 122], &[101], &[102]]; - let max = -1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn no_needle_sides_no_max() { - let v = [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9]; - let n = []; - let res: Vec<&[u8]> = vec![&[117, 104], &[102, 122], &[101, 102]]; - let res_rev: Vec<&[u8]> = vec![&[117, 104], &[102, 122], &[101, 102]]; - let max = -1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn no_needle_sides_max_zero() { - let v = [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9]; - let n = []; - let res: Vec<&[u8]> = vec![&[117, 104, 10, 102, 122, 32, 101, 102, 9, 9]]; - let res_rev: Vec<&[u8]> = vec![&[13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102]]; - let max = 0; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn no_needle_sides_max_one() { - let v = [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9]; - let n = []; - let res: Vec<&[u8]> = vec![&[117, 104], &[102, 122, 32, 101, 102, 9, 9]]; - let res_rev: Vec<&[u8]> = vec![&[13, 13, 13, 117, 104, 10, 102, 122], &[101, 102]]; - let max = 1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn no_needle_sides_max_two() { - let v = [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9]; - let n = []; - let res: Vec<&[u8]> = vec![&[117, 104], &[102, 122], &[101, 102, 9, 9]]; - let res_rev: Vec<&[u8]> = vec![&[13, 13, 13, 117, 104], &[102, 122], &[101, 102]]; - let max = 2; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - #[test] - fn no_needle_no_max_big_spaces() { - let v = [ - 13, 13, 13, 117, 104, 10, 10, 10, 102, 122, 32, 32, 101, 102, 9, 9, - ]; - let n = []; - let res: Vec<&[u8]> = vec![&[117, 104], &[102, 122], &[101, 102]]; - let res_rev: Vec<&[u8]> = vec![&[117, 104], &[102, 122], &[101, 102]]; - let max = -1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn cpython_needle() { - let v = [49, 44, 50, 44, 51]; - let n = [44]; - let res: Vec<&[u8]> = vec![&[49], &[50], &[51]]; - let res_rev: Vec<&[u8]> = vec![&[49], &[50], &[51]]; - let max = -1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn cpython_needle_max_one() { - let v = [49, 44, 50, 44, 51]; - let n = [44]; - let res: Vec<&[u8]> = vec![&[49], &[50, 44, 51]]; - let res_rev: Vec<&[u8]> = vec![&[49, 44, 50], &[51]]; - let max = 1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn cpython_nearneedle() { - let v = [49, 44, 50, 44, 44, 51, 44]; - let n = [44]; - let res: Vec<&[u8]> = vec![&[49], &[50], &[], &[51], &[]]; - let res_rev: Vec<&[u8]> = vec![&[49], &[50], &[], &[51], &[]]; - let max = -1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn cpython_space_no_sep() { - let v = [49, 32, 50, 32, 51]; - let n = []; - let res: Vec<&[u8]> = vec![&[49], &[50], &[51]]; - let res_rev: Vec<&[u8]> = vec![&[49], &[50], &[51]]; - let max = -1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn cpython_space_no_sep_max_one() { - let v = [49, 32, 50, 32, 51]; - let n = []; - let res: Vec<&[u8]> = vec![&[49], &[50, 32, 51]]; - let res_rev: Vec<&[u8]> = vec![&[49, 32, 50], &[51]]; - let max = 1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - - #[test] - fn cpython_bigspace() { - let v = [32, 32, 32, 49, 32, 32, 32, 50, 32, 32, 32, 51, 32, 32, 32]; - let n = []; - let res: Vec<&[u8]> = vec![&[49], &[50], &[51]]; - let res_rev: Vec<&[u8]> = vec![&[49], &[50], &[51]]; - let max = -1; - - assert_eq!(split_slice(&v, &n, max), res); - assert_eq!(split_slice_reverse(&v, &n, max), res_rev) - } - -} From ea535856b4f7715dec102655aabc57fa8dc9b015 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sun, 5 May 2019 20:40:32 +0300 Subject: [PATCH 539/884] Raise error on out-of-range input in `chr()` --- vm/src/builtins.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index a82914ad16..b99a386088 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -73,10 +73,10 @@ fn builtin_callable(obj: PyObjectRef, vm: &VirtualMachine) -> bool { vm.is_callable(&obj) } -fn builtin_chr(i: u32, _vm: &VirtualMachine) -> String { +fn builtin_chr(i: u32, vm: &VirtualMachine) -> PyResult { match char::from_u32(i) { - Some(value) => value.to_string(), - None => '_'.to_string(), + Some(value) => Ok(value.to_string()), + None => Err(vm.new_value_error("chr() arg not in range(0x110000)".to_string())), } } From c656c5cc72b732a6200779e04d9ecb950f63a310 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Mon, 6 May 2019 03:10:53 +0300 Subject: [PATCH 540/884] Add `chr()` test snippets --- tests/snippets/builtin_chr.py | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 tests/snippets/builtin_chr.py diff --git a/tests/snippets/builtin_chr.py b/tests/snippets/builtin_chr.py new file mode 100644 index 0000000000..1ad642a586 --- /dev/null +++ b/tests/snippets/builtin_chr.py @@ -0,0 +1,8 @@ +from testutils import assert_raises + +assert "a" == chr(97) +assert "é" == chr(233) +assert "🤡" == chr(129313) + +assert_raises(TypeError, lambda: chr(), "chr() takes exactly one argument (0 given)") +assert_raises(ValueError, lambda: chr(0x110005), "ValueError: chr() arg not in range(0x110000)") From e35dec64f9410bfc06115b2bb1bae42cb498a97d Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Mon, 6 May 2019 21:47:18 +0300 Subject: [PATCH 541/884] Add `vm.new_name_error` helper --- vm/src/frame.rs | 5 +---- vm/src/vm.rs | 5 +++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index ddddad91e2..ae9958969b 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -1090,10 +1090,7 @@ impl Frame { let value = match optional_value { Some(value) => value, None => { - let name_error_type = vm.ctx.exceptions.name_error.clone(); - let msg = format!("name '{}' is not defined", name); - let name_error = vm.new_exception(name_error_type, msg); - return Err(name_error); + return Err(vm.new_name_error(format!("name '{}' is not defined", name))); } }; diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 48bec9302b..6b2f0e0b11 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -186,6 +186,11 @@ impl VirtualMachine { self.new_exception(type_error, msg) } + pub fn new_name_error(&self, msg: String) -> PyObjectRef { + let name_error = self.ctx.exceptions.name_error.clone(); + self.new_exception(name_error, msg) + } + pub fn new_unsupported_operand_error( &self, a: PyObjectRef, From ed94ddba2dbf6e9c9e1562694cda27aa3a08dbba Mon Sep 17 00:00:00 2001 From: jgirardet Date: Mon, 6 May 2019 21:04:09 +0200 Subject: [PATCH 542/884] add split, rsplit, partition, rpartition, expandtabs, spitlines, zfill, replace to bytearray --- tests/snippets/bytearray.py | 239 +++++++++++++++++++++++++++++++++++- vm/src/obj/objbytearray.rs | 86 ++++++++++++- vm/src/obj/objbytes.rs | 4 - 3 files changed, 322 insertions(+), 7 deletions(-) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index 4f29fa1877..d00af5bbdc 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -357,6 +357,243 @@ assert bytearray(b"mississippi").rstrip(b"ipz") == bytearray(b"mississ") + +# split +assert bytearray(b"1,2,3").split(bytearray(b",")) == [bytearray(b"1"), bytearray(b"2"), bytearray(b"3")] +assert bytearray(b"1,2,3").split(bytearray(b","), maxsplit=1) == [bytearray(b"1"), bytearray(b"2,3")] +assert bytearray(b"1,2,,3,").split(bytearray(b",")) == [bytearray(b"1"), bytearray(b"2"), bytearray(b""), bytearray(b"3"), bytearray(b"")] +assert bytearray(b"1 2 3").split() == [bytearray(b"1"), bytearray(b"2"), bytearray(b"3")] +assert bytearray(b"1 2 3").split(maxsplit=1) == [bytearray(b"1"), bytearray(b"2 3")] +assert bytearray(b" 1 2 3 ").split() == [bytearray(b"1"), bytearray(b"2"), bytearray(b"3")] +assert bytearray(b"k\ruh\nfz e f").split() == [bytearray(b"k"), bytearray(b"uh"), bytearray(b"fz"), bytearray(b"e"), bytearray(b"f")] +assert bytearray(b"Two lines\n").split(bytearray(b"\n")) == [bytearray(b"Two lines"), bytearray(b"")] +assert bytearray(b"").split() == [] +assert bytearray(b"").split(bytearray(b"\n")) == [bytearray(b"")] +assert bytearray(b"\n").split(bytearray(b"\n")) == [bytearray(b""), bytearray(b"")] + +SPLIT_FIXTURES = [ + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1, 2, 3], [1, 2, 3], [1, 2, 3]], + [[1, 2, 3], [1, 2, 3], [1, 2, 3]], + -1, + ], + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5], + [4, 5], + [[1, 2, 3], [1, 2, 3], [1, 2, 3], []], + [[1, 2, 3], [1, 2, 3], [1, 2, 3], []], + -1, + ], + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3], + [4, 5], + [[1, 2, 3], [1, 2, 3], [1, 2, 3], [3]], + [[1, 2, 3], [1, 2, 3], [1, 2, 3], [3]], + -1, + ], + [ + [4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[], [2, 3], [1, 2, 3], [1, 2, 3]], + [[], [2, 3], [1, 2, 3], [1, 2, 3]], + -1, + ], + [ + [1, 4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1], [2, 3], [1, 2, 3], [1, 2, 3]], + [[1], [2, 3], [1, 2, 3], [1, 2, 3]], + -1, + ], + [ + [1, 2, 3, 4, 5, 4, 5, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1, 2, 3], [], [], [1, 2, 3], [1, 2, 3]], + [[1, 2, 3], [], [], [1, 2, 3], [1, 2, 3]], + -1, + ], + # maxsplit + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1, 2, 3], [1, 2, 3, 4, 5, 1, 2, 3]], + [[1, 2, 3, 4, 5, 1, 2, 3], [1, 2, 3]], + 1, + ], + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5], + [4, 5], + [[1, 2, 3], [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]], + [[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], []], + 1, + ], + [ + [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3], + [4, 5], + [[1, 2, 3], [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 3]], + [[1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], [3]], + 1, + ], + [ + [4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[], [2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]], + [[4, 5, 2, 3, 4, 5, 1, 2, 3], [1, 2, 3]], + 1, + ], + [ + [1, 4, 5, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1], [2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]], + [[1, 4, 5, 2, 3, 4, 5, 1, 2, 3], [1, 2, 3]], + 1, + ], + [ + [1, 2, 3, 4, 5, 4, 5, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3], + [4, 5], + [[1, 2, 3], [], [4, 5, 1, 2, 3, 4, 5, 1, 2, 3]], + [[1, 2, 3, 4, 5, 4, 5], [1, 2, 3], [1, 2, 3]], + 2, + ], + [ + [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9], + None, + [[117, 104], [102, 122], [101, 102]], + [[117, 104], [102, 122], [101, 102]], + -1, + ], + [ + [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9], + None, + [[117, 104, 10, 102, 122, 32, 101, 102, 9, 9]], + [[13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102]], + 0, + ], + [ + [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9], + None, + [[117, 104], [102, 122, 32, 101, 102, 9, 9]], + [[13, 13, 13, 117, 104, 10, 102, 122], [101, 102]], + 1, + ], + [ + [13, 13, 13, 117, 104, 10, 102, 122, 32, 101, 102, 9, 9], + None, + [[117, 104], [102, 122], [101, 102, 9, 9]], + [[13, 13, 13, 117, 104], [102, 122], [101, 102]], + 2, + ], + [ + [13, 13, 13, 117, 104, 10, 10, 10, 102, 122, 32, 32, 101, 102, 9, 9], + None, + [[117, 104], [102, 122], [101, 102]], + [[117, 104], [102, 122], [101, 102]], + -1, + ], + [[49, 44, 50, 44, 51], [44], [[49], [50], [51]], [[49], [50], [51]], -1], + [[49, 44, 50, 44, 51], [44], [[49], [50, 44, 51]], [[49, 44, 50], [51]], 1], + [ + [49, 44, 50, 44, 44, 51, 44], + [44], + [[49], [50], [], [51], []], + [[49], [50], [], [51], []], + -1, + ], + [[49, 32, 50, 32, 51], None, [[49], [50], [51]], [[49], [50], [51]], -1], + [[49, 32, 50, 32, 51], None, [[49], [50, 32, 51]], [[49, 32, 50], [51]], 1], + [ + [32, 32, 32, 49, 32, 32, 32, 50, 32, 32, 32, 51, 32, 32, 32], + None, + [[49], [50], [51]], + [[49], [50], [51]], + -1, + ], +] + + +# for i in SPLIT_FIXTURES: # for not yet implemented : TypeError: Unsupported method: __next__ +n_sp = 0 +while n_sp < len(SPLIT_FIXTURES): + i = SPLIT_FIXTURES[n_sp] + sep = None if i[1] == None else bytearray(i[1]) + try: + assert bytearray(i[0]).split(sep=sep, maxsplit=i[4]) == [bytearray(j) for j in i[2]] + except AssertionError: + print(i[0], i[1], i[2]) + print( + "Expected : ", [list(x) for x in bytearray(i[0]).split(sep=sep, maxsplit=i[4])] + ) + break + + try: + assert bytearray(i[0]).rsplit(sep=sep, maxsplit=i[4]) == [bytearray(j) for j in i[3]] + except AssertionError: + print(i[0], i[1], i[2]) + print( + "Expected Rev : ", + [list(x) for x in bytearray(i[0]).rsplit(sep=sep, maxsplit=i[4])], + ) + break + + n_sp += 1 + + +# expandtabs +a = bytearray(b"\x01\x03\r\x05\t8CYZ\t\x06CYZ\t\x17cba`\n\x12\x13\x14") +assert ( + a.expandtabs() == bytearray(b"\x01\x03\r\x05 8CYZ \x06CYZ \x17cba`\n\x12\x13\x14") +) +assert a.expandtabs(5) == bytearray(b"\x01\x03\r\x05 8CYZ \x06CYZ \x17cba`\n\x12\x13\x14") +assert bytearray(b"01\t012\t0123\t01234").expandtabs() == bytearray(b"01 012 0123 01234") +assert bytearray(b"01\t012\t0123\t01234").expandtabs(4) == bytearray(b"01 012 0123 01234") +assert bytearray(b"123\t123").expandtabs(-5) == bytearray(b"123123") +assert bytearray(b"123\t123").expandtabs(0) == bytearray(b"123123") + + +# # partition +assert bytearray(b"123456789").partition(b"45") == ((b"123"), bytearray(b"45"), bytearray(b"6789")) +assert bytearray(b"14523456789").partition(b"45") == ((b"1"), bytearray(b"45"), bytearray(b"23456789")) +a = bytearray(b"14523456789").partition(b"45") +assert isinstance(a[1], bytearray) +a = bytearray(b"14523456789").partition(memoryview(b"45")) +assert isinstance(a[1], bytearray) + +# partition +assert bytearray(b"123456789").rpartition(bytearray(b"45")) == ((bytearray(b"123")), bytearray(b"45"), bytearray(b"6789")) +assert bytearray(b"14523456789").rpartition(bytearray(b"45")) == ((bytearray(b"14523")), bytearray(b"45"), bytearray(b"6789")) +a = bytearray(b"14523456789").rpartition(b"45") +assert isinstance(a[1], bytearray) +a = bytearray(b"14523456789").rpartition(memoryview(b"45")) +assert isinstance(a[1], bytearray) + +# splitlines +assert bytearray(b"ab c\n\nde fg\rkl\r\n").splitlines() == [bytearray(b"ab c"), bytearray(b""), bytearray(b"de fg"), bytearray(b"kl")] +assert bytearray(b"ab c\n\nde fg\rkl\r\n").splitlines(keepends=True) == [ + bytearray(b"ab c\n"), + bytearray(b"\n"), + bytearray(b"de fg\r"), + bytearray(b"kl\r\n"), +] +assert bytearray(b"").splitlines() == [] +assert bytearray(b"One line\n").splitlines() == [b"One line"] + +# zfill + +assert bytearray(b"42").zfill(5) == bytearray(b"00042") +assert bytearray(b"-42").zfill(5) == bytearray(b"-0042") +assert bytearray(b"42").zfill(1) == bytearray(b"42") +assert bytearray(b"42").zfill(-1) == bytearray(b"42") + +# replace +assert bytearray(b"123456789123").replace(b"23",b"XX") ==bytearray(b'1XX4567891XX') +assert bytearray(b"123456789123").replace(b"23",b"XX", 1) ==bytearray(b'1XX456789123') +assert bytearray(b"123456789123").replace(b"23",b"XX", 0) == bytearray(b"123456789123") +assert bytearray(b"123456789123").replace(b"23",b"XX", -1) ==bytearray(b'1XX4567891XX') +assert bytearray(b"123456789123").replace(b"23", bytearray(b"")) == bytearray(b"14567891") + + # clear a = bytearray(b"abcd") a.clear() @@ -387,5 +624,3 @@ assert a == bytearray(b"append") assert len(a) == 6 assert a.pop() == 100 - -import bytes as bbytes diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 4336243399..f6441587db 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -2,7 +2,8 @@ use crate::function::OptionalArg; use crate::obj::objbyteinner::{ - ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, ByteInnerPosition, + ByteInnerExpandtabsOptions, ByteInnerFindOptions, ByteInnerNewOptions, ByteInnerPaddingOptions, + ByteInnerPosition, ByteInnerSplitOptions, ByteInnerSplitlinesOptions, ByteInnerTranslateOptions, ByteOr, PyByteInner, }; use crate::obj::objint::PyIntRef; @@ -351,6 +352,89 @@ impl PyByteArrayRef { )) } + #[pymethod(name = "split")] + fn split(self, options: ByteInnerSplitOptions, vm: &VirtualMachine) -> PyResult { + let as_bytes = self + .inner + .borrow() + .split(options, false)? + .iter() + .map(|x| vm.ctx.new_bytearray(x.to_vec())) + .collect::>(); + Ok(vm.ctx.new_list(as_bytes)) + } + + #[pymethod(name = "rsplit")] + fn rsplit(self, options: ByteInnerSplitOptions, vm: &VirtualMachine) -> PyResult { + let as_bytes = self + .inner + .borrow() + .split(options, true)? + .iter() + .map(|x| vm.ctx.new_bytearray(x.to_vec())) + .collect::>(); + Ok(vm.ctx.new_list(as_bytes)) + } + + #[pymethod(name = "partition")] + fn partition(self, sep: PyByteInner, vm: &VirtualMachine) -> PyResult { + // sep ALWAYS converted to bytearray even it's bytes or memoryview + // so its ok to accept PyByteInner + let (left, right) = self.inner.borrow().partition(&sep, false)?; + Ok(vm.ctx.new_tuple(vec![ + vm.ctx.new_bytearray(left), + vm.ctx.new_bytearray(sep.elements), + vm.ctx.new_bytearray(right), + ])) + } + + #[pymethod(name = "rpartition")] + fn rpartition(self, sep: PyByteInner, vm: &VirtualMachine) -> PyResult { + let (left, right) = self.inner.borrow().partition(&sep, true)?; + Ok(vm.ctx.new_tuple(vec![ + vm.ctx.new_bytearray(left), + vm.ctx.new_bytearray(sep.elements), + vm.ctx.new_bytearray(right), + ])) + } + + #[pymethod(name = "expandtabs")] + fn expandtabs(self, options: ByteInnerExpandtabsOptions, vm: &VirtualMachine) -> PyResult { + Ok(vm + .ctx + .new_bytearray(self.inner.borrow().expandtabs(options))) + } + + #[pymethod(name = "splitlines")] + fn splitlines(self, options: ByteInnerSplitlinesOptions, vm: &VirtualMachine) -> PyResult { + let as_bytes = self + .inner + .borrow() + .splitlines(options) + .iter() + .map(|x| vm.ctx.new_bytearray(x.to_vec())) + .collect::>(); + Ok(vm.ctx.new_list(as_bytes)) + } + + #[pymethod(name = "zfill")] + fn zfill(self, width: PyIntRef, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytearray(self.inner.borrow().zfill(width))) + } + + #[pymethod(name = "replace")] + fn replace( + self, + old: PyByteInner, + new: PyByteInner, + count: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + Ok(vm + .ctx + .new_bytearray(self.inner.borrow().replace(old, new, count)?)) + } + #[pymethod(name = "clear")] fn clear(self, _vm: &VirtualMachine) { self.inner.borrow_mut().elements.clear(); diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 8aca626464..6ffe0c0297 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -350,8 +350,6 @@ impl PyBytesRef { #[pymethod(name = "partition")] fn partition(self, sep: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // TODO: when implementing bytearray,remember sep ALWAYS converted to bytearray - // even it's bytes or memoryview so PyByteInner wiil be ok for args let sepa = PyByteInner::try_from_object(vm, sep.clone())?; let (left, right) = self.inner.partition(&sepa, false)?; @@ -361,8 +359,6 @@ impl PyBytesRef { } #[pymethod(name = "rpartition")] fn rpartition(self, sep: PyObjectRef, vm: &VirtualMachine) -> PyResult { - // TODO: when implementing bytearray,remember sep ALWAYS converted to bytearray - // even it's bytes or memoryview so PyByteInner wiil be ok for args let sepa = PyByteInner::try_from_object(vm, sep.clone())?; let (left, right) = self.inner.partition(&sepa, true)?; From dc18356947aeeffe670711024436ecc78aa3532d Mon Sep 17 00:00:00 2001 From: jgirardet Date: Mon, 6 May 2019 21:37:15 +0200 Subject: [PATCH 543/884] add bytes/byterray title --- tests/snippets/bytearray.py | 8 +++++++- tests/snippets/bytes.py | 13 +++++++++---- vm/src/obj/objbytearray.rs | 6 ++++++ vm/src/obj/objbyteinner.rs | 24 ++++++++++++++++++++++++ vm/src/obj/objbytes.rs | 5 +++++ 5 files changed, 51 insertions(+), 5 deletions(-) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index 4f29fa1877..4cda6229e1 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -388,4 +388,10 @@ assert len(a) == 6 assert a.pop() == 100 -import bytes as bbytes +# title +assert bytearray(b"Hello world").title() == bytearray(b"Hello World") +assert ( + bytearray(b"they're bill's friends from the UK").title() + == bytearray(b"They'Re Bill'S Friends From The Uk") +) + diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index 99de4898a9..a0cc348dbc 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -178,7 +178,6 @@ b"kok".center(-5) == b"kok" - # ljust assert [b"koki".ljust(i, b"|") for i in range(3, 10)] == [ b"koki", @@ -577,9 +576,15 @@ assert b"42".zfill(-1) == b"42" # replace -assert b"123456789123".replace(b"23", b"XX") == b'1XX4567891XX' -assert b"123456789123".replace(b"23", b"XX", 1) == b'1XX456789123' +assert b"123456789123".replace(b"23", b"XX") == b"1XX4567891XX" +assert b"123456789123".replace(b"23", b"XX", 1) == b"1XX456789123" assert b"123456789123".replace(b"23", b"XX", 0) == b"123456789123" -assert b"123456789123".replace(b"23", b"XX", -1) == b'1XX4567891XX' +assert b"123456789123".replace(b"23", b"XX", -1) == b"1XX4567891XX" assert b"123456789123".replace(b"23", b"") == b"14567891" +# title +assert b"Hello world".title() == b"Hello World" +assert ( + b"they're bill's friends from the UK".title() + == b"They'Re Bill'S Friends From The Uk" +) diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 4336243399..75c817ac8e 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -364,6 +364,7 @@ impl PyByteArrayRef { .push(x.as_bigint().byte_or(vm)?); Ok(()) } + #[pymethod(name = "pop")] fn pop(self, vm: &VirtualMachine) -> PyResult { let bytes = &mut self.inner.borrow_mut().elements; @@ -371,6 +372,11 @@ impl PyByteArrayRef { .pop() .ok_or_else(|| vm.new_index_error("pop from empty bytearray".to_string())) } + + #[pymethod(name = "title")] + fn title(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytearray(self.inner.borrow().title())) + } } // fn set_value(obj: &PyObjectRef, value: Vec) { diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 89e483bbc9..16b3713098 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -975,6 +975,30 @@ impl PyByteInner { Ok(res) } + + pub fn title(&self) -> Vec { + let mut res = vec![]; + let mut spaced = true; + + for i in self.elements.iter() { + match i { + 65..=90 | 97..=122 => { + if spaced { + res.push(i.to_ascii_uppercase()); + spaced = false + } else { + res.push(i.to_ascii_lowercase()); + } + } + _ => { + res.push(*i); + spaced = true + } + } + } + + res + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 8aca626464..7b8082b523 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -402,6 +402,11 @@ impl PyBytesRef { ) -> PyResult { Ok(vm.ctx.new_bytes(self.inner.replace(old, new, count)?)) } + + #[pymethod(name = "title")] + fn title(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.title())) + } } #[pyclass] From 04bff49ee0092507eed7a5d4e0c437e679cfbe0e Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Mon, 6 May 2019 22:40:00 +0300 Subject: [PATCH 544/884] Raise proper `NameError` on `del` when name is not defined Closes #929. --- tests/snippets/delete.py | 5 ++++- vm/src/frame.rs | 12 +++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/tests/snippets/delete.py b/tests/snippets/delete.py index 57d432ff6f..aa7ae0536c 100644 --- a/tests/snippets/delete.py +++ b/tests/snippets/delete.py @@ -1,4 +1,4 @@ -from testutils import assert_raises +from testutils import assert_raises, assertRaises a = 1 del a @@ -16,3 +16,6 @@ class MyObject: pass del (x, y) assert_raises(NameError, lambda: x) assert_raises(NameError, lambda: y) + +with assertRaises(NameError): + del y diff --git a/vm/src/frame.rs b/vm/src/frame.rs index ae9958969b..50c317c373 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -124,7 +124,7 @@ impl Scope { pub trait NameProtocol { fn load_name(&self, vm: &VirtualMachine, name: &str) -> Option; fn store_name(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); - fn delete_name(&self, vm: &VirtualMachine, name: &str); + fn delete_name(&self, vm: &VirtualMachine, name: &str) -> PyResult; fn load_cell(&self, vm: &VirtualMachine, name: &str) -> Option; fn store_cell(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef); fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option; @@ -169,8 +169,8 @@ impl NameProtocol for Scope { self.get_locals().set_item(key, value, vm).unwrap(); } - fn delete_name(&self, vm: &VirtualMachine, key: &str) { - self.get_locals().del_item(key, vm).unwrap(); + fn delete_name(&self, vm: &VirtualMachine, key: &str) -> PyResult { + self.get_locals().del_item(key, vm) } fn load_global(&self, vm: &VirtualMachine, name: &str) -> Option { @@ -1071,8 +1071,10 @@ impl Frame { } fn delete_name(&self, vm: &VirtualMachine, name: &str) -> FrameResult { - self.scope.delete_name(vm, name); - Ok(None) + match self.scope.delete_name(vm, name) { + Ok(_) => Ok(None), + Err(_) => Err(vm.new_name_error(format!("name '{}' is not defined", name))), + } } fn load_name( From e12c6813efcd8ab060ea464e96087c80051e3d41 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 7 May 2019 04:41:31 +0900 Subject: [PATCH 545/884] Add dict unpacking support for literal --- parser/src/ast.rs | 2 +- parser/src/python.lalrpop | 9 +++++++-- tests/snippets/dict.py | 7 +++++++ vm/src/compile.rs | 18 +++++++++++++++--- vm/src/stdlib/ast.rs | 6 +++++- vm/src/symboltable.rs | 6 +++++- 6 files changed, 40 insertions(+), 8 deletions(-) diff --git a/parser/src/ast.rs b/parser/src/ast.rs index e0a0501383..ec0677fe82 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -195,7 +195,7 @@ pub enum Expression { elements: Vec, }, Dict { - elements: Vec<(Expression, Expression)>, + elements: Vec<(Option, Expression)>, }, Set { elements: Vec, diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 02fd9e0b92..8d6dd7cac6 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -833,8 +833,8 @@ TestListComp2: ast::Expression = { }, }; -TestDict: Vec<(ast::Expression, ast::Expression)> = { - > <_trailing_comma:","?> => elements, +TestDict: Vec<(Option, ast::Expression)> = { + > <_trailing_comma:","?> => elements, }; TestDictComp: ast::Expression = { @@ -850,6 +850,11 @@ DictEntry: (ast::Expression, ast::Expression) = { ":" => (e1, e2), }; +DictElement: (Option, ast::Expression) = { + => (Some(e.0), e.1), + "**" => (None, e), +}; + TestSet: Vec = { > ","? => e1 }; diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 7d67668c93..270d2c07e2 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -191,3 +191,10 @@ def __eq__(self, other): assert {1: None, "b": None} == dict.fromkeys([1, "b"]) assert {1: 0, "b": 0} == dict.fromkeys([1, "b"], 0) + +x = {'a': 1, 'b': 1, 'c': 1} +y = {'b': 2, 'c': 2, 'd': 2} +z = {'c': 3, 'd': 3, 'e': 3} + +w = {1: 1, **x, 2: 2, **y, 3: 3, **z, 4: 4} +assert w == {1: 1, 'a': 1, 'b': 2, 'c': 3, 2: 2, 'd': 3, 3: 3, 'e': 3, 4: 4} diff --git a/vm/src/compile.rs b/vm/src/compile.rs index 21f1402a69..b1964ca557 100644 --- a/vm/src/compile.rs +++ b/vm/src/compile.rs @@ -1256,13 +1256,25 @@ impl Compiler { } ast::Expression::Dict { elements } => { let size = elements.len(); + let has_double_star = elements.iter().any(|e| e.0.is_none()); for (key, value) in elements { - self.compile_expression(key)?; - self.compile_expression(value)?; + if let Some(key) = key { + self.compile_expression(key)?; + self.compile_expression(value)?; + if has_double_star { + self.emit(Instruction::BuildMap { + size: 1, + unpack: false, + }); + } + } else { + // dict unpacking + self.compile_expression(value)?; + } } self.emit(Instruction::BuildMap { size, - unpack: false, + unpack: has_double_star, }); } ast::Expression::Slice { elements } => { diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index f27b5eb408..2ac36c81e2 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -358,7 +358,11 @@ fn expression_to_ast(vm: &VirtualMachine, expression: &ast::Expression) -> PyRes let mut keys = Vec::new(); let mut values = Vec::new(); for (k, v) in elements { - keys.push(expression_to_ast(vm, k)?.into_object()); + if let Some(k) = k { + keys.push(expression_to_ast(vm, k)?.into_object()); + } else { + keys.push(vm.ctx.none()); + } values.push(expression_to_ast(vm, v)?.into_object()); } diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs index 1d4a72ed90..ea9ae52a0a 100644 --- a/vm/src/symboltable.rs +++ b/vm/src/symboltable.rs @@ -404,7 +404,11 @@ impl SymbolTableBuilder { } ast::Expression::Dict { elements } => { for (key, value) in elements { - self.scan_expression(key)?; + if let Some(key) = key { + self.scan_expression(key)?; + } else { + // dict unpacking marker + } self.scan_expression(value)?; } } From d4a3d7eb0189559012eab51b29c7acce87c44ede Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Mon, 6 May 2019 23:23:48 +0300 Subject: [PATCH 546/884] Raise an error on `range()` with step=0 --- tests/snippets/builtin_range.py | 1 + vm/src/obj/objrange.rs | 11 ++++------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/snippets/builtin_range.py b/tests/snippets/builtin_range.py index 55d4f51943..03d1cb2cf7 100644 --- a/tests/snippets/builtin_range.py +++ b/tests/snippets/builtin_range.py @@ -17,6 +17,7 @@ assert_raises(ValueError, lambda: range(10).index(10), 'out of bounds') assert_raises(ValueError, lambda: range(4, 10, 2).index(5), 'out of step') assert_raises(ValueError, lambda: range(10).index('foo'), 'not an int') +assert_raises(ValueError, lambda: range(1, 10, 0), 'step is zero') # count tests assert range(10).count(2) == 1 diff --git a/vm/src/obj/objrange.rs b/vm/src/obj/objrange.rs index 43fcc668cb..de937535fa 100644 --- a/vm/src/obj/objrange.rs +++ b/vm/src/obj/objrange.rs @@ -130,14 +130,11 @@ impl PyRange { step: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - PyRange { - start, - stop, - step: step - .into_option() - .unwrap_or_else(|| PyInt::new(BigInt::one()).into_ref(vm)), + let step = step.unwrap_or_else(|| PyInt::new(BigInt::one()).into_ref(vm)); + if step.as_bigint().is_zero() { + return Err(vm.new_value_error("range() arg 3 must not be zero".to_string())); } - .into_ref_with_type(vm, cls) + PyRange { start, stop, step }.into_ref_with_type(vm, cls) } #[pyproperty(name = "start")] From 6153e2d6ca2d8ff85df869c8cfba274b3b7b1592 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Wed, 8 May 2019 16:25:33 +0300 Subject: [PATCH 547/884] Add CPython genericpath.py --- Lib/genericpath.py | 151 ++++++++++++++++++++++++++++++++++++ Lib/os.py | 33 ++++++++ tests/snippets/stdlib_os.py | 8 ++ 3 files changed, 192 insertions(+) create mode 100644 Lib/genericpath.py diff --git a/Lib/genericpath.py b/Lib/genericpath.py new file mode 100644 index 0000000000..5dd703d736 --- /dev/null +++ b/Lib/genericpath.py @@ -0,0 +1,151 @@ +""" +Path operations common to more than one OS +Do not use directly. The OS specific modules import the appropriate +functions from this module themselves. +""" +import os +import stat + +__all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime', + 'getsize', 'isdir', 'isfile', 'samefile', 'sameopenfile', + 'samestat'] + + +# Does a path exist? +# This is false for dangling symbolic links on systems that support them. +def exists(path): + """Test whether a path exists. Returns False for broken symbolic links""" + try: + os.stat(path) + except (OSError, ValueError): + return False + return True + + +# This follows symbolic links, so both islink() and isdir() can be true +# for the same path on systems that support symlinks +def isfile(path): + """Test whether a path is a regular file""" + try: + st = os.stat(path) + except (OSError, ValueError): + return False + return stat.S_ISREG(st.st_mode) + + +# Is a path a directory? +# This follows symbolic links, so both islink() and isdir() +# can be true for the same path on systems that support symlinks +def isdir(s): + """Return true if the pathname refers to an existing directory.""" + try: + st = os.stat(s) + except (OSError, ValueError): + return False + return stat.S_ISDIR(st.st_mode) + + +def getsize(filename): + """Return the size of a file, reported by os.stat().""" + return os.stat(filename).st_size + + +def getmtime(filename): + """Return the last modification time of a file, reported by os.stat().""" + return os.stat(filename).st_mtime + + +def getatime(filename): + """Return the last access time of a file, reported by os.stat().""" + return os.stat(filename).st_atime + + +def getctime(filename): + """Return the metadata change time of a file, reported by os.stat().""" + return os.stat(filename).st_ctime + + +# Return the longest prefix of all list elements. +def commonprefix(m): + "Given a list of pathnames, returns the longest common leading component" + if not m: return '' + # Some people pass in a list of pathname parts to operate in an OS-agnostic + # fashion; don't try to translate in that case as that's an abuse of the + # API and they are already doing what they need to be OS-agnostic and so + # they most likely won't be using an os.PathLike object in the sublists. + if not isinstance(m[0], (list, tuple)): + m = tuple(map(os.fspath, m)) + s1 = min(m) + s2 = max(m) + for i, c in enumerate(s1): + if c != s2[i]: + return s1[:i] + return s1 + +# Are two stat buffers (obtained from stat, fstat or lstat) +# describing the same file? +def samestat(s1, s2): + """Test whether two stat buffers reference the same file""" + return (s1.st_ino == s2.st_ino and + s1.st_dev == s2.st_dev) + + +# Are two filenames really pointing to the same file? +def samefile(f1, f2): + """Test whether two pathnames reference the same actual file""" + s1 = os.stat(f1) + s2 = os.stat(f2) + return samestat(s1, s2) + + +# Are two open files really referencing the same file? +# (Not necessarily the same file descriptor!) +def sameopenfile(fp1, fp2): + """Test whether two open file objects reference the same file""" + s1 = os.fstat(fp1) + s2 = os.fstat(fp2) + return samestat(s1, s2) + + +# Split a path in root and extension. +# The extension is everything starting at the last dot in the last +# pathname component; the root is everything before that. +# It is always true that root + ext == p. + +# Generic implementation of splitext, to be parametrized with +# the separators +def _splitext(p, sep, altsep, extsep): + """Split the extension from a pathname. + + Extension is everything from the last dot to the end, ignoring + leading dots. Returns "(root, ext)"; ext may be empty.""" + # NOTE: This code must work for text and bytes strings. + + sepIndex = p.rfind(sep) + if altsep: + altsepIndex = p.rfind(altsep) + sepIndex = max(sepIndex, altsepIndex) + + dotIndex = p.rfind(extsep) + if dotIndex > sepIndex: + # skip all leading dots + filenameIndex = sepIndex + 1 + while filenameIndex < dotIndex: + if p[filenameIndex:filenameIndex+1] != extsep: + return p[:dotIndex], p[dotIndex:] + filenameIndex += 1 + + return p, p[:0] + +def _check_arg_types(funcname, *args): + hasstr = hasbytes = False + for s in args: + if isinstance(s, str): + hasstr = True + elif isinstance(s, bytes): + hasbytes = True + else: + raise TypeError('%s() argument must be str or bytes, not %r' % + (funcname, s.__class__.__name__)) from None + if hasstr and hasbytes: + raise TypeError("Can't mix strings and bytes in path components") from None diff --git a/Lib/os.py b/Lib/os.py index a14702d5e4..afe7bb4e0c 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -135,3 +135,36 @@ def getenv(key, default=None): The optional second argument can specify an alternate default. key, default and the result are str.""" return environ.get(key, default) + + +def fspath(path): + """Return the path representation of a path-like object. + + If str or bytes is passed in, it is returned unchanged. Otherwise the + os.PathLike interface is used to get the path representation. If the + path representation is not str or bytes, TypeError is raised. If the + provided path is not str, bytes, or os.PathLike, TypeError is raised. + """ + if isinstance(path, (str, bytes)): + return path + + # Work from the object's type to match method resolution of other magic + # methods. + path_type = type(path) + try: + path_repr = path_type.__fspath__(path) + except AttributeError: + if hasattr(path_type, '__fspath__'): + raise + else: + raise TypeError("expected str, bytes or os.PathLike object, " + "not " + path_type.__name__) + if isinstance(path_repr, (str, bytes)): + return path_repr + else: + raise TypeError("expected {}.__fspath__() to return str or bytes, " + "not {}".format(path_type.__name__, + type(path_repr).__name__)) + + +import genericpath as path diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 8d111b6f3e..079f733c64 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -150,3 +150,11 @@ def __exit__(self, exc_type, exc_val, exc_tb): os.stat(fname, follow_symlinks=False).st_ino == os.stat(symlink_file, follow_symlinks=False).st_ino os.stat(fname, follow_symlinks=False).st_mode == os.stat(symlink_file, follow_symlinks=False).st_mode + + # os.path + assert os.path.exists(fname) == True + assert os.path.exists("NO_SUCH_FILE") == False + assert os.path.isfile(fname) == True + assert os.path.isdir(folder) == True + assert os.path.isfile(folder) == False + assert os.path.isdir(fname) == False From daae7aa998ab85b72c6c126816ca4761075ee22d Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Wed, 8 May 2019 16:31:21 +0300 Subject: [PATCH 548/884] Add CPython posixpath.py --- Lib/posixpath.py | 525 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 525 insertions(+) create mode 100644 Lib/posixpath.py diff --git a/Lib/posixpath.py b/Lib/posixpath.py new file mode 100644 index 0000000000..ecb4e5a8f7 --- /dev/null +++ b/Lib/posixpath.py @@ -0,0 +1,525 @@ +"""Common operations on Posix pathnames. + +Instead of importing this module directly, import os and refer to +this module as os.path. The "os.path" name is an alias for this +module on Posix systems; on other systems (e.g. Windows), +os.path provides the same operations in a manner specific to that +platform, and is an alias to another module (e.g. ntpath). + +Some of this can actually be useful on non-Posix systems too, e.g. +for manipulation of the pathname component of URLs. +""" + +# Strings representing various path-related bits and pieces. +# These are primarily for export; internally, they are hardcoded. +# Should be set before imports for resolving cyclic dependency. +curdir = '.' +pardir = '..' +extsep = '.' +sep = '/' +pathsep = ':' +defpath = '/bin:/usr/bin' +altsep = None +devnull = '/dev/null' + +import os +import sys +import stat +import genericpath +from genericpath import * + +__all__ = ["normcase","isabs","join","splitdrive","split","splitext", + "basename","dirname","commonprefix","getsize","getmtime", + "getatime","getctime","islink","exists","lexists","isdir","isfile", + "ismount", "expanduser","expandvars","normpath","abspath", + "samefile","sameopenfile","samestat", + "curdir","pardir","sep","pathsep","defpath","altsep","extsep", + "devnull","realpath","supports_unicode_filenames","relpath", + "commonpath"] + + +def _get_sep(path): + if isinstance(path, bytes): + return b'/' + else: + return '/' + +# Normalize the case of a pathname. Trivial in Posix, string.lower on Mac. +# On MS-DOS this may also turn slashes into backslashes; however, other +# normalizations (such as optimizing '../' away) are not allowed +# (another function should be defined to do that). + +def normcase(s): + """Normalize case of pathname. Has no effect under Posix""" + return os.fspath(s) + + +# Return whether a path is absolute. +# Trivial in Posix, harder on the Mac or MS-DOS. + +def isabs(s): + """Test whether a path is absolute""" + s = os.fspath(s) + sep = _get_sep(s) + return s.startswith(sep) + + +# Join pathnames. +# Ignore the previous parts if a part is absolute. +# Insert a '/' unless the first part is empty or already ends in '/'. + +def join(a, *p): + """Join two or more pathname components, inserting '/' as needed. + If any component is an absolute path, all previous path components + will be discarded. An empty last part will result in a path that + ends with a separator.""" + a = os.fspath(a) + sep = _get_sep(a) + path = a + try: + if not p: + path[:0] + sep #23780: Ensure compatible data type even if p is null. + for b in map(os.fspath, p): + if b.startswith(sep): + path = b + elif not path or path.endswith(sep): + path += b + else: + path += sep + b + except (TypeError, AttributeError, BytesWarning): + genericpath._check_arg_types('join', a, *p) + raise + return path + + +# Split a path in head (everything up to the last '/') and tail (the +# rest). If the path ends in '/', tail will be empty. If there is no +# '/' in the path, head will be empty. +# Trailing '/'es are stripped from head unless it is the root. + +def split(p): + """Split a pathname. Returns tuple "(head, tail)" where "tail" is + everything after the final slash. Either part may be empty.""" + p = os.fspath(p) + sep = _get_sep(p) + i = p.rfind(sep) + 1 + head, tail = p[:i], p[i:] + if head and head != sep*len(head): + head = head.rstrip(sep) + return head, tail + + +# Split a path in root and extension. +# The extension is everything starting at the last dot in the last +# pathname component; the root is everything before that. +# It is always true that root + ext == p. + +def splitext(p): + p = os.fspath(p) + if isinstance(p, bytes): + sep = b'/' + extsep = b'.' + else: + sep = '/' + extsep = '.' + return genericpath._splitext(p, sep, None, extsep) +splitext.__doc__ = genericpath._splitext.__doc__ + +# Split a pathname into a drive specification and the rest of the +# path. Useful on DOS/Windows/NT; on Unix, the drive is always empty. + +def splitdrive(p): + """Split a pathname into drive and path. On Posix, drive is always + empty.""" + p = os.fspath(p) + return p[:0], p + + +# Return the tail (basename) part of a path, same as split(path)[1]. + +def basename(p): + """Returns the final component of a pathname""" + p = os.fspath(p) + sep = _get_sep(p) + i = p.rfind(sep) + 1 + return p[i:] + + +# Return the head (dirname) part of a path, same as split(path)[0]. + +def dirname(p): + """Returns the directory component of a pathname""" + p = os.fspath(p) + sep = _get_sep(p) + i = p.rfind(sep) + 1 + head = p[:i] + if head and head != sep*len(head): + head = head.rstrip(sep) + return head + + +# Is a path a symbolic link? +# This will always return false on systems where os.lstat doesn't exist. + +def islink(path): + """Test whether a path is a symbolic link""" + try: + st = os.lstat(path) + except (OSError, ValueError, AttributeError): + return False + return stat.S_ISLNK(st.st_mode) + +# Being true for dangling symbolic links is also useful. + +def lexists(path): + """Test whether a path exists. Returns True for broken symbolic links""" + try: + os.lstat(path) + except (OSError, ValueError): + return False + return True + + +# Is a path a mount point? +# (Does this work for all UNIXes? Is it even guaranteed to work by Posix?) + +def ismount(path): + """Test whether a path is a mount point""" + try: + s1 = os.lstat(path) + except (OSError, ValueError): + # It doesn't exist -- so not a mount point. :-) + return False + else: + # A symlink can never be a mount point + if stat.S_ISLNK(s1.st_mode): + return False + + if isinstance(path, bytes): + parent = join(path, b'..') + else: + parent = join(path, '..') + parent = realpath(parent) + try: + s2 = os.lstat(parent) + except (OSError, ValueError): + return False + + dev1 = s1.st_dev + dev2 = s2.st_dev + if dev1 != dev2: + return True # path/.. on a different device as path + ino1 = s1.st_ino + ino2 = s2.st_ino + if ino1 == ino2: + return True # path/.. is the same i-node as path + return False + + +# Expand paths beginning with '~' or '~user'. +# '~' means $HOME; '~user' means that user's home directory. +# If the path doesn't begin with '~', or if the user or $HOME is unknown, +# the path is returned unchanged (leaving error reporting to whatever +# function is called with the expanded path as argument). +# See also module 'glob' for expansion of *, ? and [...] in pathnames. +# (A function should also be defined to do full *sh-style environment +# variable expansion.) + +def expanduser(path): + """Expand ~ and ~user constructions. If user or $HOME is unknown, + do nothing.""" + path = os.fspath(path) + if isinstance(path, bytes): + tilde = b'~' + else: + tilde = '~' + if not path.startswith(tilde): + return path + sep = _get_sep(path) + i = path.find(sep, 1) + if i < 0: + i = len(path) + if i == 1: + if 'HOME' not in os.environ: + import pwd + try: + userhome = pwd.getpwuid(os.getuid()).pw_dir + except KeyError: + # bpo-10496: if the current user identifier doesn't exist in the + # password database, return the path unchanged + return path + else: + userhome = os.environ['HOME'] + else: + import pwd + name = path[1:i] + if isinstance(name, bytes): + name = str(name, 'ASCII') + try: + pwent = pwd.getpwnam(name) + except KeyError: + # bpo-10496: if the user name from the path doesn't exist in the + # password database, return the path unchanged + return path + userhome = pwent.pw_dir + if isinstance(path, bytes): + userhome = os.fsencode(userhome) + root = b'/' + else: + root = '/' + userhome = userhome.rstrip(root) + return (userhome + path[i:]) or root + + +# Expand paths containing shell variable substitutions. +# This expands the forms $variable and ${variable} only. +# Non-existent variables are left unchanged. + +_varprog = None +_varprogb = None + +def expandvars(path): + """Expand shell variables of form $var and ${var}. Unknown variables + are left unchanged.""" + path = os.fspath(path) + global _varprog, _varprogb + if isinstance(path, bytes): + if b'$' not in path: + return path + if not _varprogb: + import re + _varprogb = re.compile(br'\$(\w+|\{[^}]*\})', re.ASCII) + search = _varprogb.search + start = b'{' + end = b'}' + environ = getattr(os, 'environb', None) + else: + if '$' not in path: + return path + if not _varprog: + import re + _varprog = re.compile(r'\$(\w+|\{[^}]*\})', re.ASCII) + search = _varprog.search + start = '{' + end = '}' + environ = os.environ + i = 0 + while True: + m = search(path, i) + if not m: + break + i, j = m.span(0) + name = m.group(1) + if name.startswith(start) and name.endswith(end): + name = name[1:-1] + try: + if environ is None: + value = os.fsencode(os.environ[os.fsdecode(name)]) + else: + value = environ[name] + except KeyError: + i = j + else: + tail = path[j:] + path = path[:i] + value + i = len(path) + path += tail + return path + + +# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A/B. +# It should be understood that this may change the meaning of the path +# if it contains symbolic links! + +def normpath(path): + """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) + if isinstance(path, bytes): + sep = b'/' + empty = b'' + dot = b'.' + dotdot = b'..' + else: + sep = '/' + empty = '' + dot = '.' + dotdot = '..' + if path == empty: + return dot + initial_slashes = path.startswith(sep) + # POSIX allows one or two initial slashes, but treats three or more + # as single slash. + if (initial_slashes and + path.startswith(sep*2) and not path.startswith(sep*3)): + initial_slashes = 2 + comps = path.split(sep) + new_comps = [] + for comp in comps: + if comp in (empty, dot): + continue + if (comp != dotdot or (not initial_slashes and not new_comps) or + (new_comps and new_comps[-1] == dotdot)): + new_comps.append(comp) + elif new_comps: + new_comps.pop() + comps = new_comps + path = sep.join(comps) + if initial_slashes: + path = sep*initial_slashes + path + return path or dot + + +def abspath(path): + """Return an absolute path.""" + path = os.fspath(path) + if not isabs(path): + if isinstance(path, bytes): + cwd = os.getcwdb() + else: + cwd = os.getcwd() + path = join(cwd, path) + return normpath(path) + + +# Return a canonical path (i.e. the absolute location of a file on the +# filesystem). + +def realpath(filename): + """Return the canonical path of the specified filename, eliminating any +symbolic links encountered in the path.""" + filename = os.fspath(filename) + path, ok = _joinrealpath(filename[:0], filename, {}) + return abspath(path) + +# Join two paths, normalizing and eliminating any symbolic links +# encountered in the second path. +def _joinrealpath(path, rest, seen): + if isinstance(path, bytes): + sep = b'/' + curdir = b'.' + pardir = b'..' + else: + sep = '/' + curdir = '.' + pardir = '..' + + if isabs(rest): + rest = rest[1:] + path = sep + + while rest: + name, _, rest = rest.partition(sep) + if not name or name == curdir: + # current dir + continue + if name == pardir: + # parent dir + if path: + path, name = split(path) + if name == pardir: + path = join(path, pardir, pardir) + else: + path = pardir + continue + newpath = join(path, name) + if not islink(newpath): + path = newpath + continue + # Resolve the symbolic link + if newpath in seen: + # Already seen this path + path = seen[newpath] + if path is not None: + # use cached value + continue + # The symlink is not resolved, so we must have a symlink loop. + # Return already resolved part + rest of the path unchanged. + return join(newpath, rest), False + seen[newpath] = None # not resolved symlink + path, ok = _joinrealpath(path, os.readlink(newpath), seen) + if not ok: + return join(path, rest), False + seen[newpath] = path # resolved symlink + + return path, True + + +supports_unicode_filenames = (sys.platform == 'darwin') + +def relpath(path, start=None): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + + path = os.fspath(path) + if isinstance(path, bytes): + curdir = b'.' + sep = b'/' + pardir = b'..' + else: + curdir = '.' + sep = '/' + pardir = '..' + + if start is None: + start = curdir + else: + start = os.fspath(start) + + try: + start_list = [x for x in abspath(start).split(sep) if x] + path_list = [x for x in abspath(path).split(sep) if x] + # Work out how much of the filepath is shared by start and path. + i = len(commonprefix([start_list, path_list])) + + rel_list = [pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return curdir + return join(*rel_list) + except (TypeError, AttributeError, BytesWarning, DeprecationWarning): + genericpath._check_arg_types('relpath', path, start) + raise + + +# Return the longest common sub-path of the sequence of paths given as input. +# The paths are not normalized before comparing them (this is the +# responsibility of the caller). Any trailing separator is stripped from the +# returned path. + +def commonpath(paths): + """Given a sequence of path names, returns the longest common sub-path.""" + + if not paths: + raise ValueError('commonpath() arg is an empty sequence') + + paths = tuple(map(os.fspath, paths)) + if isinstance(paths[0], bytes): + sep = b'/' + curdir = b'.' + else: + sep = '/' + curdir = '.' + + try: + split_paths = [path.split(sep) for path in paths] + + try: + isabs, = set(p[:1] == sep for p in paths) + except ValueError: + raise ValueError("Can't mix absolute and relative paths") from None + + split_paths = [[c for c in s if c and c != curdir] for s in split_paths] + s1 = min(split_paths) + s2 = max(split_paths) + common = s1 + for i, c in enumerate(s1): + if c != s2[i]: + common = s1[:i] + break + + prefix = sep if isabs else sep[:0] + return prefix + sep.join(common) + except (TypeError, AttributeError): + genericpath._check_arg_types('commonpath', *paths) + raise From ac9ddf7756b4977a8e8e85b59b8b67909238a8d4 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Wed, 8 May 2019 16:32:09 +0300 Subject: [PATCH 549/884] Add CPython ntpath.py --- Lib/ntpath.py | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 Lib/ntpath.py diff --git a/Lib/ntpath.py b/Lib/ntpath.py new file mode 100644 index 0000000000..f3cfabf023 --- /dev/null +++ b/Lib/ntpath.py @@ -0,0 +1,661 @@ +# Module 'ntpath' -- common operations on WinNT/Win95 pathnames +"""Common pathname manipulations, WindowsNT/95 version. + +Instead of importing this module directly, import os and refer to this +module as os.path. +""" + +# strings representing various path-related bits and pieces +# These are primarily for export; internally, they are hardcoded. +# Should be set before imports for resolving cyclic dependency. +curdir = '.' +pardir = '..' +extsep = '.' +sep = '\\' +pathsep = ';' +altsep = '/' +defpath = '.;C:\\bin' +devnull = 'nul' + +import os +import sys +import stat +import genericpath +from genericpath import * + +__all__ = ["normcase","isabs","join","splitdrive","split","splitext", + "basename","dirname","commonprefix","getsize","getmtime", + "getatime","getctime", "islink","exists","lexists","isdir","isfile", + "ismount", "expanduser","expandvars","normpath","abspath", + "curdir","pardir","sep","pathsep","defpath","altsep", + "extsep","devnull","realpath","supports_unicode_filenames","relpath", + "samefile", "sameopenfile", "samestat", "commonpath"] + +def _get_bothseps(path): + if isinstance(path, bytes): + return b'\\/' + else: + return '\\/' + +# Normalize the case of a pathname and map slashes to backslashes. +# Other normalizations (such as optimizing '../' away) are not done +# (this is done by normpath). + +def normcase(s): + """Normalize case of pathname. + + Makes all characters lowercase and all slashes into backslashes.""" + s = os.fspath(s) + if isinstance(s, bytes): + return s.replace(b'/', b'\\').lower() + else: + return s.replace('/', '\\').lower() + + +# Return whether a path is absolute. +# Trivial in Posix, harder on Windows. +# For Windows it is absolute if it starts with a slash or backslash (current +# volume), or if a pathname after the volume-letter-and-colon or UNC-resource +# starts with a slash or backslash. + +def isabs(s): + """Test whether a path is absolute""" + s = os.fspath(s) + s = splitdrive(s)[1] + return len(s) > 0 and s[0] in _get_bothseps(s) + + +# Join two (or more) paths. +def join(path, *paths): + path = os.fspath(path) + if isinstance(path, bytes): + sep = b'\\' + seps = b'\\/' + colon = b':' + else: + sep = '\\' + seps = '\\/' + colon = ':' + try: + if not paths: + path[:0] + sep #23780: Ensure compatible data type even if p is null. + result_drive, result_path = splitdrive(path) + for p in map(os.fspath, paths): + p_drive, p_path = splitdrive(p) + if p_path and p_path[0] in seps: + # Second path is absolute + if p_drive or not result_drive: + result_drive = p_drive + result_path = p_path + continue + elif p_drive and p_drive != result_drive: + if p_drive.lower() != result_drive.lower(): + # Different drives => ignore the first path entirely + result_drive = p_drive + result_path = p_path + continue + # Same drive in different case + result_drive = p_drive + # Second path is relative to the first + if result_path and result_path[-1] not in seps: + result_path = result_path + sep + result_path = result_path + p_path + ## add separator between UNC and non-absolute path + if (result_path and result_path[0] not in seps and + result_drive and result_drive[-1:] != colon): + return result_drive + sep + result_path + return result_drive + result_path + except (TypeError, AttributeError, BytesWarning): + genericpath._check_arg_types('join', path, *paths) + raise + + +# Split a path in a drive specification (a drive letter followed by a +# colon) and the path specification. +# It is always true that drivespec + pathspec == p +def splitdrive(p): + """Split a pathname into drive/UNC sharepoint and relative path specifiers. + Returns a 2-tuple (drive_or_unc, path); either part may be empty. + + If you assign + result = splitdrive(p) + It is always true that: + result[0] + result[1] == p + + If the path contained a drive letter, drive_or_unc will contain everything + up to and including the colon. e.g. splitdrive("c:/dir") returns ("c:", "/dir") + + If the path contained a UNC path, the drive_or_unc will contain the host name + and share up to but not including the fourth directory separator character. + e.g. splitdrive("//host/computer/dir") returns ("//host/computer", "/dir") + + Paths cannot contain both a drive letter and a UNC path. + + """ + p = os.fspath(p) + if len(p) >= 2: + if isinstance(p, bytes): + sep = b'\\' + altsep = b'/' + colon = b':' + else: + sep = '\\' + altsep = '/' + colon = ':' + normp = p.replace(altsep, sep) + if (normp[0:2] == sep*2) and (normp[2:3] != sep): + # is a UNC path: + # vvvvvvvvvvvvvvvvvvvv drive letter or UNC path + # \\machine\mountpoint\directory\etc\... + # directory ^^^^^^^^^^^^^^^ + index = normp.find(sep, 2) + if index == -1: + return p[:0], p + index2 = normp.find(sep, index + 1) + # a UNC path can't have two slashes in a row + # (after the initial two) + if index2 == index + 1: + return p[:0], p + if index2 == -1: + index2 = len(p) + return p[:index2], p[index2:] + if normp[1:2] == colon: + return p[:2], p[2:] + return p[:0], p + + +# Split a path in head (everything up to the last '/') and tail (the +# rest). After the trailing '/' is stripped, the invariant +# join(head, tail) == p holds. +# The resulting head won't end in '/' unless it is the root. + +def split(p): + """Split a pathname. + + Return tuple (head, tail) where tail is everything after the final slash. + Either part may be empty.""" + p = os.fspath(p) + seps = _get_bothseps(p) + d, p = splitdrive(p) + # set i to index beyond p's last slash + i = len(p) + while i and p[i-1] not in seps: + i -= 1 + head, tail = p[:i], p[i:] # now tail has no slashes + # remove trailing slashes from head, unless it's all slashes + head = head.rstrip(seps) or head + return d + head, tail + + +# Split a path in root and extension. +# The extension is everything starting at the last dot in the last +# pathname component; the root is everything before that. +# It is always true that root + ext == p. + +def splitext(p): + p = os.fspath(p) + if isinstance(p, bytes): + return genericpath._splitext(p, b'\\', b'/', b'.') + else: + return genericpath._splitext(p, '\\', '/', '.') +splitext.__doc__ = genericpath._splitext.__doc__ + + +# Return the tail (basename) part of a path. + +def basename(p): + """Returns the final component of a pathname""" + return split(p)[1] + + +# Return the head (dirname) part of a path. + +def dirname(p): + """Returns the directory component of a pathname""" + return split(p)[0] + +# Is a path a symbolic link? +# This will always return false on systems where os.lstat doesn't exist. + +def islink(path): + """Test whether a path is a symbolic link. + This will always return false for Windows prior to 6.0. + """ + try: + st = os.lstat(path) + except (OSError, ValueError, AttributeError): + return False + return stat.S_ISLNK(st.st_mode) + +# Being true for dangling symbolic links is also useful. + +def lexists(path): + """Test whether a path exists. Returns True for broken symbolic links""" + try: + st = os.lstat(path) + except (OSError, ValueError): + return False + return True + +# Is a path a mount point? +# Any drive letter root (eg c:\) +# Any share UNC (eg \\server\share) +# Any volume mounted on a filesystem folder +# +# No one method detects all three situations. Historically we've lexically +# detected drive letter roots and share UNCs. The canonical approach to +# detecting mounted volumes (querying the reparse tag) fails for the most +# common case: drive letter roots. The alternative which uses GetVolumePathName +# fails if the drive letter is the result of a SUBST. +try: + from nt import _getvolumepathname +except ImportError: + _getvolumepathname = None +def ismount(path): + """Test whether a path is a mount point (a drive root, the root of a + share, or a mounted volume)""" + path = os.fspath(path) + seps = _get_bothseps(path) + path = abspath(path) + root, rest = splitdrive(path) + if root and root[0] in seps: + return (not rest) or (rest in seps) + if rest in seps: + return True + + if _getvolumepathname: + return path.rstrip(seps) == _getvolumepathname(path).rstrip(seps) + else: + return False + + +# Expand paths beginning with '~' or '~user'. +# '~' means $HOME; '~user' means that user's home directory. +# If the path doesn't begin with '~', or if the user or $HOME is unknown, +# the path is returned unchanged (leaving error reporting to whatever +# function is called with the expanded path as argument). +# See also module 'glob' for expansion of *, ? and [...] in pathnames. +# (A function should also be defined to do full *sh-style environment +# variable expansion.) + +def expanduser(path): + """Expand ~ and ~user constructs. + + If user or $HOME is unknown, do nothing.""" + path = os.fspath(path) + if isinstance(path, bytes): + tilde = b'~' + else: + tilde = '~' + if not path.startswith(tilde): + return path + i, n = 1, len(path) + while i < n and path[i] not in _get_bothseps(path): + i += 1 + + if 'USERPROFILE' in os.environ: + userhome = os.environ['USERPROFILE'] + elif not 'HOMEPATH' in os.environ: + return path + else: + try: + drive = os.environ['HOMEDRIVE'] + except KeyError: + drive = '' + userhome = join(drive, os.environ['HOMEPATH']) + + if isinstance(path, bytes): + userhome = os.fsencode(userhome) + + if i != 1: #~user + userhome = join(dirname(userhome), path[1:i]) + + return userhome + path[i:] + + +# Expand paths containing shell variable substitutions. +# The following rules apply: +# - no expansion within single quotes +# - '$$' is translated into '$' +# - '%%' is translated into '%' if '%%' are not seen in %var1%%var2% +# - ${varname} is accepted. +# - $varname is accepted. +# - %varname% is accepted. +# - varnames can be made out of letters, digits and the characters '_-' +# (though is not verified in the ${varname} and %varname% cases) +# XXX With COMMAND.COM you can use any characters in a variable name, +# XXX except '^|<>='. + +def expandvars(path): + """Expand shell variables of the forms $var, ${var} and %var%. + + Unknown variables are left unchanged.""" + path = os.fspath(path) + if isinstance(path, bytes): + if b'$' not in path and b'%' not in path: + return path + import string + varchars = bytes(string.ascii_letters + string.digits + '_-', 'ascii') + quote = b'\'' + percent = b'%' + brace = b'{' + rbrace = b'}' + dollar = b'$' + environ = getattr(os, 'environb', None) + else: + if '$' not in path and '%' not in path: + return path + import string + varchars = string.ascii_letters + string.digits + '_-' + quote = '\'' + percent = '%' + brace = '{' + rbrace = '}' + dollar = '$' + environ = os.environ + res = path[:0] + index = 0 + pathlen = len(path) + while index < pathlen: + c = path[index:index+1] + if c == quote: # no expansion within single quotes + path = path[index + 1:] + pathlen = len(path) + try: + index = path.index(c) + res += c + path[:index + 1] + except ValueError: + res += c + path + index = pathlen - 1 + elif c == percent: # variable or '%' + if path[index + 1:index + 2] == percent: + res += c + index += 1 + else: + path = path[index+1:] + pathlen = len(path) + try: + index = path.index(percent) + except ValueError: + res += percent + path + index = pathlen - 1 + else: + var = path[:index] + try: + if environ is None: + value = os.fsencode(os.environ[os.fsdecode(var)]) + else: + value = environ[var] + except KeyError: + value = percent + var + percent + res += value + elif c == dollar: # variable or '$$' + if path[index + 1:index + 2] == dollar: + res += c + index += 1 + elif path[index + 1:index + 2] == brace: + path = path[index+2:] + pathlen = len(path) + try: + index = path.index(rbrace) + except ValueError: + res += dollar + brace + path + index = pathlen - 1 + else: + var = path[:index] + try: + if environ is None: + value = os.fsencode(os.environ[os.fsdecode(var)]) + else: + value = environ[var] + except KeyError: + value = dollar + brace + var + rbrace + res += value + else: + var = path[:0] + index += 1 + c = path[index:index + 1] + while c and c in varchars: + var += c + index += 1 + c = path[index:index + 1] + try: + if environ is None: + value = os.fsencode(os.environ[os.fsdecode(var)]) + else: + value = environ[var] + except KeyError: + value = dollar + var + res += value + if c: + index -= 1 + else: + res += c + index += 1 + return res + + +# Normalize a path, e.g. A//B, A/./B and A/foo/../B all become A\B. +# Previously, this function also truncated pathnames to 8+3 format, +# but as this module is called "ntpath", that's obviously wrong! + +def normpath(path): + """Normalize path, eliminating double slashes, etc.""" + path = os.fspath(path) + if isinstance(path, bytes): + sep = b'\\' + altsep = b'/' + curdir = b'.' + pardir = b'..' + special_prefixes = (b'\\\\.\\', b'\\\\?\\') + else: + sep = '\\' + altsep = '/' + curdir = '.' + pardir = '..' + special_prefixes = ('\\\\.\\', '\\\\?\\') + if path.startswith(special_prefixes): + # in the case of paths with these prefixes: + # \\.\ -> device names + # \\?\ -> literal paths + # do not do any normalization, but return the path unchanged + return path + path = path.replace(altsep, sep) + prefix, path = splitdrive(path) + + # collapse initial backslashes + if path.startswith(sep): + prefix += sep + path = path.lstrip(sep) + + comps = path.split(sep) + i = 0 + while i < len(comps): + if not comps[i] or comps[i] == curdir: + del comps[i] + elif comps[i] == pardir: + if i > 0 and comps[i-1] != pardir: + del comps[i-1:i+1] + i -= 1 + elif i == 0 and prefix.endswith(sep): + del comps[i] + else: + i += 1 + else: + i += 1 + # If the path is now empty, substitute '.' + if not prefix and not comps: + comps.append(curdir) + return prefix + sep.join(comps) + +def _abspath_fallback(path): + """Return the absolute version of a path as a fallback function in case + `nt._getfullpathname` is not available or raises OSError. See bpo-31047 for + more. + + """ + + path = os.fspath(path) + if not isabs(path): + if isinstance(path, bytes): + cwd = os.getcwdb() + else: + cwd = os.getcwd() + path = join(cwd, path) + return normpath(path) + +# Return an absolute path. +try: + from nt import _getfullpathname + +except ImportError: # not running on Windows - mock up something sensible + abspath = _abspath_fallback + +else: # use native Windows method on Windows + def abspath(path): + """Return the absolute version of a path.""" + try: + return normpath(_getfullpathname(path)) + except (OSError, ValueError): + return _abspath_fallback(path) + +# realpath is a no-op on systems without islink support +realpath = abspath +# Win9x family and earlier have no Unicode filename support. +supports_unicode_filenames = (hasattr(sys, "getwindowsversion") and + sys.getwindowsversion()[3] >= 2) + +def relpath(path, start=None): + """Return a relative version of a path""" + path = os.fspath(path) + if isinstance(path, bytes): + sep = b'\\' + curdir = b'.' + pardir = b'..' + else: + sep = '\\' + curdir = '.' + pardir = '..' + + if start is None: + start = curdir + + if not path: + raise ValueError("no path specified") + + start = os.fspath(start) + try: + start_abs = abspath(normpath(start)) + path_abs = abspath(normpath(path)) + start_drive, start_rest = splitdrive(start_abs) + path_drive, path_rest = splitdrive(path_abs) + if normcase(start_drive) != normcase(path_drive): + raise ValueError("path is on mount %r, start on mount %r" % ( + path_drive, start_drive)) + + start_list = [x for x in start_rest.split(sep) if x] + path_list = [x for x in path_rest.split(sep) if x] + # Work out how much of the filepath is shared by start and path. + i = 0 + for e1, e2 in zip(start_list, path_list): + if normcase(e1) != normcase(e2): + break + i += 1 + + rel_list = [pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return curdir + return join(*rel_list) + except (TypeError, ValueError, AttributeError, BytesWarning, DeprecationWarning): + genericpath._check_arg_types('relpath', path, start) + raise + + +# Return the longest common sub-path of the sequence of paths given as input. +# The function is case-insensitive and 'separator-insensitive', i.e. if the +# only difference between two paths is the use of '\' versus '/' as separator, +# they are deemed to be equal. +# +# However, the returned path will have the standard '\' separator (even if the +# given paths had the alternative '/' separator) and will have the case of the +# first path given in the sequence. Additionally, any trailing separator is +# stripped from the returned path. + +def commonpath(paths): + """Given a sequence of path names, returns the longest common sub-path.""" + + if not paths: + raise ValueError('commonpath() arg is an empty sequence') + + paths = tuple(map(os.fspath, paths)) + if isinstance(paths[0], bytes): + sep = b'\\' + altsep = b'/' + curdir = b'.' + else: + sep = '\\' + altsep = '/' + curdir = '.' + + try: + drivesplits = [splitdrive(p.replace(altsep, sep).lower()) for p in paths] + split_paths = [p.split(sep) for d, p in drivesplits] + + try: + isabs, = set(p[:1] == sep for d, p in drivesplits) + except ValueError: + raise ValueError("Can't mix absolute and relative paths") from None + + # Check that all drive letters or UNC paths match. The check is made only + # now otherwise type errors for mixing strings and bytes would not be + # caught. + if len(set(d for d, p in drivesplits)) != 1: + raise ValueError("Paths don't have the same drive") + + drive, path = splitdrive(paths[0].replace(altsep, sep)) + common = path.split(sep) + common = [c for c in common if c and c != curdir] + + split_paths = [[c for c in s if c and c != curdir] for s in split_paths] + s1 = min(split_paths) + s2 = max(split_paths) + for i, c in enumerate(s1): + if c != s2[i]: + common = common[:i] + break + else: + common = common[:len(s1)] + + prefix = drive + sep if isabs else drive + return prefix + sep.join(common) + except (TypeError, AttributeError): + genericpath._check_arg_types('commonpath', *paths) + raise + + +# determine if two files are in fact the same file +try: + # GetFinalPathNameByHandle is available starting with Windows 6.0. + # Windows XP and non-Windows OS'es will mock _getfinalpathname. + if sys.getwindowsversion()[:2] >= (6, 0): + from nt import _getfinalpathname + else: + raise ImportError +except (AttributeError, ImportError): + # On Windows XP and earlier, two files are the same if their absolute + # pathnames are the same. + # Non-Windows operating systems fake this method with an XP + # approximation. + def _getfinalpathname(f): + return normcase(abspath(f)) + + +try: + # The genericpath.isdir implementation uses os.stat and checks the mode + # attribute to tell whether or not the path is a directory. + # This is overkill on Windows - just pass the path to GetFileAttributes + # and check the attribute from there. + from nt import _isdir as isdir +except ImportError: + # Use genericpath.isdir as imported above. + pass From 216f69e635e77e99ff08c4321c851114953c7148 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Wed, 8 May 2019 16:46:45 +0300 Subject: [PATCH 550/884] Use os.path --- Lib/os.py | 20 +++++++++----------- Lib/posixpath.py | 2 +- tests/snippets/stdlib_os.py | 15 +++++++++------ 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index afe7bb4e0c..f1ff15b3cf 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -1,19 +1,19 @@ +import sys + from _os import * -curdir = '.' -pardir = '..' -extsep = '.' if name == 'nt': - sep = '\\' linesep = '\r\n' - altsep = '/' - pathsep = ';' + import ntpath as path else: - sep = '/' linesep = '\n' - altsep = None - pathsep = ':' + import posixpath as path + + +sys.modules['os.path'] = path +from os.path import (curdir, pardir, sep, pathsep, defpath, extsep, altsep, + devnull) # Change environ to automatically call putenv(), unsetenv if they exist. from _collections_abc import MutableMapping @@ -166,5 +166,3 @@ def fspath(path): "not {}".format(path_type.__name__, type(path_repr).__name__)) - -import genericpath as path diff --git a/Lib/posixpath.py b/Lib/posixpath.py index ecb4e5a8f7..ae54f23872 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -444,7 +444,7 @@ def _joinrealpath(path, rest, seen): return path, True -supports_unicode_filenames = (sys.platform == 'darwin') +supports_unicode_filenames = (hasattr(sys, "platform") and sys.platform == 'darwin') def relpath(path, start=None): """Return a relative version of a path""" diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 079f733c64..26a101ecb5 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -55,7 +55,7 @@ def __enter__(self): base_folder = os.environ["TEMP"] else: base_folder = "/tmp" - name = base_folder + os.sep + "rustpython_test_os_" + str(int(time.time())) + name = os.path.join(base_folder, "rustpython_test_os_" + str(int(time.time()))) os.mkdir(name) self.name = name return name @@ -75,7 +75,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): CONTENT3 = b"BOYA" with TestWithTempDir() as tmpdir: - fname = tmpdir + os.sep + FILE_NAME + fname = os.path.join(tmpdir, FILE_NAME) with open(fname, "wb"): pass fd = os.open(fname, 1) @@ -88,15 +88,15 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert os.read(fd, len(CONTENT3)) == CONTENT3 os.close(fd) - fname2 = tmpdir + os.sep + FILE_NAME2 + fname2 = os.path.join(tmpdir, FILE_NAME2) with open(fname2, "wb"): pass - folder = tmpdir + os.sep + FOLDER + folder = os.path.join(tmpdir, FOLDER) os.mkdir(folder) - symlink_file = tmpdir + os.sep + SYMLINK_FILE + symlink_file = os.path.join(tmpdir, SYMLINK_FILE) os.symlink(fname, symlink_file) - symlink_folder = tmpdir + os.sep + SYMLINK_FOLDER + symlink_folder = os.path.join(tmpdir, SYMLINK_FOLDER) os.symlink(folder, symlink_folder) names = set() @@ -158,3 +158,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert os.path.isdir(folder) == True assert os.path.isfile(folder) == False assert os.path.isdir(fname) == False + + assert os.path.basename(fname) == FILE_NAME + assert os.path.dirname(fname) == tmpdir From f5653cbc6819eb00b4200eccbc6dd2ab0ddd0d1f Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Wed, 8 May 2019 16:47:49 +0300 Subject: [PATCH 551/884] Use supported raise from --- Lib/os.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index f1ff15b3cf..c768946c52 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -33,8 +33,7 @@ def __getitem__(self, key): value = self._data[self.encodekey(key)] except KeyError: # raise KeyError with the original key value - # raise KeyError(key) from None - raise KeyError(key) + raise KeyError(key) from None return self.decodevalue(value) @@ -51,8 +50,7 @@ def __delitem__(self, key): del self._data[encodedkey] except KeyError: # raise KeyError with the original key value - # raise KeyError(key) from None - raise KeyError(key) + raise KeyError(key) from None def __iter__(self): # list() from dict object is an atomic operation From 7d0ab6395bfa2e122df72bfc4f834686c8ad6ef0 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Thu, 9 May 2019 09:45:41 +0900 Subject: [PATCH 552/884] Revert mappingproxy hack --- Lib/_collections_abc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/_collections_abc.py b/Lib/_collections_abc.py index c9f7daa16f..3158a4f16c 100644 --- a/Lib/_collections_abc.py +++ b/Lib/_collections_abc.py @@ -52,7 +52,7 @@ dict_values = type({}.values()) dict_items = type({}.items()) ## misc ## -# mappingproxy = type(type.__dict__) +mappingproxy = type(type.__dict__) # generator = type((lambda: (yield))()) ## coroutine ## # async def _coro(): pass @@ -688,7 +688,7 @@ def __eq__(self, other): __reversed__ = None -# Mapping.register(mappingproxy) +Mapping.register(mappingproxy) class MappingView(Sized): From 036b184dd38ad6022de355ef4f025aad6bb2503f Mon Sep 17 00:00:00 2001 From: Shitong Wen Date: Thu, 9 May 2019 10:52:10 +0800 Subject: [PATCH 553/884] add from_bytes for int --- tests/snippets/ints.py | 5 +++++ vm/src/obj/objint.rs | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index 40b4c2c6d1..12f6f4b448 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -70,6 +70,11 @@ assert int("101", base=2) == 5 assert int(1) == 1 +assert int.from_bytes(b'\x00\x10', 'big') == 16 +assert int.from_bytes(b'\x00\x10', 'little') == 4096 +assert int.from_bytes(b'\xfc\x00', 'big', True) == -1024 +assert int.from_bytes(b'\xfc\x00', 'big', False) == 64512 + with assertRaises(TypeError): int(base=2) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 7effb0897b..33334f2e1d 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -489,6 +489,29 @@ impl PyInt { zelf } + #[pymethod] + fn from_bytes(bytes: PyByteInner, byteorder: PyStringRef, signed: OptionalArg, vm: &VirtualMachine) -> PyResult{ + let x; + if byteorder.value == "big" { + x = match signed { + OptionalArg::Present(true) => BigInt::from_signed_bytes_be(&bytes.elements), + _ => BigInt::from_bytes_be(Sign::Plus, &bytes.elements), + } + } + else if byteorder.value == "little" { + x = match signed { + OptionalArg::Present(true) => BigInt::from_signed_bytes_le(&bytes.elements), + _ => BigInt::from_bytes_le(Sign::Plus, &bytes.elements), + } + } + else { + return Err(vm.new_value_error( + "byteorder must be either 'little' or 'big'".to_string(), + )); + } + Ok(x) + } + #[pyproperty] fn real(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { zelf From e9fea281fb4faaf12a5bf93d724ca90758467a92 Mon Sep 17 00:00:00 2001 From: Shitong Wen Date: Thu, 9 May 2019 11:02:34 +0800 Subject: [PATCH 554/884] add from_bytes for int --- vm/src/obj/objint.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 33334f2e1d..d95afa54dd 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -1,7 +1,7 @@ use std::fmt; use std::hash::{Hash, Hasher}; -use num_bigint::BigInt; +use num_bigint::{BigInt, Sign}; use num_integer::Integer; use num_traits::{One, Pow, Signed, ToPrimitive, Zero}; @@ -15,6 +15,7 @@ use crate::vm::VirtualMachine; use super::objstr::{PyString, PyStringRef}; use super::objtype; +use super::objbyteinner::{PyByteInner}; use crate::obj::objtype::PyClassRef; /// int(x=0) -> integer From d3966af25d989fb2acad3de6aa7005783a7cc7b7 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Thu, 9 May 2019 15:54:07 +0900 Subject: [PATCH 555/884] Test all and any --- tests/snippets/builtin_all.py | 10 ++++++++++ tests/snippets/builtin_any.py | 19 ++++++++----------- tests/snippets/testutils.py | 9 +++++++++ 3 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 tests/snippets/builtin_all.py diff --git a/tests/snippets/builtin_all.py b/tests/snippets/builtin_all.py new file mode 100644 index 0000000000..4195d5f2ed --- /dev/null +++ b/tests/snippets/builtin_all.py @@ -0,0 +1,10 @@ +from testutils import assert_raises +from testutils import TestFailingBool, TestFailingIter + +assert all([True]) +assert not all([False]) +assert all([]) +assert not all([False, TestFailingBool()]) + +assert_raises(RuntimeError, lambda: all(TestFailingIter())) +assert_raises(RuntimeError, lambda: all([TestFailingBool()])) diff --git a/tests/snippets/builtin_any.py b/tests/snippets/builtin_any.py index a8679d5fe3..59b4514f85 100644 --- a/tests/snippets/builtin_any.py +++ b/tests/snippets/builtin_any.py @@ -1,13 +1,10 @@ -assert any([1]); -assert not any([]); -assert not any([0,0,0,0]); -assert any([0,0,1,0,0]); -def anything(a): - return a +from testutils import assert_raises +from testutils import TestFailingBool, TestFailingIter -class Test: - def __iter__(self): - while True: - yield True +assert any([True]) +assert not any([False]) +assert not any([]) +assert any([True, TestFailingBool()]) -assert any(map(anything, Test())) +assert_raises(RuntimeError, lambda: any(TestFailingIter())) +assert_raises(RuntimeError, lambda: any([TestFailingBool()])) diff --git a/tests/snippets/testutils.py b/tests/snippets/testutils.py index 49b7fedc17..ea4dd91384 100644 --- a/tests/snippets/testutils.py +++ b/tests/snippets/testutils.py @@ -34,3 +34,12 @@ def __exit__(self, exc_type, exc_val, exc_tb): if not issubclass(exc_type, self.expected): return False return True + + +class TestFailingBool: + def __bool__(self): + raise RuntimeError + +class TestFailingIter: + def __iter__(self): + raise RuntimeError From 86fcc977305f1fa1a6d9adc3b693d3c3e348f1b9 Mon Sep 17 00:00:00 2001 From: Shitong Wen Date: Thu, 9 May 2019 20:06:28 +0800 Subject: [PATCH 556/884] int_from_bytes --- tests/snippets/ints.py | 4 ++-- vm/src/obj/objint.rs | 41 +++++++++++++++++++++++++++-------------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index 12f6f4b448..ef9328d690 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -72,8 +72,8 @@ assert int.from_bytes(b'\x00\x10', 'big') == 16 assert int.from_bytes(b'\x00\x10', 'little') == 4096 -assert int.from_bytes(b'\xfc\x00', 'big', True) == -1024 -assert int.from_bytes(b'\xfc\x00', 'big', False) == 64512 +assert int.from_bytes(b'\xfc\x00', 'big', signed=True) == -1024 +assert int.from_bytes(b'\xfc\x00', 'big', signed=False) == 64512 with assertRaises(TypeError): int(base=2) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index d95afa54dd..21f50a182b 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -6,16 +6,16 @@ use num_integer::Integer; use num_traits::{One, Pow, Signed, ToPrimitive, Zero}; use crate::format::FormatSpec; -use crate::function::{OptionalArg, PyFuncArgs}; +use crate::function::{KwArgs, OptionalArg, PyFuncArgs}; use crate::pyobject::{ IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; +use super::objbyteinner::PyByteInner; use super::objstr::{PyString, PyStringRef}; use super::objtype; -use super::objbyteinner::{PyByteInner}; use crate::obj::objtype::PyClassRef; /// int(x=0) -> integer @@ -491,24 +491,37 @@ impl PyInt { } #[pymethod] - fn from_bytes(bytes: PyByteInner, byteorder: PyStringRef, signed: OptionalArg, vm: &VirtualMachine) -> PyResult{ + fn from_bytes( + bytes: PyByteInner, + byteorder: PyStringRef, + kwargs: KwArgs, + vm: &VirtualMachine, + ) -> PyResult { + let mut signed = false; + for (key, value) in kwargs.into_iter() { + if key == "signed" { + signed = match_class!(value, + + b @ PyInt => !b.as_bigint().is_zero(), + _ => false, + ); + } + } let x; if byteorder.value == "big" { x = match signed { - OptionalArg::Present(true) => BigInt::from_signed_bytes_be(&bytes.elements), - _ => BigInt::from_bytes_be(Sign::Plus, &bytes.elements), + true => BigInt::from_signed_bytes_be(&bytes.elements), + false => BigInt::from_bytes_be(Sign::Plus, &bytes.elements), } - } - else if byteorder.value == "little" { + } else if byteorder.value == "little" { x = match signed { - OptionalArg::Present(true) => BigInt::from_signed_bytes_le(&bytes.elements), - _ => BigInt::from_bytes_le(Sign::Plus, &bytes.elements), + true => BigInt::from_signed_bytes_le(&bytes.elements), + false => BigInt::from_bytes_le(Sign::Plus, &bytes.elements), } - } - else { - return Err(vm.new_value_error( - "byteorder must be either 'little' or 'big'".to_string(), - )); + } else { + return Err( + vm.new_value_error("byteorder must be either 'little' or 'big'".to_string()) + ); } Ok(x) } From 3196b6be73219eae356092c0dcf1683c920d1d06 Mon Sep 17 00:00:00 2001 From: zer0 Date: Thu, 9 May 2019 16:59:40 +0300 Subject: [PATCH 557/884] added str.translate --- vm/src/obj/objstr.rs | 50 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 56fc373c39..fe98d19c0a 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -16,7 +16,8 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objint; +use super::objnone::PyNone; +use super::objint::{self, PyInt}; use super::objsequence::PySliceableSequence; use super::objslice::PySlice; use super::objtype::{self, PyClassRef}; @@ -829,6 +830,36 @@ impl PyString { false } } + + // https://docs.python.org/3/library/stdtypes.html#str.translate + // Make it work for only subscribtable types + #[pymethod] + fn translate(&self, table: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let mut result = String::new(); + for c in self.value.chars() { + let args = PyFuncArgs::new(vec![vm.new_int(c as i32)], vec![]); + match vm.call_method(&table, "__getitem__", args) { + Ok(value) => { + if let Some(text) = value.payload::() { + result.extend(text.value.chars()); + } else if let Some(_) = value.payload::() { + // Do Nothing + } else if let Some(bigint) = value.payload::() { + match bigint.as_bigint() + .to_u32() + .and_then(std::char::from_u32) { + Some(ch) => result.push(ch as char), + None => return Err(vm.new_value_error(format!("character mapping must be in range(0x110000)"))), + } + } else { + return Err(vm.new_type_error("character mapping must return integer, None or str".to_owned())); + } + }, + _ => result.push(c), + } + } + Ok(result) + } } impl PyValue for PyString { @@ -1104,4 +1135,19 @@ mod tests { assert!(!PyString::from(s).istitle(&vm)); } } -} + + #[test] + fn str_translate() { + let vm = VirtualMachine::new(); + + let table = vm.context().new_dict(); + vm.call_method(table.as_object(), "__setitem__", vec![vm.new_int(97), vm.new_str("🎅".to_owned())]).unwrap(); + vm.call_method(table.as_object(), "__setitem__", vec![vm.new_int(98), vm.get_none()]).unwrap(); + vm.call_method(table.as_object(), "__setitem__", vec![vm.new_int(99), vm.new_str("xda".to_owned())]).unwrap(); + let text = PyString::from("abc"); + let translated = text.translate(table.into_object(), &vm).unwrap(); + assert_eq!(translated, "🎅xda".to_owned()); + let translated = text.translate(vm.new_int(3), &vm); + println!("{:?}", translated); + } +} \ No newline at end of file From 59ae590771c0389d77f535015917db0c3f67f9c3 Mon Sep 17 00:00:00 2001 From: zer0 Date: Thu, 9 May 2019 17:09:38 +0300 Subject: [PATCH 558/884] formatting --- parser/src/lexer.rs | 6 +----- vm/src/obj/objstr.rs | 43 ++++++++++++++++++++++++++++++------------ vm/src/obj/objsuper.rs | 6 ++---- whats_left.sh | 2 +- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 11d30cbee7..d23198ca89 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -826,11 +826,7 @@ where } _ => { let tok_end = self.get_pos(); - return Some(Ok(( - tok_start, - Tok::DoubleSlash, - tok_end, - ))); + return Some(Ok((tok_start, Tok::DoubleSlash, tok_end))); } } } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index fe98d19c0a..6bbb9903d2 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -16,8 +16,8 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objnone::PyNone; use super::objint::{self, PyInt}; +use super::objnone::PyNone; use super::objsequence::PySliceableSequence; use super::objslice::PySlice; use super::objtype::{self, PyClassRef}; @@ -845,16 +845,20 @@ impl PyString { } else if let Some(_) = value.payload::() { // Do Nothing } else if let Some(bigint) = value.payload::() { - match bigint.as_bigint() - .to_u32() - .and_then(std::char::from_u32) { + match bigint.as_bigint().to_u32().and_then(std::char::from_u32) { Some(ch) => result.push(ch as char), - None => return Err(vm.new_value_error(format!("character mapping must be in range(0x110000)"))), + None => { + return Err(vm.new_value_error(format!( + "character mapping must be in range(0x110000)" + ))); + } } } else { - return Err(vm.new_type_error("character mapping must return integer, None or str".to_owned())); + return Err(vm.new_type_error( + "character mapping must return integer, None or str".to_owned(), + )); } - }, + } _ => result.push(c), } } @@ -1139,15 +1143,30 @@ mod tests { #[test] fn str_translate() { let vm = VirtualMachine::new(); - + let table = vm.context().new_dict(); - vm.call_method(table.as_object(), "__setitem__", vec![vm.new_int(97), vm.new_str("🎅".to_owned())]).unwrap(); - vm.call_method(table.as_object(), "__setitem__", vec![vm.new_int(98), vm.get_none()]).unwrap(); - vm.call_method(table.as_object(), "__setitem__", vec![vm.new_int(99), vm.new_str("xda".to_owned())]).unwrap(); + vm.call_method( + table.as_object(), + "__setitem__", + vec![vm.new_int(97), vm.new_str("🎅".to_owned())], + ) + .unwrap(); + vm.call_method( + table.as_object(), + "__setitem__", + vec![vm.new_int(98), vm.get_none()], + ) + .unwrap(); + vm.call_method( + table.as_object(), + "__setitem__", + vec![vm.new_int(99), vm.new_str("xda".to_owned())], + ) + .unwrap(); let text = PyString::from("abc"); let translated = text.translate(table.into_object(), &vm).unwrap(); assert_eq!(translated, "🎅xda".to_owned()); let translated = text.translate(vm.new_int(3), &vm); println!("{:?}", translated); } -} \ No newline at end of file +} diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 194212a492..94fe0fc7b7 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -127,10 +127,8 @@ fn super_new( match vm.get_locals().get_item_option(first_arg, vm)? { Some(obj) => obj.clone(), _ => { - return Err(vm.new_type_error(format!( - "super arguement {} was not supplied", - first_arg - ))); + return Err(vm + .new_type_error(format!("super arguement {} was not supplied", first_arg))); } } } else { diff --git a/whats_left.sh b/whats_left.sh index e05051d283..aa79d8d653 100755 --- a/whats_left.sh +++ b/whats_left.sh @@ -8,4 +8,4 @@ python3 not_impl_gen.py cd .. -cargo run -- tests/snippets/whats_left_to_implement.py +cargo +nightly run -- tests/snippets/whats_left_to_implement.py From 94d1b6585c698dde73e3b0026e4fe47aeeb01ed8 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Thu, 9 May 2019 23:50:20 +0300 Subject: [PATCH 559/884] Implement bytearray.__mul__, __imul__ and __rmul__ --- tests/snippets/bytearray.py | 15 +++++++++++ vm/src/obj/objbytearray.rs | 15 +++++++++++ vm/src/obj/objbyteinner.rs | 51 +++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index d90f52a75c..cbecf49364 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -631,3 +631,18 @@ bytearray(b"they're bill's friends from the UK").title() == bytearray(b"They'Re Bill'S Friends From The Uk") ) + + +# repeat by multiply +a = bytearray(b'abcd') +assert a * 0 == bytearray(b'') +assert a * -1 == bytearray(b'') +assert a * 1 == bytearray(b'abcd') +assert a * 3 == bytearray(b'abcdabcdabcd') +assert 3 * a == bytearray(b'abcdabcdabcd') + +a = bytearray(b'abcd') +a.__imul__(3) +assert a == bytearray(b'abcdabcdabcd') +a.__imul__(0) +assert a == bytearray(b'') diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 4981a64739..84c36148a1 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -461,6 +461,21 @@ impl PyByteArrayRef { fn title(self, vm: &VirtualMachine) -> PyResult { Ok(vm.ctx.new_bytearray(self.inner.borrow().title())) } + + #[pymethod(name = "__mul__")] + fn repeat(self, n: PyIntRef, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytearray(self.inner.borrow().repeat(n, vm)?)) + } + + #[pymethod(name = "__rmul__")] + fn rmul(self, n: PyIntRef, vm: &VirtualMachine) -> PyResult { + self.repeat(n, vm) + } + + #[pymethod(name = "__imul__")] + fn irepeat(self, n: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + self.inner.borrow_mut().irepeat(n, vm) + } } // fn set_value(obj: &PyObjectRef, value: Vec) { diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 16b3713098..ae318e6289 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -7,6 +7,7 @@ use crate::pyobject::PyRef; use crate::pyobject::PyValue; use crate::pyobject::TryFromObject; use crate::pyobject::{PyIterable, PyObjectRef}; +use core::convert::TryFrom; use core::ops::Range; use num_bigint::BigInt; @@ -999,6 +1000,56 @@ impl PyByteInner { res } + + pub fn repeat(&self, n: PyIntRef, vm: &VirtualMachine) -> PyResult> { + if self.elements.is_empty() { + // We can multiple an empty vector by any integer, even if it doesn't fit in an isize. + return Ok(vec![]); + } + + let n = n.as_bigint().to_isize().ok_or_else(|| { + vm.new_overflow_error("can't multiply bytes that many times".to_string()) + })?; + + if n <= 0 { + Ok(vec![]) + } else { + let n = usize::try_from(n).unwrap(); + + let mut new_value = Vec::with_capacity(n * self.elements.len()); + for _ in 0..n { + new_value.extend(&self.elements); + } + + Ok(new_value) + } + } + + pub fn irepeat(&mut self, n: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + if self.elements.is_empty() { + // We can multiple an empty vector by any integer, even if it doesn't fit in an isize. + return Ok(()); + } + + let n = n.as_bigint().to_isize().ok_or_else(|| { + vm.new_overflow_error("can't multiply bytes that many times".to_string()) + })?; + + if n <= 0 { + self.elements.clear(); + } else { + let n = usize::try_from(n).unwrap(); + + let old = self.elements.clone(); + + self.elements.reserve((n - 1) * old.len()); + for _ in 1..n { + self.elements.extend(&old); + } + } + + Ok(()) + } } pub fn try_as_byte(obj: &PyObjectRef) -> Option> { From 87844ff4342c7faee3ba96e802a876fe4732929e Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Thu, 9 May 2019 23:51:20 +0300 Subject: [PATCH 560/884] Implement bytes.__mul__ and __rmul__ --- tests/snippets/bytes.py | 9 +++++++++ vm/src/obj/objbytes.rs | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/tests/snippets/bytes.py b/tests/snippets/bytes.py index a0cc348dbc..1fcbae3abf 100644 --- a/tests/snippets/bytes.py +++ b/tests/snippets/bytes.py @@ -588,3 +588,12 @@ b"they're bill's friends from the UK".title() == b"They'Re Bill'S Friends From The Uk" ) + + +# repeat by multiply +a = b'abcd' +assert a * 0 == b'' +assert a * -1 == b'' +assert a * 1 == b'abcd' +assert a * 3 == b'abcdabcdabcd' +assert 3 * a == b'abcdabcdabcd' diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 9cb44f4f83..102c70d4f2 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -403,6 +403,16 @@ impl PyBytesRef { fn title(self, vm: &VirtualMachine) -> PyResult { Ok(vm.ctx.new_bytes(self.inner.title())) } + + #[pymethod(name = "__mul__")] + fn repeat(self, n: PyIntRef, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.inner.repeat(n, vm)?)) + } + + #[pymethod(name = "__rmul__")] + fn rmul(self, n: PyIntRef, vm: &VirtualMachine) -> PyResult { + self.repeat(n, vm) + } } #[pyclass] From 04ce9ea1857a74b4c48034ecf6f7e55d88fd0e3f Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 10 May 2019 00:01:27 +0300 Subject: [PATCH 561/884] Implement bytearray.copy --- tests/snippets/bytearray.py | 9 +++++++++ vm/src/obj/objbytearray.rs | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index cbecf49364..8c03a2d015 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -646,3 +646,12 @@ assert a == bytearray(b'abcdabcdabcd') a.__imul__(0) assert a == bytearray(b'') + + +# copy +a = bytearray(b"my bytearray") +b = a.copy() +assert a == b +assert a is not b +b.append(100) +assert a != b diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 84c36148a1..b2c29fe111 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -440,6 +440,11 @@ impl PyByteArrayRef { self.inner.borrow_mut().elements.clear(); } + #[pymethod(name = "copy")] + fn copy(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytearray(self.inner.borrow().elements.clone())) + } + #[pymethod(name = "append")] fn append(self, x: PyIntRef, vm: &VirtualMachine) -> Result<(), PyObjectRef> { self.inner From 485ffa01a25266d06e8b4fb8fd399426300d473d Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 10 May 2019 00:08:41 +0300 Subject: [PATCH 562/884] Implement bytearray.extend --- tests/snippets/bytearray.py | 8 ++++++++ vm/src/obj/objbytearray.rs | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index 8c03a2d015..4f08b2773a 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -655,3 +655,11 @@ assert a is not b b.append(100) assert a != b + + +# extend +a = bytearray(b"hello,") +# any iterable of ints should work +a.extend([32, 119, 111, 114]) +a.extend(b"ld") +assert a == bytearray(b"hello, world") diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index b2c29fe111..ee947deb8e 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -454,6 +454,20 @@ impl PyByteArrayRef { Ok(()) } + #[pymethod(name = "extend")] + fn extend(self, iterable_of_ints: PyIterable, vm: &VirtualMachine) -> Result<(), PyObjectRef> { + let mut inner = self.inner.borrow_mut(); + + for x in iterable_of_ints.iter(vm)? { + let x = x?; + let x = PyIntRef::try_from_object(vm, x)?; + let x = x.as_bigint().byte_or(vm)?; + inner.elements.push(x); + } + + Ok(()) + } + #[pymethod(name = "pop")] fn pop(self, vm: &VirtualMachine) -> PyResult { let bytes = &mut self.inner.borrow_mut().elements; From 43cd50270f26ef938e55c757c74076540e587c2c Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 10 May 2019 00:25:55 +0300 Subject: [PATCH 563/884] Implement bytearray.insert --- tests/snippets/bytearray.py | 15 +++++++++++++++ vm/src/obj/objbytearray.rs | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index 4f08b2773a..840c25bf46 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -663,3 +663,18 @@ a.extend([32, 119, 111, 114]) a.extend(b"ld") assert a == bytearray(b"hello, world") + + +# insert +a = bytearray(b"hello, world") +a.insert(0, 119) +assert a == bytearray(b"whello, world"), a +# -1 is not at the end, but one before +a.insert(-1, 119) +assert a == bytearray(b"whello, worlwd"), a +# inserting before the beginning just inserts at the beginning +a.insert(-1000, 111) +assert a == bytearray(b"owhello, worlwd"), a +# inserting after the end just inserts at the end +a.insert(1000, 111) +assert a == bytearray(b"owhello, worlwdo"), a diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index ee947deb8e..2da58cc960 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -15,7 +15,9 @@ use crate::pyobject::{ TryFromObject, }; use crate::vm::VirtualMachine; +use num_traits::ToPrimitive; use std::cell::{Cell, RefCell}; +use std::convert::TryFrom; use super::objiter; use super::objtype::PyClassRef; @@ -468,6 +470,37 @@ impl PyByteArrayRef { Ok(()) } + #[pymethod(name = "insert")] + fn insert(self, index: PyIntRef, x: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let bytes = &mut self.inner.borrow_mut().elements; + let len = isize::try_from(bytes.len()) + .map_err(|_e| vm.new_overflow_error("bytearray too big".to_string()))?; + + let x = x.as_bigint().byte_or(vm)?; + + let mut index = index + .as_bigint() + .to_isize() + .ok_or_else(|| vm.new_overflow_error("index too big".to_string()))?; + + if index >= len { + bytes.push(x); + return Ok(()); + } + + if index < 0 { + index += len; + index = index.max(0); + } + + let index = usize::try_from(index) + .map_err(|_e| vm.new_overflow_error("overflow in index calculation".to_string()))?; + + bytes.insert(index, x); + + Ok(()) + } + #[pymethod(name = "pop")] fn pop(self, vm: &VirtualMachine) -> PyResult { let bytes = &mut self.inner.borrow_mut().elements; From e5bae56ccb99f9c7a5ba70a72255f4db48ac1427 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 10 May 2019 00:31:37 +0300 Subject: [PATCH 564/884] Implement bytearray.remove --- tests/snippets/bytearray.py | 7 +++++++ vm/src/obj/objbytearray.rs | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index 840c25bf46..cb1a61f832 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -678,3 +678,10 @@ # inserting after the end just inserts at the end a.insert(1000, 111) assert a == bytearray(b"owhello, worlwdo"), a + + +# remove +a = bytearray(b'abcdabcd') +a.remove(99) # the letter c +# Only the first is removed +assert a == bytearray(b'abdabcd') diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 2da58cc960..244cd63ba6 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -322,6 +322,21 @@ impl PyByteArrayRef { Ok(res) } + #[pymethod(name = "remove")] + fn remove(self, x: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let x = x.as_bigint().byte_or(vm)?; + + let bytes = &mut self.inner.borrow_mut().elements; + let pos = bytes + .iter() + .position(|b| *b == x) + .ok_or(vm.new_value_error("value not found in bytearray".to_string()))?; + + bytes.remove(pos); + + Ok(()) + } + #[pymethod(name = "translate")] fn translate(self, options: ByteInnerTranslateOptions, vm: &VirtualMachine) -> PyResult { self.inner.borrow().translate(options, vm) From 32950bd0bc4a08069726ce61fb57a5097972ae89 Mon Sep 17 00:00:00 2001 From: "Y. Sapir" Date: Fri, 10 May 2019 00:33:05 +0300 Subject: [PATCH 565/884] Implement bytearray.reverse --- tests/snippets/bytearray.py | 6 ++++++ vm/src/obj/objbytearray.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index cb1a61f832..dff0852c41 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -685,3 +685,9 @@ a.remove(99) # the letter c # Only the first is removed assert a == bytearray(b'abdabcd') + + +# reverse +a = bytearray(b'hello, world') +a.reverse() +assert a == bytearray(b'dlrow ,olleh') diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 244cd63ba6..8b09a53e9f 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -543,6 +543,12 @@ impl PyByteArrayRef { fn irepeat(self, n: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { self.inner.borrow_mut().irepeat(n, vm) } + + #[pymethod(name = "reverse")] + fn reverse(self, _vm: &VirtualMachine) -> PyResult<()> { + self.inner.borrow_mut().elements.reverse(); + Ok(()) + } } // fn set_value(obj: &PyObjectRef, value: Vec) { From 50c119a503002b88cb32c7ad3781ec77444c292a Mon Sep 17 00:00:00 2001 From: Shitong Wen Date: Fri, 10 May 2019 14:21:09 +0800 Subject: [PATCH 566/884] fix floor div for int --- tests/snippets/ints.py | 4 ++++ vm/src/obj/objint.rs | 3 ++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index ef9328d690..be41c32574 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -64,6 +64,10 @@ assert (2).__pow__(3.0) == NotImplemented assert (2).__rpow__(3.0) == NotImplemented +assert 10 // 4 == 2 +assert -10 // 4 == -3 +assert 10 // -4 == -3 +assert -10 // -4 == 2 assert int() == 0 assert int("101", 2) == 5 diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 21f50a182b..403fd08ef6 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -260,7 +260,8 @@ impl PyInt { if objtype::isinstance(&other, &vm.ctx.int_type()) { let v2 = get_value(&other); if *v2 != BigInt::zero() { - Ok(vm.ctx.new_int((&self.value) / v2)) + let modulo = (&self.value % v2 + v2) % v2; + Ok(vm.ctx.new_int((&self.value - modulo) / v2)) } else { Err(vm.new_zero_division_error("integer floordiv by zero".to_string())) } From fe37ecf8d3065a361f56897b7b08475f6f9ea4c2 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Thu, 9 May 2019 15:48:42 +0300 Subject: [PATCH 567/884] Define `os_unix_stat_inner` only when required Avoids warn when building on Windows. --- vm/src/stdlib/os.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index b52e3762e8..e688737673 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -357,6 +357,7 @@ impl StatResultRef { } } +#[cfg(unix)] macro_rules! os_unix_stat_inner { ( $path:expr, $follow_symlinks:expr, $vm:expr) => {{ let metadata = match $follow_symlinks.follow_symlinks { From da3df8a0aee31d33e798c1be1e7de35cee4527a3 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 10 May 2019 13:58:10 +0300 Subject: [PATCH 568/884] Add __file__ for imported modules --- tests/snippets/builtin_file.py | 3 +++ tests/snippets/import_file.py | 4 ++++ vm/src/import.rs | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 tests/snippets/builtin_file.py create mode 100644 tests/snippets/import_file.py diff --git a/tests/snippets/builtin_file.py b/tests/snippets/builtin_file.py new file mode 100644 index 0000000000..50dfeb9312 --- /dev/null +++ b/tests/snippets/builtin_file.py @@ -0,0 +1,3 @@ +from import_file import import_file + +import_file() diff --git a/tests/snippets/import_file.py b/tests/snippets/import_file.py new file mode 100644 index 0000000000..3f9eeed704 --- /dev/null +++ b/tests/snippets/import_file.py @@ -0,0 +1,4 @@ +import os + +def import_file(): + assert os.path.basename(__file__) == "import_file.py" diff --git a/vm/src/import.rs b/vm/src/import.rs index f4c52a11e9..a03f146238 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -48,12 +48,13 @@ pub fn import_file( content: String, ) -> PyResult { let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); - let code_obj = compile::compile(vm, &content, &compile::Mode::Exec, file_path) + let code_obj = compile::compile(vm, &content, &compile::Mode::Exec, file_path.clone()) .map_err(|err| vm.new_syntax_error(&err))?; // trace!("Code object: {:?}", code_obj); let attrs = vm.ctx.new_dict(); attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; + attrs.set_item("__file__", vm.new_str(file_path), vm)?; let module = vm.ctx.new_module(module_name, attrs.clone()); // Store module in cache to prevent infinite loop with mutual importing libs: From fc1a063a2cfd2b2b5082116566cdb7bb0f8c4ec4 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 10 May 2019 14:06:23 +0300 Subject: [PATCH 569/884] Add __file__ for file executed from main --- src/main.rs | 18 +++++++++++++----- tests/snippets/builtin_file.py | 4 ++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3e77ddbc21..72bb161392 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,15 @@ extern crate rustyline; use clap::{App, Arg}; use rustpython_parser::error::ParseError; use rustpython_vm::{ - compile, error::CompileError, error::CompileErrorType, frame::Scope, import, obj::objstr, - print_exception, pyobject::PyResult, util, VirtualMachine, + compile, + error::CompileError, + error::CompileErrorType, + frame::Scope, + import, + obj::objstr, + print_exception, + pyobject::{ItemProtocol, PyResult}, + util, VirtualMachine, }; use rustyline::{error::ReadlineError, Editor}; use std::path::PathBuf; @@ -65,11 +72,12 @@ fn main() { } fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResult { - let code_obj = compile::compile(vm, source, &compile::Mode::Exec, source_path) + let code_obj = compile::compile(vm, source, &compile::Mode::Exec, source_path.clone()) .map_err(|err| vm.new_syntax_error(&err))?; // trace!("Code object: {:?}", code_obj.borrow()); - let vars = vm.ctx.new_scope(); // Keep track of local variables - vm.run_code_obj(code_obj, vars) + let attrs = vm.ctx.new_dict(); + attrs.set_item("__file__", vm.new_str(source_path), vm)?; + vm.run_code_obj(code_obj, Scope::new(None, attrs)) } fn handle_exception(vm: &VirtualMachine, result: PyResult) { diff --git a/tests/snippets/builtin_file.py b/tests/snippets/builtin_file.py index 50dfeb9312..2c80d15904 100644 --- a/tests/snippets/builtin_file.py +++ b/tests/snippets/builtin_file.py @@ -1,3 +1,7 @@ +import os + from import_file import import_file import_file() + +assert os.path.basename(__file__) == "builtin_file.py" From f69c79bdc7f12e54589c9762398c607c4aede3ab Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 27 Apr 2019 16:39:41 +0900 Subject: [PATCH 570/884] Add __builtins__ to globals() --- src/main.rs | 1 + tests/snippets/builtins_module.py | 16 ++++++++++++++++ vm/src/frame.rs | 5 +++++ wasm/lib/src/vm_class.rs | 1 + 4 files changed, 23 insertions(+) create mode 100644 tests/snippets/builtins_module.py diff --git a/src/main.rs b/src/main.rs index 72bb161392..e8dd480089 100644 --- a/src/main.rs +++ b/src/main.rs @@ -218,6 +218,7 @@ fn run_shell(vm: &VirtualMachine) -> PyResult { crate_version!() ); let vars = vm.ctx.new_scope(); + vars.init_builtins(vm)?; // Read a single line: let mut input = String::new(); diff --git a/tests/snippets/builtins_module.py b/tests/snippets/builtins_module.py new file mode 100644 index 0000000000..b9feffd0ad --- /dev/null +++ b/tests/snippets/builtins_module.py @@ -0,0 +1,16 @@ +from testutils import assertRaises + +assert '__builtins__' in globals() +# assert type(__builtins__).__name__ == 'module' +with assertRaises(AttributeError): + __builtins__.__builtins__ + +__builtins__.x = 'new' +assert x == 'new' + +# __builtins__ is deletable but names are alive +del __builtins__ +with assertRaises(NameError): + __builtins__ + +assert print diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 50c317c373..8474873065 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -119,6 +119,11 @@ impl Scope { pub fn child_scope(&self, ctx: &PyContext) -> Scope { self.child_scope_with_locals(ctx.new_dict()) } + + pub fn init_builtins(&self, vm: &VirtualMachine) -> PyResult<()> { + let globals = self.globals.clone(); + globals.set_item("__builtins__", vm.builtins.clone(), vm).and(Ok(())) + } } pub trait NameProtocol { diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 2c330e6eeb..64957e3316 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -27,6 +27,7 @@ impl StoredVirtualMachine { fn new(id: String, inject_browser_module: bool) -> StoredVirtualMachine { let mut vm = VirtualMachine::new(); let scope = vm.ctx.new_scope(); + scope.init_builtins(&vm).unwrap(); if inject_browser_module { setup_browser_module(&vm); } From 509394080bc5262d3c3961ca16919631c9e1eb5b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 4 May 2019 00:52:44 +0900 Subject: [PATCH 571/884] Inject __builtins__ to scope only when it doesn't exist --- benchmarks/bench.rs | 4 ++-- src/main.rs | 5 ++--- tests/snippets/builtins_module.py | 12 ++++++++++++ vm/src/builtins.rs | 15 ++++++++++++--- vm/src/eval.rs | 2 +- vm/src/frame.rs | 19 ++++++++++++++----- vm/src/import.rs | 2 +- vm/src/pyobject.rs | 4 ---- vm/src/vm.rs | 4 ++++ wasm/lib/src/vm_class.rs | 3 +-- 10 files changed, 49 insertions(+), 21 deletions(-) diff --git a/benchmarks/bench.rs b/benchmarks/bench.rs index b475da7cb3..256fa47ad9 100644 --- a/benchmarks/bench.rs +++ b/benchmarks/bench.rs @@ -99,7 +99,7 @@ fn bench_rustpy_nbody(b: &mut test::Bencher) { }; b.iter(|| { - let scope = vm.ctx.new_scope(); + let scope = vm.new_scope_with_builtins(); let res: PyResult = vm.run_code_obj(code.clone(), scope); assert!(res.is_ok()); }) @@ -118,7 +118,7 @@ fn bench_rustpy_mandelbrot(b: &mut test::Bencher) { }; b.iter(|| { - let scope = vm.ctx.new_scope(); + let scope = vm.new_scope_with_builtins(); let res: PyResult = vm.run_code_obj(code.clone(), scope); assert!(res.is_ok()); }) diff --git a/src/main.rs b/src/main.rs index e8dd480089..2100226c2f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,7 +77,7 @@ fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResu // trace!("Code object: {:?}", code_obj.borrow()); let attrs = vm.ctx.new_dict(); attrs.set_item("__file__", vm.new_str(source_path), vm)?; - vm.run_code_obj(code_obj, Scope::new(None, attrs)) + vm.run_code_obj(code_obj, Scope::with_builtins(None, attrs, vm)) } fn handle_exception(vm: &VirtualMachine, result: PyResult) { @@ -217,8 +217,7 @@ fn run_shell(vm: &VirtualMachine) -> PyResult { "Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}", crate_version!() ); - let vars = vm.ctx.new_scope(); - vars.init_builtins(vm)?; + let vars = vm.new_scope_with_builtins(); // Read a single line: let mut input = String::new(); diff --git a/tests/snippets/builtins_module.py b/tests/snippets/builtins_module.py index b9feffd0ad..e3bdcdc16c 100644 --- a/tests/snippets/builtins_module.py +++ b/tests/snippets/builtins_module.py @@ -8,6 +8,18 @@ __builtins__.x = 'new' assert x == 'new' +exec('assert "__builtins__" in globals()', dict()) +exec('assert __builtins__ == 7', {'__builtins__': 7}) +exec('assert not isinstance(__builtins__, dict)') +exec('assert isinstance(__builtins__, dict)', {}) + +namespace = {} +exec('', namespace) +assert namespace['__builtins__'] == __builtins__.__dict__ + +# with assertRaises(NameError): +# exec('print(__builtins__)', {'__builtins__': {}}) + # __builtins__ is deletable but names are alive del __builtins__ with assertRaises(NameError): diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index a48a3b4412..5b3082b0d9 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -218,18 +218,27 @@ fn make_scope( } None => None, }; - let current_scope = vm.current_scope(); let globals = match globals { - Some(dict) => dict.clone().downcast().unwrap(), + Some(dict) => { + let dict: PyDictRef = dict.clone().downcast().unwrap(); + if !dict.contains_key("__builtins__", vm) { + let builtins_dict = vm.builtins.dict.as_ref().unwrap().as_object(); + dict.set_item("__builtins__", builtins_dict.clone(), vm) + .unwrap(); + } + dict + } None => current_scope.globals.clone(), }; + let locals = match locals { Some(dict) => dict.clone().downcast().ok(), None => current_scope.get_only_locals(), }; - Ok(Scope::new(locals, globals)) + let scope = Scope::with_builtins(locals, globals, vm); + Ok(scope) } fn builtin_format( diff --git a/vm/src/eval.rs b/vm/src/eval.rs index 21bc9dadf5..a927f8f034 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -24,7 +24,7 @@ mod tests { fn test_print_42() { let source = String::from("print('Hello world')\n"); let mut vm = VirtualMachine::new(); - let vars = vm.ctx.new_scope(); + let vars = vm.new_scope_with_builtins(); let _result = eval(&mut vm, &source, vars, ""); // TODO: check result? diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 8474873065..5955660b61 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -98,6 +98,20 @@ impl Scope { Scope { locals, globals } } + pub fn with_builtins( + locals: Option, + globals: PyDictRef, + vm: &VirtualMachine, + ) -> Scope { + if !globals.contains_key("__builtins__", vm) { + globals + .clone() + .set_item("__builtins__", vm.builtins.clone(), vm) + .unwrap(); + } + Scope::new(locals, globals) + } + pub fn get_locals(&self) -> PyDictRef { match self.locals.iter().next() { Some(dict) => dict.clone(), @@ -119,11 +133,6 @@ impl Scope { pub fn child_scope(&self, ctx: &PyContext) -> Scope { self.child_scope_with_locals(ctx.new_dict()) } - - pub fn init_builtins(&self, vm: &VirtualMachine) -> PyResult<()> { - let globals = self.globals.clone(); - globals.set_item("__builtins__", vm.builtins.clone(), vm).and(Ok(())) - } } pub trait NameProtocol { diff --git a/vm/src/import.rs b/vm/src/import.rs index a03f146238..8445e65f9f 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -61,7 +61,7 @@ pub fn import_file( sys_modules.set_item(module_name, module.clone(), vm)?; // Execute main code in module: - vm.run_code_obj(code_obj, Scope::new(None, attrs))?; + vm.run_code_obj(code_obj, Scope::with_builtins(None, attrs, vm))?; Ok(module) } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 454a1605fa..722588b3fd 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -648,10 +648,6 @@ impl PyContext { objtype::new(self.type_type(), name, vec![base], PyAttributes::new()).unwrap() } - pub fn new_scope(&self) -> Scope { - Scope::new(None, self.new_dict()) - } - pub fn new_module(&self, name: &str, dict: PyDictRef) -> PyObjectRef { PyObject::new( PyModule { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 6b2f0e0b11..8146310159 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -250,6 +250,10 @@ impl VirtualMachine { syntax_error } + pub fn new_scope_with_builtins(&self) -> Scope { + Scope::with_builtins(None, self.ctx.new_dict(), self) + } + pub fn get_none(&self) -> PyObjectRef { self.ctx.none() } diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 64957e3316..f9040dfcbd 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -26,8 +26,7 @@ pub(crate) struct StoredVirtualMachine { impl StoredVirtualMachine { fn new(id: String, inject_browser_module: bool) -> StoredVirtualMachine { let mut vm = VirtualMachine::new(); - let scope = vm.ctx.new_scope(); - scope.init_builtins(&vm).unwrap(); + let scope = vm.new_scope_with_builtins(); if inject_browser_module { setup_browser_module(&vm); } From b720681c7b684b86dd9ba4de488ea82c7f6dc517 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 May 2019 04:38:38 +0900 Subject: [PATCH 572/884] PyClassImpl for PyClassMethod --- vm/src/obj/objclassmethod.rs | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/vm/src/obj/objclassmethod.rs b/vm/src/obj/objclassmethod.rs index 7a8d90d1c1..69d6dba4ef 100644 --- a/vm/src/obj/objclassmethod.rs +++ b/vm/src/obj/objclassmethod.rs @@ -1,7 +1,28 @@ use super::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; +/// classmethod(function) -> method +/// +/// Convert a function to be a class method. +/// +/// A class method receives the class as implicit first argument, +/// just like an instance method receives the instance. +/// To declare a class method, use this idiom: +/// +/// class C: +/// @classmethod +/// def f(cls, arg1, arg2, ...): +/// ... +/// +/// It can be called either on the class (e.g. C.f()) or on an instance +/// (e.g. C().f()). The instance is ignored except for its class. +/// If a class method is called for a derived class, the derived class +/// object is passed as the implied first argument. +/// +/// Class methods are different than C++ or Java static methods. +/// If you want those, see the staticmethod builtin. +#[pyclass] #[derive(Clone, Debug)] pub struct PyClassMethod { pub callable: PyObjectRef, @@ -16,7 +37,9 @@ impl PyValue for PyClassMethod { } } -impl PyClassMethodRef { +#[pyimpl] +impl PyClassMethod { + #[pymethod(name = "__new__")] fn new( cls: PyClassRef, callable: PyObjectRef, @@ -28,7 +51,8 @@ impl PyClassMethodRef { .into_ref_with_type(vm, cls) } - fn get(self, _inst: PyObjectRef, owner: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__get__")] + fn get(&self, _inst: PyObjectRef, owner: PyObjectRef, vm: &VirtualMachine) -> PyResult { Ok(vm .ctx .new_bound_method(self.callable.clone(), owner.clone())) @@ -36,9 +60,5 @@ impl PyClassMethodRef { } pub fn init(context: &PyContext) { - let classmethod_type = &context.classmethod_type; - extend_class!(context, classmethod_type, { - "__get__" => context.new_rustfunc(PyClassMethodRef::get), - "__new__" => context.new_rustfunc(PyClassMethodRef::new) - }); + PyClassMethod::extend_class(context, &context.classmethod_type); } From 7564833b6e3769041db391725d754d13a2a2da8a Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 May 2019 05:17:29 +0900 Subject: [PATCH 573/884] Add classmethod.__func__ --- tests/snippets/class.py | 3 +++ vm/src/obj/objclassmethod.rs | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/tests/snippets/class.py b/tests/snippets/class.py index ded02dbfac..389c227126 100644 --- a/tests/snippets/class.py +++ b/tests/snippets/class.py @@ -152,3 +152,6 @@ def t1(self): assert T4.__doc__ == "test4" assert T4.t1.__doc__ == "t1" + +cm = classmethod(lambda cls: cls) +assert cm.__func__(int) is int diff --git a/vm/src/obj/objclassmethod.rs b/vm/src/obj/objclassmethod.rs index 69d6dba4ef..10b3106ec9 100644 --- a/vm/src/obj/objclassmethod.rs +++ b/vm/src/obj/objclassmethod.rs @@ -57,6 +57,11 @@ impl PyClassMethod { .ctx .new_bound_method(self.callable.clone(), owner.clone())) } + + #[pyproperty(name = "__func__")] + fn func(&self, _vm: &VirtualMachine) -> PyObjectRef { + self.callable.clone() + } } pub fn init(context: &PyContext) { From 2f253ca0545bb71ef480df0c25461de6a118e81d Mon Sep 17 00:00:00 2001 From: zer0 Date: Fri, 10 May 2019 23:31:11 +0300 Subject: [PATCH 574/884] cargo fmt --- vm/src/obj/objstr.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 6bbb9903d2..4f65895989 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -11,8 +11,8 @@ use unicode_segmentation::UnicodeSegmentation; use crate::format::{FormatParseError, FormatPart, FormatString}; use crate::function::{OptionalArg, PyFuncArgs}; use crate::pyobject::{ - IdProtocol, IntoPyObject, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, - PyValue, TryFromObject, TryIntoRef, TypeProtocol, + IdProtocol, IntoPyObject, ItemProtocol, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, + PyResult, PyValue, TryFromObject, TryIntoRef, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -832,13 +832,13 @@ impl PyString { } // https://docs.python.org/3/library/stdtypes.html#str.translate - // Make it work for only subscribtable types #[pymethod] fn translate(&self, table: PyObjectRef, vm: &VirtualMachine) -> PyResult { let mut result = String::new(); + // It throws a type error if it is not subscribtable + vm.get_method(table.clone(), "__getitem__")?; for c in self.value.chars() { - let args = PyFuncArgs::new(vec![vm.new_int(c as i32)], vec![]); - match vm.call_method(&table, "__getitem__", args) { + match table.get_item(c as i32, vm) { Ok(value) => { if let Some(text) = value.payload::() { result.extend(text.value.chars()); @@ -1145,6 +1145,7 @@ mod tests { let vm = VirtualMachine::new(); let table = vm.context().new_dict(); + // TODO: use table.set_item vm.call_method( table.as_object(), "__setitem__", From b15a58416856f080e86c78c3e2603410016646b3 Mon Sep 17 00:00:00 2001 From: zer0 Date: Fri, 10 May 2019 23:47:11 +0300 Subject: [PATCH 575/884] added small test to snippets --- tests/snippets/strings.py | 3 +++ vm/src/obj/objstr.rs | 26 +++++++------------------- vm/src/obj/objsuper.rs | 6 ++++-- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 3364a293c5..7e2cd2ef53 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -157,6 +157,9 @@ assert 'z' >= 'b' assert 'a' >= 'a' +# str.translate +assert "abc".translate({97: '🎅', 98: None, 99: "xd"}) == "🎅xd" + def try_mutate_str(): word = "word" word[0] = 'x' diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 4f65895989..683af3484c 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1145,25 +1145,13 @@ mod tests { let vm = VirtualMachine::new(); let table = vm.context().new_dict(); - // TODO: use table.set_item - vm.call_method( - table.as_object(), - "__setitem__", - vec![vm.new_int(97), vm.new_str("🎅".to_owned())], - ) - .unwrap(); - vm.call_method( - table.as_object(), - "__setitem__", - vec![vm.new_int(98), vm.get_none()], - ) - .unwrap(); - vm.call_method( - table.as_object(), - "__setitem__", - vec![vm.new_int(99), vm.new_str("xda".to_owned())], - ) - .unwrap(); + table + .set_item(97, vm.new_str("🎅".to_owned()), &vm) + .unwrap(); + table.set_item(98, vm.get_none(), &vm).unwrap(); + table + .set_item(99, vm.new_str("xda".to_owned()), &vm) + .unwrap(); let text = PyString::from("abc"); let translated = text.translate(table.into_object(), &vm).unwrap(); assert_eq!(translated, "🎅xda".to_owned()); diff --git a/vm/src/obj/objsuper.rs b/vm/src/obj/objsuper.rs index 94fe0fc7b7..194212a492 100644 --- a/vm/src/obj/objsuper.rs +++ b/vm/src/obj/objsuper.rs @@ -127,8 +127,10 @@ fn super_new( match vm.get_locals().get_item_option(first_arg, vm)? { Some(obj) => obj.clone(), _ => { - return Err(vm - .new_type_error(format!("super arguement {} was not supplied", first_arg))); + return Err(vm.new_type_error(format!( + "super arguement {} was not supplied", + first_arg + ))); } } } else { From eab5d42ee6532df0a89dc86e25d382a3a18bf8f2 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 May 2019 05:49:11 +0900 Subject: [PATCH 576/884] Adapt PyClassImpl for PyProperty --- vm/src/obj/objproperty.rs | 173 ++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 80 deletions(-) diff --git a/vm/src/obj/objproperty.rs b/vm/src/obj/objproperty.rs index 9d72171106..1d28ca6b85 100644 --- a/vm/src/obj/objproperty.rs +++ b/vm/src/obj/objproperty.rs @@ -7,11 +7,13 @@ use crate::function::OptionalArg; use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - IdProtocol, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, + IdProtocol, PyClassImpl, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, + TypeProtocol, }; use crate::vm::VirtualMachine; -/// Read-only property, doesn't have __set__ or __delete__ +// Read-only property, doesn't have __set__ or __delete__ +#[pyclass] #[derive(Debug)] pub struct PyReadOnlyProperty { getter: PyObjectRef, @@ -25,22 +27,56 @@ impl PyValue for PyReadOnlyProperty { pub type PyReadOnlyPropertyRef = PyRef; -impl PyReadOnlyPropertyRef { +#[pyimpl] +impl PyReadOnlyProperty { + #[pymethod(name = "__get__")] fn get( - self, + zelf: PyRef, obj: PyObjectRef, _owner: OptionalArg, vm: &VirtualMachine, ) -> PyResult { if obj.is(vm.ctx.none.as_object()) { - Ok(self.into_object()) + Ok(zelf.into_object()) } else { - vm.invoke(self.getter.clone(), obj) + vm.invoke(zelf.getter.clone(), obj) } } } -/// Fully fledged property +/// Property attribute. +/// +/// fget +/// function to be used for getting an attribute value +/// fset +/// function to be used for setting an attribute value +/// fdel +/// function to be used for del'ing an attribute +/// doc +/// docstring +/// +/// Typical use is to define a managed attribute x: +/// +/// class C(object): +/// def getx(self): return self._x +/// def setx(self, value): self._x = value +/// def delx(self): del self._x +/// x = property(getx, setx, delx, "I'm the 'x' property.") +/// +/// Decorators make defining new properties or modifying existing ones easy: +/// +/// class C(object): +/// @property +/// def x(self): +/// "I am the 'x' property." +/// return self._x +/// @x.setter +/// def x(self, value): +/// self._x = value +/// @x.deleter +/// def x(self): +/// del self._x +#[pyclass] #[derive(Debug)] pub struct PyProperty { getter: Option, @@ -56,7 +92,9 @@ impl PyValue for PyProperty { pub type PyPropertyRef = PyRef; -impl PyPropertyRef { +#[pyimpl] +impl PyProperty { + #[pymethod(name = "__new__")] fn new_property( cls: PyClassRef, fget: OptionalArg, @@ -76,7 +114,7 @@ impl PyPropertyRef { // Descriptor methods // specialised version that doesn't check for None - pub(crate) fn instance_binding_get(self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + pub(crate) fn instance_binding_get(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { if let Some(getter) = self.getter.as_ref() { vm.invoke(getter.clone(), obj) } else { @@ -84,15 +122,16 @@ impl PyPropertyRef { } } + #[pymethod(name = "__get__")] fn get( - self, + zelf: PyRef, obj: PyObjectRef, _owner: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - if let Some(getter) = self.getter.as_ref() { + if let Some(getter) = zelf.getter.as_ref() { if obj.is(vm.ctx.none.as_object()) { - Ok(self.into_object()) + Ok(zelf.into_object()) } else { vm.invoke(getter.clone(), obj) } @@ -101,7 +140,8 @@ impl PyPropertyRef { } } - fn set(self, obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__set__")] + fn set(&self, obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { if let Some(setter) = self.setter.as_ref() { vm.invoke(setter.clone(), vec![obj, value]) } else { @@ -109,7 +149,8 @@ impl PyPropertyRef { } } - fn delete(self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__delete__")] + fn delete(&self, obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { if let Some(deleter) = self.deleter.as_ref() { vm.invoke(deleter.clone(), obj) } else { @@ -119,45 +160,63 @@ impl PyPropertyRef { // Access functions - fn fget(self, _vm: &VirtualMachine) -> Option { + #[pyproperty] + fn fget(&self, _vm: &VirtualMachine) -> Option { self.getter.clone() } - fn fset(self, _vm: &VirtualMachine) -> Option { + #[pyproperty] + fn fset(&self, _vm: &VirtualMachine) -> Option { self.setter.clone() } - fn fdel(self, _vm: &VirtualMachine) -> Option { + #[pyproperty] + fn fdel(&self, _vm: &VirtualMachine) -> Option { self.deleter.clone() } // Python builder functions - fn getter(self, getter: Option, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn getter( + zelf: PyRef, + getter: Option, + vm: &VirtualMachine, + ) -> PyResult { PyProperty { - getter: getter.or_else(|| self.getter.clone()), - setter: self.setter.clone(), - deleter: self.deleter.clone(), + getter: getter.or_else(|| zelf.getter.clone()), + setter: zelf.setter.clone(), + deleter: zelf.deleter.clone(), } - .into_ref_with_type(vm, TypeProtocol::class(&self)) + .into_ref_with_type(vm, TypeProtocol::class(&zelf)) } - fn setter(self, setter: Option, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn setter( + zelf: PyRef, + setter: Option, + vm: &VirtualMachine, + ) -> PyResult { PyProperty { - getter: self.getter.clone(), - setter: setter.or_else(|| self.setter.clone()), - deleter: self.deleter.clone(), + getter: zelf.getter.clone(), + setter: setter.or_else(|| zelf.setter.clone()), + deleter: zelf.deleter.clone(), } - .into_ref_with_type(vm, TypeProtocol::class(&self)) + .into_ref_with_type(vm, TypeProtocol::class(&zelf)) } - fn deleter(self, deleter: Option, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn deleter( + zelf: PyRef, + deleter: Option, + vm: &VirtualMachine, + ) -> PyResult { PyProperty { - getter: self.getter.clone(), - setter: self.setter.clone(), - deleter: deleter.or_else(|| self.deleter.clone()), + getter: zelf.getter.clone(), + setter: zelf.setter.clone(), + deleter: deleter.or_else(|| zelf.deleter.clone()), } - .into_ref_with_type(vm, TypeProtocol::class(&self)) + .into_ref_with_type(vm, TypeProtocol::class(&zelf)) } } @@ -216,52 +275,6 @@ impl<'a> PropertyBuilder<'a> { } pub fn init(context: &PyContext) { - extend_class!(context, &context.readonly_property_type, { - "__get__" => context.new_rustfunc(PyReadOnlyPropertyRef::get), - }); - - let property_doc = - "Property attribute.\n\n \ - fget\n \ - function to be used for getting an attribute value\n \ - fset\n \ - function to be used for setting an attribute value\n \ - fdel\n \ - function to be used for del\'ing an attribute\n \ - doc\n \ - docstring\n\n\ - Typical use is to define a managed attribute x:\n\n\ - class C(object):\n \ - def getx(self): return self._x\n \ - def setx(self, value): self._x = value\n \ - def delx(self): del self._x\n \ - x = property(getx, setx, delx, \"I\'m the \'x\' property.\")\n\n\ - Decorators make defining new properties or modifying existing ones easy:\n\n\ - class C(object):\n \ - @property\n \ - def x(self):\n \"I am the \'x\' property.\"\n \ - return self._x\n \ - @x.setter\n \ - def x(self, value):\n \ - self._x = value\n \ - @x.deleter\n \ - def x(self):\n \ - del self._x"; - - extend_class!(context, &context.property_type, { - "__new__" => context.new_rustfunc(PyPropertyRef::new_property), - "__doc__" => context.new_str(property_doc.to_string()), - - "__get__" => context.new_rustfunc(PyPropertyRef::get), - "__set__" => context.new_rustfunc(PyPropertyRef::set), - "__delete__" => context.new_rustfunc(PyPropertyRef::delete), - - "fget" => context.new_property(PyPropertyRef::fget), - "fset" => context.new_property(PyPropertyRef::fset), - "fdel" => context.new_property(PyPropertyRef::fdel), - - "getter" => context.new_rustfunc(PyPropertyRef::getter), - "setter" => context.new_rustfunc(PyPropertyRef::setter), - "deleter" => context.new_rustfunc(PyPropertyRef::deleter), - }); + PyReadOnlyProperty::extend_class(context, &context.readonly_property_type); + PyProperty::extend_class(context, &context.property_type); } From 268164e957cd1e7afb3be1460a56798de392a753 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 May 2019 06:02:56 +0900 Subject: [PATCH 577/884] Fix property to take keyword arguments --- tests/snippets/property.py | 6 ++++++ vm/src/obj/objproperty.rs | 38 ++++++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/tests/snippets/property.py b/tests/snippets/property.py index c61f131899..da2c1ee632 100644 --- a/tests/snippets/property.py +++ b/tests/snippets/property.py @@ -59,6 +59,8 @@ def foo(self): with assertRaises(TypeError): property.__new__(object) +# assert p.__doc__ is None + p1 = property("a", "b", "c") @@ -75,3 +77,7 @@ def foo(self): assert p1.deleter(None).fdel == "c" assert p1.__get__(None, object) is p1 +# assert p1.__doc__ is 'a'.__doc__ + +p2 = property('a', doc='pdoc') +# assert p2.__doc__ == 'pdoc' diff --git a/vm/src/obj/objproperty.rs b/vm/src/obj/objproperty.rs index 1d28ca6b85..9c8394a4cc 100644 --- a/vm/src/obj/objproperty.rs +++ b/vm/src/obj/objproperty.rs @@ -2,9 +2,7 @@ */ -use crate::function::IntoPyNativeFunc; -use crate::function::OptionalArg; -use crate::obj::objstr::PyStringRef; +use crate::function::{IntoPyNativeFunc, OptionalArg, PyFuncArgs}; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ IdProtocol, PyClassImpl, PyContext, PyObject, PyObjectRef, PyRef, PyResult, PyValue, @@ -82,6 +80,7 @@ pub struct PyProperty { getter: Option, setter: Option, deleter: Option, + doc: Option, } impl PyValue for PyProperty { @@ -97,16 +96,31 @@ impl PyProperty { #[pymethod(name = "__new__")] fn new_property( cls: PyClassRef, - fget: OptionalArg, - fset: OptionalArg, - fdel: OptionalArg, - _doc: OptionalArg, + args: PyFuncArgs, vm: &VirtualMachine, ) -> PyResult { + arg_check!( + vm, + args, + required = [], + optional = [(fget, None), (fset, None), (fdel, None), (doc, None)] + ); + + fn into_option(vm: &VirtualMachine, arg: Option<&PyObjectRef>) -> Option { + arg.and_then(|arg| { + if vm.ctx.none().is(arg) { + None + } else { + Some(arg.clone()) + } + }) + } + PyProperty { - getter: fget.into_option(), - setter: fset.into_option(), - deleter: fdel.into_option(), + getter: into_option(vm, fget), + setter: into_option(vm, fset), + deleter: into_option(vm, fdel), + doc: into_option(vm, doc), } .into_ref_with_type(vm, cls) } @@ -187,6 +201,7 @@ impl PyProperty { getter: getter.or_else(|| zelf.getter.clone()), setter: zelf.setter.clone(), deleter: zelf.deleter.clone(), + doc: None, } .into_ref_with_type(vm, TypeProtocol::class(&zelf)) } @@ -201,6 +216,7 @@ impl PyProperty { getter: zelf.getter.clone(), setter: setter.or_else(|| zelf.setter.clone()), deleter: zelf.deleter.clone(), + doc: None, } .into_ref_with_type(vm, TypeProtocol::class(&zelf)) } @@ -215,6 +231,7 @@ impl PyProperty { getter: zelf.getter.clone(), setter: zelf.setter.clone(), deleter: deleter.or_else(|| zelf.deleter.clone()), + doc: None, } .into_ref_with_type(vm, TypeProtocol::class(&zelf)) } @@ -259,6 +276,7 @@ impl<'a> PropertyBuilder<'a> { getter: self.getter.clone(), setter: self.setter.clone(), deleter: None, + doc: None, }; PyObject::new(payload, self.ctx.property_type(), None) From 6e057955213fd01559bb4fb0301d692745c6c6de Mon Sep 17 00:00:00 2001 From: zer0 Date: Sat, 11 May 2019 01:03:22 +0300 Subject: [PATCH 578/884] added str.maketrans --- parser/src/lexer.rs | 6 +++- tests/snippets/strings.py | 4 +++ vm/src/obj/objstr.rs | 61 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index d23198ca89..11d30cbee7 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -826,7 +826,11 @@ where } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::DoubleSlash, tok_end))); + return Some(Ok(( + tok_start, + Tok::DoubleSlash, + tok_end, + ))); } } } diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 7e2cd2ef53..480500e9f8 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -160,6 +160,10 @@ # str.translate assert "abc".translate({97: '🎅', 98: None, 99: "xd"}) == "🎅xd" +# str.maketrans +assert str.maketrans({"a": "abc", "b": None, "c": 33}) == {97: "abc", 98: None, 99: 33} +assert str.maketrans("hello", "world", "rust") == {103: "w", 101: "o", 108: "l", 111: "d", "r": None, "u": None, "s": None, "t": None} + def try_mutate_str(): word = "word" word[0] = 'x' diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 683af3484c..a4e6da8c50 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -16,6 +16,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; +use super::objdict::{PyDict, PyDictRef}; use super::objint::{self, PyInt}; use super::objnone::PyNone; use super::objsequence::PySliceableSequence; @@ -838,7 +839,7 @@ impl PyString { // It throws a type error if it is not subscribtable vm.get_method(table.clone(), "__getitem__")?; for c in self.value.chars() { - match table.get_item(c as i32, vm) { + match table.get_item(c as u32, vm) { Ok(value) => { if let Some(text) = value.payload::() { result.extend(text.value.chars()); @@ -864,6 +865,64 @@ impl PyString { } Ok(result) } + + #[pymethod] + fn maketrans( + dict_or_str: PyObjectRef, + to_str: OptionalArg, + none_str: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + if let OptionalArg::Present(to_str) = to_str { + // dict_or_str is str + let from_str = dict_or_str.payload::().ok_or( + vm.new_type_error( + "first maketrans argument must be a string if there is a second argument" + .to_owned(), + ), + )?; + let new_dict = vm.context().new_dict(); + if to_str.len(vm) == from_str.len(vm) { + for (c1, c2) in from_str.value.chars().zip(to_str.value.chars()) { + new_dict.set_item(c1.to_string(), vm.new_str(c2.to_string()), vm)?; + } + } else { + return Err(vm.new_value_error( + "the first two maketrans arguments must have equal length".to_owned(), + )); + } + if let OptionalArg::Present(none_str) = none_str { + for c in none_str.value.chars() { + new_dict.set_item(c.to_string(), vm.get_none(), vm)?; + } + } + new_dict.into_pyobject(vm) + } else { + // dict_str must be a dict + if let Ok(dict) = dict_or_str.downcast::() { + let new_dict = vm.context().new_dict(); + for (key, val) in dict { + if let Some(num) = key.payload::() { + new_dict.set_item(num.as_bigint().to_i32(), val, vm)?; + } else if let Some(string) = key.payload::() { + if string.len(vm) == 1 { + let num_value = string.value.chars().next().unwrap() as u32; + new_dict.set_item(num_value, val, vm)?; + } else { + return Err(vm.new_value_error( + "string keys in translate table must be of length 1".to_owned(), + )); + } + } + } + new_dict.into_pyobject(vm) + } else { + Err(vm.new_value_error( + "if you give only one argument to maketrans it must be a dict".to_owned(), + )) + } + } + } } impl PyValue for PyString { From c0f14eace3e0530c78eac5c476f3eed41572db77 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 May 2019 03:12:36 +0900 Subject: [PATCH 579/884] Fix str.isidentifier() and add tests snippets --- Cargo.lock | 1 + tests/snippets/strings.py | 7 +++++++ vm/Cargo.toml | 1 + vm/src/obj/objstr.rs | 23 ++++++++++------------- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 33e9ddcfd6..0303e06e9e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -849,6 +849,7 @@ dependencies = [ "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb)", "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 28df3f47f4..0cb3782b17 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -164,6 +164,13 @@ assert '123'.isdecimal() assert not '\u00B2'.isdecimal() +assert not ''.isidentifier() +assert 'python'.isidentifier() +assert '_'.isidentifier() +assert '유니코드'.isidentifier() +assert not '😂'.isidentifier() +assert not '123'.isidentifier() + # String Formatting assert "{} {}".format(1,2) == "1 2" assert "{0} {1}".format(2,3) == "2 3" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 5af648084a..8ac5ebabbe 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -24,6 +24,7 @@ rustc_version_runtime = "0.1.*" statrs = "0.10.0" caseless = "0.2.1" unicode-segmentation = "1.2.1" +unicode-xid = "0.1.0" lazy_static = "^1.0.1" lexical = "2.0.0" itertools = "^0.8.0" diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index e5e2afe370..7b2fcbe9a1 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1,3 +1,5 @@ +extern crate unicode_xid; + use std::fmt; use std::hash::{Hash, Hasher}; use std::ops::Range; @@ -7,6 +9,7 @@ use std::string::ToString; use num_traits::ToPrimitive; use unicode_casing::CharExt; use unicode_segmentation::UnicodeSegmentation; +use unicode_xid::UnicodeXID; use crate::format::{FormatParseError, FormatPart, FormatString}; use crate::function::{OptionalArg, PyFuncArgs}; @@ -832,20 +835,14 @@ impl PyString { #[pymethod] fn isidentifier(&self, _vm: &VirtualMachine) -> bool { - let value = &self.value; + let mut chars = self.value.chars(); + let is_identifier_start = match chars.next() { + Some('_') => true, + Some(c) => UnicodeXID::is_xid_start(c), + None => false, + }; // a string is not an identifier if it has whitespace or starts with a number - if !value.chars().any(|c| c.is_ascii_whitespace()) - && !value.chars().nth(0).unwrap().is_digit(10) - { - for c in value.chars() { - if c != "_".chars().nth(0).unwrap() && !c.is_digit(10) && !c.is_alphabetic() { - return false; - } - } - true - } else { - false - } + is_identifier_start && chars.all(|c| UnicodeXID::is_xid_continue(c)) } } From 8294ac54513700ab05ae353e2d80104b41e9b717 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 11 May 2019 09:17:23 +0300 Subject: [PATCH 580/884] Add os.getcwd --- vm/src/stdlib/os.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index e688737673..ad5ecf9d11 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -479,6 +479,15 @@ fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResu unimplemented!(); } +fn os_getcwd(vm: &VirtualMachine) -> PyResult { + Ok(env::current_dir() + .map_err(|s| vm.new_os_error(s.to_string()))? + .as_path() + .to_str() + .unwrap() + .to_string()) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -534,6 +543,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "stat_result" => stat_result, "stat" => ctx.new_rustfunc(os_stat), "symlink" => ctx.new_rustfunc(os_symlink), + "getcwd" => ctx.new_rustfunc(os_getcwd), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), "O_RDWR" => ctx.new_int(2), From f0fd371b690464313066435cac482c6a5b7fa555 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 11 May 2019 09:29:09 +0300 Subject: [PATCH 581/884] Add os.chdir --- vm/src/stdlib/os.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index ad5ecf9d11..c01b5aaac2 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -488,6 +488,10 @@ fn os_getcwd(vm: &VirtualMachine) -> PyResult { .to_string()) } +fn os_chdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + env::set_current_dir(&path.value).map_err(|s| vm.new_os_error(s.to_string())) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -544,6 +548,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "stat" => ctx.new_rustfunc(os_stat), "symlink" => ctx.new_rustfunc(os_symlink), "getcwd" => ctx.new_rustfunc(os_getcwd), + "chdir" => ctx.new_rustfunc(os_chdir), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), "O_RDWR" => ctx.new_int(2), From c7e9bdd4640de5345c04e97965caba3c80eacf16 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 11 May 2019 09:29:24 +0300 Subject: [PATCH 582/884] Add tests for chdir and getcwd --- tests/snippets/stdlib_os.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 26a101ecb5..39d5e7bdcb 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -65,6 +65,14 @@ def __exit__(self, exc_type, exc_val, exc_tb): pass +class TestWithTempCurrentDir(): + def __enter__(self): + self.prev_cwd = os.getcwd() + + def __exit__(self, exc_type, exc_val, exc_tb): + os.chdir(self.prev_cwd) + + FILE_NAME = "test1" FILE_NAME2 = "test2" SYMLINK_FILE = "symlink" @@ -161,3 +169,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert os.path.basename(fname) == FILE_NAME assert os.path.dirname(fname) == tmpdir + + with TestWithTempCurrentDir(): + os.chdir(tmpdir) + assert os.getcwd() == tmpdir + os.path.exists(FILE_NAME) From b01e960da711dfc612a83205ed389ea7b5b830d1 Mon Sep 17 00:00:00 2001 From: ben Date: Sat, 11 May 2019 19:15:54 +1200 Subject: [PATCH 583/884] Add st_{a,m,c}time to os.stat --- tests/snippets/stdlib_os.py | 7 ++ vm/src/stdlib/os.rs | 146 ++++++++++++++++++++++++++---------- 2 files changed, 112 insertions(+), 41 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 26a101ecb5..9317a8e825 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -143,6 +143,13 @@ def __exit__(self, exc_type, exc_val, exc_tb): print(stat_res.st_gid) print(stat_res.st_size) assert stat_res.st_size == len(CONTENT2) + len(CONTENT3) + print(stat_res.st_atime) + print(stat_res.st_ctime) + print(stat_res.st_mtime) + # test that it all of these times are greater than the 10 May 2019, when this test was written + assert stat_res.st_atime > 1557500000 + assert stat_res.st_ctime > 1557500000 + assert stat_res.st_mtime > 1557500000 # stat default is follow_symlink=True os.stat(fname).st_ino == os.stat(symlink_file).st_ino diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index e688737673..e1b44839db 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -1,7 +1,8 @@ use std::cell::RefCell; use std::fs::File; use std::fs::OpenOptions; -use std::io::{ErrorKind, Read, Write}; +use std::io::{self, ErrorKind, Read, Write}; +use std::time::{Duration, SystemTime}; use std::{env, fs}; use num_traits::cast::ToPrimitive; @@ -261,7 +262,7 @@ impl DirEntryRef { .is_symlink()) } - fn stat(self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { + fn stat(self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { os_stat(self.path(vm).try_into_ref(vm)?, follow_symlinks, vm) } } @@ -317,6 +318,9 @@ struct StatResult { st_uid: u32, st_gid: u32, st_size: u64, + st_atime: f64, + st_ctime: f64, + st_mtime: f64, } impl PyValue for StatResult { @@ -355,47 +359,93 @@ impl StatResultRef { fn st_size(self, _vm: &VirtualMachine) -> u64 { self.st_size } + + fn st_atime(self, _vm: &VirtualMachine) -> f64 { + self.st_atime + } + + fn st_ctime(self, _vm: &VirtualMachine) -> f64 { + self.st_ctime + } + + fn st_mtime(self, _vm: &VirtualMachine) -> f64 { + self.st_mtime + } +} + +// Copied code from Duration::as_secs_f64 as it's still unstable +fn duration_as_secs_f64(duration: Duration) -> f64 { + (duration.as_secs() as f64) + (duration.subsec_nanos() as f64) / (1_000_000_000 as f64) +} + +fn to_seconds_from_unix_epoch(sys_time: SystemTime) -> f64 { + match sys_time.duration_since(SystemTime::UNIX_EPOCH) { + Ok(duration) => duration_as_secs_f64(duration), + Err(err) => -duration_as_secs_f64(err.duration()), + } +} + +fn to_seconds_from_nanos(secs: i64, nanos: i64) -> f64 { + let duration = Duration::new(secs as u64, nanos as u32); + duration_as_secs_f64(duration) } #[cfg(unix)] macro_rules! os_unix_stat_inner { - ( $path:expr, $follow_symlinks:expr, $vm:expr) => {{ - let metadata = match $follow_symlinks.follow_symlinks { - true => fs::metadata($path), - false => fs::symlink_metadata($path), - }; - let meta = metadata.map_err(|s| $vm.new_os_error(s.to_string()))?; - - Ok(StatResult { - st_mode: meta.st_mode(), - st_ino: meta.st_ino(), - st_dev: meta.st_dev(), - st_nlink: meta.st_nlink(), - st_uid: meta.st_uid(), - st_gid: meta.st_gid(), - st_size: meta.st_size(), + ( $path:expr, $follow_symlinks:expr, $vm:expr ) => {{ + fn get_stats(path: &str, follow_symlinks: bool) -> io::Result { + let meta = match follow_symlinks { + true => fs::metadata(path)?, + false => fs::symlink_metadata(path)?, + }; + + Ok(StatResult { + st_mode: meta.st_mode(), + st_ino: meta.st_ino(), + st_dev: meta.st_dev(), + st_nlink: meta.st_nlink(), + st_uid: meta.st_uid(), + st_gid: meta.st_gid(), + st_size: meta.st_size(), + st_atime: to_seconds_from_unix_epoch(meta.accessed()?), + st_mtime: to_seconds_from_unix_epoch(meta.modified()?), + st_ctime: to_seconds_from_nanos(meta.st_ctime(), meta.st_ctime_nsec()), + }) } - .into_ref($vm) - .into_object()) + + get_stats(&$path.value, $follow_symlinks.follow_symlinks) + .map_err(|s| $vm.new_os_error(s.to_string())) }}; } #[cfg(target_os = "linux")] -fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { +fn os_stat( + path: PyStringRef, + follow_symlinks: FollowSymlinks, + vm: &VirtualMachine, +) -> PyResult { use std::os::linux::fs::MetadataExt; - os_unix_stat_inner!(&path.value, follow_symlinks, vm) + os_unix_stat_inner!(path, follow_symlinks, vm) } #[cfg(target_os = "macos")] -fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { +fn os_stat( + path: PyStringRef, + follow_symlinks: FollowSymlinks, + vm: &VirtualMachine, +) -> PyResult { use std::os::macos::fs::MetadataExt; - os_unix_stat_inner!(&path.value, follow_symlinks, vm) + os_unix_stat_inner!(path, follow_symlinks, vm) } #[cfg(target_os = "android")] -fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { +fn os_stat( + path: PyStringRef, + follow_symlinks: FollowSymlinks, + vm: &VirtualMachine, +) -> PyResult { use std::os::android::fs::MetadataExt; - os_unix_stat_inner!(&path.value, follow_symlinks, vm) + os_unix_stat_inner!(path, follow_symlinks, vm) } // Copied from CPython fileutils.c @@ -420,24 +470,35 @@ fn attributes_to_mode(attr: u32) -> u32 { } #[cfg(windows)] -fn os_stat(path: PyStringRef, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { +fn os_stat( + path: PyStringRef, + follow_symlinks: FollowSymlinks, + vm: &VirtualMachine, +) -> PyResult { use std::os::windows::fs::MetadataExt; - let metadata = match follow_symlinks.follow_symlinks { - true => fs::metadata(&path.value), - false => fs::symlink_metadata(&path.value), - }; - let meta = metadata.map_err(|s| vm.new_os_error(s.to_string()))?; - Ok(StatResult { - st_mode: attributes_to_mode(meta.file_attributes()), - st_ino: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. - st_dev: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. - st_nlink: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. - st_uid: 0, // 0 on windows - st_gid: 0, // 0 on windows - st_size: meta.file_size(), + + fn get_stats(path: &str, follow_symlinks: bool) -> io::Result { + let meta = match follow_symlinks { + true => fs::metadata(path)?, + false => fs::symlink_metadata(path)?, + }; + + Ok(StatResult { + st_mode: attributes_to_mode(meta.file_attributes()), + st_ino: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_dev: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_nlink: 0, // TODO: Not implemented in std::os::windows::fs::MetadataExt. + st_uid: 0, // 0 on windows + st_gid: 0, // 0 on windows + st_size: meta.file_size(), + st_atime: to_seconds_from_unix_epoch(meta.accessed()?), + st_mtime: to_seconds_from_unix_epoch(meta.modified()?), + st_ctime: to_seconds_from_unix_epoch(meta.created()?), + }) } - .into_ref(vm) - .into_object()) + + get_stats(&path.value, follow_symlinks.follow_symlinks) + .map_err(|s| vm.new_os_error(s.to_string())) } #[cfg(not(any( @@ -510,6 +571,9 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "st_uid" => ctx.new_property(StatResultRef::st_uid), "st_gid" => ctx.new_property(StatResultRef::st_gid), "st_size" => ctx.new_property(StatResultRef::st_size), + "st_atime" => ctx.new_property(StatResultRef::st_atime), + "st_ctime" => ctx.new_property(StatResultRef::st_ctime), + "st_mtime" => ctx.new_property(StatResultRef::st_mtime), }); py_module!(vm, "_os", { From 152c1654d6e5138f626f0614594638fc77054d89 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 11 May 2019 11:43:11 +0300 Subject: [PATCH 584/884] Add sys.platform --- tests/snippets/sysmod.py | 2 ++ vm/src/sysmodule.rs | 14 ++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/tests/snippets/sysmod.py b/tests/snippets/sysmod.py index bfcf17201e..b7cdc22ec5 100644 --- a/tests/snippets/sysmod.py +++ b/tests/snippets/sysmod.py @@ -2,3 +2,5 @@ print(sys.argv) assert sys.argv[0].endswith('.py') + +assert sys.platform == "linux" or sys.platform == "darwin" or sys.platform == "win32" or sys.platform == "unknown" diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index a5bf2a6f4f..4174fcf102 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -62,6 +62,19 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectR }; let path = ctx.new_list(path_list); + let platform = if cfg!(target_os = "linux") { + "linux".to_string() + } else if cfg!(target_os = "macos") { + "darwin".to_string() + } else if cfg!(target_os = "windows") { + "win32".to_string() + } else if cfg!(target_os = "android") { + // Linux as well. see https://bugs.python.org/issue32637 + "linux".to_string() + } else { + "unknown".to_string() + }; + let sys_doc = "This module provides access to some objects used or maintained by the interpreter and to functions that interact strongly with the interpreter. @@ -145,6 +158,7 @@ settrace() -- set the global debug tracing function "_getframe" => ctx.new_rustfunc(getframe), "modules" => modules.clone(), "warnoptions" => ctx.new_list(vec![]), + "platform" => ctx.new_str(platform), }); modules.set_item("sys", module.clone(), vm).unwrap(); From 7490724b65536140fe11fd51a13a3642a6b0b32c Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Fri, 10 May 2019 11:35:12 +0300 Subject: [PATCH 585/884] Add `itertools` module skeleton --- tests/snippets/stdlib_itertools.py | 1 + vm/src/stdlib/itertools.rs | 7 +++++++ vm/src/stdlib/mod.rs | 2 ++ 3 files changed, 10 insertions(+) create mode 100644 tests/snippets/stdlib_itertools.py create mode 100644 vm/src/stdlib/itertools.rs diff --git a/tests/snippets/stdlib_itertools.py b/tests/snippets/stdlib_itertools.py new file mode 100644 index 0000000000..994a79fbbc --- /dev/null +++ b/tests/snippets/stdlib_itertools.py @@ -0,0 +1 @@ +import itertools diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs new file mode 100644 index 0000000000..f37c9c5f8f --- /dev/null +++ b/vm/src/stdlib/itertools.rs @@ -0,0 +1,7 @@ +use crate::pyobject::PyObjectRef; +use crate::vm::VirtualMachine; + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + py_module!(vm, "itertools", { + }) +} diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 0933b71926..bd59ef9c68 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -1,5 +1,6 @@ mod ast; mod dis; +mod itertools; pub(crate) mod json; mod keyword; mod math; @@ -34,6 +35,7 @@ pub fn get_module_inits() -> HashMap { Box::new(ast::make_module) as StdlibInitFunc, ); modules.insert("dis".to_string(), Box::new(dis::make_module)); + modules.insert("itertools".to_string(), Box::new(itertools::make_module)); modules.insert("json".to_string(), Box::new(json::make_module)); modules.insert("keyword".to_string(), Box::new(keyword::make_module)); modules.insert("math".to_string(), Box::new(math::make_module)); From ce514d2aa5dcc30f8fa4e744adc8eb537b7776e3 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Fri, 10 May 2019 17:27:28 +0300 Subject: [PATCH 586/884] Add `itertools.count` --- tests/snippets/stdlib_itertools.py | 54 ++++++++++++++++++++++++ vm/src/stdlib/itertools.rs | 68 +++++++++++++++++++++++++++++- 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_itertools.py b/tests/snippets/stdlib_itertools.py index 994a79fbbc..cfda2fb689 100644 --- a/tests/snippets/stdlib_itertools.py +++ b/tests/snippets/stdlib_itertools.py @@ -1 +1,55 @@ import itertools + +# count + +# default arguments +c = itertools.count() +assert next(c) == 0 +assert next(c) == 1 +assert next(c) == 2 + +# positional +c = itertools.count(2, 3) +assert next(c) == 2 +assert next(c) == 5 +assert next(c) == 8 + +# backwards +c = itertools.count(1, -10) +assert next(c) == 1 +assert next(c) == -9 +assert next(c) == -19 + +# step = 0 +c = itertools.count(5, 0) +assert next(c) == 5 +assert next(c) == 5 + +# itertools.count TODOs: kwargs and floats + +# step kwarg +# c = itertools.count(step=5) +# assert next(c) == 0 +# assert next(c) == 5 + +# start kwarg +# c = itertools.count(start=10) +# assert next(c) == 10 + +# float start +# c = itertools.count(0.5) +# assert next(c) == 0.5 +# assert next(c) == 1.5 +# assert next(c) == 2.5 + +# float step +# c = itertools.count(1, 0.5) +# assert next(c) == 1 +# assert next(c) == 1.5 +# assert next(c) == 2 + +# float start + step +# c = itertools.count(0.5, 0.5) +# assert next(c) == 0.5 +# assert next(c) == 1 +# assert next(c) == 1.5 diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index f37c9c5f8f..a7a955fa59 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -1,7 +1,73 @@ -use crate::pyobject::PyObjectRef; +use std::cell::RefCell; +use std::ops::AddAssign; + +use num_bigint::BigInt; + +use crate::function::OptionalArg; +use crate::obj::objint::{PyInt, PyIntRef}; +use crate::obj::objtype::PyClassRef; +use crate::pyobject::{PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; +#[pyclass] +#[derive(Debug)] +struct PyItertoolsCount { + cur: RefCell, + step: BigInt, +} + +impl PyValue for PyItertoolsCount { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("itertools", "count") + } +} + +#[pyimpl] +impl PyItertoolsCount { + #[pymethod(name = "__new__")] + fn new( + _cls: PyClassRef, + start: OptionalArg, + step: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let start = match start.into_option() { + Some(int) => int.as_bigint().clone(), + None => BigInt::from(0), + }; + let step = match step.into_option() { + Some(int) => int.as_bigint().clone(), + None => BigInt::from(1), + }; + + Ok(PyItertoolsCount { + cur: RefCell::new(start), + step: step, + } + .into_ref(vm) + .into_object()) + } + + #[pymethod(name = "__next__")] + fn next(&self, _vm: &VirtualMachine) -> PyResult { + let result = self.cur.borrow().clone(); + AddAssign::add_assign(&mut self.cur.borrow_mut() as &mut BigInt, &self.step); + Ok(PyInt::new(result)) + } + + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + let count = ctx.new_class("count", ctx.object()); + PyItertoolsCount::extend_class(ctx, &count); + py_module!(vm, "itertools", { + "count" => count, }) } From f7531d8b084ed37f1e95c7f0c30752b6a341facb Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Fri, 10 May 2019 23:54:35 +0300 Subject: [PATCH 587/884] Add `binascii` module skeleton --- tests/snippets/stdlib_binascii.py | 1 + vm/src/stdlib/binascii.rs | 7 +++++++ vm/src/stdlib/mod.rs | 2 ++ 3 files changed, 10 insertions(+) create mode 100644 tests/snippets/stdlib_binascii.py create mode 100644 vm/src/stdlib/binascii.rs diff --git a/tests/snippets/stdlib_binascii.py b/tests/snippets/stdlib_binascii.py new file mode 100644 index 0000000000..35c5e9f0a9 --- /dev/null +++ b/tests/snippets/stdlib_binascii.py @@ -0,0 +1 @@ +import binascii diff --git a/vm/src/stdlib/binascii.rs b/vm/src/stdlib/binascii.rs new file mode 100644 index 0000000000..b0675a1a9d --- /dev/null +++ b/vm/src/stdlib/binascii.rs @@ -0,0 +1,7 @@ +use crate::pyobject::PyObjectRef; +use crate::vm::VirtualMachine; + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + py_module!(vm, "binascii", { + }) +} diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 0933b71926..c17a270ac1 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -1,4 +1,5 @@ mod ast; +mod binascii; mod dis; pub(crate) mod json; mod keyword; @@ -33,6 +34,7 @@ pub fn get_module_inits() -> HashMap { "ast".to_string(), Box::new(ast::make_module) as StdlibInitFunc, ); + modules.insert("binascii".to_string(), Box::new(binascii::make_module)); modules.insert("dis".to_string(), Box::new(dis::make_module)); modules.insert("json".to_string(), Box::new(json::make_module)); modules.insert("keyword".to_string(), Box::new(keyword::make_module)); From 8dd664ef2013c4bb0c16cf71da2f89a8272f074c Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sat, 11 May 2019 00:29:42 +0300 Subject: [PATCH 588/884] Add `binascii.hexlify` --- tests/snippets/stdlib_binascii.py | 13 +++++++++++++ vm/src/stdlib/binascii.rs | 28 +++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_binascii.py b/tests/snippets/stdlib_binascii.py index 35c5e9f0a9..cee3564628 100644 --- a/tests/snippets/stdlib_binascii.py +++ b/tests/snippets/stdlib_binascii.py @@ -1 +1,14 @@ import binascii +from testutils import assertRaises + + +# hexlify tests +h = binascii.hexlify + +assert h(b"abc") == b"616263" +assert h(1000 * b"x") == 1000 * b"78" +# bytearray not supported yet +# assert h(bytearray(b"a")) = b"61" + +with assertRaises(TypeError): + h("a") diff --git a/vm/src/stdlib/binascii.rs b/vm/src/stdlib/binascii.rs index b0675a1a9d..166376b570 100644 --- a/vm/src/stdlib/binascii.rs +++ b/vm/src/stdlib/binascii.rs @@ -1,7 +1,33 @@ -use crate::pyobject::PyObjectRef; +use crate::function::PyFuncArgs; +use crate::obj::objbytes; +use crate::pyobject::{PyObjectRef, PyResult}; use crate::vm::VirtualMachine; +fn hex_nibble(n: u8) -> u8 { + match n { + 0..=9 => b'0' + n, + 10..=15 => b'a' + n, + _ => unreachable!(), + } +} + +fn binascii_hexlify(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(data, Some(vm.ctx.bytes_type()))]); + + let bytes = objbytes::get_value(data); + let mut hex = Vec::::with_capacity(bytes.len() * 2); + for b in bytes.iter() { + hex.push(hex_nibble(b >> 4)); + hex.push(hex_nibble(b & 0xf)); + } + + Ok(vm.ctx.new_bytes(hex)) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + py_module!(vm, "binascii", { + "hexlify" => ctx.new_rustfunc(binascii_hexlify), }) } From 070529d8c7b2e3f4fb9d6cfb04a89db96ac9ac54 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sat, 11 May 2019 02:16:32 +0300 Subject: [PATCH 589/884] Add `binascii.unhexlify` --- tests/snippets/stdlib_binascii.py | 19 ++++++++++++++++++ vm/src/stdlib/binascii.rs | 33 +++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/tests/snippets/stdlib_binascii.py b/tests/snippets/stdlib_binascii.py index cee3564628..418fad36df 100644 --- a/tests/snippets/stdlib_binascii.py +++ b/tests/snippets/stdlib_binascii.py @@ -12,3 +12,22 @@ with assertRaises(TypeError): h("a") + + +# unhexlify tests +uh = binascii.unhexlify + +assert uh(b"616263") == b"abc" +assert uh(1000 * b"78") == 1000 * b"x" +x = 1000 * b"1234" +assert uh(h(x)) == x +assert uh(b"ABCDEF") == b"\xab\xcd\xef" + +# unhexlify on strings not supported yet +# assert uh("abcd") == b"\xab\xcd" + +with assertRaises(ValueError): + uh(b"a") # Odd-length string + +with assertRaises(ValueError): + uh(b"nn") # Non-hexadecimal digit found diff --git a/vm/src/stdlib/binascii.rs b/vm/src/stdlib/binascii.rs index 166376b570..3297c5e1a4 100644 --- a/vm/src/stdlib/binascii.rs +++ b/vm/src/stdlib/binascii.rs @@ -24,10 +24,43 @@ fn binascii_hexlify(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bytes(hex)) } +fn unhex_nibble(c: u8) -> Option { + match c { + b'0'..=b'9' => Some(c - b'0'), + b'a'..=b'f' => Some(c - b'a' + 10), + b'A'..=b'F' => Some(c - b'A' + 10), + _ => None, + } +} + +fn binascii_unhexlify(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + // TODO: allow 'str' hexstrings as well + arg_check!(vm, args, required = [(hexstr, Some(vm.ctx.bytes_type()))]); + + let hex_bytes = &objbytes::get_value(hexstr); + if hex_bytes.len() % 2 != 0 { + return Err(vm.new_value_error("Odd-length string".to_string())); + } + + let mut unhex = Vec::::with_capacity(hex_bytes.len() / 2); + for i in (0..hex_bytes.len()).step_by(2) { + let n1 = unhex_nibble(hex_bytes[i]); + let n2 = unhex_nibble(hex_bytes[i + 1]); + if n1.is_some() && n2.is_some() { + unhex.push(n1.unwrap() << 4 | n2.unwrap()); + } else { + return Err(vm.new_value_error("Non-hexadecimal digit found".to_string())); + } + } + + Ok(vm.ctx.new_bytes(unhex)) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; py_module!(vm, "binascii", { "hexlify" => ctx.new_rustfunc(binascii_hexlify), + "unhexlify" => ctx.new_rustfunc(binascii_unhexlify), }) } From 322aff70df0d2dece219c178d32ea9e1893c4641 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sat, 11 May 2019 02:19:10 +0300 Subject: [PATCH 590/884] Add `binascii.b2a_hex`, `binascii.a2b_hex` --- tests/snippets/stdlib_binascii.py | 2 ++ vm/src/stdlib/binascii.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/tests/snippets/stdlib_binascii.py b/tests/snippets/stdlib_binascii.py index 418fad36df..e73e162bd2 100644 --- a/tests/snippets/stdlib_binascii.py +++ b/tests/snippets/stdlib_binascii.py @@ -9,6 +9,7 @@ assert h(1000 * b"x") == 1000 * b"78" # bytearray not supported yet # assert h(bytearray(b"a")) = b"61" +assert binascii.b2a_hex(b"aa") == b"6161" with assertRaises(TypeError): h("a") @@ -22,6 +23,7 @@ x = 1000 * b"1234" assert uh(h(x)) == x assert uh(b"ABCDEF") == b"\xab\xcd\xef" +assert binascii.a2b_hex(b"6161") == b"aa" # unhexlify on strings not supported yet # assert uh("abcd") == b"\xab\xcd" diff --git a/vm/src/stdlib/binascii.rs b/vm/src/stdlib/binascii.rs index 3297c5e1a4..97fb76f8e2 100644 --- a/vm/src/stdlib/binascii.rs +++ b/vm/src/stdlib/binascii.rs @@ -61,6 +61,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { py_module!(vm, "binascii", { "hexlify" => ctx.new_rustfunc(binascii_hexlify), + "b2a_hex" => ctx.new_rustfunc(binascii_hexlify), "unhexlify" => ctx.new_rustfunc(binascii_unhexlify), + "a2b_hex" => ctx.new_rustfunc(binascii_unhexlify), }) } From 12e831380d867b9a4fbf09010b40146908610987 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Fri, 10 May 2019 18:36:17 +0300 Subject: [PATCH 591/884] Add `itertools.repeat` --- tests/snippets/stdlib_itertools.py | 31 +++++++++++++- vm/src/stdlib/itertools.rs | 66 +++++++++++++++++++++++++++++- 2 files changed, 95 insertions(+), 2 deletions(-) diff --git a/tests/snippets/stdlib_itertools.py b/tests/snippets/stdlib_itertools.py index cfda2fb689..61c031dd78 100644 --- a/tests/snippets/stdlib_itertools.py +++ b/tests/snippets/stdlib_itertools.py @@ -1,6 +1,9 @@ import itertools -# count +from testutils import assertRaises + + +# itertools.count tests # default arguments c = itertools.count() @@ -53,3 +56,29 @@ # assert next(c) == 0.5 # assert next(c) == 1 # assert next(c) == 1.5 + + +# itertools.repeat tests + +# no times +r = itertools.repeat(5) +assert next(r) == 5 +assert next(r) == 5 +assert next(r) == 5 + +# times +r = itertools.repeat(1, 2) +assert next(r) == 1 +assert next(r) == 1 +with assertRaises(StopIteration): + next(r) + +# timees = 0 +r = itertools.repeat(1, 0) +with assertRaises(StopIteration): + next(r) + +# negative times +r = itertools.repeat(1, -1) +with assertRaises(StopIteration): + next(r) diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index a7a955fa59..3856fe92f2 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -1,10 +1,12 @@ use std::cell::RefCell; -use std::ops::AddAssign; +use std::cmp::Ordering; +use std::ops::{AddAssign, SubAssign}; use num_bigint::BigInt; use crate::function::OptionalArg; use crate::obj::objint::{PyInt, PyIntRef}; +use crate::obj::objiter::new_stop_iteration; use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; @@ -61,13 +63,75 @@ impl PyItertoolsCount { } } +#[pyclass] +#[derive(Debug)] +struct PyItertoolsRepeat { + object: PyObjectRef, + times: Option>, +} + +impl PyValue for PyItertoolsRepeat { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("itertools", "repeat") + } +} + +#[pyimpl] +impl PyItertoolsRepeat { + #[pymethod(name = "__new__")] + fn new( + _cls: PyClassRef, + object: PyObjectRef, + times: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let times = match times.into_option() { + Some(int) => Some(RefCell::new(int.as_bigint().clone())), + None => None, + }; + + Ok(PyItertoolsRepeat { + object: object.clone(), + times: times, + } + .into_ref(vm) + .into_object()) + } + + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { + if self.times.is_some() { + match self.times.as_ref().unwrap().borrow().cmp(&BigInt::from(0)) { + Ordering::Less | Ordering::Equal => return Err(new_stop_iteration(vm)), + _ => (), + }; + + SubAssign::sub_assign( + &mut self.times.as_ref().unwrap().borrow_mut() as &mut BigInt, + &BigInt::from(1), + ); + } + + Ok(self.object.clone()) + } + + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let count = ctx.new_class("count", ctx.object()); PyItertoolsCount::extend_class(ctx, &count); + let repeat = ctx.new_class("repeat", ctx.object()); + PyItertoolsRepeat::extend_class(ctx, &repeat); + py_module!(vm, "itertools", { "count" => count, + "repeat" => repeat, }) } From d1c95d1c513a206f02d6b71eedba40a1671ddc02 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 11 May 2019 17:17:00 +0300 Subject: [PATCH 592/884] Remove patch in posixpath.py --- Lib/posixpath.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/posixpath.py b/Lib/posixpath.py index ae54f23872..ecb4e5a8f7 100644 --- a/Lib/posixpath.py +++ b/Lib/posixpath.py @@ -444,7 +444,7 @@ def _joinrealpath(path, rest, seen): return path, True -supports_unicode_filenames = (hasattr(sys, "platform") and sys.platform == 'darwin') +supports_unicode_filenames = (sys.platform == 'darwin') def relpath(path, start=None): """Return a relative version of a path""" From b54354860f865f4acb175bda9bbb94fc26def6e3 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 11 May 2019 18:06:06 +0300 Subject: [PATCH 593/884] Move fspath to _os --- Lib/os.py | 31 ------------------------------- vm/src/stdlib/os.rs | 17 ++++++++++++++++- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/Lib/os.py b/Lib/os.py index c768946c52..cb49481dff 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -133,34 +133,3 @@ def getenv(key, default=None): The optional second argument can specify an alternate default. key, default and the result are str.""" return environ.get(key, default) - - -def fspath(path): - """Return the path representation of a path-like object. - - If str or bytes is passed in, it is returned unchanged. Otherwise the - os.PathLike interface is used to get the path representation. If the - path representation is not str or bytes, TypeError is raised. If the - provided path is not str, bytes, or os.PathLike, TypeError is raised. - """ - if isinstance(path, (str, bytes)): - return path - - # Work from the object's type to match method resolution of other magic - # methods. - path_type = type(path) - try: - path_repr = path_type.__fspath__(path) - except AttributeError: - if hasattr(path_type, '__fspath__'): - raise - else: - raise TypeError("expected str, bytes or os.PathLike object, " - "not " + path_type.__name__) - if isinstance(path_repr, (str, bytes)): - return path_repr - else: - raise TypeError("expected {}.__fspath__() to return str or bytes, " - "not {}".format(path_type.__name__, - type(path_repr).__name__)) - diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index c01b5aaac2..a69a97cc24 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -14,9 +14,10 @@ use crate::obj::objint::PyIntRef; use crate::obj::objiter; use crate::obj::objstr; use crate::obj::objstr::PyStringRef; +use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ - ItemProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryIntoRef, + ItemProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryIntoRef, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -492,6 +493,19 @@ fn os_chdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { env::set_current_dir(&path.value).map_err(|s| vm.new_os_error(s.to_string())) } +fn os_fspath(path: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::issubclass(&path.class(), &vm.ctx.str_type()) + || objtype::issubclass(&path.class(), &vm.ctx.bytes_type()) + { + Ok(path) + } else { + Err(vm.new_type_error(format!( + "expected str or bytes object, not {}", + path.class() + ))) + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -549,6 +563,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "symlink" => ctx.new_rustfunc(os_symlink), "getcwd" => ctx.new_rustfunc(os_getcwd), "chdir" => ctx.new_rustfunc(os_chdir), + "fspath" => ctx.new_rustfunc(os_fspath), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), "O_RDWR" => ctx.new_int(2), From d96d04f0fde2bb4b567bac75d70ebf0c53cf8069 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 11 May 2019 18:08:47 +0300 Subject: [PATCH 594/884] Add os.fspath tests --- tests/snippets/stdlib_os.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 39d5e7bdcb..32cb7d4a9b 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -49,6 +49,10 @@ assert os.altsep == None assert os.pathsep == ":" +assert os.fspath("Testing") == "Testing" +assert os.fspath(b"Testing") == b"Testing" +assert_raises(TypeError, lambda: os.fspath([1,2,3])) + class TestWithTempDir(): def __enter__(self): if os.name == "nt": From 4070baa6ac71a1a6731622e68fd1baea40fd5b3f Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 12 May 2019 07:51:14 +1200 Subject: [PATCH 595/884] Add fsync and more tests for file times --- tests/snippets/stdlib_os.py | 30 ++++++++++++++++++++++++++++++ vm/src/stdlib/os.rs | 11 +++++++++++ 2 files changed, 41 insertions(+) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 9317a8e825..30b48d0033 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -88,6 +88,9 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert os.read(fd, len(CONTENT3)) == CONTENT3 os.close(fd) + # wait a little bit to ensure that the file times aren't the same + time.sleep(0.1) + fname2 = os.path.join(tmpdir, FILE_NAME2) with open(fname2, "wb"): pass @@ -151,6 +154,33 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert stat_res.st_ctime > 1557500000 assert stat_res.st_mtime > 1557500000 + stat_file2 = os.stat(fname2) + print(stat_file2.st_ctime) + assert stat_file2.st_ctime > stat_res.st_ctime + + # wait a little bit to ensures that the access/modify time is different + time.sleep(0.1) + + old_atime = stat_res.st_atime + old_mtime = stat_res.st_mtime + + fd = os.open(fname, os.O_RDWR) + os.write(fd, CONTENT) + os.fsync(fd) + + os.read(fd, 1) + os.fsync(fd) + os.close(fd) + + # retrieve update file stats + stat_res = os.stat(fname) + print(stat_res.st_atime) + print(stat_res.st_ctime) + print(stat_res.st_mtime) + assert stat_res.st_atime > old_atime, "Access time should be update" + assert stat_res.st_mtime > old_mtime, "Modified time should be update" + assert stat_res.st_atime > stat_res.st_mtime + # stat default is follow_symlink=True os.stat(fname).st_ino == os.stat(symlink_file).st_ino os.stat(fname).st_mode == os.stat(symlink_file).st_mode diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index e1b44839db..93d40b38ba 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -89,6 +89,7 @@ pub fn os_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let handle = match objint::get_value(mode).to_u16().unwrap() { 0 => OpenOptions::new().read(true).open(&fname), 1 => OpenOptions::new().write(true).open(&fname), + 2 => OpenOptions::new().read(true).write(true).open(&fname), 512 => OpenOptions::new().write(true).create(true).open(&fname), _ => OpenOptions::new().read(true).open(&fname), } @@ -124,6 +125,15 @@ fn os_error(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Err(vm.new_os_error(msg)) } +fn os_fsync(fd: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { + let file = rust_file(fd.as_bigint().to_i64().unwrap()); + file.sync_all() + .map_err(|s| vm.new_os_error(s.to_string()))?; + // Avoid closing the fd + raw_file_number(file); + Ok(()) +} + fn os_read(fd: PyIntRef, n: PyIntRef, vm: &VirtualMachine) -> PyResult { let mut buffer = vec![0u8; n.as_bigint().to_usize().unwrap()]; let mut file = rust_file(fd.as_bigint().to_i64().unwrap()); @@ -580,6 +590,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "open" => ctx.new_rustfunc(os_open), "close" => ctx.new_rustfunc(os_close), "error" => ctx.new_rustfunc(os_error), + "fsync" => ctx.new_rustfunc(os_fsync), "read" => ctx.new_rustfunc(os_read), "write" => ctx.new_rustfunc(os_write), "remove" => ctx.new_rustfunc(os_remove), From f2703511b959973ec7cfdc4f7eee6c8e1b2807c5 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 12 May 2019 08:26:37 +1200 Subject: [PATCH 596/884] Fix dead code warning on windows and disable access time tests as file access time on windows has quite bad resolution. --- tests/snippets/stdlib_os.py | 7 +++++-- vm/src/stdlib/os.rs | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 30b48d0033..96906308a7 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -177,9 +177,12 @@ def __exit__(self, exc_type, exc_val, exc_tb): print(stat_res.st_atime) print(stat_res.st_ctime) print(stat_res.st_mtime) - assert stat_res.st_atime > old_atime, "Access time should be update" + if os.name != "nt": + # access time on windows has a resolution ranging from 1 hour to 1 day + # https://docs.microsoft.com/en-gb/windows/desktop/api/minwinbase/ns-minwinbase-filetime + assert stat_res.st_atime > old_atime, "Access time should be update" + assert stat_res.st_atime > stat_res.st_mtime assert stat_res.st_mtime > old_mtime, "Modified time should be update" - assert stat_res.st_atime > stat_res.st_mtime # stat default is follow_symlink=True os.stat(fname).st_ino == os.stat(symlink_file).st_ino diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 93d40b38ba..6fd2daf724 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -395,6 +395,7 @@ fn to_seconds_from_unix_epoch(sys_time: SystemTime) -> f64 { } } +#[cfg(unix)] fn to_seconds_from_nanos(secs: i64, nanos: i64) -> f64 { let duration = Duration::new(secs as u64, nanos as u32); duration_as_secs_f64(duration) From 645c6dec48e956d35683970bb922f60a495e88f5 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 12 May 2019 08:55:39 +1200 Subject: [PATCH 597/884] Add wait between file modification and access --- tests/snippets/stdlib_os.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 96906308a7..7d8d84a9d4 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -158,7 +158,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): print(stat_file2.st_ctime) assert stat_file2.st_ctime > stat_res.st_ctime - # wait a little bit to ensures that the access/modify time is different + # wait a little bit to ensures that the access/modify time will change time.sleep(0.1) old_atime = stat_res.st_atime @@ -168,6 +168,9 @@ def __exit__(self, exc_type, exc_val, exc_tb): os.write(fd, CONTENT) os.fsync(fd) + # wait a little bit to ensures that the access/modify time is different + time.sleep(0.1) + os.read(fd, 1) os.fsync(fd) os.close(fd) From 268cb8d21776e110f1b9ef288592dc465e78ec57 Mon Sep 17 00:00:00 2001 From: zer0 Date: Sat, 11 May 2019 23:58:42 +0300 Subject: [PATCH 598/884] fix str.maketrans 2 and 3 arguments cases --- tests/snippets/strings.py | 2 +- vm/src/obj/objstr.rs | 4 ++-- whats_left.sh | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 480500e9f8..531e8f83c1 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -162,7 +162,7 @@ # str.maketrans assert str.maketrans({"a": "abc", "b": None, "c": 33}) == {97: "abc", 98: None, 99: 33} -assert str.maketrans("hello", "world", "rust") == {103: "w", 101: "o", 108: "l", 111: "d", "r": None, "u": None, "s": None, "t": None} +assert str.maketrans("hello", "world", "rust") == {104: 119, 101: 111, 108: 108, 111: 100, 114: None, 117: None, 115: None, 116: None} def try_mutate_str(): word = "word" diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index a4e6da8c50..6062d1d178 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -884,7 +884,7 @@ impl PyString { let new_dict = vm.context().new_dict(); if to_str.len(vm) == from_str.len(vm) { for (c1, c2) in from_str.value.chars().zip(to_str.value.chars()) { - new_dict.set_item(c1.to_string(), vm.new_str(c2.to_string()), vm)?; + new_dict.set_item(c1 as u32, vm.new_int(c2 as u32), vm)?; } } else { return Err(vm.new_value_error( @@ -893,7 +893,7 @@ impl PyString { } if let OptionalArg::Present(none_str) = none_str { for c in none_str.value.chars() { - new_dict.set_item(c.to_string(), vm.get_none(), vm)?; + new_dict.set_item(c as u32, vm.get_none(), vm)?; } } new_dict.into_pyobject(vm) diff --git a/whats_left.sh b/whats_left.sh index aa79d8d653..66e64f1a37 100755 --- a/whats_left.sh +++ b/whats_left.sh @@ -8,4 +8,4 @@ python3 not_impl_gen.py cd .. -cargo +nightly run -- tests/snippets/whats_left_to_implement.py +cargo run -- tests/snippets/whats_left_to_implement.py \ No newline at end of file From b94923314db7990bf11f21bff8de261cde6152ae Mon Sep 17 00:00:00 2001 From: zer0 Date: Sun, 12 May 2019 00:00:21 +0300 Subject: [PATCH 599/884] removed import warning --- vm/src/obj/objstr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 6062d1d178..a87aad20e2 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -16,7 +16,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objdict::{PyDict, PyDictRef}; +use super::objdict::PyDict; use super::objint::{self, PyInt}; use super::objnone::PyNone; use super::objsequence::PySliceableSequence; From 764215151d1aabfeff608c9503209a8c57ae6eda Mon Sep 17 00:00:00 2001 From: zer0 Date: Sun, 12 May 2019 00:40:43 +0300 Subject: [PATCH 600/884] more idiomatic code --- vm/src/obj/objstr.rs | 90 +++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 42 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index a87aad20e2..c084ca0ce6 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -835,19 +835,19 @@ impl PyString { // https://docs.python.org/3/library/stdtypes.html#str.translate #[pymethod] fn translate(&self, table: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let mut result = String::new(); + let mut translated = String::new(); // It throws a type error if it is not subscribtable vm.get_method(table.clone(), "__getitem__")?; for c in self.value.chars() { match table.get_item(c as u32, vm) { Ok(value) => { if let Some(text) = value.payload::() { - result.extend(text.value.chars()); + translated.extend(text.value.chars()); } else if let Some(_) = value.payload::() { // Do Nothing } else if let Some(bigint) = value.payload::() { match bigint.as_bigint().to_u32().and_then(std::char::from_u32) { - Some(ch) => result.push(ch as char), + Some(ch) => translated.push(ch as char), None => { return Err(vm.new_value_error(format!( "character mapping must be in range(0x110000)" @@ -860,10 +860,10 @@ impl PyString { )); } } - _ => result.push(c), + _ => translated.push(c), } } - Ok(result) + Ok(translated) } #[pymethod] @@ -873,53 +873,54 @@ impl PyString { none_str: OptionalArg, vm: &VirtualMachine, ) -> PyResult { + let new_dict = vm.context().new_dict(); if let OptionalArg::Present(to_str) = to_str { - // dict_or_str is str - let from_str = dict_or_str.payload::().ok_or( - vm.new_type_error( + match dict_or_str.downcast::() { + Ok(from_str) => { + if to_str.len(vm) == from_str.len(vm) { + for (c1, c2) in from_str.value.chars().zip(to_str.value.chars()) { + new_dict.set_item(c1 as u32, vm.new_int(c2 as u32), vm)?; + } + if let OptionalArg::Present(none_str) = none_str { + for c in none_str.value.chars() { + new_dict.set_item(c as u32, vm.get_none(), vm)?; + } + } + new_dict.into_pyobject(vm) + } else { + Err(vm.new_value_error( + "the first two maketrans arguments must have equal length".to_owned(), + )) + } + } + _ => Err(vm.new_type_error( "first maketrans argument must be a string if there is a second argument" .to_owned(), - ), - )?; - let new_dict = vm.context().new_dict(); - if to_str.len(vm) == from_str.len(vm) { - for (c1, c2) in from_str.value.chars().zip(to_str.value.chars()) { - new_dict.set_item(c1 as u32, vm.new_int(c2 as u32), vm)?; - } - } else { - return Err(vm.new_value_error( - "the first two maketrans arguments must have equal length".to_owned(), - )); + )), } - if let OptionalArg::Present(none_str) = none_str { - for c in none_str.value.chars() { - new_dict.set_item(c as u32, vm.get_none(), vm)?; - } - } - new_dict.into_pyobject(vm) } else { // dict_str must be a dict - if let Ok(dict) = dict_or_str.downcast::() { - let new_dict = vm.context().new_dict(); - for (key, val) in dict { - if let Some(num) = key.payload::() { - new_dict.set_item(num.as_bigint().to_i32(), val, vm)?; - } else if let Some(string) = key.payload::() { - if string.len(vm) == 1 { - let num_value = string.value.chars().next().unwrap() as u32; - new_dict.set_item(num_value, val, vm)?; - } else { - return Err(vm.new_value_error( - "string keys in translate table must be of length 1".to_owned(), - )); + match dict_or_str.downcast::() { + Ok(dict) => { + for (key, val) in dict { + if let Some(num) = key.payload::() { + new_dict.set_item(num.as_bigint().to_i32(), val, vm)?; + } else if let Some(string) = key.payload::() { + if string.len(vm) == 1 { + let num_value = string.value.chars().next().unwrap() as u32; + new_dict.set_item(num_value, val, vm)?; + } else { + return Err(vm.new_value_error( + "string keys in translate table must be of length 1".to_owned(), + )); + } } } + new_dict.into_pyobject(vm) } - new_dict.into_pyobject(vm) - } else { - Err(vm.new_value_error( + _ => Err(vm.new_value_error( "if you give only one argument to maketrans it must be a dict".to_owned(), - )) + )), } } } @@ -1217,4 +1218,9 @@ mod tests { let translated = text.translate(vm.new_int(3), &vm); println!("{:?}", translated); } + + #[test] + fn str_maketrans() { + let vm = VirtualMachine::new(); + } } From 90711f10a09f62062a358041fbf3ecf7c6f49506 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 12 May 2019 13:14:27 +1200 Subject: [PATCH 601/884] Accept tuple for first arg in str.startswith and str.endswith --- tests/snippets/strings.py | 7 ++++++ vm/src/builtins.rs | 48 +++++++++++++++++++-------------------- vm/src/function.rs | 26 +++++++++++++++++++++ vm/src/obj/objstr.rs | 44 ++++++++++++++++++++++++++--------- 4 files changed, 89 insertions(+), 36 deletions(-) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 0cb3782b17..4a480cbf54 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -42,9 +42,15 @@ assert a.upper() == 'HALLO' assert a.split('al') == ['H', 'lo'] assert a.startswith('H') +assert a.startswith(('H', 1)) +assert a.startswith(('A', 'H')) assert not a.startswith('f') +assert not a.startswith(('A', 'f')) assert a.endswith('llo') +assert a.endswith(('lo', 1)) +assert a.endswith(('A', 'lo')) assert not a.endswith('on') +assert not a.endswith(('A', 'll')) assert a.zfill(8) == '000Hallo' assert a.isalnum() assert not a.isdigit() @@ -144,6 +150,7 @@ assert '___a__'.find('a', 4, 3) == -1 assert 'abcd'.startswith('b', 1) +assert 'abcd'.startswith(('b', 'z'), 1) assert not 'abcd'.startswith('b', -4) assert 'abcd'.startswith('b', -3) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index a48a3b4412..e9056e2eaf 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -17,11 +17,10 @@ use crate::obj::objdict::PyDictRef; use crate::obj::objint::{self, PyIntRef}; use crate::obj::objiter; use crate::obj::objstr::{self, PyString, PyStringRef}; -use crate::obj::objtuple::PyTuple; -use crate::obj::objtype::{self, PyClass, PyClassRef}; +use crate::obj::objtype::{self, PyClassRef}; use crate::frame::Scope; -use crate::function::{Args, KwArgs, OptionalArg, PyFuncArgs}; +use crate::function::{single_or_tuple_any, Args, KwArgs, OptionalArg, PyFuncArgs}; use crate::pyobject::{ IdProtocol, IntoPyObject, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, @@ -317,37 +316,36 @@ fn builtin_id(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // builtin_input -fn type_test( - vm: &VirtualMachine, - typ: PyObjectRef, - test: impl Fn(&PyClassRef) -> PyResult, - test_name: &str, -) -> PyResult { - match_class!(typ, - cls @ PyClass => test(&cls), - tuple @ PyTuple => { - for cls_obj in tuple.elements.borrow().iter() { - let cls = PyClassRef::try_from_object(vm, cls_obj.clone())?; - if test(&cls)? { - return Ok(true); - } - } - Ok(false) +fn builtin_isinstance(obj: PyObjectRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult { + single_or_tuple_any( + typ, + |cls: PyClassRef| vm.isinstance(&obj, &cls), + |o| { + format!( + "isinstance() arg 2 must be a type or tuple of types, not {}", + o.class() + ) }, - _ => Err(vm.new_type_error(format!("{}() arg 2 must be a type or tuple of types", test_name))) + vm, ) } -fn builtin_isinstance(obj: PyObjectRef, typ: PyObjectRef, vm: &VirtualMachine) -> PyResult { - type_test(vm, typ, |cls| vm.isinstance(&obj, cls), "isinstance") -} - fn builtin_issubclass( subclass: PyClassRef, typ: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - type_test(vm, typ, |cls| vm.issubclass(&subclass, cls), "issubclass") + single_or_tuple_any( + typ, + |cls: PyClassRef| vm.issubclass(&subclass, &cls), + |o| { + format!( + "issubclass() arg 2 must be a class or tuple of classes, not {}", + o.class() + ) + }, + vm, + ) } fn builtin_iter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/function.rs b/vm/src/function.rs index d3f26950f0..170c395f6e 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::mem; use std::ops::RangeInclusive; +use crate::obj::objtuple::PyTuple; use crate::obj::objtype::{isinstance, PyClassRef}; use crate::pyobject::{ IntoPyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, @@ -523,3 +524,28 @@ into_py_native_func_tuple!((a, A), (b, B)); into_py_native_func_tuple!((a, A), (b, B), (c, C)); into_py_native_func_tuple!((a, A), (b, B), (c, C), (d, D)); into_py_native_func_tuple!((a, A), (b, B), (c, C), (d, D), (e, E)); + +/// Tests that the predicate is True on a single value, or if the value is a tuple a tuple, then +/// test that any of the values contained within the tuples satisfies the predicate. Type parameter +/// T specifies the type that is expected, if the input value is not of that type or a tuple of +/// values of that type, then a TypeError is raised. +pub fn single_or_tuple_any) -> PyResult>( + obj: PyObjectRef, + predicate: F, + message: fn(&PyObjectRef) -> String, + vm: &VirtualMachine, +) -> PyResult { + match_class!(obj, + obj @ T => predicate(obj), + tuple @ PyTuple => { + for obj in tuple.elements.borrow().iter() { + let inner_val = PyRef::::try_from_object(vm, obj.clone())?; + if predicate(inner_val)? { + return Ok(true); + } + } + Ok(false) + }, + obj => Err(vm.new_type_error(message(&obj))) + ) +} diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 7b2fcbe9a1..621cbf5bcd 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -12,7 +12,7 @@ use unicode_segmentation::UnicodeSegmentation; use unicode_xid::UnicodeXID; use crate::format::{FormatParseError, FormatPart, FormatString}; -use crate::function::{OptionalArg, PyFuncArgs}; +use crate::function::{single_or_tuple_any, OptionalArg, PyFuncArgs}; use crate::pyobject::{ IdProtocol, IntoPyObject, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TryIntoRef, TypeProtocol, @@ -342,30 +342,52 @@ impl PyString { #[pymethod] fn endswith( &self, - suffix: PyStringRef, + suffix: PyObjectRef, start: OptionalArg, end: OptionalArg, - _vm: &VirtualMachine, - ) -> bool { + vm: &VirtualMachine, + ) -> PyResult { if let Some((start, end)) = adjust_indices(start, end, self.value.len()) { - self.value[start..end].ends_with(&suffix.value) + let value = &self.value[start..end]; + single_or_tuple_any( + suffix, + |s: PyStringRef| Ok(value.ends_with(&s.value)), + |o| { + format!( + "endswith first arg must be str or a tuple of str, not {}", + o.class(), + ) + }, + vm, + ) } else { - false + Ok(false) } } #[pymethod] fn startswith( &self, - prefix: PyStringRef, + prefix: PyObjectRef, start: OptionalArg, end: OptionalArg, - _vm: &VirtualMachine, - ) -> bool { + vm: &VirtualMachine, + ) -> PyResult { if let Some((start, end)) = adjust_indices(start, end, self.value.len()) { - self.value[start..end].starts_with(&prefix.value) + let value = &self.value[start..end]; + single_or_tuple_any( + prefix, + |s: PyStringRef| Ok(value.starts_with(&s.value)), + |o| { + format!( + "startswith first arg must be str or a tuple of str, not {}", + o.class(), + ) + }, + vm, + ) } else { - false + Ok(false) } } From 04c017d2682cf6c8975a6b5fbd3f4c22e0e44cd3 Mon Sep 17 00:00:00 2001 From: zer0 Date: Sun, 12 May 2019 12:47:23 +0300 Subject: [PATCH 602/884] unit testing for str.maketrans --- vm/src/obj/objstr.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index c084ca0ce6..d81b69f93e 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1201,26 +1201,28 @@ mod tests { } #[test] - fn str_translate() { + fn str_maketrans_and_translate() { let vm = VirtualMachine::new(); let table = vm.context().new_dict(); table - .set_item(97, vm.new_str("🎅".to_owned()), &vm) + .set_item("a", vm.new_str("🎅".to_owned()), &vm) .unwrap(); - table.set_item(98, vm.get_none(), &vm).unwrap(); + table.set_item("b", vm.get_none(), &vm).unwrap(); table - .set_item(99, vm.new_str("xda".to_owned()), &vm) + .set_item("c", vm.new_str("xda".to_owned()), &vm) .unwrap(); + let translated = PyString::maketrans( + table.into_object(), + OptionalArg::Missing, + OptionalArg::Missing, + &vm, + ) + .unwrap(); let text = PyString::from("abc"); - let translated = text.translate(table.into_object(), &vm).unwrap(); + let translated = text.translate(translated, &vm).unwrap(); assert_eq!(translated, "🎅xda".to_owned()); let translated = text.translate(vm.new_int(3), &vm); - println!("{:?}", translated); - } - - #[test] - fn str_maketrans() { - let vm = VirtualMachine::new(); + assert_eq!(translated.unwrap_err().class().name, "TypeError".to_owned()); } } From e311b4f156e5ebb7970459b3e66c69b30144a0e6 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 May 2019 04:26:55 +0900 Subject: [PATCH 603/884] Fix exec to populate locals only when globals() is not given --- tests/snippets/test_exec.py | 20 ++++++++++++++++++++ vm/src/builtins.rs | 17 +++++++++++------ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/tests/snippets/test_exec.py b/tests/snippets/test_exec.py index 61932c24cb..506882e165 100644 --- a/tests/snippets/test_exec.py +++ b/tests/snippets/test_exec.py @@ -44,3 +44,23 @@ def f(): exec("del x") assert 'x' not in g + +assert 'g' in globals() +assert 'g' in locals() +exec("assert 'g' in globals()") +exec("assert 'g' in locals()") +exec("assert 'g' not in globals()", {}) +exec("assert 'g' not in locals()", {}) + +del g + +def f(): + g = 1 + assert 'g' not in globals() + assert 'g' in locals() + exec("assert 'g' not in globals()") + exec("assert 'g' in locals()") + exec("assert 'g' not in globals()", {}) + exec("assert 'g' not in locals()", {}) + +f() diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 5b3082b0d9..068ff43ff5 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -171,7 +171,7 @@ fn builtin_exec(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { vm, args, required = [(source, None)], - optional = [(globals, None), (locals, Some(vm.ctx.dict_type()))] + optional = [(globals, None), (locals, None)] ); let scope = make_scope(vm, globals, locals)?; @@ -219,6 +219,16 @@ fn make_scope( None => None, }; let current_scope = vm.current_scope(); + let locals = match locals { + Some(dict) => dict.clone().downcast().ok(), + None => { + if globals.is_some() { + None + } else { + current_scope.get_only_locals() + } + } + }; let globals = match globals { Some(dict) => { let dict: PyDictRef = dict.clone().downcast().unwrap(); @@ -232,11 +242,6 @@ fn make_scope( None => current_scope.globals.clone(), }; - let locals = match locals { - Some(dict) => dict.clone().downcast().ok(), - None => current_scope.get_only_locals(), - }; - let scope = Scope::with_builtins(locals, globals, vm); Ok(scope) } From ddf676569acad211fc06ff3ba9f082073a4a7d45 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 May 2019 07:16:45 +0900 Subject: [PATCH 604/884] Add sys.builtin_module_names --- tests/snippets/sysmod.py | 3 +++ vm/src/sysmodule.rs | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/snippets/sysmod.py b/tests/snippets/sysmod.py index b7cdc22ec5..423ecf08d8 100644 --- a/tests/snippets/sysmod.py +++ b/tests/snippets/sysmod.py @@ -4,3 +4,6 @@ assert sys.argv[0].endswith('.py') assert sys.platform == "linux" or sys.platform == "darwin" or sys.platform == "win32" or sys.platform == "unknown" + +assert isinstance(sys.builtin_module_names, tuple) +assert 'sys' in sys.builtin_module_names diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 4174fcf102..8c6bafe74f 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -4,7 +4,7 @@ use std::{env, mem}; use crate::frame::FrameRef; use crate::function::{OptionalArg, PyFuncArgs}; use crate::obj::objstr::PyStringRef; -use crate::pyobject::{ItemProtocol, PyContext, PyObjectRef, PyResult}; +use crate::pyobject::{IntoPyObject, ItemProtocol, PyContext, PyObjectRef, PyResult}; use crate::vm::VirtualMachine; /* @@ -144,9 +144,14 @@ setprofile() -- set the global profiling function setrecursionlimit() -- set the max recursion depth for the interpreter settrace() -- set the global debug tracing function "; + let mut module_names: Vec<_> = vm.stdlib_inits.borrow().keys().cloned().collect(); + module_names.push("sys".to_string()); + module_names.push("builtins".to_string()); + module_names.sort(); let modules = ctx.new_dict(); extend_module!(vm, module, { "argv" => argv(ctx), + "builtin_module_names" => ctx.new_tuple(module_names.iter().map(|v| v.into_pyobject(vm).unwrap()).collect()), "getrefcount" => ctx.new_rustfunc(sys_getrefcount), "getsizeof" => ctx.new_rustfunc(sys_getsizeof), "intern" => ctx.new_rustfunc(sys_intern), From 72043dac99c2f44949252edd1ab3673489a7ab77 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 9 May 2019 02:23:28 +0900 Subject: [PATCH 605/884] Fix builtin hash() --- vm/src/builtins.rs | 3 +-- vm/src/vm.rs | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 53d760cc8b..3250bef808 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -303,8 +303,7 @@ fn builtin_hasattr(obj: PyObjectRef, attr: PyStringRef, vm: &VirtualMachine) -> fn builtin_hash(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - - vm.call_method(obj, "__hash__", vec![]) + vm._hash(obj).and_then(|v| Ok(vm.new_int(v))) } // builtin_help diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 8146310159..eab6fca8c8 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -23,6 +23,7 @@ use crate::obj::objcode::PyCodeRef; use crate::obj::objdict::PyDictRef; use crate::obj::objfunction::{PyFunction, PyMethod}; use crate::obj::objgenerator::PyGenerator; +use crate::obj::objint; use crate::obj::objiter; use crate::obj::objsequence; use crate::obj::objstr::{PyString, PyStringRef}; @@ -36,6 +37,7 @@ use crate::pyobject::{ use crate::stdlib; use crate::sysmodule; use num_bigint::BigInt; +use num_traits::ToPrimitive; // use objects::objects; @@ -912,6 +914,19 @@ impl VirtualMachine { }) } + pub fn _hash(&self, obj: &PyObjectRef) -> PyResult { + const PRIME: usize = 0x1fff_ffff_ffff_ffff; + let hash_obj = self.call_method(obj, "__hash__", vec![])?; + if objtype::isinstance(&hash_obj, &self.ctx.int_type()) { + let hash_int = objint::get_value(&hash_obj); + Ok(hash_int + .to_usize() + .unwrap_or_else(|| (hash_int % PRIME).to_usize().unwrap())) + } else { + Err(self.new_type_error("__hash__ method should return an integer".to_string())) + } + } + // https://docs.python.org/3/reference/expressions.html#membership-test-operations fn _membership_iter_search(&self, haystack: PyObjectRef, needle: PyObjectRef) -> PyResult { let iter = objiter::get_iter(self, &haystack)?; From 2c77dc208ff4719fc61ebb3e6d7ce37bd66d14bd Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 9 May 2019 07:01:39 +0900 Subject: [PATCH 606/884] Add float.__hash__ --- tests/snippets/dict.py | 2 ++ tests/snippets/floats.py | 15 +++++++++ vm/src/lib.rs | 1 + vm/src/obj/objfloat.rs | 6 ++++ vm/src/pyhash.rs | 67 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 91 insertions(+) create mode 100644 vm/src/pyhash.rs diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 270d2c07e2..f7506a2059 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -198,3 +198,5 @@ def __eq__(self, other): w = {1: 1, **x, 2: 2, **y, 3: 3, **z, 4: 4} assert w == {1: 1, 'a': 1, 'b': 2, 'c': 3, 2: 2, 'd': 3, 3: 3, 'e': 3, 4: 4} + +assert str({True: True, 1.0: 1.0}) == str({True: 1.0}) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 82f2b86e31..c116b8010c 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -98,6 +98,21 @@ assert_raises(ValueError, lambda: float('foo')) assert_raises(OverflowError, lambda: float(2**10000)) +# check eq and hash for small numbers + +assert 1.0 == 1 +assert 1.0 == True +assert 0.0 == 0 +assert 0.0 == False +assert hash(1.0) == hash(1) +assert hash(1.0) == hash(True) +assert hash(0.0) == hash(0) +assert hash(0.0) == hash(False) +assert hash(1.0) != hash(1.0000000001) + +assert 5.0 in {3, 4, 5} +assert {-1: 2}[-1.0] == 2 + # check that magic methods are implemented for ints and floats assert 1.0.__add__(1.0) == 2.0 diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 8b14454fe7..2e5fcb3f46 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -55,6 +55,7 @@ pub mod frame; pub mod function; pub mod import; pub mod obj; +mod pyhash; pub mod pyobject; pub mod stdlib; mod symboltable; diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index add61a6843..8d983c3aee 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -4,6 +4,7 @@ use super::objstr; use super::objtype; use crate::function::OptionalArg; use crate::obj::objtype::PyClassRef; +use crate::pyhash; use crate::pyobject::{ IdProtocol, IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TypeProtocol, @@ -429,6 +430,11 @@ impl PyFloat { zelf } + #[pymethod(name = "__hash__")] + fn hash(&self, _vm: &VirtualMachine) -> pyhash::PyHash { + pyhash::hash_float(self.value) + } + #[pyproperty(name = "real")] fn real(zelf: PyRef, _vm: &VirtualMachine) -> PyFloatRef { zelf diff --git a/vm/src/pyhash.rs b/vm/src/pyhash.rs new file mode 100644 index 0000000000..81c6055842 --- /dev/null +++ b/vm/src/pyhash.rs @@ -0,0 +1,67 @@ +use std::hash::{Hash, Hasher}; + +use crate::pyobject::PyObjectRef; +use crate::pyobject::PyResult; +use crate::vm::VirtualMachine; + +pub type PyHash = i64; +pub type PyUHash = u64; + +pub const BITS: usize = 61; +pub const MODULUS: PyUHash = (1 << BITS) - 1; +// pub const CUTOFF: usize = 7; + +pub const INF: PyHash = 314159; +pub const NAN: PyHash = 0; + +pub fn hash_float(value: f64) -> PyHash { + // cpython _Py_HashDouble + if !value.is_finite() { + return if value.is_infinite() { + if value > 0.0 { + INF + } else { + -INF + } + } else { + NAN + }; + } + + let frexp = if 0.0 == value { + (value, 0i32) + } else { + let bits = value.to_bits(); + let exponent: i32 = ((bits >> 52) & 0x7ff) as i32 - 1022; + let mantissa_bits = bits & (0x000fffffffffffff) | (1022 << 52); + (f64::from_bits(mantissa_bits), exponent) + }; + + // process 28 bits at a time; this should work well both for binary + // and hexadecimal floating point. + let mut m = frexp.0; + let mut e = frexp.1; + let mut x: PyUHash = 0; + while m != 0.0 { + x = ((x << 28) & MODULUS) | x >> (BITS - 28); + m *= 268435456.0; // 2**28 + e -= 28; + let y = m as PyUHash; // pull out integer part + m -= y as f64; + x += y; + if x >= MODULUS { + x -= MODULUS; + } + } + + // adjust for the exponent; first reduce it modulo BITS + const BITS32: i32 = BITS as i32; + e = if e >= 0 { + e % BITS32 + } else { + BITS32 - 1 - ((-1 - e) % BITS32) + }; + x = ((x << e) & MODULUS) | x >> (BITS32 - e); + + x as PyHash * value.signum() as PyHash +} From 3bebe448961c641bfd66ead4a080aab360549960 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 9 May 2019 07:02:59 +0900 Subject: [PATCH 607/884] Fix int.__hash__ --- vm/src/obj/objint.rs | 11 ++++++----- vm/src/vm.rs | 12 ++++-------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 403fd08ef6..9c1ccf9218 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -1,5 +1,4 @@ use std::fmt; -use std::hash::{Hash, Hasher}; use num_bigint::{BigInt, Sign}; use num_integer::Integer; @@ -7,6 +6,7 @@ use num_traits::{One, Pow, Signed, ToPrimitive, Zero}; use crate::format::FormatSpec; use crate::function::{KwArgs, OptionalArg, PyFuncArgs}; +use crate::pyhash; use crate::pyobject::{ IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TypeProtocol, @@ -400,10 +400,11 @@ impl PyInt { } #[pymethod(name = "__hash__")] - fn hash(&self, _vm: &VirtualMachine) -> u64 { - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - self.value.hash(&mut hasher); - hasher.finish() + pub fn hash(&self, _vm: &VirtualMachine) -> pyhash::PyHash { + match self.value.to_i64() { + Some(value) => (value % pyhash::MODULUS as i64), + None => (&self.value % pyhash::MODULUS).to_i64().unwrap(), + } } #[pymethod(name = "__abs__")] diff --git a/vm/src/vm.rs b/vm/src/vm.rs index eab6fca8c8..6cd1165db3 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -23,13 +23,14 @@ use crate::obj::objcode::PyCodeRef; use crate::obj::objdict::PyDictRef; use crate::obj::objfunction::{PyFunction, PyMethod}; use crate::obj::objgenerator::PyGenerator; -use crate::obj::objint; +use crate::obj::objint::PyInt; use crate::obj::objiter; use crate::obj::objsequence; use crate::obj::objstr::{PyString, PyStringRef}; use crate::obj::objtuple::PyTupleRef; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; +use crate::pyhash; use crate::pyobject::{ IdProtocol, ItemProtocol, PyContext, PyObjectRef, PyResult, PyValue, TryFromObject, TryIntoRef, TypeProtocol, @@ -37,7 +38,6 @@ use crate::pyobject::{ use crate::stdlib; use crate::sysmodule; use num_bigint::BigInt; -use num_traits::ToPrimitive; // use objects::objects; @@ -914,14 +914,10 @@ impl VirtualMachine { }) } - pub fn _hash(&self, obj: &PyObjectRef) -> PyResult { - const PRIME: usize = 0x1fff_ffff_ffff_ffff; + pub fn _hash(&self, obj: &PyObjectRef) -> PyResult { let hash_obj = self.call_method(obj, "__hash__", vec![])?; if objtype::isinstance(&hash_obj, &self.ctx.int_type()) { - let hash_int = objint::get_value(&hash_obj); - Ok(hash_int - .to_usize() - .unwrap_or_else(|| (hash_int % PRIME).to_usize().unwrap())) + Ok(hash_obj.payload::().unwrap().hash(self)) } else { Err(self.new_type_error("__hash__ method should return an integer".to_string())) } From c768b25673a9d95665405559c7ed06d8c8d8b715 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 9 May 2019 07:32:25 +0900 Subject: [PATCH 608/884] Refine hash types and refactor hash impl --- vm/src/obj/objbytearray.rs | 2 +- vm/src/obj/objbyteinner.rs | 16 +++++----------- vm/src/obj/objbytes.rs | 4 ++-- vm/src/obj/objdict.rs | 2 +- vm/src/obj/objlist.rs | 2 +- vm/src/obj/objobject.rs | 5 +++-- vm/src/obj/objset.rs | 2 +- vm/src/obj/objstr.rs | 8 +++----- vm/src/obj/objtuple.rs | 13 +++---------- vm/src/pyhash.rs | 18 ++++++++++++++++++ 10 files changed, 38 insertions(+), 34 deletions(-) diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 8b09a53e9f..e5078bdbbc 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -135,7 +135,7 @@ impl PyByteArrayRef { } #[pymethod(name = "__hash__")] - fn hash(self, vm: &VirtualMachine) -> PyResult { + fn hash(self, vm: &VirtualMachine) -> PyResult<()> { Err(vm.new_type_error("unhashable type: bytearray".to_string())) } diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index ae318e6289..4e51c41707 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -2,6 +2,7 @@ use crate::obj::objint::PyIntRef; use crate::obj::objnone::PyNoneRef; use crate::obj::objslice::PySliceRef; use crate::obj::objtuple::PyTupleRef; +use crate::pyhash; use crate::pyobject::Either; use crate::pyobject::PyRef; use crate::pyobject::PyValue; @@ -12,17 +13,12 @@ use core::ops::Range; use num_bigint::BigInt; use crate::function::OptionalArg; - -use crate::vm::VirtualMachine; - use crate::pyobject::{PyResult, TypeProtocol}; - -use crate::obj::objstr::{PyString, PyStringRef}; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; +use crate::vm::VirtualMachine; use super::objint; use super::objsequence::{is_valid_slice_arg, PySliceableSequence}; +use super::objstr::{PyString, PyStringRef}; use crate::obj::objint::PyInt; use num_integer::Integer; @@ -379,10 +375,8 @@ impl PyByteInner { } } - pub fn hash(&self) -> usize { - let mut hasher = DefaultHasher::new(); - self.elements.hash(&mut hasher); - hasher.finish() as usize + pub fn hash(&self) -> pyhash::PyHash { + pyhash::hash_value(&self.elements) } pub fn add(&self, other: PyByteInner) -> Vec { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index 102c70d4f2..dd2e1518c0 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -1,8 +1,8 @@ use crate::obj::objint::PyIntRef; - use crate::obj::objslice::PySliceRef; use crate::obj::objstr::PyStringRef; use crate::obj::objtuple::PyTupleRef; +use crate::pyhash; use crate::pyobject::Either; use crate::vm::VirtualMachine; @@ -125,7 +125,7 @@ impl PyBytesRef { } #[pymethod(name = "__hash__")] - fn hash(self, _vm: &VirtualMachine) -> usize { + fn hash(self, _vm: &VirtualMachine) -> pyhash::PyHash { self.inner.hash() } diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 87cf3010b6..396c711f45 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -298,7 +298,7 @@ impl PyDictRef { Ok(PyDict { entries }.into_ref(vm)) } - fn hash(self, vm: &VirtualMachine) -> PyResult { + fn hash(self, vm: &VirtualMachine) -> PyResult<()> { Err(vm.new_type_error("unhashable type".to_string())) } diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 6b00b6828c..88842f9b79 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -387,7 +387,7 @@ impl PyListRef { Ok(s) } - fn hash(self, vm: &VirtualMachine) -> PyResult { + fn hash(self, vm: &VirtualMachine) -> PyResult<()> { Err(vm.new_type_error("unhashable type".to_string())) } diff --git a/vm/src/obj/objobject.rs b/vm/src/obj/objobject.rs index 31e52fc990..77830a402c 100644 --- a/vm/src/obj/objobject.rs +++ b/vm/src/obj/objobject.rs @@ -5,6 +5,7 @@ use super::objtype; use crate::function::PyFuncArgs; use crate::obj::objproperty::PropertyBuilder; use crate::obj::objtype::PyClassRef; +use crate::pyhash; use crate::pyobject::{ IdProtocol, ItemProtocol, PyAttributes, PyContext, PyObject, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, @@ -55,8 +56,8 @@ fn object_ge(_zelf: PyObjectRef, _other: PyObjectRef, vm: &VirtualMachine) -> Py vm.ctx.not_implemented() } -fn object_hash(zelf: PyObjectRef, _vm: &VirtualMachine) -> u64 { - zelf.get_id() as u64 +fn object_hash(zelf: PyObjectRef, _vm: &VirtualMachine) -> pyhash::PyHash { + zelf.get_id() as pyhash::PyHash } fn object_setattr( diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index b4f3bfabc2..41bb065476 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -759,7 +759,7 @@ impl TryFromObject for SetIterable { } } -fn set_hash(_zelf: PySetRef, vm: &VirtualMachine) -> PyResult { +fn set_hash(_zelf: PySetRef, vm: &VirtualMachine) -> PyResult<()> { Err(vm.new_type_error("unhashable type".to_string())) } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index ab7525e743..cf42a1a5c4 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1,7 +1,6 @@ extern crate unicode_xid; use std::fmt; -use std::hash::{Hash, Hasher}; use std::ops::Range; use std::str::FromStr; use std::string::ToString; @@ -13,6 +12,7 @@ use unicode_xid::UnicodeXID; use crate::format::{FormatParseError, FormatPart, FormatString}; use crate::function::{single_or_tuple_any, OptionalArg, PyFuncArgs}; +use crate::pyhash; use crate::pyobject::{ IdProtocol, IntoPyObject, ItemProtocol, PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, TryIntoRef, TypeProtocol, @@ -172,10 +172,8 @@ impl PyString { } #[pymethod(name = "__hash__")] - fn hash(&self, _vm: &VirtualMachine) -> usize { - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - self.value.hash(&mut hasher); - hasher.finish() as usize + fn hash(&self, _vm: &VirtualMachine) -> pyhash::PyHash { + pyhash::hash_value(&self.value) } #[pymethod(name = "__len__")] diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 42999189f1..795e17d628 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -1,13 +1,12 @@ use std::cell::{Cell, RefCell}; use std::fmt; -use std::hash::{Hash, Hasher}; use crate::function::OptionalArg; +use crate::pyhash; use crate::pyobject::{IdProtocol, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; -use super::objint; use super::objiter; use super::objsequence::{ get_elements, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, @@ -129,14 +128,8 @@ impl PyTupleRef { } } - fn hash(self, vm: &VirtualMachine) -> PyResult { - let mut hasher = std::collections::hash_map::DefaultHasher::new(); - for element in self.elements.borrow().iter() { - let hash_result = vm.call_method(element, "__hash__", vec![])?; - let element_hash = objint::get_value(&hash_result); - element_hash.hash(&mut hasher); - } - Ok(hasher.finish()) + fn hash(self, vm: &VirtualMachine) -> PyResult { + pyhash::hash_iter(self.elements.borrow().iter(), vm) } fn iter(self, _vm: &VirtualMachine) -> PyTupleIterator { diff --git a/vm/src/pyhash.rs b/vm/src/pyhash.rs index 81c6055842..f87337359e 100644 --- a/vm/src/pyhash.rs +++ b/vm/src/pyhash.rs @@ -65,3 +65,21 @@ pub fn hash_float(value: f64) -> PyHash { x as PyHash * value.signum() as PyHash } + +pub fn hash_value(data: &T) -> PyHash { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + data.hash(&mut hasher); + hasher.finish() as PyHash +} + +pub fn hash_iter<'a, I: std::iter::Iterator>( + iter: I, + vm: &VirtualMachine, +) -> PyResult { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + for element in iter { + let item_hash = vm._hash(&element)?; + item_hash.hash(&mut hasher); + } + Ok(hasher.finish() as PyHash) +} From 7a64f3e797231d0f6c5ff8d71fe7b03532d2d177 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 9 May 2019 07:32:45 +0900 Subject: [PATCH 609/884] Fix dict hash impl --- vm/src/dictdatatype.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index b0bd4894f5..a1aa9e3c38 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -1,8 +1,6 @@ use crate::obj::objbool; -use crate::obj::objint; use crate::pyobject::{IdProtocol, PyObjectRef, PyResult}; use crate::vm::VirtualMachine; -use num_traits::ToPrimitive; /// Ordered dictionary implementation. /// Inspired by: https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html /// And: https://www.youtube.com/watch?v=p33CVV29OG8 @@ -218,8 +216,7 @@ enum LookupResult { } fn calc_hash(vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { - let hash = vm.call_method(key, "__hash__", vec![])?; - Ok(objint::get_value(&hash).to_usize().unwrap()) + Ok(vm._hash(key)? as usize) } /// Invoke __eq__ on two keys From 64afd5ccf8f9f76e7dcb23ca81bb54bebfdf6ba5 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 May 2019 17:56:11 +0900 Subject: [PATCH 610/884] Add os.supports_* placeholders --- tests/snippets/stdlib_os.py | 5 + vm/src/obj/objset.rs | 2 +- vm/src/stdlib/os.rs | 185 ++++++++++++++++++++++++++++++------ 3 files changed, 164 insertions(+), 28 deletions(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 61fa428555..e24ac65033 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -217,3 +217,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): os.chdir(tmpdir) assert os.getcwd() == tmpdir os.path.exists(FILE_NAME) + +# supports +assert isinstance(os.supports_fd, set) +assert isinstance(os.supports_dir_fd, set) +assert isinstance(os.supports_follow_symlinks, set) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index b4f3bfabc2..7593edf50e 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -472,7 +472,7 @@ impl PySetRef { Ok(vm.new_str(s)) } - fn add(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { + pub fn add(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.inner.borrow_mut().add(&item, vm) } diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 4394bdb89b..1e80146eaf 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -7,14 +7,13 @@ use std::{env, fs}; use num_traits::cast::ToPrimitive; -use crate::function::PyFuncArgs; +use crate::function::{IntoPyNativeFunc, PyFuncArgs}; use crate::obj::objbytes::PyBytesRef; use crate::obj::objdict::PyDictRef; -use crate::obj::objint; -use crate::obj::objint::PyIntRef; +use crate::obj::objint::{self, PyInt, PyIntRef}; use crate::obj::objiter; -use crate::obj::objstr; -use crate::obj::objstr::PyStringRef; +use crate::obj::objset::PySet; +use crate::obj::objstr::{self, PyString, PyStringRef}; use crate::obj::objtype::PyClassRef; use crate::pyobject::{ ItemProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryIntoRef, @@ -61,6 +60,14 @@ pub fn raw_file_number(handle: File) -> i64 { unimplemented!(); } +fn make_path(_vm: &VirtualMachine, path: PyStringRef, dir_fd: &DirFd) -> PyStringRef { + if dir_fd.dir_fd.is_some() { + unimplemented!(); + } else { + path + } +} + pub fn os_close(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(fileno, Some(vm.ctx.int_type()))]); @@ -81,10 +88,19 @@ pub fn os_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { required = [ (name, Some(vm.ctx.str_type())), (mode, Some(vm.ctx.int_type())) - ] + ], + optional = [(dir_fd, Some(vm.ctx.int_type()))] ); - let fname = objstr::get_value(&name); + let name = name.clone().downcast::().unwrap(); + let dir_fd = if let Some(obj) = dir_fd { + DirFd { + dir_fd: Some(obj.clone().downcast::().unwrap()), + } + } else { + DirFd::default() + }; + let fname = &make_path(vm, name, &dir_fd).value; let handle = match objint::get_value(mode).to_u16().unwrap() { 0 => OpenOptions::new().read(true).open(&fname), @@ -159,11 +175,13 @@ fn os_write(fd: PyIntRef, data: PyBytesRef, vm: &VirtualMachine) -> PyResult { Ok(vm.ctx.new_int(written)) } -fn os_remove(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { +fn os_remove(path: PyStringRef, dir_fd: DirFd, vm: &VirtualMachine) -> PyResult<()> { + let path = make_path(vm, path, &dir_fd); fs::remove_file(&path.value).map_err(|s| vm.new_os_error(s.to_string())) } -fn os_mkdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { +fn os_mkdir(path: PyStringRef, dir_fd: DirFd, vm: &VirtualMachine) -> PyResult<()> { + let path = make_path(vm, path, &dir_fd); fs::create_dir(&path.value).map_err(|s| vm.new_os_error(s.to_string())) } @@ -171,7 +189,8 @@ fn os_mkdirs(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { fs::create_dir_all(&path.value).map_err(|s| vm.new_os_error(s.to_string())) } -fn os_rmdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { +fn os_rmdir(path: PyStringRef, dir_fd: DirFd, vm: &VirtualMachine) -> PyResult<()> { + let path = make_path(vm, path, &dir_fd); fs::remove_dir(&path.value).map_err(|s| vm.new_os_error(s.to_string())) } @@ -219,6 +238,12 @@ impl PyValue for DirEntry { } } +#[derive(FromArgs, Default)] +struct DirFd { + #[pyarg(keyword_only, default = "None")] + dir_fd: Option, +} + #[derive(FromArgs)] struct FollowSymlinks { #[pyarg(keyword_only, default = "true")] @@ -272,8 +297,13 @@ impl DirEntryRef { .is_symlink()) } - fn stat(self, follow_symlinks: FollowSymlinks, vm: &VirtualMachine) -> PyResult { - os_stat(self.path(vm).try_into_ref(vm)?, follow_symlinks, vm) + fn stat( + self, + dir_fd: DirFd, + follow_symlinks: FollowSymlinks, + vm: &VirtualMachine, + ) -> PyResult { + os_stat(self.path(vm).try_into_ref(vm)?, dir_fd, follow_symlinks, vm) } } @@ -432,30 +462,36 @@ macro_rules! os_unix_stat_inner { #[cfg(target_os = "linux")] fn os_stat( path: PyStringRef, + dir_fd: DirFd, follow_symlinks: FollowSymlinks, vm: &VirtualMachine, ) -> PyResult { use std::os::linux::fs::MetadataExt; + let path = make_path(vm, path, &dir_fd); os_unix_stat_inner!(path, follow_symlinks, vm) } #[cfg(target_os = "macos")] fn os_stat( path: PyStringRef, + dir_fd: DirFd, follow_symlinks: FollowSymlinks, vm: &VirtualMachine, ) -> PyResult { use std::os::macos::fs::MetadataExt; + let path = make_path(vm, path, &dir_fd); os_unix_stat_inner!(path, follow_symlinks, vm) } #[cfg(target_os = "android")] fn os_stat( path: PyStringRef, + dir_fd: DirFd, follow_symlinks: FollowSymlinks, vm: &VirtualMachine, ) -> PyResult { use std::os::android::fs::MetadataExt; + let path = make_path(vm, path, &dir_fd); os_unix_stat_inner!(path, follow_symlinks, vm) } @@ -483,6 +519,7 @@ fn attributes_to_mode(attr: u32) -> u32 { #[cfg(windows)] fn os_stat( path: PyStringRef, + _dir_fd: DirFd, // TODO: error follow_symlinks: FollowSymlinks, vm: &VirtualMachine, ) -> PyResult { @@ -523,13 +560,24 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } #[cfg(unix)] -fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { +fn os_symlink( + src: PyStringRef, + dst: PyStringRef, + dir_fd: DirFd, + vm: &VirtualMachine, +) -> PyResult<()> { use std::os::unix::fs as unix_fs; + let dst = make_path(vm, dst, &dir_fd); unix_fs::symlink(&src.value, &dst.value).map_err(|s| vm.new_os_error(s.to_string())) } #[cfg(windows)] -fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { +fn os_symlink( + src: PyStringRef, + dst: PyStringRef, + _dir_fd: DirFd, + vm: &VirtualMachine, +) -> PyResult<()> { use std::os::windows::fs as win_fs; let ret = match fs::metadata(&dst.value) { Ok(meta) => { @@ -547,7 +595,12 @@ fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResu } #[cfg(all(not(unix), not(windows)))] -fn os_symlink(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { +fn os_symlink( + src: PyStringRef, + dst: PyStringRef, + dir_fd: DirFd, + vm: &VirtualMachine, +) -> PyResult<()> { unimplemented!(); } @@ -600,36 +653,114 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "st_mtime" => ctx.new_property(StatResultRef::st_mtime), }); - py_module!(vm, "_os", { - "open" => ctx.new_rustfunc(os_open), + struct SupportFunc<'a> { + name: &'a str, + func_obj: PyObjectRef, + fd: Option, + dir_fd: Option, + follow_symlinks: Option, + }; + impl<'a> SupportFunc<'a> { + fn new( + vm: &VirtualMachine, + name: &'a str, + func: F, + fd: Option, + dir_fd: Option, + follow_symlinks: Option, + ) -> Self + where + F: IntoPyNativeFunc, + { + Self { + name: name, + func_obj: vm.ctx.new_rustfunc(func), + fd: fd, + dir_fd: dir_fd, + follow_symlinks: follow_symlinks, + } + } + } + let support_funcs = vec![ + SupportFunc::new(vm, "open", os_open, None, Some(false), None), + // access Some Some None + SupportFunc::new(vm, "chdir", os_chdir, Some(false), None, None), + // chflags Some, None Some + // chmod Some Some Some + // chown Some Some Some + // chroot Some None None + SupportFunc::new(vm, "listdir", os_listdir, Some(false), None, None), + SupportFunc::new(vm, "mkdir", os_mkdir, Some(false), Some(false), None), + // mkfifo Some Some None + // mknod Some Some None + // pathconf Some None None + // readlink Some Some None + SupportFunc::new(vm, "remove", os_remove, Some(false), Some(false), None), + // rename Some Some None + // replace Some Some None + SupportFunc::new(vm, "rmdir", os_rmdir, Some(false), Some(false), None), + SupportFunc::new(vm, "scandir", os_scandir, Some(false), None, None), + SupportFunc::new(vm, "stat", os_stat, Some(false), Some(false), Some(false)), + SupportFunc::new(vm, "symlink", os_symlink, None, Some(false), None), + // truncate Some None None + SupportFunc::new(vm, "unlink", os_remove, Some(false), Some(false), None), + // utime Some Some Some + ]; + let supports_fd = PySet::default().into_ref(vm); + let supports_dir_fd = PySet::default().into_ref(vm); + let supports_follow_symlinks = PySet::default().into_ref(vm); + + let module = py_module!(vm, "_os", { "close" => ctx.new_rustfunc(os_close), "error" => ctx.new_rustfunc(os_error), "fsync" => ctx.new_rustfunc(os_fsync), "read" => ctx.new_rustfunc(os_read), "write" => ctx.new_rustfunc(os_write), - "remove" => ctx.new_rustfunc(os_remove), - "unlink" => ctx.new_rustfunc(os_remove), - "mkdir" => ctx.new_rustfunc(os_mkdir), "mkdirs" => ctx.new_rustfunc(os_mkdirs), - "rmdir" => ctx.new_rustfunc(os_rmdir), - "listdir" => ctx.new_rustfunc(os_listdir), "putenv" => ctx.new_rustfunc(os_putenv), "unsetenv" => ctx.new_rustfunc(os_unsetenv), "environ" => environ, "name" => ctx.new_str(os_name), - "scandir" => ctx.new_rustfunc(os_scandir), "ScandirIter" => scandir_iter, "DirEntry" => dir_entry, "stat_result" => stat_result, - "stat" => ctx.new_rustfunc(os_stat), - "symlink" => ctx.new_rustfunc(os_symlink), "getcwd" => ctx.new_rustfunc(os_getcwd), - "chdir" => ctx.new_rustfunc(os_chdir), "O_RDONLY" => ctx.new_int(0), "O_WRONLY" => ctx.new_int(1), "O_RDWR" => ctx.new_int(2), "O_NONBLOCK" => ctx.new_int(4), "O_APPEND" => ctx.new_int(8), "O_CREAT" => ctx.new_int(512) - }) + }); + + for support in support_funcs { + if support.fd.unwrap_or(false) { + supports_fd + .clone() + .add(support.func_obj.clone(), vm) + .unwrap(); + } + if support.dir_fd.unwrap_or(false) { + supports_dir_fd + .clone() + .add(support.func_obj.clone(), vm) + .unwrap(); + } + if support.follow_symlinks.unwrap_or(false) { + supports_follow_symlinks + .clone() + .add(support.func_obj.clone(), vm) + .unwrap(); + } + vm.set_attr(&module, support.name, support.func_obj) + .unwrap(); + } + + extend_module!(vm, module, { + "supports_fd" => supports_fd.into_object(), + "supports_dir_fd" => supports_dir_fd.into_object(), + "supports_follow_symlinks" => supports_follow_symlinks.into_object(), + }); + + module } From 98d90c830b0c8b3e2545d0dc7895bc47c6f37172 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sun, 12 May 2019 23:16:23 +0300 Subject: [PATCH 611/884] Add `itertools.takewhile` --- tests/snippets/stdlib_itertools.py | 44 ++++++++++++++++++++ vm/src/stdlib/itertools.rs | 66 +++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_itertools.py b/tests/snippets/stdlib_itertools.py index 61c031dd78..92fbe4a297 100644 --- a/tests/snippets/stdlib_itertools.py +++ b/tests/snippets/stdlib_itertools.py @@ -82,3 +82,47 @@ r = itertools.repeat(1, -1) with assertRaises(StopIteration): next(r) + + +# itertools.takewhile tests + +from itertools import takewhile as tw + +t = tw(lambda n: n < 5, [1, 2, 5, 1, 3]) +assert next(t) == 1 +assert next(t) == 2 +with assertRaises(StopIteration): + next(t) + +# not iterable +with assertRaises(TypeError): + tw(lambda n: n < 1, 1) + +# not callable +t = tw(5, [1, 2]) +with assertRaises(TypeError): + next(t) + +# non-bool predicate +t = tw(lambda n: n, [1, 2, 0]) +assert next(t) == 1 +assert next(t) == 2 +with assertRaises(StopIteration): + next(t) + +# bad predicate prototype +t = tw(lambda: True, [1]) +with assertRaises(TypeError): + next(t) + +# StopIteration before attempting to call (bad) predicate +t = tw(lambda: True, []) +with assertRaises(StopIteration): + next(t) + +# doesn't try again after the first predicate failure +t = tw(lambda n: n < 1, [1, 0]) +with assertRaises(StopIteration): + next(t) +with assertRaises(StopIteration): + next(t) diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index 3856fe92f2..e97c53bfab 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -5,8 +5,9 @@ use std::ops::{AddAssign, SubAssign}; use num_bigint::BigInt; use crate::function::OptionalArg; +use crate::obj::objbool; use crate::obj::objint::{PyInt, PyIntRef}; -use crate::obj::objiter::new_stop_iteration; +use crate::obj::objiter::{call_next, get_iter, new_stop_iteration}; use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; @@ -121,6 +122,65 @@ impl PyItertoolsRepeat { } } +#[pyclass] +#[derive(Debug)] +struct PyItertoolsTakewhile { + predicate: PyObjectRef, + iterable: PyObjectRef, + stop_flag: RefCell, +} + +impl PyValue for PyItertoolsTakewhile { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("itertools", "takewhile") + } +} + +#[pyimpl] +impl PyItertoolsTakewhile { + #[pymethod(name = "__new__")] + fn new( + _cls: PyClassRef, + predicate: PyObjectRef, + iterable: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + let iter = get_iter(vm, &iterable)?; + + Ok(PyItertoolsTakewhile { + predicate: predicate, + iterable: iter, + stop_flag: RefCell::new(false), + } + .into_ref(vm) + .into_object()) + } + + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { + if *self.stop_flag.borrow() { + return Err(new_stop_iteration(vm)); + } + + // might be StopIteration or anything else, which is propaged upwwards + let obj = call_next(vm, &self.iterable)?; + + let verdict = vm.invoke(self.predicate.clone(), vec![obj.clone()])?; + let verdict = objbool::boolval(vm, verdict)?; + if verdict { + Ok(obj) + } else { + *self.stop_flag.borrow_mut() = true; + Err(new_stop_iteration(vm)) + } + } + + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -130,8 +190,12 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let repeat = ctx.new_class("repeat", ctx.object()); PyItertoolsRepeat::extend_class(ctx, &repeat); + let takewhile = ctx.new_class("takewhile", ctx.object()); + PyItertoolsTakewhile::extend_class(ctx, &takewhile); + py_module!(vm, "itertools", { "count" => count, "repeat" => repeat, + "takewhile" => takewhile, }) } From b8c788197e3aaf4c1fa44870be84a128118ff036 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 12 May 2019 22:35:28 +0200 Subject: [PATCH 612/884] Add walrus operator to lexer and try to make lexer code cleaner. --- parser/src/lexer.rs | 188 +++++++++++++++++++++----------------------- parser/src/token.rs | 2 + 2 files changed, 92 insertions(+), 98 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index f95563eac3..88bbe079c7 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -649,9 +649,9 @@ where self.location.column = 1; } - fn inner_next(&mut self) -> Option { + fn inner_next(&mut self) -> LexResult { if !self.pending.is_empty() { - return Some(self.pending.remove(0)); + return self.pending.remove(0); } 'top_loop: loop { @@ -684,13 +684,13 @@ where // Don't allow tabs after spaces as part of indentation. // This is technically stricter than python3 but spaces before // tabs is even more insane than mixing spaces and tabs. - return Some(Err(LexicalError { + return Err(LexicalError { error: LexicalErrorType::OtherError( "Tabs not allowed as part of indentation after spaces" .to_string(), ), location: self.get_pos(), - })); + }); } self.next_char(); tabs += 1; @@ -728,7 +728,7 @@ where self.indentation_stack.push(indentation_level); let tok_start = self.get_pos(); let tok_end = tok_start.clone(); - return Some(Ok((tok_start, Tok::Indent, tok_end))); + return Ok((tok_start, Tok::Indent, tok_end)); } Some(Ordering::Less) => { // One or more dedentations @@ -745,10 +745,10 @@ where self.pending.push(Ok((tok_start, Tok::Dedent, tok_end))); } None => { - return Some(Err(LexicalError { + return Err(LexicalError { error: LexicalErrorType::OtherError("inconsistent use of tabs and spaces in indentation".to_string()), location: self.get_pos(), - })); + }); } _ => { break; @@ -758,24 +758,24 @@ where if indentation_level != *self.indentation_stack.last().unwrap() { // TODO: handle wrong indentations - return Some(Err(LexicalError { + return Err(LexicalError { error: LexicalErrorType::OtherError( "Non matching indentation levels!".to_string(), ), location: self.get_pos(), - })); + }); } - return Some(self.pending.remove(0)); + return self.pending.remove(0); } None => { - return Some(Err(LexicalError { + return Err(LexicalError { error: LexicalErrorType::OtherError( "inconsistent use of tabs and spaces in indentation" .to_string(), ), location: self.get_pos(), - })); + }); } } } @@ -785,30 +785,30 @@ where if let Some(c) = self.chr0 { // First check identifier: if self.is_identifier_start(c) { - return Some(self.lex_identifier()); + return self.lex_identifier(); } else if is_emoji_presentation(c) { let tok_start = self.get_pos(); self.next_char(); let tok_end = self.get_pos(); - return Some(Ok(( + return Ok(( tok_start, Tok::Name { name: c.to_string(), }, tok_end, - ))); + )); } else { match c { - '0'..='9' => return Some(self.lex_number()), + '0'..='9' => return self.lex_number(), '#' => { self.lex_comment(); continue; } '"' => { - return Some(self.lex_string(false, false, false, false)); + return self.lex_string(false, false, false, false); } '\'' => { - return Some(self.lex_string(false, false, false, false)); + return self.lex_string(false, false, false, false); } '=' => { let tok_start = self.get_pos(); @@ -817,11 +817,11 @@ where Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::EqEqual, tok_end))); + return Ok((tok_start, Tok::EqEqual, tok_end)); } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Equal, tok_end))); + return Ok((tok_start, Tok::Equal, tok_end)); } } } @@ -831,10 +831,10 @@ where if let Some('=') = self.chr0 { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::PlusEqual, tok_end))); + return Ok((tok_start, Tok::PlusEqual, tok_end)); } else { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Plus, tok_end))); + return Ok((tok_start, Tok::Plus, tok_end)); } } '*' => { @@ -844,7 +844,7 @@ where Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::StarEqual, tok_end))); + return Ok((tok_start, Tok::StarEqual, tok_end)); } Some('*') => { self.next_char(); @@ -852,21 +852,17 @@ where Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok(( - tok_start, - Tok::DoubleStarEqual, - tok_end, - ))); + return Ok((tok_start, Tok::DoubleStarEqual, tok_end)); } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::DoubleStar, tok_end))); + return Ok((tok_start, Tok::DoubleStar, tok_end)); } } } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Star, tok_end))); + return Ok((tok_start, Tok::Star, tok_end)); } } } @@ -877,7 +873,7 @@ where Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::SlashEqual, tok_end))); + return Ok((tok_start, Tok::SlashEqual, tok_end)); } Some('/') => { self.next_char(); @@ -885,25 +881,17 @@ where Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok(( - tok_start, - Tok::DoubleSlashEqual, - tok_end, - ))); + return Ok((tok_start, Tok::DoubleSlashEqual, tok_end)); } _ => { let tok_end = self.get_pos(); - return Some(Ok(( - tok_start, - Tok::DoubleSlash, - tok_end, - ))); + return Ok((tok_start, Tok::DoubleSlash, tok_end)); } } } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Slash, tok_end))); + return Ok((tok_start, Tok::Slash, tok_end)); } } } @@ -913,10 +901,10 @@ where if let Some('=') = self.chr0 { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::PercentEqual, tok_end))); + return Ok((tok_start, Tok::PercentEqual, tok_end)); } else { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Percent, tok_end))); + return Ok((tok_start, Tok::Percent, tok_end)); } } '|' => { @@ -925,10 +913,10 @@ where if let Some('=') = self.chr0 { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::VbarEqual, tok_end))); + return Ok((tok_start, Tok::VbarEqual, tok_end)); } else { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Vbar, tok_end))); + return Ok((tok_start, Tok::Vbar, tok_end)); } } '^' => { @@ -937,10 +925,10 @@ where if let Some('=') = self.chr0 { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::CircumflexEqual, tok_end))); + return Ok((tok_start, Tok::CircumflexEqual, tok_end)); } else { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::CircumFlex, tok_end))); + return Ok((tok_start, Tok::CircumFlex, tok_end)); } } '&' => { @@ -949,10 +937,10 @@ where if let Some('=') = self.chr0 { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::AmperEqual, tok_end))); + return Ok((tok_start, Tok::AmperEqual, tok_end)); } else { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Amper, tok_end))); + return Ok((tok_start, Tok::Amper, tok_end)); } } '-' => { @@ -962,16 +950,16 @@ where Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::MinusEqual, tok_end))); + return Ok((tok_start, Tok::MinusEqual, tok_end)); } Some('>') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Rarrow, tok_end))); + return Ok((tok_start, Tok::Rarrow, tok_end)); } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Minus, tok_end))); + return Ok((tok_start, Tok::Minus, tok_end)); } } } @@ -981,10 +969,10 @@ where if let Some('=') = self.chr0 { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::AtEqual, tok_end))); + return Ok((tok_start, Tok::AtEqual, tok_end)); } else { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::At, tok_end))); + return Ok((tok_start, Tok::At, tok_end)); } } '!' => { @@ -993,70 +981,77 @@ where if let Some('=') = self.chr0 { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::NotEqual, tok_end))); + return Ok((tok_start, Tok::NotEqual, tok_end)); } else { - return Some(Err(LexicalError { + return Err(LexicalError { error: LexicalErrorType::UnrecognizedToken { tok: '!' }, location: tok_start, - })); + }); } } '~' => { - return Some(self.eat_single_char(Tok::Tilde)); + return self.eat_single_char(Tok::Tilde); } '(' => { let result = self.eat_single_char(Tok::Lpar); self.nesting += 1; - return Some(result); + return result; } ')' => { let result = self.eat_single_char(Tok::Rpar); if self.nesting == 0 { - return Some(Err(LexicalError { + return Err(LexicalError { error: LexicalErrorType::NestingError, location: self.get_pos(), - })); + }); } self.nesting -= 1; - return Some(result); + return result; } '[' => { let result = self.eat_single_char(Tok::Lsqb); self.nesting += 1; - return Some(result); + return result; } ']' => { let result = self.eat_single_char(Tok::Rsqb); if self.nesting == 0 { - return Some(Err(LexicalError { + return Err(LexicalError { error: LexicalErrorType::NestingError, location: self.get_pos(), - })); + }); } self.nesting -= 1; - return Some(result); + return result; } '{' => { let result = self.eat_single_char(Tok::Lbrace); self.nesting += 1; - return Some(result); + return result; } '}' => { let result = self.eat_single_char(Tok::Rbrace); if self.nesting == 0 { - return Some(Err(LexicalError { + return Err(LexicalError { error: LexicalErrorType::NestingError, location: self.get_pos(), - })); + }); } self.nesting -= 1; - return Some(result); + return result; } ':' => { - return Some(self.eat_single_char(Tok::Colon)); + let tok_start = self.get_pos(); + if let Some('=') = self.chr0 { + self.next_char(); + let tok_end = self.get_pos(); + return Ok((tok_start, Tok::Walrus, tok_end)); + } else { + return self.eat_single_char(Tok::Colon); + } } ';' => { - return Some(self.eat_single_char(Tok::Semi)); + return self.eat_single_char(Tok::Semi); } '<' => { let tok_start = self.get_pos(); @@ -1068,26 +1063,22 @@ where Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok(( - tok_start, - Tok::LeftShiftEqual, - tok_end, - ))); + return Ok((tok_start, Tok::LeftShiftEqual, tok_end)); } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::LeftShift, tok_end))); + return Ok((tok_start, Tok::LeftShift, tok_end)); } } } Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::LessEqual, tok_end))); + return Ok((tok_start, Tok::LessEqual, tok_end)); } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Less, tok_end))); + return Ok((tok_start, Tok::Less, tok_end)); } } } @@ -1101,26 +1092,22 @@ where Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok(( - tok_start, - Tok::RightShiftEqual, - tok_end, - ))); + return Ok((tok_start, Tok::RightShiftEqual, tok_end)); } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::RightShift, tok_end))); + return Ok((tok_start, Tok::RightShift, tok_end)); } } } Some('=') => { self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::GreaterEqual, tok_end))); + return Ok((tok_start, Tok::GreaterEqual, tok_end)); } _ => { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Greater, tok_end))); + return Ok((tok_start, Tok::Greater, tok_end)); } } } @@ -1128,11 +1115,11 @@ where let tok_start = self.get_pos(); self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Comma, tok_end))); + return Ok((tok_start, Tok::Comma, tok_end)); } '.' => { if let Some('0'..='9') = self.chr1 { - return Some(self.lex_number()); + return self.lex_number(); } else { let tok_start = self.get_pos(); self.next_char(); @@ -1140,10 +1127,10 @@ where self.next_char(); self.next_char(); let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Ellipsis, tok_end))); + return Ok((tok_start, Tok::Ellipsis, tok_end)); } else { let tok_end = self.get_pos(); - return Some(Ok((tok_start, Tok::Dot, tok_end))); + return Ok((tok_start, Tok::Dot, tok_end)); } } } @@ -1156,7 +1143,7 @@ where // Depending on the nesting level, we emit newline or not: if self.nesting == 0 { self.at_begin_of_line = true; - return Some(Ok((tok_start, Tok::Newline, tok_end))); + return Ok((tok_start, Tok::Newline, tok_end)); } else { continue; } @@ -1168,15 +1155,16 @@ where } _ => { let c = self.next_char(); - return Some(Err(LexicalError { + return Err(LexicalError { error: LexicalErrorType::UnrecognizedToken { tok: c.unwrap() }, location: self.get_pos(), - })); + }); } // Ignore all the rest.. } } } else { - return None; + let tok_pos = self.get_pos(); + return Ok((tok_pos.clone(), Tok::EndOfFile, tok_pos)); } } } @@ -1211,7 +1199,11 @@ where self.nesting, self.indentation_stack ); - token + + match token { + Ok((_, Tok::EndOfFile, _)) => None, + r => Some(r), + } } } diff --git a/parser/src/token.rs b/parser/src/token.rs index f9bd2a5684..cc1a787714 100644 --- a/parser/src/token.rs +++ b/parser/src/token.rs @@ -17,11 +17,13 @@ pub enum Tok { StartProgram, StartStatement, StartExpression, + EndOfFile, Lpar, Rpar, Lsqb, Rsqb, Colon, + Walrus, // ':=' Comma, Semi, Plus, From 29fff5ab4d2ca6ea558fbbb40ac493252ce02529 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Mon, 13 May 2019 11:41:46 +0900 Subject: [PATCH 613/884] Remove types module --- vm/src/stdlib/mod.rs | 2 -- vm/src/stdlib/types.rs | 39 --------------------------------------- 2 files changed, 41 deletions(-) delete mode 100644 vm/src/stdlib/types.rs diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 830aa2dcaa..149f37d9e9 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -14,7 +14,6 @@ mod string; mod thread; mod time_module; mod tokenize; -mod types; mod weakref; use std::collections::HashMap; @@ -49,7 +48,6 @@ pub fn get_module_inits() -> HashMap { modules.insert("_thread".to_string(), Box::new(thread::make_module)); modules.insert("time".to_string(), Box::new(time_module::make_module)); modules.insert("tokenize".to_string(), Box::new(tokenize::make_module)); - modules.insert("types".to_string(), Box::new(types::make_module)); modules.insert("_weakref".to_string(), Box::new(weakref::make_module)); // disable some modules on WASM diff --git a/vm/src/stdlib/types.rs b/vm/src/stdlib/types.rs deleted file mode 100644 index e224a93c68..0000000000 --- a/vm/src/stdlib/types.rs +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Dynamic type creation and names for built in types. - */ - -use crate::function::OptionalArg; -use crate::obj::objdict::PyDict; -use crate::obj::objstr::PyStringRef; -use crate::obj::objtype; -use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject}; -use crate::VirtualMachine; - -fn types_new_class( - name: PyStringRef, - bases: OptionalArg>, - vm: &VirtualMachine, -) -> PyResult { - // TODO kwds and exec_body parameter - - let bases = match bases { - OptionalArg::Present(bases) => bases, - OptionalArg::Missing => PyIterable::try_from_object(vm, vm.ctx.new_tuple(vec![]))?, - }; - let dict = PyDict::default().into_ref(vm); - objtype::type_new_class(vm, vm.ctx.type_type(), name, bases, dict) -} - -pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { - let ctx = &vm.ctx; - - py_module!(vm, "types", { - "new_class" => ctx.new_rustfunc(types_new_class), - "FunctionType" => ctx.function_type(), - "MethodType" => ctx.bound_method_type(), - "LambdaType" => ctx.function_type(), - "CodeType" => ctx.code_type(), - "FrameType" => ctx.frame_type() - }) -} From b048a66bc330ea9f9fc9194e8c9cba48a4b953c1 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Mon, 13 May 2019 11:42:16 +0900 Subject: [PATCH 614/884] Copy types.py from CPython --- Lib/types.py | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 Lib/types.py diff --git a/Lib/types.py b/Lib/types.py new file mode 100644 index 0000000000..2e0513ca15 --- /dev/null +++ b/Lib/types.py @@ -0,0 +1,295 @@ +""" +Define names for built-in types that aren't directly accessible as a builtin. +""" +import sys + +# Iterators in Python aren't a matter of type but of protocol. A large +# and changing number of builtin types implement *some* flavor of +# iterator. Don't check the type! Use hasattr to check for both +# "__iter__" and "__next__" attributes instead. + +def _f(): pass +FunctionType = type(_f) +LambdaType = type(lambda: None) # Same as FunctionType +CodeType = type(_f.__code__) +MappingProxyType = type(type.__dict__) +SimpleNamespace = type(sys.implementation) + +def _g(): + yield 1 +GeneratorType = type(_g()) + +async def _c(): pass +_c = _c() +CoroutineType = type(_c) +_c.close() # Prevent ResourceWarning + +async def _ag(): + yield +_ag = _ag() +AsyncGeneratorType = type(_ag) + +class _C: + def _m(self): pass +MethodType = type(_C()._m) + +BuiltinFunctionType = type(len) +BuiltinMethodType = type([].append) # Same as BuiltinFunctionType + +WrapperDescriptorType = type(object.__init__) +MethodWrapperType = type(object().__str__) +MethodDescriptorType = type(str.join) +ClassMethodDescriptorType = type(dict.__dict__['fromkeys']) + +ModuleType = type(sys) + +try: + raise TypeError +except TypeError: + tb = sys.exc_info()[2] + TracebackType = type(tb) + FrameType = type(tb.tb_frame) + tb = None; del tb + +# For Jython, the following two types are identical +GetSetDescriptorType = type(FunctionType.__code__) +MemberDescriptorType = type(FunctionType.__globals__) + +del sys, _f, _g, _C, _c, _ag # Not for export + + +# Provide a PEP 3115 compliant mechanism for class creation +def new_class(name, bases=(), kwds=None, exec_body=None): + """Create a class object dynamically using the appropriate metaclass.""" + resolved_bases = resolve_bases(bases) + meta, ns, kwds = prepare_class(name, resolved_bases, kwds) + if exec_body is not None: + exec_body(ns) + if resolved_bases is not bases: + ns['__orig_bases__'] = bases + return meta(name, resolved_bases, ns, **kwds) + +def resolve_bases(bases): + """Resolve MRO entries dynamically as specified by PEP 560.""" + new_bases = list(bases) + updated = False + shift = 0 + for i, base in enumerate(bases): + if isinstance(base, type): + continue + if not hasattr(base, "__mro_entries__"): + continue + new_base = base.__mro_entries__(bases) + updated = True + if not isinstance(new_base, tuple): + raise TypeError("__mro_entries__ must return a tuple") + else: + new_bases[i+shift:i+shift+1] = new_base + shift += len(new_base) - 1 + if not updated: + return bases + return tuple(new_bases) + +def prepare_class(name, bases=(), kwds=None): + """Call the __prepare__ method of the appropriate metaclass. + + Returns (metaclass, namespace, kwds) as a 3-tuple + + *metaclass* is the appropriate metaclass + *namespace* is the prepared class namespace + *kwds* is an updated copy of the passed in kwds argument with any + 'metaclass' entry removed. If no kwds argument is passed in, this will + be an empty dict. + """ + if kwds is None: + kwds = {} + else: + kwds = dict(kwds) # Don't alter the provided mapping + if 'metaclass' in kwds: + meta = kwds.pop('metaclass') + else: + if bases: + meta = type(bases[0]) + else: + meta = type + if isinstance(meta, type): + # when meta is a type, we first determine the most-derived metaclass + # instead of invoking the initial candidate directly + meta = _calculate_meta(meta, bases) + if hasattr(meta, '__prepare__'): + ns = meta.__prepare__(name, bases, **kwds) + else: + ns = {} + return meta, ns, kwds + +def _calculate_meta(meta, bases): + """Calculate the most derived metaclass.""" + winner = meta + for base in bases: + base_meta = type(base) + if issubclass(winner, base_meta): + continue + if issubclass(base_meta, winner): + winner = base_meta + continue + # else: + raise TypeError("metaclass conflict: " + "the metaclass of a derived class " + "must be a (non-strict) subclass " + "of the metaclasses of all its bases") + return winner + +class DynamicClassAttribute: + """Route attribute access on a class to __getattr__. + + This is a descriptor, used to define attributes that act differently when + accessed through an instance and through a class. Instance access remains + normal, but access to an attribute through a class will be routed to the + class's __getattr__ method; this is done by raising AttributeError. + + This allows one to have properties active on an instance, and have virtual + attributes on the class with the same name (see Enum for an example). + + """ + def __init__(self, fget=None, fset=None, fdel=None, doc=None): + self.fget = fget + self.fset = fset + self.fdel = fdel + # next two lines make DynamicClassAttribute act the same as property + self.__doc__ = doc or fget.__doc__ + self.overwrite_doc = doc is None + # support for abstract methods + self.__isabstractmethod__ = bool(getattr(fget, '__isabstractmethod__', False)) + + def __get__(self, instance, ownerclass=None): + if instance is None: + if self.__isabstractmethod__: + return self + raise AttributeError() + elif self.fget is None: + raise AttributeError("unreadable attribute") + return self.fget(instance) + + def __set__(self, instance, value): + if self.fset is None: + raise AttributeError("can't set attribute") + self.fset(instance, value) + + def __delete__(self, instance): + if self.fdel is None: + raise AttributeError("can't delete attribute") + self.fdel(instance) + + def getter(self, fget): + fdoc = fget.__doc__ if self.overwrite_doc else None + result = type(self)(fget, self.fset, self.fdel, fdoc or self.__doc__) + result.overwrite_doc = self.overwrite_doc + return result + + def setter(self, fset): + result = type(self)(self.fget, fset, self.fdel, self.__doc__) + result.overwrite_doc = self.overwrite_doc + return result + + def deleter(self, fdel): + result = type(self)(self.fget, self.fset, fdel, self.__doc__) + result.overwrite_doc = self.overwrite_doc + return result + + +class _GeneratorWrapper: + # TODO: Implement this in C. + def __init__(self, gen): + self.__wrapped = gen + self.__isgen = gen.__class__ is GeneratorType + self.__name__ = getattr(gen, '__name__', None) + self.__qualname__ = getattr(gen, '__qualname__', None) + def send(self, val): + return self.__wrapped.send(val) + def throw(self, tp, *rest): + return self.__wrapped.throw(tp, *rest) + def close(self): + return self.__wrapped.close() + @property + def gi_code(self): + return self.__wrapped.gi_code + @property + def gi_frame(self): + return self.__wrapped.gi_frame + @property + def gi_running(self): + return self.__wrapped.gi_running + @property + def gi_yieldfrom(self): + return self.__wrapped.gi_yieldfrom + cr_code = gi_code + cr_frame = gi_frame + cr_running = gi_running + cr_await = gi_yieldfrom + def __next__(self): + return next(self.__wrapped) + def __iter__(self): + if self.__isgen: + return self.__wrapped + return self + __await__ = __iter__ + +def coroutine(func): + """Convert regular generator function to a coroutine.""" + + if not callable(func): + raise TypeError('types.coroutine() expects a callable') + + if (func.__class__ is FunctionType and + getattr(func, '__code__', None).__class__ is CodeType): + + co_flags = func.__code__.co_flags + + # Check if 'func' is a coroutine function. + # (0x180 == CO_COROUTINE | CO_ITERABLE_COROUTINE) + if co_flags & 0x180: + return func + + # Check if 'func' is a generator function. + # (0x20 == CO_GENERATOR) + if co_flags & 0x20: + # TODO: Implement this in C. + co = func.__code__ + func.__code__ = CodeType( + co.co_argcount, co.co_kwonlyargcount, co.co_nlocals, + co.co_stacksize, + co.co_flags | 0x100, # 0x100 == CO_ITERABLE_COROUTINE + co.co_code, + co.co_consts, co.co_names, co.co_varnames, co.co_filename, + co.co_name, co.co_firstlineno, co.co_lnotab, co.co_freevars, + co.co_cellvars) + return func + + # The following code is primarily to support functions that + # return generator-like objects (for instance generators + # compiled with Cython). + + # Delay functools and _collections_abc import for speeding up types import. + import functools + import _collections_abc + @functools.wraps(func) + def wrapped(*args, **kwargs): + coro = func(*args, **kwargs) + if (coro.__class__ is CoroutineType or + coro.__class__ is GeneratorType and coro.gi_code.co_flags & 0x100): + # 'coro' is a native coroutine object or an iterable coroutine + return coro + if (isinstance(coro, _collections_abc.Generator) and + not isinstance(coro, _collections_abc.Coroutine)): + # 'coro' is either a pure Python generator iterator, or it + # implements collections.abc.Generator (and does not implement + # collections.abc.Coroutine). + return _GeneratorWrapper(coro) + # 'coro' is either an instance of collections.abc.Coroutine or + # some other object -- pass it through. + return coro + + return wrapped + + +__all__ = [n for n in globals() if n[:1] != '_'] From b76cb680e9680be2054f78b4da6326d02634768e Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Mon, 13 May 2019 11:44:12 +0900 Subject: [PATCH 615/884] Comment out unsupported code from types.py --- Lib/types.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/Lib/types.py b/Lib/types.py index 2e0513ca15..4ef4619618 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -13,21 +13,21 @@ def _f(): pass LambdaType = type(lambda: None) # Same as FunctionType CodeType = type(_f.__code__) MappingProxyType = type(type.__dict__) -SimpleNamespace = type(sys.implementation) +# SimpleNamespace = type(sys.implementation) def _g(): yield 1 GeneratorType = type(_g()) -async def _c(): pass -_c = _c() -CoroutineType = type(_c) -_c.close() # Prevent ResourceWarning +# async def _c(): pass +# _c = _c() +# CoroutineType = type(_c) +# _c.close() # Prevent ResourceWarning -async def _ag(): - yield -_ag = _ag() -AsyncGeneratorType = type(_ag) +# async def _ag(): +# yield +# _ag = _ag() +# AsyncGeneratorType = type(_ag) class _C: def _m(self): pass @@ -43,19 +43,19 @@ def _m(self): pass ModuleType = type(sys) -try: - raise TypeError -except TypeError: - tb = sys.exc_info()[2] - TracebackType = type(tb) - FrameType = type(tb.tb_frame) - tb = None; del tb +# try: +# raise TypeError +# except TypeError: +# tb = sys.exc_info()[2] +# TracebackType = type(tb) +# FrameType = type(tb.tb_frame) +# tb = None; del tb # For Jython, the following two types are identical GetSetDescriptorType = type(FunctionType.__code__) -MemberDescriptorType = type(FunctionType.__globals__) +# MemberDescriptorType = type(FunctionType.__globals__) -del sys, _f, _g, _C, _c, _ag # Not for export +del sys, _f, _g, _C # Not for export # Provide a PEP 3115 compliant mechanism for class creation From 5cff843782c598a16fa3a748a2d27c2f593f43f5 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Mon, 13 May 2019 19:41:06 +0900 Subject: [PATCH 616/884] Implement re.escape --- tests/snippets/test_re.py | 2 ++ vm/src/stdlib/re.rs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/tests/snippets/test_re.py b/tests/snippets/test_re.py index 2463f380b5..2bc7308e6a 100644 --- a/tests/snippets/test_re.py +++ b/tests/snippets/test_re.py @@ -11,3 +11,5 @@ # assert isinstance(mo, re.Match) assert mo.start() == 1 assert mo.end() == 5 + +assert re.escape('python.exe') == 'python\\.exe' diff --git a/vm/src/stdlib/re.rs b/vm/src/stdlib/re.rs index b9f9215020..f427389802 100644 --- a/vm/src/stdlib/re.rs +++ b/vm/src/stdlib/re.rs @@ -79,6 +79,10 @@ fn re_compile(pattern: PyStringRef, vm: &VirtualMachine) -> PyResult { make_regex(vm, &pattern.value) } +fn re_escape(pattern: PyStringRef, _vm: &VirtualMachine) -> String { + regex::escape(&pattern.value) +} + impl PyRegexRef { fn match_(self, text: PyStringRef, vm: &VirtualMachine) -> PyResult { do_match(vm, &self, &text.value) @@ -113,6 +117,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { py_module!(vm, "re", { "compile" => ctx.new_rustfunc(re_compile), + "escape" => ctx.new_rustfunc(re_escape), "Match" => match_type, "match" => ctx.new_rustfunc(re_match), "Pattern" => pattern_type, From 189bf171ab42fe08d6b90385d88d05a3545475ef Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Tue, 14 May 2019 12:24:44 +0900 Subject: [PATCH 617/884] Remove extern crate unneeded for Rust 2018 --- vm/src/lib.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 8b14454fe7..f21d5dcf4a 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -20,13 +20,6 @@ extern crate lexical; #[macro_use] extern crate log; // extern crate env_logger; -extern crate num_bigint; -extern crate num_complex; -extern crate num_integer; -extern crate num_traits; -extern crate serde; -extern crate serde_json; -extern crate statrs; extern crate rustpython_parser; #[macro_use] From 68a1a9f6480214e1f3c541ce5e4dd543643d4d61 Mon Sep 17 00:00:00 2001 From: Shitong Wen Date: Tue, 14 May 2019 16:56:50 +0800 Subject: [PATCH 618/884] add to_bytes for int --- tests/snippets/ints.py | 7 +++++ vm/src/obj/objint.rs | 68 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index be41c32574..b947797ac6 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -79,6 +79,13 @@ assert int.from_bytes(b'\xfc\x00', 'big', signed=True) == -1024 assert int.from_bytes(b'\xfc\x00', 'big', signed=False) == 64512 +assert (1024).to_bytes(4, 'big') == b'\x00\x00\x04\x00' +assert (1024).to_bytes(2, 'little', signed=True) == b'\x00\x04' +assert (-1024).to_bytes(4, 'big', signed=True) == b'\xff\xff\xfc\x00' +assert (-1024).to_bytes(4, 'little', signed=True) == b'\x00\xfc\xff\xff' +assert (2147483647).to_bytes(8, 'big', signed=False) == b'\x00\x00\x00\x00\x7f\xff\xff\xff' +assert (-2147483648).to_bytes(8, 'little', signed=True) == b'\x00\x00\x00\x80\xff\xff\xff\xff' + with assertRaises(TypeError): int(base=2) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 403fd08ef6..a8c74aaf76 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -14,6 +14,7 @@ use crate::pyobject::{ use crate::vm::VirtualMachine; use super::objbyteinner::PyByteInner; +use super::objbytes::PyBytes; use super::objstr::{PyString, PyStringRef}; use super::objtype; use crate::obj::objtype::PyClassRef; @@ -526,7 +527,74 @@ impl PyInt { } Ok(x) } + #[pymethod] + fn to_bytes( + &self, + length: PyIntRef, + byteorder: PyStringRef, + kwargs: KwArgs, + vm: &VirtualMachine, + ) -> PyResult { + let mut signed = false; + let value = self.as_bigint(); + for (key, value) in kwargs.into_iter() { + if key == "signed" { + signed = match_class!(value, + + b @ PyInt => !b.as_bigint().is_zero(), + _ => false, + ); + } + } + if value.sign() == Sign::Minus && signed == false { + return Err(vm.new_overflow_error("can't convert negative int to unsigned".to_string())); + } + let byte_len; + if let Some(temp) = length.as_bigint().to_usize() { + byte_len = temp; + } else { + return Err(vm.new_value_error("length parameter is illegal".to_string())); + } + + let mut origin_bytes = match byteorder.value.as_str() { + "big" => match signed { + true => value.to_signed_bytes_be(), + false => value.to_bytes_be().1, + }, + "little" => match signed { + true => value.to_signed_bytes_le(), + false => value.to_bytes_le().1, + }, + _ => { + return Err( + vm.new_value_error("byteorder must be either 'little' or 'big'".to_string()) + ); + } + }; + let origin_len = origin_bytes.len(); + if origin_len > byte_len { + return Err(vm.new_value_error("int too big to convert".to_string())); + } + + let mut append_bytes = match value.sign() { + Sign::Minus => vec![255u8; byte_len - origin_len], + _ => vec![0u8; byte_len - origin_len], + }; + let mut bytes = vec![]; + match byteorder.value.as_str() { + "big" => { + bytes = append_bytes; + bytes.append(&mut origin_bytes); + } + "little" => { + bytes = origin_bytes; + bytes.append(&mut append_bytes); + } + _ => (), + } + Ok(PyBytes::new(bytes)) + } #[pyproperty] fn real(zelf: PyRef, _vm: &VirtualMachine) -> PyIntRef { zelf From 13187fad5c10162b7282df39b8b6d528c7d8a2fb Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 14 May 2019 03:30:25 +0900 Subject: [PATCH 619/884] Fix dictdatatype::Dict eq to regard __bool__ --- vm/src/dictdatatype.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index a1aa9e3c38..a075ec9370 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -222,7 +222,7 @@ fn calc_hash(vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { /// Invoke __eq__ on two keys fn do_eq(vm: &VirtualMachine, key1: &PyObjectRef, key2: &PyObjectRef) -> Result { let result = vm._eq(key1.clone(), key2.clone())?; - Ok(objbool::get_value(&result)) + objbool::boolval(vm, result) } #[cfg(test)] From cc9dcbc9f811fe9cbc49ba2e1fea76e2de9d6792 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 9 May 2019 02:26:30 +0900 Subject: [PATCH 620/884] Refactor dictdatatype::Dict --- tests/snippets/dict.py | 17 ++++++++ vm/src/dictdatatype.rs | 93 +++++++++++++++++++++++++++--------------- vm/src/obj/objdict.rs | 14 +++---- 3 files changed, 81 insertions(+), 43 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index f7506a2059..4545f7f584 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -200,3 +200,20 @@ def __eq__(self, other): assert w == {1: 1, 'a': 1, 'b': 2, 'c': 3, 2: 2, 'd': 3, 3: 3, 'e': 3, 4: 4} assert str({True: True, 1.0: 1.0}) == str({True: 1.0}) + +class A: + def __hash__(self): + return 1 + def __eq__(self, other): + return isinstance(other, A) +class B: + def __hash__(self): + return 1 + def __eq__(self, other): + return isinstance(other, B) + +s = {1: 0, A(): 1, B(): 2} +assert len(s) == 3 +assert s[1] == 0 +assert s[A()] == 1 +assert s[B()] == 2 diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index a075ec9370..858cf43e18 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -1,16 +1,25 @@ use crate::obj::objbool; +use crate::pyhash; use crate::pyobject::{IdProtocol, PyObjectRef, PyResult}; use crate::vm::VirtualMachine; /// Ordered dictionary implementation. /// Inspired by: https://morepypy.blogspot.com/2015/01/faster-more-memory-efficient-and-more.html /// And: https://www.youtube.com/watch?v=p33CVV29OG8 /// And: http://code.activestate.com/recipes/578375/ -use std::collections::HashMap; +use std::collections::{hash_map::DefaultHasher, HashMap}; +use std::hash::{Hash, Hasher}; + +/// hash value of an object returned by __hash__ +type HashValue = pyhash::PyHash; +/// index calculated by resolving collision +type HashIndex = pyhash::PyHash; +/// entry index mapped in indices +type EntryIndex = usize; #[derive(Clone)] pub struct Dict { size: usize, - indices: HashMap, + indices: HashMap, entries: Vec>>, } @@ -26,7 +35,7 @@ impl Default for Dict { #[derive(Clone)] struct DictEntry { - hash: usize, + hash: HashValue, key: PyObjectRef, value: T, } @@ -38,12 +47,27 @@ pub struct DictSize { } impl Dict { - pub fn new() -> Self { - Dict { - size: 0, - indices: HashMap::new(), - entries: Vec::new(), - } + fn unchecked_push( + &mut self, + hash_index: HashIndex, + hash_value: HashValue, + key: &PyObjectRef, + value: T, + ) { + let entry = DictEntry { + hash: hash_value, + key: key.clone(), + value, + }; + let entry_index = self.entries.len(); + self.entries.push(Some(entry)); + self.indices.insert(hash_index, entry_index); + self.size += 1; + } + + fn unchecked_delete(&mut self, entry_index: EntryIndex) { + self.entries[entry_index] = None; + self.size -= 1; } /// Store a key @@ -63,29 +87,21 @@ impl Dict { hash_value, } => { // New key: - let entry = DictEntry { - hash: hash_value, - key: key.clone(), - value, - }; - let index = self.entries.len(); - self.entries.push(Some(entry)); - self.indices.insert(hash_index, index); - self.size += 1; + self.unchecked_push(hash_index, hash_value, key, value); Ok(()) } } } pub fn contains(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { - if let LookupResult::Existing(_index) = self.lookup(vm, key)? { + if let LookupResult::Existing(_) = self.lookup(vm, key)? { Ok(true) } else { Ok(false) } } - fn unchecked_get(&self, index: usize) -> T { + fn unchecked_get(&self, index: EntryIndex) -> T { if let Some(entry) = &self.entries[index] { entry.value.clone() } else { @@ -110,9 +126,7 @@ impl Dict { /// Delete a key pub fn delete(&mut self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult<()> { - if let LookupResult::Existing(index) = self.lookup(vm, key)? { - self.entries[index] = None; - self.size -= 1; + if self.delete_if_exists(vm, key)? { Ok(()) } else { let key_repr = vm.to_pystr(key)?; @@ -120,6 +134,15 @@ impl Dict { } } + pub fn delete_if_exists(&mut self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { + if let LookupResult::Existing(entry_index) = self.lookup(vm, key)? { + self.unchecked_delete(entry_index); + Ok(true) + } else { + Ok(false) + } + } + pub fn len(&self) -> usize { self.size } @@ -135,7 +158,7 @@ impl Dict { } } - pub fn next_entry(&self, position: &mut usize) -> Option<(&PyObjectRef, &T)> { + pub fn next_entry(&self, position: &mut EntryIndex) -> Option<(&PyObjectRef, &T)> { while *position < self.entries.len() { if let Some(DictEntry { key, value, .. }) = &self.entries[*position] { *position += 1; @@ -152,9 +175,9 @@ impl Dict { /// Lookup the index for the given key. fn lookup(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { - let hash_value = calc_hash(vm, key)?; + let hash_value = collection_hash(vm, key)?; let perturb = hash_value; - let mut hash_index: usize = hash_value; + let mut hash_index: HashIndex = hash_value; loop { if self.indices.contains_key(&hash_index) { // Now we have an index, lets check the key. @@ -197,8 +220,7 @@ impl Dict { pub fn pop(&mut self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { if let LookupResult::Existing(index) = self.lookup(vm, key)? { let value = self.unchecked_get(index); - self.entries[index] = None; - self.size -= 1; + self.unchecked_delete(index); Ok(value) } else { let key_repr = vm.to_pystr(key)?; @@ -209,14 +231,17 @@ impl Dict { enum LookupResult { NewIndex { - hash_value: usize, - hash_index: usize, + hash_value: HashValue, + hash_index: HashIndex, }, // return not found, index into indices - Existing(usize), // Existing record, index into entries + Existing(EntryIndex), // Existing record, index into entries } -fn calc_hash(vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { - Ok(vm._hash(key)? as usize) +fn collection_hash(vm: &VirtualMachine, object: &PyObjectRef) -> PyResult { + let raw_hash = vm._hash(object)?; + let mut hasher = DefaultHasher::new(); + raw_hash.hash(&mut hasher); + Ok(hasher.finish() as HashValue) } /// Invoke __eq__ on two keys @@ -232,7 +257,7 @@ mod tests { #[test] fn test_insert() { let mut vm = VirtualMachine::new(); - let mut dict = Dict::new(); + let mut dict = Dict::default(); assert_eq!(0, dict.len()); let key1 = vm.new_bool(true); diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 396c711f45..87e4b19191 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -264,15 +264,11 @@ impl PyDictRef { fn popitem(self, vm: &VirtualMachine) -> PyResult { let mut entries = self.entries.borrow_mut(); - let (key, value) = match entries.next_entry(&mut 0) { - Some((key, value)) => (key.clone(), value.clone()), - None => { - return Err(vm.new_key_error("popitem(): dictionary is empty".to_string())); - } - }; - - entries.delete(vm, &key)?; - Ok(vm.ctx.new_tuple(vec![key, value])) + if let Some((key, value)) = entries.pop_front() { + Ok(vm.ctx.new_tuple(vec![key, value])) + } else { + Err(vm.new_key_error("popitem(): dictionary is empty".to_string())) + } } /// Take a python dictionary and convert it to attributes. From 81186ea5b7df966e678e9ab52c2e61a13f301704 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 9 May 2019 02:27:32 +0900 Subject: [PATCH 621/884] Fix set() hash collision based on dictdatatype::Dict --- tests/snippets/set.py | 35 ++++++- vm/src/dictdatatype.rs | 36 ++++++++ vm/src/obj/objset.rs | 205 ++++++++++++----------------------------- 3 files changed, 128 insertions(+), 148 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index ba20fbae62..65a568f9d9 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -103,7 +103,7 @@ def __hash__(self): b = a.pop() assert b in [1,2] c = a.pop() -assert (c in [1,2] and c != b) +assert (c in [1,2] and c != b) assert_raises(KeyError, lambda: a.pop()) a = set([1,2,3]) @@ -268,3 +268,36 @@ def __hash__(self): assert frozenset([1,2,3]) ^ set([4,5]) == frozenset([1,2,3,4,5]) assert set([1,2,3]) ^ frozenset([4,5]) == set([1,2,3,4,5]) + +class A: + def __hash__(self): + return 1 +class B: + def __hash__(self): + return 1 + +s = {1, A(), B()} +assert len(s) == 3 + +s = {True} +s.add(1.0) +assert str(s) == '{True}' + +class EqObject: + def __init__(self, eq): + self.eq = eq + def __eq__(self, other): + return self.eq + def __hash__(self): + return bool(self.eq) + +assert 'x' == (EqObject('x') == EqObject('x')) +s = {EqObject('x')} +assert EqObject('x') in s +assert '[]' == (EqObject('[]') == EqObject('[]')) +s = {EqObject([])} +assert EqObject([]) not in s +x = object() +assert x == (EqObject(x) == EqObject(x)) +s = {EqObject(x)} +assert EqObject(x) in s diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 858cf43e18..7885872667 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -143,6 +143,22 @@ impl Dict { } } + pub fn delete_or_insert( + &mut self, + vm: &VirtualMachine, + key: &PyObjectRef, + value: T, + ) -> PyResult<()> { + match self.lookup(vm, &key)? { + LookupResult::Existing(entry_index) => self.unchecked_delete(entry_index), + LookupResult::NewIndex { + hash_value, + hash_index, + } => self.unchecked_push(hash_index, hash_value, &key, value), + }; + Ok(()) + } + pub fn len(&self) -> usize { self.size } @@ -173,6 +189,14 @@ impl Dict { position.size != self.size || self.entries.len() != position.entries_size } + pub fn keys<'a>(&'a self) -> Box + 'a> { + Box::new( + self.entries + .iter() + .filter_map(|v| v.as_ref().map_or(None, |v| Some(v.key.clone()))), + ) + } + /// Lookup the index for the given key. fn lookup(&self, vm: &VirtualMachine, key: &PyObjectRef) -> PyResult { let hash_value = collection_hash(vm, key)?; @@ -227,6 +251,18 @@ impl Dict { Err(vm.new_key_error(format!("Key not found: {}", key_repr))) } } + + pub fn pop_front(&mut self) -> Option<(PyObjectRef, T)> { + let mut entry_index = 0; + match self.next_entry(&mut entry_index) { + Some((key, value)) => { + let item = (key.clone(), value.clone()); + self.unchecked_delete(entry_index - 1); + Some(item) + } + None => None, + } + } } enum LookupResult { diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index aed3f83515..e404fa9249 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -3,10 +3,9 @@ */ use std::cell::{Cell, RefCell}; -use std::collections::{hash_map::DefaultHasher, HashMap}; use std::fmt; -use std::hash::{Hash, Hasher}; +use crate::dictdatatype; use crate::function::OptionalArg; use crate::pyobject::{ PyContext, PyIterable, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, @@ -14,12 +13,12 @@ use crate::pyobject::{ }; use crate::vm::{ReprGuard, VirtualMachine}; -use super::objbool; -use super::objint; use super::objlist::PyListIterator; use super::objtype; use super::objtype::PyClassRef; +pub type SetContentType = dictdatatype::Dict<()>; + #[derive(Default)] pub struct PySet { inner: RefCell, @@ -60,42 +59,32 @@ impl PyValue for PyFrozenSet { #[derive(Default, Clone)] struct PySetInner { - elements: HashMap, + content: SetContentType, } impl PySetInner { fn new(iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { - let elements: HashMap = match iterable { - OptionalArg::Missing => HashMap::new(), - OptionalArg::Present(iterable) => { - let mut elements = HashMap::new(); - for item in iterable.iter(vm)? { - insert_into_set(vm, &mut elements, &item?)?; - } - elements + let mut set = PySetInner::default(); + if let OptionalArg::Present(iterable) = iterable { + for item in iterable.iter(vm)? { + set.add(&item?, vm)?; } - }; - - Ok(PySetInner { elements }) + } + Ok(set) } fn len(&self) -> usize { - self.elements.len() + self.content.len() } + fn copy(&self) -> PySetInner { PySetInner { - elements: self.elements.clone(), + content: self.content.clone(), } } - fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - for element in self.elements.iter() { - let value = vm._eq(needle.clone(), element.1.clone())?; - if objbool::get_value(&value) { - return Ok(true); - } - } - Ok(false) + fn contains(&self, needle: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.content.contains(vm, needle) } fn _compare_inner( @@ -123,8 +112,8 @@ impl PySetInner { if size_func(get_zelf(swap).len(), get_other(swap).len()) { return Ok(vm.new_bool(false)); } - for element in get_other(swap).elements.iter() { - if !get_zelf(swap).contains(element.1.clone(), vm)? { + for key in get_other(swap).content.keys() { + if !get_zelf(swap).contains(&key, vm)? { return Ok(vm.new_bool(false)); } } @@ -177,46 +166,38 @@ impl PySetInner { } fn union(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - let mut elements = self.elements.clone(); + let mut set = self.clone(); for item in other.iter(vm)? { - insert_into_set(vm, &mut elements, &item?)?; + set.add(&item?, vm)?; } - Ok(PySetInner { elements }) + Ok(set) } fn intersection(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - let mut elements = HashMap::new(); + let mut set = PySetInner::default(); for item in other.iter(vm)? { let obj = item?; - if self.contains(obj.clone(), vm)? { - insert_into_set(vm, &mut elements, &obj)?; + if self.contains(&obj, vm)? { + set.add(&obj, vm)?; } } - Ok(PySetInner { elements }) + Ok(set) } fn difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - let mut elements = self.elements.clone(); + let mut set = self.copy(); for item in other.iter(vm)? { - let obj = item?; - if self.contains(obj.clone(), vm)? { - remove_from_set(vm, &mut elements, &obj)?; - } + set.content.delete_if_exists(vm, &item?)?; } - Ok(PySetInner { elements }) + Ok(set) } fn symmetric_difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { let mut new_inner = self.clone(); for item in other.iter(vm)? { - let obj = item?; - if !self.contains(obj.clone(), vm)? { - new_inner.add(&obj, vm)?; - } else { - new_inner.remove(&obj, vm)?; - } + new_inner.content.delete_or_insert(vm, &item?, ())? } Ok(new_inner) @@ -224,8 +205,7 @@ impl PySetInner { fn isdisjoint(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { for item in other.iter(vm)? { - let obj = item?; - if self.contains(obj.clone(), vm)? { + if self.contains(&item?, vm)? { return Ok(false); } } @@ -233,7 +213,7 @@ impl PySetInner { } fn iter(&self, vm: &VirtualMachine) -> PyListIterator { - let items = self.elements.values().cloned().collect(); + let items = self.content.keys().collect(); let set_list = vm.ctx.new_list(items); PyListIterator { position: Cell::new(0), @@ -243,52 +223,43 @@ impl PySetInner { fn repr(&self, vm: &VirtualMachine) -> PyResult { let mut str_parts = vec![]; - for elem in self.elements.values() { - let part = vm.to_repr(elem)?; + for key in self.content.keys() { + let part = vm.to_repr(&key)?; str_parts.push(part.value.clone()); } Ok(format!("{{{}}}", str_parts.join(", "))) } - fn add(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult { - insert_into_set(vm, &mut self.elements, &item) + fn add(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + self.content.insert(vm, item, ()) } - fn remove(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult { - remove_from_set(vm, &mut self.elements, &item) + fn remove(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + self.content.delete(vm, item) } - fn discard(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult { - fn discard( - vm: &VirtualMachine, - elements: &mut HashMap, - key: u64, - _value: &PyObjectRef, - ) -> PyResult { - elements.remove(&key); - Ok(vm.get_none()) - } - perform_action_with_hash(vm, &mut self.elements, &item, &discard) + fn discard(&mut self, item: &PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.content.delete_if_exists(vm, item) } fn clear(&mut self) { - self.elements.clear(); + self.content.clear() } fn pop(&mut self, vm: &VirtualMachine) -> PyResult { - let elements = &mut self.elements; - match elements.clone().keys().next() { - Some(key) => Ok(elements.remove(key).unwrap()), - None => Err(vm.new_key_error("pop from an empty set".to_string())), + if let Some((key, _)) = self.content.pop_front() { + Ok(key) + } else { + Err(vm.new_key_error("pop from an empty set".to_string())) } } - fn update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + fn update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult<()> { for item in iterable.iter(vm)? { - insert_into_set(vm, &mut self.elements, &item?)?; + self.add(&item?, vm)?; } - Ok(vm.get_none()) + Ok(()) } fn intersection_update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { @@ -296,7 +267,7 @@ impl PySetInner { self.clear(); for item in iterable.iter(vm)? { let obj = item?; - if temp_inner.contains(obj.clone(), vm)? { + if temp_inner.contains(&obj, vm)? { self.add(&obj, vm)?; } } @@ -305,10 +276,7 @@ impl PySetInner { fn difference_update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { for item in iterable.iter(vm)? { - let obj = item?; - if self.contains(obj.clone(), vm)? { - self.remove(&obj, vm)?; - } + self.content.delete_if_exists(vm, &item?)?; } Ok(vm.get_none()) } @@ -319,12 +287,7 @@ impl PySetInner { vm: &VirtualMachine, ) -> PyResult { for item in iterable.iter(vm)? { - let obj = item?; - if !self.contains(obj.clone(), vm)? { - self.add(&obj, vm)?; - } else { - self.remove(&obj, vm)?; - } + self.content.delete_or_insert(vm, &item?, ())?; } Ok(vm.get_none()) } @@ -353,7 +316,7 @@ impl PySetRef { } fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow().contains(needle, vm) + self.inner.borrow().contains(&needle, vm) } fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -472,16 +435,18 @@ impl PySetRef { Ok(vm.new_str(s)) } - pub fn add(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().add(&item, vm) + pub fn add(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + self.inner.borrow_mut().add(&item, vm)?; + Ok(()) } - fn remove(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn remove(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self.inner.borrow_mut().remove(&item, vm) } - fn discard(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().discard(&item, vm) + fn discard(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + self.inner.borrow_mut().discard(&item, vm)?; + Ok(()) } fn clear(self, _vm: &VirtualMachine) { @@ -564,7 +529,7 @@ impl PyFrozenSetRef { } fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.inner.contains(needle, vm) + self.inner.contains(&needle, vm) } fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -684,60 +649,6 @@ impl PyFrozenSetRef { } } -fn perform_action_with_hash( - vm: &VirtualMachine, - elements: &mut HashMap, - item: &PyObjectRef, - f: &Fn(&VirtualMachine, &mut HashMap, u64, &PyObjectRef) -> PyResult, -) -> PyResult { - let hash: PyObjectRef = vm.call_method(item, "__hash__", vec![])?; - - let hash_value = objint::get_value(&hash); - let mut hasher = DefaultHasher::new(); - hash_value.hash(&mut hasher); - let key = hasher.finish(); - f(vm, elements, key, item) -} - -fn insert_into_set( - vm: &VirtualMachine, - elements: &mut HashMap, - item: &PyObjectRef, -) -> PyResult { - fn insert( - vm: &VirtualMachine, - elements: &mut HashMap, - key: u64, - value: &PyObjectRef, - ) -> PyResult { - elements.insert(key, value.clone()); - Ok(vm.get_none()) - } - perform_action_with_hash(vm, elements, item, &insert) -} - -fn remove_from_set( - vm: &VirtualMachine, - elements: &mut HashMap, - item: &PyObjectRef, -) -> PyResult { - fn remove( - vm: &VirtualMachine, - elements: &mut HashMap, - key: u64, - value: &PyObjectRef, - ) -> PyResult { - match elements.remove(&key) { - None => { - let item_str = format!("{:?}", value); - Err(vm.new_key_error(item_str)) - } - Some(_) => Ok(vm.get_none()), - } - } - perform_action_with_hash(vm, elements, item, &remove) -} - struct SetIterable { iterable: PyIterable, } From 87fc4cc470196ae37ea3d6d434d9b1e59ee67646 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 16 May 2019 04:58:53 +0900 Subject: [PATCH 622/884] Add complex.__hash__ --- tests/snippets/builtin_complex.py | 8 ++++++++ vm/src/obj/objcomplex.rs | 9 +++++++++ vm/src/pyhash.rs | 8 ++++++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index 8d883a9dd4..06d46a9800 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -86,6 +86,14 @@ assert bool(complex(0, 1)) is True assert bool(complex(1, 0)) is True +# __hash__ + +assert hash(complex(1)) == hash(float(1)) == hash(int(1)) +assert hash(complex(-1)) == hash(float(-1)) == hash(int(-1)) +assert hash(complex(3.14)) == hash(float(3.14)) +assert hash(complex(-float('inf'))) == hash(-float('inf')) +assert hash(1j) != hash(1) + # numbers.Complex a = complex(3, 4) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 677763213a..a349354a23 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -2,6 +2,7 @@ use num_complex::Complex64; use num_traits::Zero; use crate::function::OptionalArg; +use crate::pyhash; use crate::pyobject::{ IntoPyObject, PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue, }; @@ -248,4 +249,12 @@ impl PyComplex { let value = Complex64::new(real, imag); PyComplex { value }.into_ref_with_type(vm, cls) } + + #[pymethod(name = "__hash__")] + fn hash(&self, _vm: &VirtualMachine) -> pyhash::PyHash { + let re_hash = pyhash::hash_float(self.value.re); + let im_hash = pyhash::hash_float(self.value.im); + + re_hash + im_hash * pyhash::IMAG + } } diff --git a/vm/src/pyhash.rs b/vm/src/pyhash.rs index f87337359e..1f0f3d1218 100644 --- a/vm/src/pyhash.rs +++ b/vm/src/pyhash.rs @@ -7,12 +7,16 @@ use crate::vm::VirtualMachine; pub type PyHash = i64; pub type PyUHash = u64; +/// Prime multiplier used in string and various other hashes. +pub const MULTIPLIER: PyHash = 1000003; // 0xf4243 +/// Numeric hashes are based on reduction modulo the prime 2**_BITS - 1 pub const BITS: usize = 61; pub const MODULUS: PyUHash = (1 << BITS) - 1; -// pub const CUTOFF: usize = 7; - pub const INF: PyHash = 314159; pub const NAN: PyHash = 0; +pub const IMAG: PyHash = MULTIPLIER; + +// pub const CUTOFF: usize = 7; pub fn hash_float(value: f64) -> PyHash { // cpython _Py_HashDouble From 83c5a3d33064c95b592b8f9d59033208cfa99c30 Mon Sep 17 00:00:00 2001 From: ypyf Date: Thu, 16 May 2019 20:35:40 +0800 Subject: [PATCH 623/884] improve error message for type_new --- vm/src/obj/objtype.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index c66e57354b..29c66ffd19 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -248,7 +248,7 @@ pub fn type_new(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let (typ, name, bases, dict) = args.bind(vm)?; type_new_class(vm, typ, name, bases, dict).map(PyRef::into_object) } else { - Err(vm.new_type_error(format!(": type_new: {:?}", args))) + Err(vm.new_type_error("type() takes 1 or 3 arguments".to_string())) } } From d223af645b8bace49842717e23792b2a0fb6e3d2 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 16 May 2019 05:40:35 +0900 Subject: [PATCH 624/884] Add math.frexp --- tests/snippets/math_module.py | 11 ++++++++++- vm/src/obj/objfloat.rs | 16 ++++++++++++++++ vm/src/pyhash.rs | 10 ++-------- vm/src/stdlib/math.rs | 19 +++++++++++++++++++ 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/tests/snippets/math_module.py b/tests/snippets/math_module.py index 1064610f1e..dc7fc07062 100644 --- a/tests/snippets/math_module.py +++ b/tests/snippets/math_module.py @@ -1,5 +1,5 @@ import math -from testutils import assertRaises +from testutils import assertRaises, assert_raises # assert(math.exp(2) == math.exp(2.0)) # assert(math.exp(True) == math.exp(1.0)) @@ -78,3 +78,12 @@ def __floor__(self): math.ceil(object()) with assertRaises(TypeError): math.floor(object()) + +assert str(math.frexp(0.0)) == str((+0.0, 0)) +assert str(math.frexp(-0.0)) == str((-0.0, 0)) +assert math.frexp(1) == (0.5, 1) +assert math.frexp(1.5) == (0.75, 1) + +assert math.frexp(float('inf')) == (float('inf'), 0) +assert str(math.frexp(float('nan'))) == str((float('nan'), 0)) +assert_raises(TypeError, lambda: math.frexp(None)) diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 8d983c3aee..45e8d6624d 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -298,6 +298,11 @@ impl PyFloat { ) } + #[pymethod(name = "__pos__")] + fn pos(&self, _vm: &VirtualMachine) -> f64 { + self.value + } + #[pymethod(name = "__neg__")] fn neg(&self, _vm: &VirtualMachine) -> f64 { -self.value @@ -475,6 +480,17 @@ impl PyFloat { } } +pub fn ufrexp(value: f64) -> (f64, i32) { + if 0.0 == value { + (0.0, 0i32) + } else { + let bits = value.to_bits(); + let exponent: i32 = ((bits >> 52) & 0x7ff) as i32 - 1022; + let mantissa_bits = bits & (0x000fffffffffffff) | (1022 << 52); + (f64::from_bits(mantissa_bits), exponent) + } +} + pub type PyFloatRef = PyRef; // Retrieve inner float value: diff --git a/vm/src/pyhash.rs b/vm/src/pyhash.rs index f87337359e..3d2d0b0d14 100644 --- a/vm/src/pyhash.rs +++ b/vm/src/pyhash.rs @@ -1,5 +1,6 @@ use std::hash::{Hash, Hasher}; +use crate::obj::objfloat; use crate::pyobject::PyObjectRef; use crate::pyobject::PyResult; use crate::vm::VirtualMachine; @@ -28,14 +29,7 @@ pub fn hash_float(value: f64) -> PyHash { }; } - let frexp = if 0.0 == value { - (value, 0i32) - } else { - let bits = value.to_bits(); - let exponent: i32 = ((bits >> 52) & 0x7ff) as i32 - 1022; - let mantissa_bits = bits & (0x000fffffffffffff) | (1022 << 52); - (f64::from_bits(mantissa_bits), exponent) - }; + let frexp = objfloat::ufrexp(value); // process 28 bits at a time; this should work well both for binary // and hexadecimal floating point. diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index 58b3ee953a..c8ee632478 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -209,6 +209,23 @@ fn math_floor(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } +fn math_frexp(value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + objfloat::try_float(&value, vm)?.map_or_else( + || Err(vm.new_type_error(format!("must be real number, not {}", value.class()))), + |value| { + let (m, e) = if value.is_finite() { + let (m, e) = objfloat::ufrexp(value); + (m * value.signum(), e) + } else { + (value, 0) + }; + Ok(vm + .ctx + .new_tuple(vec![vm.ctx.new_float(m), vm.ctx.new_int(e)])) + }, + ) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -256,6 +273,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "gamma" => ctx.new_rustfunc(math_gamma), "lgamma" => ctx.new_rustfunc(math_lgamma), + "frexp" => ctx.new_rustfunc(math_frexp), + // Rounding functions: "trunc" => ctx.new_rustfunc(math_trunc), "ceil" => ctx.new_rustfunc(math_ceil), From ceca2ed98e9755a751fd1a1566f85c935a28128b Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 16 May 2019 06:17:50 +0900 Subject: [PATCH 625/884] Add float.fromhex --- Cargo.lock | 76 ++++++++++++++++++++++++++++++++++++++++ tests/snippets/floats.py | 6 ++++ vm/Cargo.toml | 1 + vm/src/obj/objfloat.rs | 12 +++++++ 4 files changed, 95 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 0303e06e9e..1194fd899b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -323,6 +323,31 @@ dependencies = [ "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hexf" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hexf-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hexf-parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hexf-impl" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "hexf-parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hexf-parse" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "humantime" version = "1.2.0" @@ -553,6 +578,19 @@ name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro-hack" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro-hack-impl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "proc-macro-hack-impl" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "proc-macro2" version = "0.4.27" @@ -575,6 +613,11 @@ name = "quick-error" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "quote" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "quote" version = "0.6.11" @@ -829,6 +872,7 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1014,6 +1058,16 @@ name = "strsim" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "syn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" version = "0.15.29" @@ -1024,6 +1078,14 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "synom" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "synstructure" version = "0.10.1" @@ -1147,6 +1209,11 @@ name = "unicode-width" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode-xid" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unicode-xid" version = "0.1.0" @@ -1372,6 +1439,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e54653cc32d838771a36532647afad59c4bf7155745eeeec406f71fd5d7e7538" +"checksum hexf-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "22eadcfadba76a730b2764eaa577d045f35e0ef5174b9c5b46adf1ee42b85e12" +"checksum hexf-parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "79296f72d53a89096cbc9a88c9547ee8dfe793388674620e2207593d370550ac" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" @@ -1401,9 +1471,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +"checksum proc-macro-hack 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b773f824ff2a495833f85fcdddcf85e096949971decada2e93249fa2c6c3d32f" +"checksum proc-macro-hack-impl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0f674ccc446da486175527473ec8aa064f980b0966bbf767ee743a5dff6244a7" "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" "checksum python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "61e4aac43f833fd637e429506cb2ac9d7df672c4b68f2eaaa163649b7fdc0444" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" "checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" "checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" @@ -1446,7 +1519,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eea1eee654ef80933142157fdad9dd8bc43cf7c74e999e369263496f04ff4da" "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" +"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" "checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" @@ -1464,6 +1539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" "checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index c116b8010c..3d19ac2de3 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -196,3 +196,9 @@ a = .5 assert a == 0.5 +assert float.fromhex('0x0.0p+0') == 0.0 +assert float.fromhex('-0x0.0p+0') == -0.0 +assert float.fromhex('0x1.000000p+0') == 1.0 +assert float.fromhex('-0x1.800000p+0') == -1.5 +assert float.fromhex('inf') == float('inf') +assert math.isnan(float.fromhex('nan')) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 8ac5ebabbe..522bcc50f7 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -28,6 +28,7 @@ unicode-xid = "0.1.0" lazy_static = "^1.0.1" lexical = "2.0.0" itertools = "^0.8.0" +hexf = "0.1.0" # TODO: release and publish to crates.io [dependencies.unicode-casing] diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 8d983c3aee..bc8c014d91 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -3,6 +3,7 @@ use super::objint; use super::objstr; use super::objtype; use crate::function::OptionalArg; +use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; use crate::pyhash; use crate::pyobject::{ @@ -10,6 +11,7 @@ use crate::pyobject::{ TypeProtocol, }; use crate::vm::VirtualMachine; +use hexf; use num_bigint::{BigInt, ToBigInt}; use num_rational::Ratio; use num_traits::{ToPrimitive, Zero}; @@ -473,6 +475,16 @@ impl PyFloat { let denom = vm.ctx.new_int(ratio.denom().clone()); Ok(vm.ctx.new_tuple(vec![numer, denom])) } + + #[pymethod] + fn fromhex(repr: PyStringRef, vm: &VirtualMachine) -> PyResult { + hexf::parse_hexf64(&repr.value, false).or_else(|_| match repr.value.as_ref() { + "nan" => Ok(std::f64::NAN), + "inf" => Ok(std::f64::INFINITY), + "-inf" => Ok(std::f64::NEG_INFINITY), + _ => Err(vm.new_value_error("invalid hexadecimal floating-point string".to_string())), + }) + } } pub type PyFloatRef = PyRef; From ca912a168d692a0ded187cb7e2aad080a9e0f936 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Fri, 17 May 2019 02:35:59 +0900 Subject: [PATCH 626/884] Add float.to_hex --- tests/snippets/floats.py | 12 +++++++++++ vm/src/obj/objfloat.rs | 45 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/tests/snippets/floats.py b/tests/snippets/floats.py index 3d19ac2de3..101d58cc92 100644 --- a/tests/snippets/floats.py +++ b/tests/snippets/floats.py @@ -202,3 +202,15 @@ assert float.fromhex('-0x1.800000p+0') == -1.5 assert float.fromhex('inf') == float('inf') assert math.isnan(float.fromhex('nan')) + +assert (0.0).hex() == '0x0.0p+0' +assert (-0.0).hex() == '-0x0.0p+0' +assert (1.0).hex() == '0x1.0000000000000p+0' +assert (-1.5).hex() == '-0x1.8000000000000p+0' +assert float('inf').hex() == 'inf' +assert float('-inf').hex() == '-inf' +assert float('nan').hex() == 'nan' + +#for _ in range(10000): +# f = random.random() * random.randint(0, 0x10000000000000000) +# assert f == float.fromhex(f.hex()) diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index bc8c014d91..d240961bbc 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -14,7 +14,7 @@ use crate::vm::VirtualMachine; use hexf; use num_bigint::{BigInt, ToBigInt}; use num_rational::Ratio; -use num_traits::{ToPrimitive, Zero}; +use num_traits::{float::Float, ToPrimitive, Zero}; /// Convert a string or number to a floating point number, if possible. #[pyclass(name = "float")] @@ -485,6 +485,49 @@ impl PyFloat { _ => Err(vm.new_value_error("invalid hexadecimal floating-point string".to_string())), }) } + + #[pymethod] + fn hex(&self, _vm: &VirtualMachine) -> String { + to_hex(self.value) + } +} + +fn to_hex(value: f64) -> String { + let (mantissa, exponent, sign) = value.integer_decode(); + let sign_fmt = if sign < 0 { "-" } else { "" }; + match value { + value if value.is_zero() => format!("{}0x0.0p+0", sign_fmt), + value if value.is_infinite() => format!("{}inf", sign_fmt), + value if value.is_nan() => "nan".to_string(), + _ => { + const BITS: i16 = 52; + const FRACT_MASK: u64 = 0xf_ffff_ffff_ffff; + format!( + "{}0x{:x}.{:013x}p{:+}", + sign_fmt, + mantissa >> BITS, + mantissa & FRACT_MASK, + exponent + BITS + ) + } + } +} + +#[test] +fn test_to_hex() { + use rand::Rng; + for _ in 0..20000 { + let bytes = rand::thread_rng().gen::<[u64; 1]>(); + let f = f64::from_bits(bytes[0]); + if !f.is_finite() { + continue; + } + let hex = to_hex(f); + // println!("{} -> {}", f, hex); + let roundtrip = hexf::parse_hexf64(&hex, false).unwrap(); + // println!(" -> {}", roundtrip); + assert!(f == roundtrip, "{} {} {}", f, hex, roundtrip); + } } pub type PyFloatRef = PyRef; From b76da2ad0533bd59bd0a4666ce8e8ec4234b0471 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Fri, 17 May 2019 18:58:59 +0900 Subject: [PATCH 627/884] Avoid timeout in WASM test --- wasm/tests/.travis-runner.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/tests/.travis-runner.sh b/wasm/tests/.travis-runner.sh index add96c6b24..40e1dee504 100755 --- a/wasm/tests/.travis-runner.sh +++ b/wasm/tests/.travis-runner.sh @@ -20,4 +20,4 @@ export PATH=$PATH:$PWD/geckodriver pip install pipenv (cd wasm/tests; pipenv install) -(cd wasm/demo; npm install; npm run ci) +(cd wasm/demo; npm install; npm run build; npm run ci) From cb0367e708d75dab214723fcb60e8e46c810bd83 Mon Sep 17 00:00:00 2001 From: rbrtberglund Date: Sat, 18 May 2019 16:14:43 +0200 Subject: [PATCH 628/884] Add exit/quit builtin function --- vm/src/builtins.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 3250bef808..f87ea8e7ed 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -613,6 +613,24 @@ impl Printer for std::io::StdoutLock<'_> { } } +pub fn builtin_exit(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [], optional = [(object, None)]); + match object { + Some(value) => match i32::try_from_object(&vm, value.clone()) { + Ok(code) => std::process::exit(code), + _ => { + let stdout = io::stdout(); + let mut printer: Box = Box::new(stdout.lock()); + printer.write(vm, value.clone())?; + printer.write(vm, "\n".into_pyobject(vm).unwrap())?; + printer.flush(vm)?; + } + }, + _ => {} + } + std::process::exit(0); +} + pub fn builtin_print(objects: Args, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> { let stdout = io::stdout(); @@ -823,6 +841,8 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "tuple" => ctx.tuple_type(), "type" => ctx.type_type(), "zip" => ctx.zip_type(), + "exit" => ctx.new_rustfunc(builtin_exit), + "quit" => ctx.new_rustfunc(builtin_exit), "__import__" => ctx.new_rustfunc(builtin_import), // Constants From 7f310bd81f4e6a8f951cd269e2602fce965b9d4f Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sat, 18 May 2019 23:53:40 +0300 Subject: [PATCH 629/884] Add `itertools.starmap` --- tests/snippets/stdlib_itertools.py | 10 +++++++ vm/src/stdlib/itertools.rs | 48 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/tests/snippets/stdlib_itertools.py b/tests/snippets/stdlib_itertools.py index 92fbe4a297..410f39732d 100644 --- a/tests/snippets/stdlib_itertools.py +++ b/tests/snippets/stdlib_itertools.py @@ -84,6 +84,16 @@ next(r) +# itertools.starmap tests +starmap = itertools.starmap + +assert list(starmap(pow, zip(range(3), range(1,7)))) == [0**1, 1**2, 2**3] +assert list(starmap(pow, [])) == [] +assert list(starmap(pow, [iter([4,5])])) == [4**5] +with assertRaises(TypeError): + starmap(pow) + + # itertools.takewhile tests from itertools import takewhile as tw diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index e97c53bfab..4e6addd2e5 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -122,6 +122,51 @@ impl PyItertoolsRepeat { } } +#[pyclass(name = "starmap")] +#[derive(Debug)] +struct PyItertoolsStarmap { + function: PyObjectRef, + iter: PyObjectRef, +} + +impl PyValue for PyItertoolsStarmap { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("itertools", "starmap") + } +} + +#[pyimpl] +impl PyItertoolsStarmap { + #[pymethod(name = "__new__")] + fn new( + _cls: PyClassRef, + function: PyObjectRef, + iterable: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + let iter = get_iter(vm, &iterable)?; + + Ok(PyItertoolsStarmap { + function: function, + iter: iter, + } + .into_ref(vm) + .into_object()) + } + + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { + let obj = call_next(vm, &self.iter)?; + + vm.invoke(self.function.clone(), vm.extract_elements(&obj)?) + } + + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf + } +} + #[pyclass] #[derive(Debug)] struct PyItertoolsTakewhile { @@ -190,12 +235,15 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let repeat = ctx.new_class("repeat", ctx.object()); PyItertoolsRepeat::extend_class(ctx, &repeat); + let starmap = PyItertoolsStarmap::make_class(ctx); + let takewhile = ctx.new_class("takewhile", ctx.object()); PyItertoolsTakewhile::extend_class(ctx, &takewhile); py_module!(vm, "itertools", { "count" => count, "repeat" => repeat, + "starmap" => starmap, "takewhile" => takewhile, }) } From 2ca96c8ae1282a6a5a5a36af57d0a280f366e3f9 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 19 May 2019 10:12:33 +1200 Subject: [PATCH 630/884] Add sys.flags by creating a pystruct_sequence macro --- derive/src/lib.rs | 9 +++ derive/src/pyclass.rs | 127 +++++++++++++++++++++++++++++++++++---- tests/snippets/sysmod.py | 5 ++ vm/src/obj/objtuple.rs | 6 ++ vm/src/sysmodule.rs | 29 ++++++++- 5 files changed, 162 insertions(+), 14 deletions(-) diff --git a/derive/src/lib.rs b/derive/src/lib.rs index 72ab44d4c3..c88ca67692 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -1,3 +1,5 @@ +#![recursion_limit = "128"] + extern crate proc_macro; #[macro_use] @@ -38,3 +40,10 @@ pub fn pyimpl(attr: TokenStream, item: TokenStream) -> TokenStream { let item = parse_macro_input!(item as Item); result_to_tokens(pyclass::impl_pyimpl(attr, item)) } + +#[proc_macro_attribute] +pub fn pystruct_sequence(attr: TokenStream, item: TokenStream) -> TokenStream { + let attr = parse_macro_input!(attr as AttributeArgs); + let item = parse_macro_input!(item as Item); + result_to_tokens(pyclass::impl_pystruct_sequence(attr, item)) +} diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index a2cf57b4a3..021ee92926 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -2,7 +2,9 @@ use super::Diagnostic; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use std::collections::HashMap; -use syn::{Attribute, AttributeArgs, Ident, ImplItem, Item, Lit, Meta, MethodSig, NestedMeta}; +use syn::{ + Attribute, AttributeArgs, Ident, ImplItem, Index, Item, Lit, Meta, MethodSig, NestedMeta, +}; enum ClassItem { Method { @@ -266,16 +268,12 @@ pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> Result Result { - let (item, ident, attrs) = match item { - Item::Struct(struc) => (quote!(#struc), struc.ident, struc.attrs), - Item::Enum(enu) => (quote!(#enu), enu.ident, enu.attrs), - other => bail_span!( - other, - "#[pyclass] can only be on a struct or enum declaration" - ), - }; - +fn generate_class_def( + ident: &Ident, + attr_name: &'static str, + attr: AttributeArgs, + attrs: &Vec, +) -> Result { let mut class_name = None; for attr in attr { if let NestedMeta::Meta(meta) = attr { @@ -284,7 +282,11 @@ pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result Result = #doc; @@ -324,3 +325,103 @@ pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result Result { + let (item, ident, attrs) = match item { + Item::Struct(struc) => (quote!(#struc), struc.ident, struc.attrs), + Item::Enum(enu) => (quote!(#enu), enu.ident, enu.attrs), + other => bail_span!( + other, + "#[pyclass] can only be on a struct or enum declaration" + ), + }; + + let class_def = generate_class_def(&ident, "pyclass", attr, &attrs)?; + + let ret = quote! { + #item + #class_def + }; + Ok(ret) +} + +pub fn impl_pystruct_sequence(attr: AttributeArgs, item: Item) -> Result { + if let Item::Struct(struc) = item { + let class_def = generate_class_def(&struc.ident, "pystruct_sequence", attr, &struc.attrs)?; + let mut methods = Vec::new(); + let mut method_references = Vec::new(); + let mut field_names = Vec::new(); + for (i, field) in struc.fields.iter().enumerate() { + let idx = Index::from(i); + if let Some(ref field_name) = field.ident { + let method_name = format!("get_{}", field_name); + let method_ident = Ident::new(&method_name, field_name.span()); + + let method = quote! { + fn #method_ident( + zelf: &::rustpython_vm::obj::objtuple::PyTuple, + _vm: &::rustpython_vm::vm::VirtualMachine) + -> ::rustpython_vm::pyobject::PyObjectRef { + zelf.fast_getitem(#idx) + } + }; + methods.push(method); + let field_name_str = field_name.to_string(); + let method_reference = quote! { + class.set_str_attr( + #field_name_str, + ::rustpython_vm::obj::objproperty::PropertyBuilder::new(ctx) + .add_getter(Self::#method_ident) + .create(), + ); + }; + method_references.push(method_reference); + field_names.push(quote!(#field_name)); + } else { + field_names.push(quote!(#idx)); + } + } + + let ty = &struc.ident; + let ret = quote! { + #struc + #class_def + impl #ty { + fn into_struct_sequence(&self, + vm: &::rustpython_vm::vm::VirtualMachine, + cls: ::rustpython_vm::obj::objtype::PyClassRef, + ) -> ::rustpython_vm::pyobject::PyResult<::rustpython_vm::obj::objtuple::PyTupleRef> { + let tuple: ::rustpython_vm::obj::objtuple::PyTuple = + vec![#(::rustpython_vm::pyobject::IntoPyObject + ::into_pyobject(self.#field_names, vm)? + ),*].into(); + ::rustpython_vm::pyobject::PyValue::into_ref_with_type(tuple, vm, cls) + } + + #(#methods)* + } + impl ::rustpython_vm::pyobject::PyClassImpl for #ty { + fn impl_extend_class( + ctx: &::rustpython_vm::pyobject::PyContext, + class: &::rustpython_vm::obj::objtype::PyClassRef, + ) { + #(#method_references)* + } + + fn make_class( + ctx: &::rustpython_vm::pyobject::PyContext + ) -> ::rustpython_vm::obj::objtype::PyClassRef { + let py_class = ctx.new_class(::NAME, ctx.tuple_type()); + Self::extend_class(ctx, &py_class); + py_class + } + } + }; + Ok(ret) + } else { + bail_span!( + item, + "#[pystruct_sequence] can only be on a struct declaration" + ) + } +} diff --git a/tests/snippets/sysmod.py b/tests/snippets/sysmod.py index 423ecf08d8..7de155e178 100644 --- a/tests/snippets/sysmod.py +++ b/tests/snippets/sysmod.py @@ -7,3 +7,8 @@ assert isinstance(sys.builtin_module_names, tuple) assert 'sys' in sys.builtin_module_names + +assert isinstance(sys.flags, tuple) +assert type(sys.flags).__name__ == "flags" +assert type(sys.flags.optimize) is int +assert sys.flags[3] == sys.flags.optimize diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 795e17d628..e4b828f3cb 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -40,6 +40,12 @@ impl PyValue for PyTuple { } } +impl PyTuple { + pub fn fast_getitem(&self, idx: usize) -> PyObjectRef { + self.elements.borrow()[idx].clone() + } +} + pub type PyTupleRef = PyRef; impl PyTupleRef { diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 8c6bafe74f..6400c77f3d 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -4,7 +4,7 @@ use std::{env, mem}; use crate::frame::FrameRef; use crate::function::{OptionalArg, PyFuncArgs}; use crate::obj::objstr::PyStringRef; -use crate::pyobject::{IntoPyObject, ItemProtocol, PyContext, PyObjectRef, PyResult}; +use crate::pyobject::{IntoPyObject, ItemProtocol, PyClassImpl, PyContext, PyObjectRef, PyResult}; use crate::vm::VirtualMachine; /* @@ -27,6 +27,26 @@ fn getframe(offset: OptionalArg, vm: &VirtualMachine) -> PyResult PyResult { arg_check!(vm, args, required = [(object, None)]); let size = Rc::strong_count(&object); @@ -48,6 +68,12 @@ fn sys_intern(value: PyStringRef, _vm: &VirtualMachine) -> PyStringRef { pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectRef) { let ctx = &vm.ctx; + let flags_type = SysFlags::make_class(ctx); + // TODO parse command line arguments and environment variables to populate SysFlags + let flags = SysFlags::default() + .into_struct_sequence(vm, flags_type) + .unwrap(); + let path_list = match env::var_os("PYTHONPATH") { Some(paths) => env::split_paths(&paths) .map(|path| { @@ -152,6 +178,7 @@ settrace() -- set the global debug tracing function extend_module!(vm, module, { "argv" => argv(ctx), "builtin_module_names" => ctx.new_tuple(module_names.iter().map(|v| v.into_pyobject(vm).unwrap()).collect()), + "flags" => flags, "getrefcount" => ctx.new_rustfunc(sys_getrefcount), "getsizeof" => ctx.new_rustfunc(sys_getsizeof), "intern" => ctx.new_rustfunc(sys_intern), From 8b657ab81e0dbefc5a87a26669d1842d9b5908f9 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 19 May 2019 10:32:07 +1200 Subject: [PATCH 631/884] Add documentation for sys.flags --- derive/src/pyclass.rs | 1 + vm/src/sysmodule.rs | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 021ee92926..21de60d1ae 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -367,6 +367,7 @@ pub fn impl_pystruct_sequence(attr: AttributeArgs, item: Item) -> Result, vm: &VirtualMachine) -> PyResult Date: Sun, 19 May 2019 00:42:55 +0200 Subject: [PATCH 632/884] applied suggestions to improve exit function implementation --- vm/src/builtins.rs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index f87ea8e7ed..a1b52a17bc 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -613,18 +613,11 @@ impl Printer for std::io::StdoutLock<'_> { } } -pub fn builtin_exit(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [], optional = [(object, None)]); +pub fn builtin_exit(object: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { match object { - Some(value) => match i32::try_from_object(&vm, value.clone()) { + OptionalArg::Present(object) => match i32::try_from_object(&vm, object.clone()) { Ok(code) => std::process::exit(code), - _ => { - let stdout = io::stdout(); - let mut printer: Box = Box::new(stdout.lock()); - printer.write(vm, value.clone())?; - printer.write(vm, "\n".into_pyobject(vm).unwrap())?; - printer.flush(vm)?; - } + _ => println!("{}", vm.to_str(&object)?.as_str()), }, _ => {} } From b9814ce3c71aae23c2605cae75a47070a5cda79f Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Sun, 19 May 2019 12:00:36 +0900 Subject: [PATCH 633/884] Update rustyline to 4.1.0 to fix nightly build --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1194fd899b..f81dfa1429 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -473,7 +473,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "nix" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -838,7 +838,7 @@ dependencies = [ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", - "rustyline 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -913,14 +913,14 @@ dependencies = [ [[package]] name = "rustyline" -version = "2.1.0" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1457,7 +1457,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30" -"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17" +"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" @@ -1500,7 +1500,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6de8ecd7fad7731f306f69b6e10ec5a3178c61e464dcc06979427aa4cc891145" -"checksum rustyline 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6010155119d53aac4f5b987cb8f6ea913d0d64d9b237da36f8f96a90cb3f5385" +"checksum rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0f47ea1ceb347d2deae482d655dc8eef4bd82363d3329baffa3818bd76fea48b" "checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" diff --git a/Cargo.toml b/Cargo.toml index ef2838a1c4..71db1d8396 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ env_logger="0.5.10" clap = "2.31.2" rustpython_parser = {path = "parser"} rustpython_vm = {path = "vm"} -rustyline = "2.1.0" +rustyline = "4.1.0" xdg = "2.2.0" [dev-dependencies.cpython] From 60c9d5004ab128c243e11db5c4203350e739760e Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 19 May 2019 15:01:22 +1200 Subject: [PATCH 634/884] Add SimpleNamespace and sys.implementation --- Lib/types.py | 2 +- tests/snippets/stdlib_types.py | 10 ++++++++++ tests/snippets/sysmod.py | 3 +++ vm/src/macros.rs | 13 +++++++++++++ vm/src/obj/mod.rs | 1 + vm/src/obj/objnamespace.rs | 32 ++++++++++++++++++++++++++++++++ vm/src/pyobject.rs | 13 +++++++++++++ vm/src/sysmodule.rs | 7 +++++++ 8 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/snippets/stdlib_types.py create mode 100644 vm/src/obj/objnamespace.rs diff --git a/Lib/types.py b/Lib/types.py index 4ef4619618..a7034901c6 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -13,7 +13,7 @@ def _f(): pass LambdaType = type(lambda: None) # Same as FunctionType CodeType = type(_f.__code__) MappingProxyType = type(type.__dict__) -# SimpleNamespace = type(sys.implementation) +SimpleNamespace = type(sys.implementation) def _g(): yield 1 diff --git a/tests/snippets/stdlib_types.py b/tests/snippets/stdlib_types.py new file mode 100644 index 0000000000..b8011bf40b --- /dev/null +++ b/tests/snippets/stdlib_types.py @@ -0,0 +1,10 @@ +import types + +from testutils import assertRaises + +ns = types.SimpleNamespace(a=2, b='Rust') + +assert ns.a == 2 +assert ns.b == "Rust" +with assertRaises(AttributeError): + _ = ns.c diff --git a/tests/snippets/sysmod.py b/tests/snippets/sysmod.py index 423ecf08d8..409d961edb 100644 --- a/tests/snippets/sysmod.py +++ b/tests/snippets/sysmod.py @@ -7,3 +7,6 @@ assert isinstance(sys.builtin_module_names, tuple) assert 'sys' in sys.builtin_module_names + +assert isinstance(sys.implementation.name, str) +assert isinstance(sys.implementation.cache_tag, str) diff --git a/vm/src/macros.rs b/vm/src/macros.rs index 3336739852..c3267ff157 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -157,6 +157,19 @@ macro_rules! extend_class { } } +#[macro_export] +macro_rules! py_namespace { + ( $vm:expr, { $($name:expr => $value:expr),* $(,)* }) => { + { + let namespace = $vm.ctx.new_namespace(); + $( + $vm.set_attr(&namespace, $name, $value).unwrap(); + )* + namespace + } + } +} + /// Macro to match on the built-in class of a Python object. /// /// Like `match`, `match_class!` must be exhaustive, so a default arm with diff --git a/vm/src/obj/mod.rs b/vm/src/obj/mod.rs index c9401d8c44..ad7af74d5d 100644 --- a/vm/src/obj/mod.rs +++ b/vm/src/obj/mod.rs @@ -23,6 +23,7 @@ pub mod objmap; pub mod objmappingproxy; pub mod objmemory; pub mod objmodule; +pub mod objnamespace; pub mod objnone; pub mod objobject; pub mod objproperty; diff --git a/vm/src/obj/objnamespace.rs b/vm/src/obj/objnamespace.rs new file mode 100644 index 0000000000..e79b561c01 --- /dev/null +++ b/vm/src/obj/objnamespace.rs @@ -0,0 +1,32 @@ +use crate::function::KwArgs; +use crate::obj::objtype::PyClassRef; +use crate::pyobject::{PyClassImpl, PyContext, PyRef, PyResult, PyValue}; +use crate::vm::VirtualMachine; + +/// A simple attribute-based namespace. +/// +/// SimpleNamespace(**kwargs) +#[pyclass(name = "SimpleNamespace")] +#[derive(Debug)] +pub struct PyNamespace; + +impl PyValue for PyNamespace { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.ctx.namespace_type() + } +} + +#[pyimpl] +impl PyNamespace { + #[pymethod(name = "__init__")] + fn init(zelf: PyRef, kwargs: KwArgs, vm: &VirtualMachine) -> PyResult<()> { + for (name, value) in kwargs.into_iter() { + vm.set_attr(zelf.as_object(), name, value)?; + } + Ok(()) + } +} + +pub fn init(context: &PyContext) { + PyNamespace::extend_class(context, &context.namespace_type); +} diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 722588b3fd..afaad09b69 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -40,6 +40,7 @@ use crate::obj::objmap; use crate::obj::objmappingproxy; use crate::obj::objmemory; use crate::obj::objmodule::{self, PyModule}; +use crate::obj::objnamespace::{self, PyNamespace}; use crate::obj::objnone::{self, PyNone, PyNoneRef}; use crate::obj::objobject; use crate::obj::objproperty; @@ -158,6 +159,7 @@ pub struct PyContext { pub property_type: PyClassRef, pub readonly_property_type: PyClassRef, pub module_type: PyClassRef, + pub namespace_type: PyClassRef, pub bound_method_type: PyClassRef, pub weakref_type: PyClassRef, pub weakproxy_type: PyClassRef, @@ -250,6 +252,7 @@ impl PyContext { let dict_type = create_type("dict", &type_type, &object_type); let module_type = create_type("module", &type_type, &object_type); + let namespace_type = create_type("SimpleNamespace", &type_type, &object_type); let classmethod_type = create_type("classmethod", &type_type, &object_type); let staticmethod_type = create_type("staticmethod", &type_type, &object_type); let function_type = create_type("function", &type_type, &object_type); @@ -366,6 +369,7 @@ impl PyContext { readonly_property_type, generator_type, module_type, + namespace_type, bound_method_type, weakref_type, weakproxy_type, @@ -407,6 +411,7 @@ impl PyContext { objweakproxy::init(&context); objnone::init(&context); objmodule::init(&context); + objnamespace::init(&context); objmappingproxy::init(&context); exceptions::init(&context); context @@ -464,6 +469,10 @@ impl PyContext { self.module_type.clone() } + pub fn namespace_type(&self) -> PyClassRef { + self.namespace_type.clone() + } + pub fn set_type(&self) -> PyClassRef { self.set_type.clone() } @@ -658,6 +667,10 @@ impl PyContext { ) } + pub fn new_namespace(&self) -> PyObjectRef { + PyObject::new(PyNamespace, self.namespace_type(), Some(self.new_dict())) + } + pub fn new_rustfunc(&self, f: F) -> PyObjectRef where F: IntoPyNativeFunc, diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 8c6bafe74f..aefdb117b2 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -48,6 +48,12 @@ fn sys_intern(value: PyStringRef, _vm: &VirtualMachine) -> PyStringRef { pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectRef) { let ctx = &vm.ctx; + // TODO Add crate version to this namespace + let implementation = py_namespace!(vm, { + "name" => ctx.new_str("RustPython".to_string()), + "cache_tag" => ctx.new_str("rustpython-01".to_string()), + }); + let path_list = match env::var_os("PYTHONPATH") { Some(paths) => env::split_paths(&paths) .map(|path| { @@ -154,6 +160,7 @@ settrace() -- set the global debug tracing function "builtin_module_names" => ctx.new_tuple(module_names.iter().map(|v| v.into_pyobject(vm).unwrap()).collect()), "getrefcount" => ctx.new_rustfunc(sys_getrefcount), "getsizeof" => ctx.new_rustfunc(sys_getsizeof), + "implementation" => implementation, "intern" => ctx.new_rustfunc(sys_intern), "maxsize" => ctx.new_int(std::usize::MAX), "path" => path, From 52039845dd59019372c3ab4952df7950eef84ae2 Mon Sep 17 00:00:00 2001 From: ben Date: Sun, 19 May 2019 15:23:51 +1200 Subject: [PATCH 635/884] Use closure for generated properties in struct sequences. --- derive/src/pyclass.rs | 134 +++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 73 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 21de60d1ae..d216b9357e 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -346,83 +346,71 @@ pub fn impl_pyclass(attr: AttributeArgs, item: Item) -> Result Result { - if let Item::Struct(struc) = item { - let class_def = generate_class_def(&struc.ident, "pystruct_sequence", attr, &struc.attrs)?; - let mut methods = Vec::new(); - let mut method_references = Vec::new(); - let mut field_names = Vec::new(); - for (i, field) in struc.fields.iter().enumerate() { - let idx = Index::from(i); - if let Some(ref field_name) = field.ident { - let method_name = format!("get_{}", field_name); - let method_ident = Ident::new(&method_name, field_name.span()); - - let method = quote! { - fn #method_ident( - zelf: &::rustpython_vm::obj::objtuple::PyTuple, - _vm: &::rustpython_vm::vm::VirtualMachine) - -> ::rustpython_vm::pyobject::PyObjectRef { - zelf.fast_getitem(#idx) - } - }; - methods.push(method); - let field_name_str = field_name.to_string(); - // TODO add doc to the generated property - let method_reference = quote! { - class.set_str_attr( - #field_name_str, - ::rustpython_vm::obj::objproperty::PropertyBuilder::new(ctx) - .add_getter(Self::#method_ident) - .create(), - ); - }; - method_references.push(method_reference); - field_names.push(quote!(#field_name)); - } else { - field_names.push(quote!(#idx)); - } - } - - let ty = &struc.ident; - let ret = quote! { - #struc - #class_def - impl #ty { - fn into_struct_sequence(&self, - vm: &::rustpython_vm::vm::VirtualMachine, - cls: ::rustpython_vm::obj::objtype::PyClassRef, - ) -> ::rustpython_vm::pyobject::PyResult<::rustpython_vm::obj::objtuple::PyTupleRef> { - let tuple: ::rustpython_vm::obj::objtuple::PyTuple = - vec![#(::rustpython_vm::pyobject::IntoPyObject - ::into_pyobject(self.#field_names, vm)? - ),*].into(); - ::rustpython_vm::pyobject::PyValue::into_ref_with_type(tuple, vm, cls) - } - - #(#methods)* - } - impl ::rustpython_vm::pyobject::PyClassImpl for #ty { - fn impl_extend_class( - ctx: &::rustpython_vm::pyobject::PyContext, - class: &::rustpython_vm::obj::objtype::PyClassRef, - ) { - #(#method_references)* - } - - fn make_class( - ctx: &::rustpython_vm::pyobject::PyContext - ) -> ::rustpython_vm::obj::objtype::PyClassRef { - let py_class = ctx.new_class(::NAME, ctx.tuple_type()); - Self::extend_class(ctx, &py_class); - py_class - } - } - }; - Ok(ret) + let struc = if let Item::Struct(struc) = item { + struc } else { bail_span!( item, "#[pystruct_sequence] can only be on a struct declaration" ) + }; + let class_def = generate_class_def(&struc.ident, "pystruct_sequence", attr, &struc.attrs)?; + let mut properties = Vec::new(); + let mut field_names = Vec::new(); + for (i, field) in struc.fields.iter().enumerate() { + let idx = Index::from(i); + if let Some(ref field_name) = field.ident { + let field_name_str = field_name.to_string(); + // TODO add doc to the generated property + let property = quote! { + class.set_str_attr( + #field_name_str, + ::rustpython_vm::obj::objproperty::PropertyBuilder::new(ctx) + .add_getter(|zelf: &::rustpython_vm::obj::objtuple::PyTuple, + _vm: &::rustpython_vm::vm::VirtualMachine| + zelf.fast_getitem(#idx)) + .create(), + ); + }; + properties.push(property); + field_names.push(quote!(#field_name)); + } else { + field_names.push(quote!(#idx)); + } } + + let ty = &struc.ident; + let ret = quote! { + #struc + #class_def + impl #ty { + fn into_struct_sequence(&self, + vm: &::rustpython_vm::vm::VirtualMachine, + cls: ::rustpython_vm::obj::objtype::PyClassRef, + ) -> ::rustpython_vm::pyobject::PyResult<::rustpython_vm::obj::objtuple::PyTupleRef> { + let tuple: ::rustpython_vm::obj::objtuple::PyTuple = + vec![#(::rustpython_vm::pyobject::IntoPyObject + ::into_pyobject(self.#field_names, vm)? + ),*].into(); + ::rustpython_vm::pyobject::PyValue::into_ref_with_type(tuple, vm, cls) + } + } + impl ::rustpython_vm::pyobject::PyClassImpl for #ty { + fn impl_extend_class( + ctx: &::rustpython_vm::pyobject::PyContext, + class: &::rustpython_vm::obj::objtype::PyClassRef, + ) { + #(#properties)* + } + + fn make_class( + ctx: &::rustpython_vm::pyobject::PyContext + ) -> ::rustpython_vm::obj::objtype::PyClassRef { + let py_class = ctx.new_class(::NAME, ctx.tuple_type()); + Self::extend_class(ctx, &py_class); + py_class + } + } + }; + Ok(ret) } From 964256439bf2c49e3795766fff81535e500f3d84 Mon Sep 17 00:00:00 2001 From: Ben Lewis Date: Sun, 19 May 2019 15:25:52 +1200 Subject: [PATCH 636/884] Update vm/src/obj/objnamespace.rs Co-Authored-By: coolreader18 <33094578+coolreader18@users.noreply.github.com> --- vm/src/obj/objnamespace.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objnamespace.rs b/vm/src/obj/objnamespace.rs index e79b561c01..f8bfc93696 100644 --- a/vm/src/obj/objnamespace.rs +++ b/vm/src/obj/objnamespace.rs @@ -19,7 +19,7 @@ impl PyValue for PyNamespace { #[pyimpl] impl PyNamespace { #[pymethod(name = "__init__")] - fn init(zelf: PyRef, kwargs: KwArgs, vm: &VirtualMachine) -> PyResult<()> { + fn init(zelf: PyRef, kwargs: KwArgs, vm: &VirtualMachine) -> PyResult<()> { for (name, value) in kwargs.into_iter() { vm.set_attr(zelf.as_object(), name, value)?; } From 36412c57a4ad17d17011d634f2b6f745533280e3 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Wed, 15 May 2019 00:29:23 +0900 Subject: [PATCH 637/884] Implement pwd module --- Cargo.lock | 11 ++++++ vm/Cargo.toml | 1 + vm/src/stdlib/mod.rs | 2 ++ vm/src/stdlib/pwd.rs | 85 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+) create mode 100644 vm/src/stdlib/pwd.rs diff --git a/Cargo.lock b/Cargo.lock index f81dfa1429..66b1f01aff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,6 +599,15 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "pwd" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "python3-sys" version = "0.2.1" @@ -882,6 +891,7 @@ dependencies = [ "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", "num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "pwd 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1474,6 +1484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum proc-macro-hack 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b773f824ff2a495833f85fcdddcf85e096949971decada2e93249fa2c6c3d32f" "checksum proc-macro-hack-impl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0f674ccc446da486175527473ec8aa064f980b0966bbf767ee743a5dff6244a7" "checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" +"checksum pwd 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5dd32d8bece608e144ca20251e714ed107cdecdabb20c2d383cfc687825106a5" "checksum python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "61e4aac43f833fd637e429506cb2ac9d7df672c4b68f2eaaa163649b7fdc0444" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 522bcc50f7..cdd7bbb30e 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -19,6 +19,7 @@ serde = "1.0.66" serde_derive = "1.0.66" serde_json = "1.0.26" byteorder = "1.2.6" +pwd = "1" regex = "1" rustc_version_runtime = "0.1.*" statrs = "0.10.0" diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 149f37d9e9..9abc8b26cc 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -6,6 +6,7 @@ pub(crate) mod json; mod keyword; mod math; mod platform; +mod pwd; mod pystruct; mod random; mod re; @@ -41,6 +42,7 @@ pub fn get_module_inits() -> HashMap { modules.insert("keyword".to_string(), Box::new(keyword::make_module)); modules.insert("math".to_string(), Box::new(math::make_module)); modules.insert("platform".to_string(), Box::new(platform::make_module)); + modules.insert("pwd".to_string(), Box::new(pwd::make_module)); modules.insert("re".to_string(), Box::new(re::make_module)); modules.insert("random".to_string(), Box::new(random::make_module)); modules.insert("string".to_string(), Box::new(string::make_module)); diff --git a/vm/src/stdlib/pwd.rs b/vm/src/stdlib/pwd.rs new file mode 100644 index 0000000000..1f50925409 --- /dev/null +++ b/vm/src/stdlib/pwd.rs @@ -0,0 +1,85 @@ +use pwd::Passwd; + +use crate::obj::objstr::PyStringRef; +use crate::obj::objtype::PyClassRef; +use crate::pyobject::{PyObjectRef, PyRef, PyResult, PyValue}; +use crate::vm::VirtualMachine; + +impl PyValue for Passwd { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("pwd", "struct_passwd") + } +} + +type PasswdRef = PyRef; + +impl PasswdRef { + fn pw_name(self, _vm: &VirtualMachine) -> String { + self.name.clone() + } + + fn pw_passwd(self, _vm: &VirtualMachine) -> Option { + self.passwd.clone() + } + + fn pw_uid(self, _vm: &VirtualMachine) -> u32 { + self.uid + } + + fn pw_gid(self, _vm: &VirtualMachine) -> u32 { + self.gid + } + + fn pw_gecos(self, _vm: &VirtualMachine) -> Option { + self.gecos.clone() + } + + fn pw_dir(self, _vm: &VirtualMachine) -> String { + self.dir.clone() + } + + fn pw_shell(self, _vm: &VirtualMachine) -> String { + self.shell.clone() + } +} + +fn pwd_getpwnam(name: PyStringRef, vm: &VirtualMachine) -> PyResult { + match Passwd::from_name(&name.value) { + Ok(Some(passwd)) => Ok(passwd), + _ => { + let name_repr = vm.to_repr(name.as_object())?; + let message = format!("getpwnam(): name not found: {}", name_repr); + Err(vm.new_key_error(message)) + } + } +} + +fn pwd_getpwuid(uid: u32, vm: &VirtualMachine) -> PyResult { + match Passwd::from_uid(uid) { + Some(passwd) => Ok(passwd), + _ => { + let message = format!("getpwuid(): uid not found: {}", uid); + Err(vm.new_key_error(message)) + } + } +} + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + let passwd_type = py_class!(ctx, "struct_passwd", ctx.object(), { + "pw_name" => ctx.new_property(PasswdRef::pw_name), + "pw_passwd" => ctx.new_property(PasswdRef::pw_passwd), + "pw_uid" => ctx.new_property(PasswdRef::pw_uid), + "pw_gid" => ctx.new_property(PasswdRef::pw_gid), + "pw_gecos" => ctx.new_property(PasswdRef::pw_gecos), + "pw_dir" => ctx.new_property(PasswdRef::pw_dir), + "pw_shell" => ctx.new_property(PasswdRef::pw_shell), + }); + + py_module!(vm, "pwd", { + "struct_passwd" => passwd_type, + "getpwnam" => ctx.new_rustfunc(pwd_getpwnam), + "getpwuid" => ctx.new_rustfunc(pwd_getpwuid), + }) +} From 347d1fd2405c2f8f7f7de04d6c7bed6d3685d850 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Sun, 19 May 2019 13:16:37 +0900 Subject: [PATCH 638/884] Enable pwd module only on Unix --- vm/Cargo.toml | 4 +++- vm/src/stdlib/mod.rs | 10 ++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index cdd7bbb30e..0d80923fd1 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -19,7 +19,6 @@ serde = "1.0.66" serde_derive = "1.0.66" serde_json = "1.0.26" byteorder = "1.2.6" -pwd = "1" regex = "1" rustc_version_runtime = "0.1.*" statrs = "0.10.0" @@ -35,3 +34,6 @@ hexf = "0.1.0" [dependencies.unicode-casing] git = "https://github.com/OddCoincidence/unicode-casing" rev = "90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb" + +[target.'cfg(unix)'.dependencies] +pwd = "1" diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 9abc8b26cc..2b17586c69 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -6,7 +6,6 @@ pub(crate) mod json; mod keyword; mod math; mod platform; -mod pwd; mod pystruct; mod random; mod re; @@ -24,6 +23,8 @@ use crate::vm::VirtualMachine; pub mod io; #[cfg(not(target_arch = "wasm32"))] mod os; +#[cfg(unix)] +mod pwd; use crate::pyobject::PyObjectRef; @@ -42,7 +43,6 @@ pub fn get_module_inits() -> HashMap { modules.insert("keyword".to_string(), Box::new(keyword::make_module)); modules.insert("math".to_string(), Box::new(math::make_module)); modules.insert("platform".to_string(), Box::new(platform::make_module)); - modules.insert("pwd".to_string(), Box::new(pwd::make_module)); modules.insert("re".to_string(), Box::new(re::make_module)); modules.insert("random".to_string(), Box::new(random::make_module)); modules.insert("string".to_string(), Box::new(string::make_module)); @@ -60,5 +60,11 @@ pub fn get_module_inits() -> HashMap { modules.insert("socket".to_string(), Box::new(socket::make_module)); } + // Unix-only + #[cfg(unix)] + { + modules.insert("pwd".to_string(), Box::new(pwd::make_module)); + } + modules } From 1e7fa52e0ffce47db5a463e1704e91d5725f120f Mon Sep 17 00:00:00 2001 From: Adrian Wielgosik Date: Tue, 14 May 2019 19:56:54 +0200 Subject: [PATCH 639/884] Fix no-arg dir(), convert it to new args style --- tests/snippets/builtin_dir.py | 12 ++++++++++++ vm/src/builtins.rs | 17 ++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/tests/snippets/builtin_dir.py b/tests/snippets/builtin_dir.py index b5fbf8d0b1..3e808597c1 100644 --- a/tests/snippets/builtin_dir.py +++ b/tests/snippets/builtin_dir.py @@ -1,3 +1,5 @@ +assert isinstance(dir(), list) +assert '__builtins__' in dir() class A: def test(): @@ -21,6 +23,16 @@ def __dir__(self): # This calls type.__dir__ so isn't changed (but inheritance works)! assert 'test' in dir(A) +# eval() takes any mapping-like type, so dir() must support them +# TODO: eval() should take any mapping as locals, not just dict-derived types +class A(dict): + def __getitem__(self, x): + return dir + def keys(self): + yield 6 + yield 5 +assert eval("dir()", {}, A()) == [5, 6] + import socket assert "AF_INET" in dir(socket) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 3250bef808..7206fe579e 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -111,15 +111,13 @@ fn builtin_delattr(obj: PyObjectRef, attr: PyStringRef, vm: &VirtualMachine) -> vm.del_attr(&obj, attr.into_object()) } -fn builtin_dir(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - if args.args.is_empty() { - Ok(vm.get_locals().into_object()) - } else { - let obj = args.args.into_iter().next().unwrap(); - let seq = vm.call_method(&obj, "__dir__", vec![])?; - let sorted = builtin_sorted(vm, PyFuncArgs::new(vec![seq], vec![]))?; - Ok(sorted) - } +fn builtin_dir(obj: OptionalArg, vm: &VirtualMachine) -> PyResult { + let seq = match obj { + OptionalArg::Present(obj) => vm.call_method(&obj, "__dir__", vec![])?, + OptionalArg::Missing => vm.call_method(&vm.get_locals().into_object(), "keys", vec![])?, + }; + let sorted = builtin_sorted(vm, PyFuncArgs::new(vec![seq], vec![]))?; + Ok(sorted) } fn builtin_divmod(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -136,6 +134,7 @@ fn builtin_divmod(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { /// Implements `eval`. /// See also: https://docs.python.org/3/library/functions.html#eval fn builtin_eval(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + // TODO: support any mapping for `locals` arg_check!( vm, args, From 8f8194ae1391089fe66722a186d66708b398aa65 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 19 May 2019 14:05:09 -0500 Subject: [PATCH 640/884] Fix os.rs --- vm/src/stdlib/os.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 45e8620490..f5a6ced96d 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -14,7 +14,7 @@ use crate::obj::objint::{self, PyInt, PyIntRef}; use crate::obj::objiter; use crate::obj::objset::PySet; use crate::obj::objstr::{self, PyString, PyStringRef}; -use crate::obj::objtype::PyClassRef; +use crate::obj::objtype::{self, PyClassRef}; use crate::pyobject::{ ItemProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryIntoRef, TypeProtocol, }; From 9bf386ea876227f434495a470fc70d4ef3d5a99d Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sat, 18 May 2019 23:31:47 +0300 Subject: [PATCH 641/884] Add `itertools.chain` --- tests/snippets/stdlib_itertools.py | 18 +++++++++ vm/src/stdlib/itertools.rs | 63 +++++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_itertools.py b/tests/snippets/stdlib_itertools.py index 410f39732d..c8921cb912 100644 --- a/tests/snippets/stdlib_itertools.py +++ b/tests/snippets/stdlib_itertools.py @@ -3,6 +3,24 @@ from testutils import assertRaises +# itertools.chain tests +chain = itertools.chain + +# empty +assert list(chain()) == [] +assert list(chain([], "", b"", ())) == [] + +assert list(chain([1, 2, 3, 4])) == [1, 2, 3, 4] +assert list(chain("ab", "cd", (), 'e')) == ['a', 'b', 'c', 'd', 'e'] +with assertRaises(TypeError): + list(chain(1)) + +x = chain("ab", 1) +assert next(x) == 'a' +assert next(x) == 'b' +with assertRaises(TypeError): + next(x) + # itertools.count tests # default arguments diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index 4e6addd2e5..7380a76774 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -4,14 +4,72 @@ use std::ops::{AddAssign, SubAssign}; use num_bigint::BigInt; -use crate::function::OptionalArg; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::obj::objbool; use crate::obj::objint::{PyInt, PyIntRef}; use crate::obj::objiter::{call_next, get_iter, new_stop_iteration}; +use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; +#[pyclass(name = "chain")] +#[derive(Debug)] +struct PyItertoolsChain { + iterables: Vec, + cur: RefCell<(usize, Option)>, +} + +impl PyValue for PyItertoolsChain { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("itertools", "chain") + } +} + +#[pyimpl] +impl PyItertoolsChain { + #[pymethod(name = "__new__")] + fn new(_cls: PyClassRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { + Ok(PyItertoolsChain { + iterables: args.args, + cur: RefCell::new((0, None)), + } + .into_ref(vm) + .into_object()) + } + + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { + let (ref mut cur_idx, ref mut cur_iter) = *self.cur.borrow_mut(); + while *cur_idx < self.iterables.len() { + if cur_iter.is_none() { + *cur_iter = Some(get_iter(vm, &self.iterables[*cur_idx])?); + } + + // can't be directly inside the 'match' clause, otherwise the borrows collide. + let obj = call_next(vm, cur_iter.as_ref().unwrap()); + match obj { + Ok(ok) => return Ok(ok), + Err(err) => { + if objtype::isinstance(&err, &vm.ctx.exceptions.stop_iteration) { + *cur_idx += 1; + *cur_iter = None; + } else { + return Err(err); + } + } + } + } + + Err(new_stop_iteration(vm)) + } + + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf + } +} + #[pyclass] #[derive(Debug)] struct PyItertoolsCount { @@ -229,6 +287,8 @@ impl PyItertoolsTakewhile { pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; + let chain = PyItertoolsChain::make_class(ctx); + let count = ctx.new_class("count", ctx.object()); PyItertoolsCount::extend_class(ctx, &count); @@ -241,6 +301,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { PyItertoolsTakewhile::extend_class(ctx, &takewhile); py_module!(vm, "itertools", { + "chain" => chain, "count" => count, "repeat" => repeat, "starmap" => starmap, From 9ad527c800a7ed2c4c94a0b9eba754c4af429f37 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sun, 19 May 2019 22:40:10 +0300 Subject: [PATCH 642/884] Remove tuple handling from `get_mut_elements` This function is unused anyway; But even was it used, it makes no sense to return mutable references to tuple items, when the tuple holding them is immutable. --- vm/src/obj/objsequence.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index af20343708..9b38ccbe80 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -368,9 +368,6 @@ pub fn get_mut_elements<'a>(obj: &'a PyObjectRef) -> impl DerefMut() { return list.elements.borrow_mut(); } - if let Some(tuple) = obj.payload::() { - return tuple.elements.borrow_mut(); - } panic!("Cannot extract elements from non-sequence"); } From 802f07980e3585fd403b1e0ee5ad0b6a3c03968a Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sun, 19 May 2019 22:42:05 +0300 Subject: [PATCH 643/884] Remove tuple handling from `get_elements_cell` Like my HEAD, this shouldn't be allowed for a tuple (and the only call-site of this function doesn't run on tuples anyway). --- vm/src/obj/objsequence.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index 9b38ccbe80..cee66752cb 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -348,9 +348,6 @@ pub fn get_elements_cell<'a>(obj: &'a PyObjectRef) -> &'a RefCell() { return &list.elements; } - if let Some(tuple) = obj.payload::() { - return &tuple.elements; - } panic!("Cannot extract elements from non-sequence"); } From 84684e5f7deeb85607f0cc803887e0fb628d0493 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Sun, 19 May 2019 22:51:05 +0300 Subject: [PATCH 644/884] Don't use `RefCell` in tuple object --- vm/src/exceptions.rs | 4 +-- vm/src/function.rs | 2 +- vm/src/import.rs | 2 +- vm/src/obj/objbyteinner.rs | 2 +- vm/src/obj/objlist.rs | 18 +++++----- vm/src/obj/objsequence.rs | 8 +++-- vm/src/obj/objtuple.rs | 68 +++++++++++++++----------------------- vm/src/stdlib/json.rs | 9 ++--- vm/src/stdlib/socket.rs | 6 ++-- vm/src/vm.rs | 12 +++---- wasm/lib/src/convert.rs | 4 +-- 11 files changed, 63 insertions(+), 72 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index a8b753e1a3..e02cd2894f 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -44,11 +44,11 @@ pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) { if let Ok(tb) = vm.get_attribute(exc.clone(), "__traceback__") { println!("Traceback (most recent call last):"); if objtype::isinstance(&tb, &vm.ctx.list_type()) { - let mut elements = objsequence::get_elements(&tb).to_vec(); + let mut elements = objsequence::get_elements_list(&tb).to_vec(); elements.reverse(); for element in elements.iter() { if objtype::isinstance(&element, &vm.ctx.tuple_type()) { - let element = objsequence::get_elements(&element); + let element = objsequence::get_elements_tuple(&element); let filename = if let Ok(x) = vm.to_str(&element[0]) { x.value.clone() } else { diff --git a/vm/src/function.rs b/vm/src/function.rs index 170c395f6e..e7dbe59e40 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -538,7 +538,7 @@ pub fn single_or_tuple_any) -> PyResult>( match_class!(obj, obj @ T => predicate(obj), tuple @ PyTuple => { - for obj in tuple.elements.borrow().iter() { + for obj in tuple.elements.iter() { let inner_val = PyRef::::try_from_object(vm, obj.clone())?; if predicate(inner_val)? { return Ok(true); diff --git a/vm/src/import.rs b/vm/src/import.rs index 8445e65f9f..16385e3d32 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -67,7 +67,7 @@ pub fn import_file( fn find_source(vm: &VirtualMachine, current_path: PathBuf, name: &str) -> Result { let sys_path = vm.get_attribute(vm.sys_module.clone(), "path").unwrap(); - let mut paths: Vec = objsequence::get_elements(&sys_path) + let mut paths: Vec = objsequence::get_elements_list(&sys_path) .iter() .map(|item| PathBuf::from(objstr::get_value(item))) .collect(); diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 4e51c41707..c07470be26 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -686,7 +686,7 @@ impl PyByteInner { Either::A(byte) => byte.elements, Either::B(tuple) => { let mut flatten = vec![]; - for v in objsequence::get_elements(tuple.as_object()).to_vec() { + for v in objsequence::get_elements_tuple(tuple.as_object()).to_vec() { flatten.extend(PyByteInner::try_from_object(vm, v)?.elements) } flatten diff --git a/vm/src/obj/objlist.rs b/vm/src/obj/objlist.rs index 88842f9b79..77c1b4c9f8 100644 --- a/vm/src/obj/objlist.rs +++ b/vm/src/obj/objlist.rs @@ -17,8 +17,8 @@ use super::objbool; //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, - SequenceIndex, + get_elements_cell, get_elements_list, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, + seq_mul, SequenceIndex, }; use super::objslice::PySliceRef; use super::objtype; @@ -129,7 +129,7 @@ impl PyListRef { fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.list_type()) { let e1 = self.elements.borrow(); - let e2 = get_elements(&other); + let e2 = get_elements_list(&other); let elements = e1.iter().chain(e2.iter()).cloned().collect(); Ok(vm.ctx.new_list(elements)) } else { @@ -141,7 +141,7 @@ impl PyListRef { if objtype::isinstance(&other, &vm.ctx.list_type()) { self.elements .borrow_mut() - .extend_from_slice(&get_elements(&other)); + .extend_from_slice(&get_elements_list(&other)); Ok(self.into_object()) } else { Ok(vm.ctx.not_implemented()) @@ -490,7 +490,7 @@ impl PyListRef { if objtype::isinstance(&other, &vm.ctx.list_type()) { let zelf = self.elements.borrow(); - let other = get_elements(&other); + let other = get_elements_list(&other); let res = seq_equal(vm, &zelf, &other)?; Ok(vm.new_bool(res)) } else { @@ -501,7 +501,7 @@ impl PyListRef { fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.list_type()) { let zelf = self.elements.borrow(); - let other = get_elements(&other); + let other = get_elements_list(&other); let res = seq_lt(vm, &zelf, &other)?; Ok(vm.new_bool(res)) } else { @@ -512,7 +512,7 @@ impl PyListRef { fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.list_type()) { let zelf = self.elements.borrow(); - let other = get_elements(&other); + let other = get_elements_list(&other); let res = seq_gt(vm, &zelf, &other)?; Ok(vm.new_bool(res)) } else { @@ -523,7 +523,7 @@ impl PyListRef { fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.list_type()) { let zelf = self.elements.borrow(); - let other = get_elements(&other); + let other = get_elements_list(&other); let res = seq_ge(vm, &zelf, &other)?; Ok(vm.new_bool(res)) } else { @@ -534,7 +534,7 @@ impl PyListRef { fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.list_type()) { let zelf = self.elements.borrow(); - let other = get_elements(&other); + let other = get_elements_list(&other); let res = seq_le(vm, &zelf, &other)?; Ok(vm.new_bool(res)) } else { diff --git a/vm/src/obj/objsequence.rs b/vm/src/obj/objsequence.rs index cee66752cb..d4226d51cd 100644 --- a/vm/src/obj/objsequence.rs +++ b/vm/src/obj/objsequence.rs @@ -351,12 +351,16 @@ pub fn get_elements_cell<'a>(obj: &'a PyObjectRef) -> &'a RefCell(obj: &'a PyObjectRef) -> impl Deref> + 'a { +pub fn get_elements_list<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { if let Some(list) = obj.payload::() { return list.elements.borrow(); } + panic!("Cannot extract elements from non-sequence"); +} + +pub fn get_elements_tuple<'a>(obj: &'a PyObjectRef) -> impl Deref> + 'a { if let Some(tuple) = obj.payload::() { - return tuple.elements.borrow(); + return &tuple.elements; } panic!("Cannot extract elements from non-sequence"); } diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 795e17d628..d7faed04a2 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -1,4 +1,4 @@ -use std::cell::{Cell, RefCell}; +use std::cell::Cell; use std::fmt; use crate::function::OptionalArg; @@ -9,14 +9,13 @@ use crate::vm::{ReprGuard, VirtualMachine}; use super::objbool; use super::objiter; use super::objsequence::{ - get_elements, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, + get_elements_tuple, get_item, seq_equal, seq_ge, seq_gt, seq_le, seq_lt, seq_mul, }; use super::objtype::{self, PyClassRef}; pub struct PyTuple { // TODO: shouldn't be public - // TODO: tuples are immutable, remove this RefCell - pub elements: RefCell>, + pub elements: Vec, } impl fmt::Debug for PyTuple { @@ -28,9 +27,7 @@ impl fmt::Debug for PyTuple { impl From> for PyTuple { fn from(elements: Vec) -> Self { - PyTuple { - elements: RefCell::new(elements), - } + PyTuple { elements: elements } } } @@ -45,9 +42,8 @@ pub type PyTupleRef = PyRef; impl PyTupleRef { fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.tuple_type()) { - let zelf = self.elements.borrow(); - let other = get_elements(&other); - let res = seq_lt(vm, &zelf, &other)?; + let other = get_elements_tuple(&other); + let res = seq_lt(vm, &self.elements, &other)?; Ok(vm.new_bool(res)) } else { Ok(vm.ctx.not_implemented()) @@ -56,9 +52,8 @@ impl PyTupleRef { fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.tuple_type()) { - let zelf = self.elements.borrow(); - let other = get_elements(&other); - let res = seq_gt(vm, &zelf, &other)?; + let other = get_elements_tuple(&other); + let res = seq_gt(vm, &self.elements, &other)?; Ok(vm.new_bool(res)) } else { Ok(vm.ctx.not_implemented()) @@ -67,9 +62,8 @@ impl PyTupleRef { fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.tuple_type()) { - let zelf = self.elements.borrow(); - let other = get_elements(&other); - let res = seq_ge(vm, &zelf, &other)?; + let other = get_elements_tuple(&other); + let res = seq_ge(vm, &self.elements, &other)?; Ok(vm.new_bool(res)) } else { Ok(vm.ctx.not_implemented()) @@ -78,9 +72,8 @@ impl PyTupleRef { fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.tuple_type()) { - let zelf = self.elements.borrow(); - let other = get_elements(&other); - let res = seq_le(vm, &zelf, &other)?; + let other = get_elements_tuple(&other); + let res = seq_le(vm, &self.elements, &other)?; Ok(vm.new_bool(res)) } else { Ok(vm.ctx.not_implemented()) @@ -89,9 +82,8 @@ impl PyTupleRef { fn add(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.tuple_type()) { - let e1 = self.elements.borrow(); - let e2 = get_elements(&other); - let elements = e1.iter().chain(e2.iter()).cloned().collect(); + let e2 = get_elements_tuple(&other); + let elements = self.elements.iter().chain(e2.iter()).cloned().collect(); Ok(vm.ctx.new_tuple(elements)) } else { Ok(vm.ctx.not_implemented()) @@ -99,12 +91,12 @@ impl PyTupleRef { } fn bool(self, _vm: &VirtualMachine) -> bool { - !self.elements.borrow().is_empty() + !self.elements.is_empty() } fn count(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { let mut count: usize = 0; - for element in self.elements.borrow().iter() { + for element in self.elements.iter() { if element.is(&needle) { count += 1; } else { @@ -119,9 +111,8 @@ impl PyTupleRef { fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.tuple_type()) { - let zelf = &self.elements.borrow(); - let other = get_elements(&other); - let res = seq_equal(vm, &zelf, &other)?; + let other = get_elements_tuple(&other); + let res = seq_equal(vm, &self.elements, &other)?; Ok(vm.new_bool(res)) } else { Ok(vm.ctx.not_implemented()) @@ -129,7 +120,7 @@ impl PyTupleRef { } fn hash(self, vm: &VirtualMachine) -> PyResult { - pyhash::hash_iter(self.elements.borrow().iter(), vm) + pyhash::hash_iter(self.elements.iter(), vm) } fn iter(self, _vm: &VirtualMachine) -> PyTupleIterator { @@ -140,13 +131,13 @@ impl PyTupleRef { } fn len(self, _vm: &VirtualMachine) -> usize { - self.elements.borrow().len() + self.elements.len() } fn repr(self, vm: &VirtualMachine) -> PyResult { let s = if let Some(_guard) = ReprGuard::enter(self.as_object()) { let mut str_parts = vec![]; - for elem in self.elements.borrow().iter() { + for elem in self.elements.iter() { let s = vm.to_repr(elem)?; str_parts.push(s.value.clone()); } @@ -163,21 +154,16 @@ impl PyTupleRef { } fn mul(self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { - let new_elements = seq_mul(&self.elements.borrow(), counter); + let new_elements = seq_mul(&self.elements, counter); vm.ctx.new_tuple(new_elements) } fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - get_item( - vm, - self.as_object(), - &self.elements.borrow(), - needle.clone(), - ) + get_item(vm, self.as_object(), &self.elements, needle.clone()) } fn index(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - for (index, element) in self.elements.borrow().iter().enumerate() { + for (index, element) in self.elements.iter().enumerate() { if element.is(&needle) { return Ok(index); } @@ -190,7 +176,7 @@ impl PyTupleRef { } fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { - for element in self.elements.borrow().iter() { + for element in self.elements.iter() { if element.is(&needle) { return Ok(true); } @@ -234,8 +220,8 @@ impl PyValue for PyTupleIterator { impl PyTupleIterator { #[pymethod(name = "__next__")] fn next(&self, vm: &VirtualMachine) -> PyResult { - if self.position.get() < self.tuple.elements.borrow().len() { - let ret = self.tuple.elements.borrow()[self.position.get()].clone(); + if self.position.get() < self.tuple.elements.len() { + let ret = self.tuple.elements[self.position.get()].clone(); self.position.set(self.position.get() + 1); Ok(ret) } else { diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index d57dd14a99..4b0ac1ca97 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -57,10 +57,11 @@ impl<'s> serde::Serialize for PyObjectSerializer<'s> { serializer.serialize_i64(v.to_i64().unwrap()) // Although this may seem nice, it does not give the right result: // v.serialize(serializer) - } else if objtype::isinstance(self.pyobject, &self.vm.ctx.list_type()) - || objtype::isinstance(self.pyobject, &self.vm.ctx.tuple_type()) - { - let elements = objsequence::get_elements(self.pyobject); + } else if objtype::isinstance(self.pyobject, &self.vm.ctx.list_type()) { + let elements = objsequence::get_elements_list(self.pyobject); + serialize_seq_elements(serializer, &elements) + } else if objtype::isinstance(self.pyobject, &self.vm.ctx.tuple_type()) { + let elements = objsequence::get_elements_tuple(self.pyobject); serialize_seq_elements(serializer, &elements) } else if objtype::isinstance(self.pyobject, &self.vm.ctx.dict_type()) { let dict: PyDictRef = self.pyobject.clone().downcast().unwrap(); diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index edab574b38..4f7cab5a14 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -349,14 +349,14 @@ impl Address { impl TryFromObject for Address { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { let tuple = PyTupleRef::try_from_object(vm, obj)?; - if tuple.elements.borrow().len() != 2 { + if tuple.elements.len() != 2 { Err(vm.new_type_error("Address tuple should have only 2 values".to_string())) } else { Ok(Address { - host: PyStringRef::try_from_object(vm, tuple.elements.borrow()[0].clone())? + host: PyStringRef::try_from_object(vm, tuple.elements[0].clone())? .value .to_string(), - port: PyIntRef::try_from_object(vm, tuple.elements.borrow()[1].clone())? + port: PyIntRef::try_from_object(vm, tuple.elements[1].clone())? .as_bigint() .to_usize() .unwrap(), diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 6cd1165db3..b2a46930ff 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -527,7 +527,7 @@ impl VirtualMachine { // Add missing positional arguments, if we have fewer positional arguments than the // function definition calls for if nargs < nexpected_args { - let num_defaults_available = defaults.as_ref().map_or(0, |d| d.elements.borrow().len()); + let num_defaults_available = defaults.as_ref().map_or(0, |d| d.elements.len()); // Given the number of defaults available, check all the arguments for which we // _don't_ have defaults; if any are missing, raise an exception @@ -547,7 +547,7 @@ impl VirtualMachine { ))); } if let Some(defaults) = defaults { - let defaults = defaults.elements.borrow(); + let defaults = &defaults.elements; // We have sufficient defaults, so iterate over the corresponding names and use // the default if we don't already have a value for (default_index, i) in (required_args..nexpected_args).enumerate() { @@ -580,10 +580,10 @@ impl VirtualMachine { pub fn extract_elements(&self, value: &PyObjectRef) -> PyResult> { // Extract elements from item, if possible: - let elements = if objtype::isinstance(value, &self.ctx.tuple_type()) - || objtype::isinstance(value, &self.ctx.list_type()) - { - objsequence::get_elements(value).to_vec() + let elements = if objtype::isinstance(value, &self.ctx.tuple_type()) { + objsequence::get_elements_tuple(value).to_vec() + } else if objtype::isinstance(value, &self.ctx.list_type()) { + objsequence::get_elements_list(value).to_vec() } else { let iter = objiter::get_iter(self, value)?; objiter::get_all(self, &iter)? diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 6f40fb1825..2f3b72076e 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -40,10 +40,10 @@ pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { }); if let Ok(tb) = vm.get_attribute(py_err.clone(), "__traceback__") { if objtype::isinstance(&tb, &vm.ctx.list_type()) { - let elements = objsequence::get_elements(&tb).to_vec(); + let elements = objsequence::get_elements_list(&tb).to_vec(); if let Some(top) = elements.get(0) { if objtype::isinstance(&top, &vm.ctx.tuple_type()) { - let element = objsequence::get_elements(&top); + let element = objsequence::get_elements_list(&top); if let Some(lineno) = objint::to_int(vm, &element[1], 10) .ok() From 433434a1bbb242c90ebfadcba3010c77af160710 Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Tue, 21 May 2019 23:01:04 +0900 Subject: [PATCH 645/884] Fix rsplit --- tests/snippets/strings.py | 5 +++-- vm/src/obj/objstr.rs | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 521b563537..2abd35da77 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -40,7 +40,6 @@ a = 'Hallo' assert a.lower() == 'hallo' assert a.upper() == 'HALLO' -assert a.split('al') == ['H', 'lo'] assert a.startswith('H') assert a.startswith(('H', 1)) assert a.startswith(('A', 'H')) @@ -59,7 +58,9 @@ assert a.istitle() assert a.isalpha() - +s = '1 2 3' +assert s.split(' ', 1) == ['1', '2 3'] +assert s.rsplit(' ', 1) == ['1 2', '3'] b = ' hallo ' assert b.strip() == 'hallo' diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index cf42a1a5c4..44542a9705 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -301,10 +301,13 @@ impl PyString { let num_splits = num .into_option() .unwrap_or_else(|| value.split(pattern).count()); - let elements = value + let mut elements: Vec<_> = value .rsplitn(num_splits + 1, pattern) .map(|o| vm.ctx.new_str(o.to_string())) .collect(); + // Unlike Python rsplit, Rust rsplitn returns an iterator that + // starts from the end of the string. + elements.reverse(); vm.ctx.new_list(elements) } From 0098e8485a2b8c7a261d915892d6ab07ea48873f Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Wed, 22 May 2019 03:17:25 +0300 Subject: [PATCH 646/884] move PyFuncArgs.kwargs to IndexMap --- Cargo.lock | 7 +++++ tests/snippets/function_args.py | 14 +++++++++ vm/Cargo.toml | 1 + vm/src/frame.rs | 5 ++-- vm/src/function.rs | 50 +++++++++++++++------------------ vm/src/pyobject.rs | 3 +- wasm/lib/src/convert.rs | 2 +- 7 files changed, 51 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 66b1f01aff..272786ef3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -356,6 +356,11 @@ dependencies = [ "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "indexmap" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "itertools" version = "0.8.0" @@ -882,6 +887,7 @@ dependencies = [ "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1453,6 +1459,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum hexf-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "22eadcfadba76a730b2764eaa577d045f35e0ef5174b9c5b46adf1ee42b85e12" "checksum hexf-parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "79296f72d53a89096cbc9a88c9547ee8dfe793388674620e2207593d370550ac" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" +"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" "checksum js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "d4bda84b98977341bb6ba1086ecbd9eab8bc929de0f2923c118baf76c21dd0c8" diff --git a/tests/snippets/function_args.py b/tests/snippets/function_args.py index b9c5a16264..86c92617ac 100644 --- a/tests/snippets/function_args.py +++ b/tests/snippets/function_args.py @@ -81,3 +81,17 @@ def fubar(x, y, obj=None): rest = [4, 5] fubar(obj=6, *rest) + + +# https://www.python.org/dev/peps/pep-0468/ +def func(**kwargs): + return list(kwargs.items()) + +empty_kwargs = func() +assert empty_kwargs == [] + +kwargs = func(a=1, b=2) +assert kwargs == [('a', 1), ('b', 2)] + +kwargs = func(a=1, b=2, c=3) +assert kwargs == [('a', 1), ('b', 2), ('c', 3)] diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 0d80923fd1..5ee8e81c44 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -29,6 +29,7 @@ lazy_static = "^1.0.1" lexical = "2.0.0" itertools = "^0.8.0" hexf = "0.1.0" +indexmap = "1.0.2" # TODO: release and publish to crates.io [dependencies.unicode-casing] diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 5955660b61..c5e3b53ca9 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -23,6 +23,7 @@ use crate::pyobject::{ TypeProtocol, }; use crate::vm::VirtualMachine; +use indexmap::IndexMap; use itertools::Itertools; /* @@ -674,7 +675,7 @@ impl Frame { let args: Vec = self.pop_multiple(*count); PyFuncArgs { args, - kwargs: vec![], + kwargs: IndexMap::new(), } } bytecode::CallType::Keyword(count) => { @@ -697,7 +698,7 @@ impl Frame { .map(|elem| (objstr::get_value(&elem.0), elem.1)) .collect() } else { - vec![] + IndexMap::new() }; let args = self.pop_value(); let args = vm.extract_elements(&args)?; diff --git a/vm/src/function.rs b/vm/src/function.rs index e7dbe59e40..a02c85bb0c 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -2,6 +2,8 @@ use std::collections::HashMap; use std::mem; use std::ops::RangeInclusive; +use indexmap::IndexMap; + use crate::obj::objtuple::PyTuple; use crate::obj::objtype::{isinstance, PyClassRef}; use crate::pyobject::{ @@ -17,7 +19,8 @@ use self::OptionalArg::*; #[derive(Debug, Default, Clone)] pub struct PyFuncArgs { pub args: Vec, - pub kwargs: Vec<(String, PyObjectRef)>, + // sorted map, according to https://www.python.org/dev/peps/pep-0468/ + pub kwargs: IndexMap, } /// Conversion from vector of python objects to function arguments. @@ -25,7 +28,7 @@ impl From> for PyFuncArgs { fn from(args: Vec) -> Self { PyFuncArgs { args, - kwargs: vec![], + kwargs: IndexMap::new(), } } } @@ -34,7 +37,7 @@ impl From for PyFuncArgs { fn from(arg: PyObjectRef) -> Self { PyFuncArgs { args: vec![arg], - kwargs: vec![], + kwargs: IndexMap::new(), } } } @@ -58,9 +61,12 @@ impl FromArgs for PyFuncArgs { impl PyFuncArgs { pub fn new(mut args: Vec, kwarg_names: Vec) -> PyFuncArgs { - let mut kwargs = vec![]; - for name in kwarg_names.iter().rev() { - kwargs.push((name.clone(), args.pop().unwrap())); + // last `kwarg_names.len()` elements of args in order of appearance in the call signature + let kwarg_values = args.drain((args.len() - kwarg_names.len())..); + + let mut kwargs = IndexMap::new(); + for (name, value) in kwarg_names.iter().zip(kwarg_values) { + kwargs.insert(name.clone(), value); } PyFuncArgs { args, kwargs } } @@ -79,19 +85,15 @@ impl PyFuncArgs { } pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef { - for (arg_name, arg_value) in self.kwargs.iter() { - if arg_name == key { - return arg_value.clone(); - } + if let Some(kwarg_value) = self.kwargs.get(key) { + return kwarg_value.clone(); } default.clone() } pub fn get_optional_kwarg(&self, key: &str) -> Option { - for (arg_name, arg_value) in self.kwargs.iter() { - if arg_name == key { - return Some(arg_value.clone()); - } + if let Some(kwarg_value) = self.kwargs.get(key) { + return Some(kwarg_value.clone()); } None } @@ -132,19 +134,10 @@ impl PyFuncArgs { } pub fn take_keyword(&mut self, name: &str) -> Option { - // TODO: change kwarg representation so this scan isn't necessary - if let Some(index) = self - .kwargs - .iter() - .position(|(arg_name, _)| arg_name == name) - { - Some(self.kwargs.remove(index).1) - } else { - None - } + self.kwargs.remove(name) } - pub fn remaining_keyword<'a>(&'a mut self) -> impl Iterator + 'a { + pub fn remaining_keywords<'a>(&'a mut self) -> impl Iterator + 'a { self.kwargs.drain(..) } @@ -192,7 +185,10 @@ impl PyFuncArgs { given_args, ))) } else if !self.kwargs.is_empty() { - Err(vm.new_type_error(format!("Unexpected keyword argument {}", self.kwargs[0].0))) + Err(vm.new_type_error(format!( + "Unexpected keyword argument {}", + self.kwargs.keys().next().unwrap() + ))) } else { Ok(bound) } @@ -264,7 +260,7 @@ where { fn from_args(vm: &VirtualMachine, args: &mut PyFuncArgs) -> Result { let mut kwargs = HashMap::new(); - for (name, value) in args.remaining_keyword() { + for (name, value) in args.remaining_keywords() { kwargs.insert(name, T::try_from_object(vm, value)?); } Ok(KwArgs(kwargs)) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index afaad09b69..78c78e3333 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -57,6 +57,7 @@ use crate::obj::objweakproxy; use crate::obj::objweakref; use crate::obj::objzip; use crate::vm::VirtualMachine; +use indexmap::IndexMap; /* Python objects and references. @@ -1088,7 +1089,7 @@ impl PyIterable { self.method.clone(), PyFuncArgs { args: vec![], - kwargs: vec![], + kwargs: IndexMap::new(), }, )?; diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 2f3b72076e..a65141c571 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -93,7 +93,7 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { let (key, val) = pair?; py_func_args .kwargs - .push((js_sys::JsString::from(key).into(), js_to_py(vm, val))); + .insert(js_sys::JsString::from(key).into(), js_to_py(vm, val)); } } let result = vm.invoke(py_obj.clone(), py_func_args); From b5352da2c04285aa03b936016e983f6074ebf68b Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Wed, 22 May 2019 03:30:30 +0300 Subject: [PATCH 647/884] fix formatting --- vm/src/function.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vm/src/function.rs b/vm/src/function.rs index a02c85bb0c..b9a7e09dcc 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -137,7 +137,9 @@ impl PyFuncArgs { self.kwargs.remove(name) } - pub fn remaining_keywords<'a>(&'a mut self) -> impl Iterator + 'a { + pub fn remaining_keywords<'a>( + &'a mut self, + ) -> impl Iterator + 'a { self.kwargs.drain(..) } From 737ec52365ee0e3bca18ee7826f9ce5043045c44 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Wed, 22 May 2019 12:47:28 +0300 Subject: [PATCH 648/884] simplify PyFuncArgs get_kwarg/get_optional_kwarg with cloned() --- vm/src/function.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/vm/src/function.rs b/vm/src/function.rs index b9a7e09dcc..47fc051d59 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -85,17 +85,14 @@ impl PyFuncArgs { } pub fn get_kwarg(&self, key: &str, default: PyObjectRef) -> PyObjectRef { - if let Some(kwarg_value) = self.kwargs.get(key) { - return kwarg_value.clone(); - } - default.clone() + self.kwargs + .get(key) + .cloned() + .unwrap_or_else(|| default.clone()) } pub fn get_optional_kwarg(&self, key: &str) -> Option { - if let Some(kwarg_value) = self.kwargs.get(key) { - return Some(kwarg_value.clone()); - } - None + self.kwargs.get(key).cloned() } pub fn get_optional_kwarg_with_type( From fdd569cb8b397e18edcef4f81c2f6fa1adf6f744 Mon Sep 17 00:00:00 2001 From: Shitong Wen Date: Thu, 23 May 2019 16:08:12 +0800 Subject: [PATCH 649/884] add crc32 --- vm/src/stdlib/binascii.rs | 60 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/vm/src/stdlib/binascii.rs b/vm/src/stdlib/binascii.rs index 97fb76f8e2..f0c6cc8ca5 100644 --- a/vm/src/stdlib/binascii.rs +++ b/vm/src/stdlib/binascii.rs @@ -1,7 +1,9 @@ use crate::function::PyFuncArgs; use crate::obj::objbytes; +use crate::obj::objint; use crate::pyobject::{PyObjectRef, PyResult}; use crate::vm::VirtualMachine; +use num_traits::ToPrimitive; fn hex_nibble(n: u8) -> u8 { match n { @@ -56,6 +58,63 @@ fn binascii_unhexlify(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bytes(unhex)) } +static CRC32_TABLE: [u32; 256] = [ + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, +]; +fn binascii_crc32(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(data, Some(vm.ctx.bytes_type()))], + optional = [(value, None)] + ); + + let bytes = objbytes::get_value(data); + let mut crc = match value { + None => 0u32, + Some(value) => objint::get_value(&value).to_u32().unwrap(), + }; + + crc = !crc; + for b in bytes.iter() { + let b = *b as u32; + crc = CRC32_TABLE[((crc ^ b) & 0xff) as usize] ^ (crc >> 8); + } + crc = crc ^ 0xffffffff; + Ok(vm.ctx.new_int(crc)) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -64,5 +123,6 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "b2a_hex" => ctx.new_rustfunc(binascii_hexlify), "unhexlify" => ctx.new_rustfunc(binascii_unhexlify), "a2b_hex" => ctx.new_rustfunc(binascii_unhexlify), + "crc32" => ctx.new_rustfunc(binascii_crc32), }) } From 21bffc8ffa5b5b6586e7e7e37451af1e94575c2c Mon Sep 17 00:00:00 2001 From: Shitong Wen Date: Thu, 23 May 2019 16:11:17 +0800 Subject: [PATCH 650/884] add crc32 test --- tests/snippets/stdlib_binascii.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/snippets/stdlib_binascii.py b/tests/snippets/stdlib_binascii.py index e73e162bd2..6b116fb9f5 100644 --- a/tests/snippets/stdlib_binascii.py +++ b/tests/snippets/stdlib_binascii.py @@ -33,3 +33,7 @@ with assertRaises(ValueError): uh(b"nn") # Non-hexadecimal digit found + +assert binascii.crc32(b"hello world") == 222957957 +assert binascii.crc32(b"hello world", 555555) == 1216827162 +assert binascii.crc32(b"goodbye interesting world",777777) == 1885538403 \ No newline at end of file From 813c6112e69f33f36dc60def6f6996d4054313c3 Mon Sep 17 00:00:00 2001 From: Shitong Wen Date: Thu, 23 May 2019 16:30:19 +0800 Subject: [PATCH 651/884] add crc32 test --- tests/snippets/stdlib_binascii.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_binascii.py b/tests/snippets/stdlib_binascii.py index 6b116fb9f5..e56b3e47d8 100644 --- a/tests/snippets/stdlib_binascii.py +++ b/tests/snippets/stdlib_binascii.py @@ -36,4 +36,4 @@ assert binascii.crc32(b"hello world") == 222957957 assert binascii.crc32(b"hello world", 555555) == 1216827162 -assert binascii.crc32(b"goodbye interesting world",777777) == 1885538403 \ No newline at end of file +assert binascii.crc32(b"goodbye interesting world",777777) == 1885538403 From 8afb6a201c42088067f92f8e23986a78d43131d5 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 24 May 2019 10:23:30 +0300 Subject: [PATCH 652/884] RawIOBase inherits IOBase --- tests/snippets/stdlib_io.py | 6 ++++++ vm/src/stdlib/io.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_io.py b/tests/snippets/stdlib_io.py index 503759df7a..e6da760d24 100644 --- a/tests/snippets/stdlib_io.py +++ b/tests/snippets/stdlib_io.py @@ -8,3 +8,9 @@ assert len(result) <= 8*1024 assert len(result) >= 0 assert isinstance(result, bytes) + +with FileIO('README.md') as fio: + res = fio.read() + assert len(result) <= 8*1024 + assert len(result) >= 0 + assert isinstance(result, bytes) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index e9b49ec442..8b203cc781 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -377,7 +377,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { }); // IOBase Subclasses - let raw_io_base = py_class!(ctx, "RawIOBase", ctx.object(), {}); + let raw_io_base = py_class!(ctx, "RawIOBase", io_base.clone(), {}); let buffered_io_base = py_class!(ctx, "BufferedIOBase", io_base.clone(), { "__init__" => ctx.new_rustfunc(buffered_io_base_init) From fedbd29051702d1f984f7c4dc335b79ecef13c25 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 24 May 2019 13:01:15 +0300 Subject: [PATCH 653/884] objstr: Refactored the 'mul' method - Used `str::repeat` instead of manually building the result string - Rewritten the int type check to return error at the beginning of the method - Replaced the `unwrap` calls with the single `unwrap_or` --- vm/src/obj/objstr.rs | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 44542a9705..80b37d29ad 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -183,22 +183,16 @@ impl PyString { #[pymethod(name = "__mul__")] fn mul(&self, val: PyObjectRef, vm: &VirtualMachine) -> PyResult { - if objtype::isinstance(&val, &vm.ctx.int_type()) { - let value = &self.value; - let multiplier = objint::get_value(&val).to_i32().unwrap(); - let capacity = if multiplier > 0 { - multiplier.to_usize().unwrap() * value.len() - } else { - 0 - }; - let mut result = String::with_capacity(capacity); - for _x in 0..multiplier { - result.push_str(value.as_str()); - } - Ok(result) - } else { - Err(vm.new_type_error(format!("Cannot multiply {} and {}", self, val))) + if !objtype::isinstance(&val, &vm.ctx.int_type()) { + return Err(vm.new_type_error(format!("Cannot multiply {} and {}", self, val))); } + let value = &self.value; + let multiplier = objint::get_value(&val) + .to_i32() + .map(|multiplier| if multiplier < 0 { 0 } else { multiplier }) + .and_then(|multiplier| multiplier.to_usize()) + .unwrap_or(0); + Ok(value.repeat(multiplier)) } #[pymethod(name = "__rmul__")] From e38c54985e3fd40ff0c22263f53079b71cd9361a Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Fri, 24 May 2019 20:37:47 +0300 Subject: [PATCH 654/884] objstr: Replaced the if-else construct with the max function --- vm/src/obj/objstr.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 80b37d29ad..3082a0c6ed 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -189,7 +189,7 @@ impl PyString { let value = &self.value; let multiplier = objint::get_value(&val) .to_i32() - .map(|multiplier| if multiplier < 0 { 0 } else { multiplier }) + .map(|multiplier| multiplier.max(0)) .and_then(|multiplier| multiplier.to_usize()) .unwrap_or(0); Ok(value.repeat(multiplier)) From a38f205f0e52ef607202c53333f08b05bb468200 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 25 May 2019 11:13:25 +0300 Subject: [PATCH 655/884] Add support for frozen modules --- vm/src/frozen.rs | 11 +++++++++++ vm/src/import.rs | 2 ++ vm/src/lib.rs | 1 + vm/src/vm.rs | 4 ++++ 4 files changed, 18 insertions(+) create mode 100644 vm/src/frozen.rs diff --git a/vm/src/frozen.rs b/vm/src/frozen.rs new file mode 100644 index 0000000000..cc6eb85fe5 --- /dev/null +++ b/vm/src/frozen.rs @@ -0,0 +1,11 @@ +use std::collections::hash_map::HashMap; + +const HELLO: &str = "initialized = True +print(\"Hello world!\") +"; + +pub fn get_module_inits() -> HashMap { + let mut modules = HashMap::new(); + modules.insert("__hello__".to_string(), HELLO); + modules +} diff --git a/vm/src/import.rs b/vm/src/import.rs index 16385e3d32..0e8b7ba535 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -18,6 +18,8 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s // First, see if we already loaded the module: if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) { Ok(module) + } else if let Some(frozen) = vm.frozen.borrow().get(module_name) { + import_file(vm, module_name, "frozen".to_string(), frozen.to_string()) } else if let Some(make_module_func) = vm.stdlib_inits.borrow().get(module_name) { let module = make_module_func(vm); sys_modules.set_item(module_name, module.clone(), vm)?; diff --git a/vm/src/lib.rs b/vm/src/lib.rs index caa63d1452..24f28709ea 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -45,6 +45,7 @@ pub mod eval; mod exceptions; pub mod format; pub mod frame; +mod frozen; pub mod function; pub mod import; pub mod obj; diff --git a/vm/src/vm.rs b/vm/src/vm.rs index b2a46930ff..17d1caffb9 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -16,6 +16,7 @@ use crate::builtins; use crate::bytecode; use crate::error::CompileError; use crate::frame::{ExecutionResult, Frame, FrameRef, Scope}; +use crate::frozen; use crate::function::PyFuncArgs; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; @@ -53,6 +54,7 @@ pub struct VirtualMachine { pub frames: RefCell>, pub wasm_id: Option, pub exceptions: RefCell>, + pub frozen: RefCell>, } impl VirtualMachine { @@ -65,6 +67,7 @@ impl VirtualMachine { let sysmod = ctx.new_module("sys", ctx.new_dict()); let stdlib_inits = RefCell::new(stdlib::get_module_inits()); + let frozen = RefCell::new(frozen::get_module_inits()); let vm = VirtualMachine { builtins: builtins.clone(), sys_module: sysmod.clone(), @@ -73,6 +76,7 @@ impl VirtualMachine { frames: RefCell::new(vec![]), wasm_id: None, exceptions: RefCell::new(vec![]), + frozen, }; builtins::make_module(&vm, builtins.clone()); From f33d15ab6af5c1ba5c7008c925acbe3f8cb5af96 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 25 May 2019 11:15:17 +0300 Subject: [PATCH 656/884] Test frozen modules import --- tests/snippets/frozen.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/snippets/frozen.py diff --git a/tests/snippets/frozen.py b/tests/snippets/frozen.py new file mode 100644 index 0000000000..d03658c191 --- /dev/null +++ b/tests/snippets/frozen.py @@ -0,0 +1,2 @@ +import __hello__ +assert __hello__.initialized == True From 2c74d7cc1245f0ea66113b5ddf8005c6d9578598 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 25 May 2019 15:02:03 +0300 Subject: [PATCH 657/884] Add _imp --- vm/src/stdlib/imp.rs | 15 +++++++++++++++ vm/src/stdlib/mod.rs | 2 ++ 2 files changed, 17 insertions(+) create mode 100644 vm/src/stdlib/imp.rs diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs new file mode 100644 index 0000000000..1498f74357 --- /dev/null +++ b/vm/src/stdlib/imp.rs @@ -0,0 +1,15 @@ +use crate::pyobject::{PyObjectRef, PyResult}; +use crate::vm::VirtualMachine; + +fn imp_extension_suffixes(vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_list(vec![])) +} + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + let module = py_module!(vm, "_imp", { + "extension_suffixes" => ctx.new_rustfunc(imp_extension_suffixes) + }); + + module +} diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 2b17586c69..e475ba0576 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -1,6 +1,7 @@ mod ast; mod binascii; mod dis; +mod imp; mod itertools; pub(crate) mod json; mod keyword; @@ -51,6 +52,7 @@ pub fn get_module_inits() -> HashMap { modules.insert("time".to_string(), Box::new(time_module::make_module)); modules.insert("tokenize".to_string(), Box::new(tokenize::make_module)); modules.insert("_weakref".to_string(), Box::new(weakref::make_module)); + modules.insert("_imp".to_string(), Box::new(imp::make_module)); // disable some modules on WASM #[cfg(not(target_arch = "wasm32"))] From 8197b17c1462ae14b3892476215d9004da73fc98 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 25 May 2019 15:07:51 +0300 Subject: [PATCH 658/884] Add empty _imp locks methods --- vm/src/stdlib/imp.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index 1498f74357..a60f897517 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -5,10 +5,28 @@ fn imp_extension_suffixes(vm: &VirtualMachine) -> PyResult { Ok(vm.ctx.new_list(vec![])) } +fn imp_acquire_lock(_vm: &VirtualMachine) -> PyResult<()> { + // TODO + Ok(()) +} + +fn imp_release_lock(_vm: &VirtualMachine) -> PyResult<()> { + // TODO + Ok(()) +} + +fn imp_lock_held(_vm: &VirtualMachine) -> PyResult<()> { + // TODO + Ok(()) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let module = py_module!(vm, "_imp", { - "extension_suffixes" => ctx.new_rustfunc(imp_extension_suffixes) + "extension_suffixes" => ctx.new_rustfunc(imp_extension_suffixes), + "acquire_lock" => ctx.new_rustfunc(imp_acquire_lock), + "release_lock" => ctx.new_rustfunc(imp_release_lock), + "lock_held" => ctx.new_rustfunc(imp_lock_held), }); module From 7a467781e2e0dc12a10b97d9e8fba51e1ca0256d Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 25 May 2019 15:33:57 +0300 Subject: [PATCH 659/884] Add _imp.is_builtin --- tests/snippets/imp.py | 5 +++++ vm/src/stdlib/imp.rs | 10 ++++++++++ 2 files changed, 15 insertions(+) create mode 100644 tests/snippets/imp.py diff --git a/tests/snippets/imp.py b/tests/snippets/imp.py new file mode 100644 index 0000000000..45332a9b96 --- /dev/null +++ b/tests/snippets/imp.py @@ -0,0 +1,5 @@ +import _imp + +assert _imp.is_builtin("time") == True +assert _imp.is_builtin("os") == False +assert _imp.is_builtin("not existing module") == False diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index a60f897517..4c6c2e0084 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -1,3 +1,4 @@ +use crate::obj::objstr::PyStringRef; use crate::pyobject::{PyObjectRef, PyResult}; use crate::vm::VirtualMachine; @@ -20,6 +21,14 @@ fn imp_lock_held(_vm: &VirtualMachine) -> PyResult<()> { Ok(()) } +fn imp_is_builtin(name: PyStringRef, vm: &VirtualMachine) -> bool { + if let Some(_) = vm.stdlib_inits.borrow().get(name.as_str()) { + true + } else { + false + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let module = py_module!(vm, "_imp", { @@ -27,6 +36,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "acquire_lock" => ctx.new_rustfunc(imp_acquire_lock), "release_lock" => ctx.new_rustfunc(imp_release_lock), "lock_held" => ctx.new_rustfunc(imp_lock_held), + "is_builtin" => ctx.new_rustfunc(imp_is_builtin), }); module From a2e64c0425f9d55310d4e69ab46bea88a5896dd8 Mon Sep 17 00:00:00 2001 From: ZapAnton Date: Sat, 25 May 2019 16:33:47 +0300 Subject: [PATCH 660/884] objstr: Replaced the unwrap_or with the OverflowError --- tests/snippets/strings.py | 2 ++ vm/src/obj/objstr.rs | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 2abd35da77..786622e9f5 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -37,6 +37,8 @@ assert 0 * "x" == "" assert -1 * "x" == "" +assert_raises(OverflowError, lambda: 'xy' * 234234234234234234234234234234) + a = 'Hallo' assert a.lower() == 'hallo' assert a.upper() == 'HALLO' diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 3082a0c6ed..6545c99858 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -186,13 +186,14 @@ impl PyString { if !objtype::isinstance(&val, &vm.ctx.int_type()) { return Err(vm.new_type_error(format!("Cannot multiply {} and {}", self, val))); } - let value = &self.value; - let multiplier = objint::get_value(&val) - .to_i32() + objint::get_value(&val) + .to_isize() .map(|multiplier| multiplier.max(0)) .and_then(|multiplier| multiplier.to_usize()) - .unwrap_or(0); - Ok(value.repeat(multiplier)) + .map(|multiplier| self.value.repeat(multiplier)) + .ok_or_else(|| { + vm.new_overflow_error("cannot fit 'int' into an index-sized integer".to_string()) + }) } #[pymethod(name = "__rmul__")] From 017377061d4acc322cd3adf8ed208e7bb7a683ec Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 25 May 2019 15:52:44 -0500 Subject: [PATCH 661/884] Check RUSTPYTHONPATH as well as PYTHONPATH env variables --- vm/src/sysmodule.rs | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index aefdb117b2..bc24baf1bb 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -54,18 +54,23 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectR "cache_tag" => ctx.new_str("rustpython-01".to_string()), }); - let path_list = match env::var_os("PYTHONPATH") { - Some(paths) => env::split_paths(&paths) - .map(|path| { - ctx.new_str( - path.to_str() - .expect("PYTHONPATH isn't valid unicode") - .to_string(), - ) - }) - .collect(), - None => vec![], - }; + fn get_paths(env_name: &str) -> impl Iterator { + match env::var_os(env_name) { + Some(paths) => env::split_paths(&paths), + None => env::split_paths(""), + } + } + + let path_list = get_paths("RUSTPYTHONPATH") + .chain(get_paths("PYTHONPATH")) + .map(|path| { + ctx.new_str( + path.to_str() + .expect("PYTHONPATH isn't valid unicode") + .to_string(), + ) + }) + .collect(); let path = ctx.new_list(path_list); let platform = if cfg!(target_os = "linux") { From 770f54791eea3484a46340dc7749fccd3e07f446 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 25 May 2019 17:28:25 -0500 Subject: [PATCH 662/884] Fix ownership errors --- vm/src/sysmodule.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index bc24baf1bb..dd52a189a8 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -54,15 +54,19 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectR "cache_tag" => ctx.new_str("rustpython-01".to_string()), }); - fn get_paths(env_name: &str) -> impl Iterator { - match env::var_os(env_name) { - Some(paths) => env::split_paths(&paths), + fn get_paths<'a>( + paths: &'a Option, + ) -> impl Iterator + 'a { + match paths { + Some(paths) => env::split_paths(paths), None => env::split_paths(""), } } - let path_list = get_paths("RUSTPYTHONPATH") - .chain(get_paths("PYTHONPATH")) + let rustpy_path = env::var_os("RUSTPYTHONPATH"); + let py_path = env::var_os("PYTHONPATH"); + let path_list = get_paths(&rustpy_path) + .chain(get_paths(&py_path)) .map(|path| { ctx.new_str( path.to_str() From 042a673ea23350ccdbc4cd10135bad0b33d32499 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 26 May 2019 21:16:40 +0300 Subject: [PATCH 663/884] Use contains_key --- vm/src/stdlib/imp.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index 4c6c2e0084..f5d889852a 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -22,11 +22,7 @@ fn imp_lock_held(_vm: &VirtualMachine) -> PyResult<()> { } fn imp_is_builtin(name: PyStringRef, vm: &VirtualMachine) -> bool { - if let Some(_) = vm.stdlib_inits.borrow().get(name.as_str()) { - true - } else { - false - } + vm.stdlib_inits.borrow().contains_key(name.as_str()) } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { From b2f17a18ea4467f25f93529bdc076bfb450d90d3 Mon Sep 17 00:00:00 2001 From: Daniel Shaulov Date: Mon, 27 May 2019 22:09:47 +0300 Subject: [PATCH 664/884] Add support for marshal with loads and dumps --- Cargo.lock | 19 ++++++++++++++++++- parser/Cargo.toml | 1 + parser/src/ast.rs | 4 +++- parser/src/lexer.rs | 3 ++- tests/snippets/stdlib_marshal.py | 7 +++++++ vm/Cargo.toml | 8 ++++---- vm/src/bytecode.rs | 20 +++++++++++--------- vm/src/pyobject.rs | 2 +- vm/src/stdlib/marshal.rs | 24 ++++++++++++++++++++++++ vm/src/stdlib/mod.rs | 2 ++ 10 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 tests/snippets/stdlib_marshal.py create mode 100644 vm/src/stdlib/marshal.rs diff --git a/Cargo.lock b/Cargo.lock index 272786ef3c..44ad4ceed2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,6 +78,16 @@ dependencies = [ "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bincode" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bit-set" version = "0.5.1" @@ -509,6 +519,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -517,6 +528,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -875,6 +887,7 @@ dependencies = [ "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "unic-emoji-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -883,6 +896,7 @@ dependencies = [ name = "rustpython_vm" version = "0.1.0" dependencies = [ + "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -904,7 +918,6 @@ dependencies = [ "rustpython_derive 0.1.0", "rustpython_parser 0.0.1", "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb)", @@ -970,6 +983,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "serde" version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "serde_derive" @@ -1423,6 +1439,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" "checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4" "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" +"checksum bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9f04a5e50dc80b3d5d35320889053637d15011aed5e66b66b37ae798c65da6f7" "checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" "checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" "checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" diff --git a/parser/Cargo.toml b/parser/Cargo.toml index e3c4e36bde..6cc7805c66 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -16,3 +16,4 @@ num-bigint = "0.2" num-traits = "0.2" unicode-xid = "0.1.0" unic-emoji-char = "0.9.0" +serde = { version = "1.0.66", features = ["derive"] } diff --git a/parser/src/ast.rs b/parser/src/ast.rs index ec0677fe82..cf7676a3b7 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -4,6 +4,8 @@ pub use super::lexer::Location; use num_bigint::BigInt; +use serde::{Serialize, Deserialize}; + /* #[derive(Debug)] @@ -381,7 +383,7 @@ pub enum Number { } /// Transforms a value prior to formatting it. -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum ConversionFlag { /// Converts by calling `str()`. Str, diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index f95563eac3..269864daf9 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -12,6 +12,7 @@ use std::collections::HashMap; use std::str::FromStr; use unic_emoji_char::is_emoji_presentation; use unicode_xid::UnicodeXID; +use serde::{Serialize, Deserialize}; #[derive(Clone, Copy, PartialEq, Debug)] struct IndentationLevel { @@ -71,7 +72,7 @@ pub enum LexicalErrorType { OtherError(String), } -#[derive(Clone, Debug, Default, PartialEq)] +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct Location { row: usize, column: usize, diff --git a/tests/snippets/stdlib_marshal.py b/tests/snippets/stdlib_marshal.py new file mode 100644 index 0000000000..118bb1bddf --- /dev/null +++ b/tests/snippets/stdlib_marshal.py @@ -0,0 +1,7 @@ +import marshal +orig = compile("1 + 1", "", 'eval') + +dumped = marshal.dumps(orig) +loaded = marshal.loads(dumped) + +assert eval(loaded) == eval(orig) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 5ee8e81c44..4f6e4b1b2b 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -6,8 +6,8 @@ edition = "2018" [dependencies] bitflags = "1.0.4" -num-complex = "0.2" -num-bigint = "0.2.1" +num-complex = { version = "0.2", features = ["serde"] } +num-bigint = { version = "0.2.1", features = ["serde"] } num-traits = "0.2" num-integer = "0.1.39" num-rational = "0.2.1" @@ -15,8 +15,7 @@ rand = "0.5" log = "0.3" rustpython_derive = {path = "../derive"} rustpython_parser = {path = "../parser"} -serde = "1.0.66" -serde_derive = "1.0.66" +serde = { version = "1.0.66", features = ["derive"] } serde_json = "1.0.26" byteorder = "1.2.6" regex = "1" @@ -30,6 +29,7 @@ lexical = "2.0.0" itertools = "^0.8.0" hexf = "0.1.0" indexmap = "1.0.2" +bincode = "1.1.4" # TODO: release and publish to crates.io [dependencies.unicode-casing] diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 09bb7e70a3..5291cd0c3d 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -10,10 +10,11 @@ use num_complex::Complex64; use rustpython_parser::ast; use std::collections::{HashMap, HashSet}; use std::fmt; +use serde::{Serialize, Deserialize}; /// Primary container of a single code object. Each python function has /// a codeobject. Also a module has a codeobject. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Serialize, Deserialize)] pub struct CodeObject { pub instructions: Vec, pub label_map: HashMap, @@ -29,6 +30,7 @@ pub struct CodeObject { } bitflags! { + #[derive(Serialize, Deserialize)] pub struct FunctionOpArg: u8 { const HAS_DEFAULTS = 0x01; const HAS_KW_ONLY_DEFAULTS = 0x02; @@ -38,7 +40,7 @@ bitflags! { pub type Label = usize; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum NameScope { Local, NonLocal, @@ -46,7 +48,7 @@ pub enum NameScope { } /// A Single bytecode instruction. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Instruction { Import { name: String, @@ -187,14 +189,14 @@ pub enum Instruction { use self::Instruction::*; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum CallType { Positional(usize), Keyword(usize), Ex(bool), } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Constant { Integer { value: BigInt }, Float { value: f64 }, @@ -208,7 +210,7 @@ pub enum Constant { Ellipsis, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ComparisonOperator { Greater, GreaterOrEqual, @@ -222,7 +224,7 @@ pub enum ComparisonOperator { IsNot, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum BinaryOperator { Power, Multiply, @@ -240,7 +242,7 @@ pub enum BinaryOperator { Or, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum UnaryOperator { Not, Invert, @@ -248,7 +250,7 @@ pub enum UnaryOperator { Plus, } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Varargs { None, Unnamed, diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 78c78e3333..d88991b9ef 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -294,7 +294,7 @@ impl PyContext { let zip_type = create_type("zip", &type_type, &object_type); let bool_type = create_type("bool", &type_type, &int_type); let memoryview_type = create_type("memoryview", &type_type, &object_type); - let code_type = create_type("code", &type_type, &int_type); + let code_type = create_type("code", &type_type, &object_type); let range_type = create_type("range", &type_type, &object_type); let rangeiterator_type = create_type("range_iterator", &type_type, &object_type); let slice_type = create_type("slice", &type_type, &object_type); diff --git a/vm/src/stdlib/marshal.rs b/vm/src/stdlib/marshal.rs new file mode 100644 index 0000000000..0475598811 --- /dev/null +++ b/vm/src/stdlib/marshal.rs @@ -0,0 +1,24 @@ +use crate::obj::objcode::{PyCodeRef, PyCode}; +use crate::pyobject::{PyObjectRef, PyResult, IntoPyObject}; +use crate::vm::VirtualMachine; +use crate::obj::objbytes::{PyBytes, PyBytesRef}; +use crate::bytecode; + +fn marshal_dumps(co: PyCodeRef, vm: &VirtualMachine) -> PyResult { + PyBytes::new(bincode::serialize(&co.code).unwrap()).into_pyobject(vm) +} + +fn marshal_loads(code_bytes: PyBytesRef, vm: &VirtualMachine) -> PyResult { + let code = bincode::deserialize::(&code_bytes).unwrap(); + let pycode = PyCode { code }; + pycode.into_pyobject(vm) +} + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + py_module!(vm, "marshal", { + "loads" => ctx.new_rustfunc(marshal_loads), + "dumps" => ctx.new_rustfunc(marshal_dumps) + }) +} \ No newline at end of file diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index e475ba0576..ef2d7f3a53 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -5,6 +5,7 @@ mod imp; mod itertools; pub(crate) mod json; mod keyword; +mod marshal; mod math; mod platform; mod pystruct; @@ -42,6 +43,7 @@ pub fn get_module_inits() -> HashMap { modules.insert("itertools".to_string(), Box::new(itertools::make_module)); modules.insert("json".to_string(), Box::new(json::make_module)); modules.insert("keyword".to_string(), Box::new(keyword::make_module)); + modules.insert("marshal".to_string(), Box::new(marshal::make_module)); modules.insert("math".to_string(), Box::new(math::make_module)); modules.insert("platform".to_string(), Box::new(platform::make_module)); modules.insert("re".to_string(), Box::new(re::make_module)); From bd89c4e32b28b6b907e9ee72ae437137b025479a Mon Sep 17 00:00:00 2001 From: Daniel Shaulov Date: Mon, 27 May 2019 22:19:07 +0300 Subject: [PATCH 665/884] Reformat with rustfmt --- parser/src/ast.rs | 2 +- parser/src/lexer.rs | 2 +- vm/src/bytecode.rs | 2 +- vm/src/stdlib/marshal.rs | 10 +++++----- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/parser/src/ast.rs b/parser/src/ast.rs index cf7676a3b7..1b985db2db 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -4,7 +4,7 @@ pub use super::lexer::Location; use num_bigint::BigInt; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; /* #[derive(Debug)] diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 269864daf9..1fe74e3e13 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -7,12 +7,12 @@ extern crate unicode_xid; pub use super::token::Tok; use num_bigint::BigInt; use num_traits::Num; +use serde::{Deserialize, Serialize}; use std::cmp::Ordering; use std::collections::HashMap; use std::str::FromStr; use unic_emoji_char::is_emoji_presentation; use unicode_xid::UnicodeXID; -use serde::{Serialize, Deserialize}; #[derive(Clone, Copy, PartialEq, Debug)] struct IndentationLevel { diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index 5291cd0c3d..fb8cab98bc 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -8,9 +8,9 @@ use num_bigint::BigInt; use num_complex::Complex64; use rustpython_parser::ast; +use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fmt; -use serde::{Serialize, Deserialize}; /// Primary container of a single code object. Each python function has /// a codeobject. Also a module has a codeobject. diff --git a/vm/src/stdlib/marshal.rs b/vm/src/stdlib/marshal.rs index 0475598811..e7afc86d2a 100644 --- a/vm/src/stdlib/marshal.rs +++ b/vm/src/stdlib/marshal.rs @@ -1,8 +1,8 @@ -use crate::obj::objcode::{PyCodeRef, PyCode}; -use crate::pyobject::{PyObjectRef, PyResult, IntoPyObject}; -use crate::vm::VirtualMachine; -use crate::obj::objbytes::{PyBytes, PyBytesRef}; use crate::bytecode; +use crate::obj::objbytes::{PyBytes, PyBytesRef}; +use crate::obj::objcode::{PyCode, PyCodeRef}; +use crate::pyobject::{IntoPyObject, PyObjectRef, PyResult}; +use crate::vm::VirtualMachine; fn marshal_dumps(co: PyCodeRef, vm: &VirtualMachine) -> PyResult { PyBytes::new(bincode::serialize(&co.code).unwrap()).into_pyobject(vm) @@ -21,4 +21,4 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "loads" => ctx.new_rustfunc(marshal_loads), "dumps" => ctx.new_rustfunc(marshal_dumps) }) -} \ No newline at end of file +} From 7be801db2f9d8b671387aa400d3e30e2f6f972e2 Mon Sep 17 00:00:00 2001 From: rbrtberglund Date: Sun, 26 May 2019 00:42:01 +0200 Subject: [PATCH 666/884] implemented __setitem__ for bytearray --- tests/snippets/bytearray.py | 15 +++++++++ vm/src/obj/objbytearray.rs | 10 ++++++ vm/src/obj/objbyteinner.rs | 63 +++++++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index dff0852c41..d3930c9cc6 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -691,3 +691,18 @@ a = bytearray(b'hello, world') a.reverse() assert a == bytearray(b'dlrow ,olleh') + +# __setitem__ +a = bytearray(b'test') +a[0] = 1 +assert a == bytearray(b'\x01est') +with assertRaises(TypeError): + a[0] = b'a' +with assertRaises(TypeError): + a[0] = memoryview(b'a') +a[:2] = [0, 9] +assert a == bytearray(b'\x00\x09st') +a[1:3] = b'test' +assert a == bytearray(b'\x00testt') +a[:6] = memoryview(b'test') +assert a == bytearray(b'test') \ No newline at end of file diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index e5078bdbbc..2c72a3f462 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -166,6 +166,16 @@ impl PyByteArrayRef { self.inner.borrow().getitem(needle, vm) } + #[pymethod(name = "__setitem__")] + fn setitem( + self, + needle: Either, + value: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + self.inner.borrow_mut().setitem(needle, value, vm) + } + #[pymethod(name = "isalnum")] fn isalnum(self, vm: &VirtualMachine) -> PyResult { self.inner.borrow().isalnum(vm) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index c07470be26..8dca0d33a4 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -426,6 +426,69 @@ impl PyByteInner { } } + pub fn setitem( + &mut self, + needle: Either, + object: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + match needle { + Either::A(int) => { + if let Some(idx) = self.elements.get_pos(int.as_bigint().to_i32().unwrap()) { + let result = match_class!(object, + i @ PyInt => { + if let Some(value) = i.as_bigint().to_u8() { + Ok(value) + }else{ + Err(vm.new_value_error("byte must be in range(0, 256)".to_string())) + } + }, + _ => {Err(vm.new_type_error("an integer is required".to_string()))} + ); + match result { + Ok(value) => { + self.elements[idx] = value; + Ok(vm.new_int(value)) + } + Err(error) => Err(error), + } + } else { + Err(vm.new_index_error("index out of range".to_string())) + } + } + Either::B(slice) => { + let sec = match PyIterable::try_from_object(vm, object.clone()) { + Ok(sec) => { + let items: Result, _> = sec.iter(vm)?.collect(); + Ok(items? + .into_iter() + .map(|obj| u8::try_from_object(vm, obj)) + .collect::>>()?) + } + _ => match_class!(object, + i @ PyMemoryView => { + Ok(i.get_obj_value().unwrap()) + }, + _ => Err(vm.new_index_error( + "can assign only bytes, buffers, or iterables of ints in range(0, 256)" + .to_string()))), + }; + match sec { + Ok(items) => { + let range = self + .elements + .get_slice_range(&slice.start_index(vm)?, &slice.stop_index(vm)?); + self.elements.splice(range, items); + Ok(vm + .ctx + .new_bytes(self.elements.get_slice_items(vm, slice.as_object())?)) + } + Err(error) => Err(error), + } + } + } + } + pub fn isalnum(&self, vm: &VirtualMachine) -> PyResult { Ok(vm.new_bool( !self.elements.is_empty() From 9b4b2d7fa6b9196f558ef063da9c01db13133c04 Mon Sep 17 00:00:00 2001 From: rbrtberglund Date: Mon, 27 May 2019 22:48:25 +0200 Subject: [PATCH 667/884] fixed SyntaxError: Got unexpected EOF in bytearray.py --- tests/snippets/bytearray.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/snippets/bytearray.py b/tests/snippets/bytearray.py index d3930c9cc6..f69edeac1b 100644 --- a/tests/snippets/bytearray.py +++ b/tests/snippets/bytearray.py @@ -705,4 +705,4 @@ a[1:3] = b'test' assert a == bytearray(b'\x00testt') a[:6] = memoryview(b'test') -assert a == bytearray(b'test') \ No newline at end of file +assert a == bytearray(b'test') From eed9c44a74319b54bb1f61096cc61ef937b48e8a Mon Sep 17 00:00:00 2001 From: rbrtberglund Date: Wed, 29 May 2019 00:02:58 +0200 Subject: [PATCH 668/884] improved PyByteInner.setitem --- vm/src/obj/objbyteinner.rs | 99 +++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 49 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 8dca0d33a4..e420fa038b 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -426,66 +426,67 @@ impl PyByteInner { } } - pub fn setitem( + fn setindex(&mut self, int: PyIntRef, object: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if let Some(idx) = self.elements.get_pos(int.as_bigint().to_i32().unwrap()) { + let result = match_class!(object, + i @ PyInt => { + if let Some(value) = i.as_bigint().to_u8() { + Ok(value) + }else{ + Err(vm.new_value_error("byte must be in range(0, 256)".to_string())) + } + }, + _ => {Err(vm.new_type_error("an integer is required".to_string()))} + ); + let value = result?; + self.elements[idx] = value; + Ok(vm.new_int(value)) + } else { + Err(vm.new_index_error("index out of range".to_string())) + } + } + + fn setslice( &mut self, - needle: Either, + slice: PySliceRef, object: PyObjectRef, vm: &VirtualMachine, ) -> PyResult { - match needle { - Either::A(int) => { - if let Some(idx) = self.elements.get_pos(int.as_bigint().to_i32().unwrap()) { - let result = match_class!(object, - i @ PyInt => { - if let Some(value) = i.as_bigint().to_u8() { - Ok(value) - }else{ - Err(vm.new_value_error("byte must be in range(0, 256)".to_string())) - } - }, - _ => {Err(vm.new_type_error("an integer is required".to_string()))} - ); - match result { - Ok(value) => { - self.elements[idx] = value; - Ok(vm.new_int(value)) - } - Err(error) => Err(error), - } - } else { - Err(vm.new_index_error("index out of range".to_string())) - } + let sec = match PyIterable::try_from_object(vm, object.clone()) { + Ok(sec) => { + let items: Result, _> = sec.iter(vm)?.collect(); + Ok(items? + .into_iter() + .map(|obj| u8::try_from_object(vm, obj)) + .collect::>>()?) } - Either::B(slice) => { - let sec = match PyIterable::try_from_object(vm, object.clone()) { - Ok(sec) => { - let items: Result, _> = sec.iter(vm)?.collect(); - Ok(items? - .into_iter() - .map(|obj| u8::try_from_object(vm, obj)) - .collect::>>()?) - } - _ => match_class!(object, + _ => match_class!(object, i @ PyMemoryView => { Ok(i.get_obj_value().unwrap()) }, _ => Err(vm.new_index_error( "can assign only bytes, buffers, or iterables of ints in range(0, 256)" .to_string()))), - }; - match sec { - Ok(items) => { - let range = self - .elements - .get_slice_range(&slice.start_index(vm)?, &slice.stop_index(vm)?); - self.elements.splice(range, items); - Ok(vm - .ctx - .new_bytes(self.elements.get_slice_items(vm, slice.as_object())?)) - } - Err(error) => Err(error), - } - } + }; + let items = sec?; + let range = self + .elements + .get_slice_range(&slice.start_index(vm)?, &slice.stop_index(vm)?); + self.elements.splice(range, items); + Ok(vm + .ctx + .new_bytes(self.elements.get_slice_items(vm, slice.as_object())?)) + } + + pub fn setitem( + &mut self, + needle: Either, + object: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + match needle { + Either::A(int) => self.setindex(int, object, vm), + Either::B(slice) => self.setslice(slice, object, vm), } } From 3675ce859a8fc2fe914507368de8c560b395386c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Sat, 11 May 2019 15:13:17 +0900 Subject: [PATCH 669/884] Add sys.getfilesystemencoding, sys.getfilesystemencodeerrors --- tests/snippets/sysmod.py | 3 +++ vm/src/sysmodule.rs | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/tests/snippets/sysmod.py b/tests/snippets/sysmod.py index 409d961edb..b8b9bed9fa 100644 --- a/tests/snippets/sysmod.py +++ b/tests/snippets/sysmod.py @@ -10,3 +10,6 @@ assert isinstance(sys.implementation.name, str) assert isinstance(sys.implementation.cache_tag, str) + +assert sys.getfilesystemencoding() == 'utf-8' +assert sys.getfilesystemencodeerrors().startswith('surrogate') diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index aefdb117b2..ec0f3d4330 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -40,6 +40,21 @@ fn sys_getsizeof(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_int(size)) } +fn sys_getfilesystemencoding(_vm: &VirtualMachine) -> String { + // TODO: implmement non-utf-8 mode. + "utf-8".to_string() +} + +#[cfg(not(windows))] +fn sys_getfilesystemencodeerrors(_vm: &VirtualMachine) -> String { + "surrogateescape".to_string() +} + +#[cfg(windows)] +fn sys_getfilesystemencodeerrors(_vm: &VirtualMachine) -> String { + "surrogatepass".to_string() +} + // TODO implement string interning, this will be key for performance fn sys_intern(value: PyStringRef, _vm: &VirtualMachine) -> PyStringRef { value @@ -161,6 +176,8 @@ settrace() -- set the global debug tracing function "getrefcount" => ctx.new_rustfunc(sys_getrefcount), "getsizeof" => ctx.new_rustfunc(sys_getsizeof), "implementation" => implementation, + "getfilesystemencoding" => ctx.new_rustfunc(sys_getfilesystemencoding), + "getfilesystemencodeerrors" => ctx.new_rustfunc(sys_getfilesystemencodeerrors), "intern" => ctx.new_rustfunc(sys_intern), "maxsize" => ctx.new_int(std::usize::MAX), "path" => path, From ad357d08af064ff6611760467a9450321f50c6fe Mon Sep 17 00:00:00 2001 From: jgirardet Date: Tue, 7 May 2019 01:42:16 +0900 Subject: [PATCH 670/884] normalize_encoding --- vm/src/obj/objbyteinner.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index e420fa038b..ee7ad3d3a8 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -74,6 +74,25 @@ pub struct ByteInnerNewOptions { encoding: OptionalArg, } +//same algorithm as cpython +pub fn normalize_encoding(encoding: &str) -> String { + let mut res = String::new(); + let mut punct = false; + + for c in encoding.chars() { + if c.is_alphanumeric() || c == '.' { + if punct && !res.is_empty() { + res.push('_') + } + res.push(c.to_ascii_lowercase()); + punct = false; + } else { + punct = true; + } + } + res +} + impl ByteInnerNewOptions { pub fn get_value(self, vm: &VirtualMachine) -> PyResult { // First handle bytes(string, encoding[, errors]) From 7f2560c9e1b89d63559870048b6469711400a336 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Wed, 1 May 2019 13:48:36 +0900 Subject: [PATCH 671/884] Add str.encode for utf-8 --- tests/snippets/strings.py | 10 ++++++++++ vm/src/function.rs | 11 +++++++++++ vm/src/obj/objbyteinner.rs | 26 +++++++++++++++----------- vm/src/obj/objstr.rs | 26 ++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 11 deletions(-) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 786622e9f5..aaeaed5f3e 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -206,3 +206,13 @@ def try_mutate_str(): word[0] = 'x' assert_raises(TypeError, try_mutate_str) + +ss = ['Hello', '안녕', '👋'] +bs = [b'Hello', b'\xec\x95\x88\xeb\x85\x95', b'\xf0\x9f\x91\x8b'] + +for s, b in zip(ss, bs): + assert s.encode() == b + +for s, b, e in zip(ss, bs, ['u8', 'U8', 'utf-8', 'UTF-8', 'utf_8']): + assert s.encode(e) == b + # assert s.encode(encoding=e) == b diff --git a/vm/src/function.rs b/vm/src/function.rs index 47fc051d59..22802ac1fa 100644 --- a/vm/src/function.rs +++ b/vm/src/function.rs @@ -374,6 +374,17 @@ impl OptionalArg { Missing => f(), } } + + pub fn map_or_else(self, default: D, f: F) -> U + where + D: FnOnce() -> U, + F: FnOnce(T) -> U, + { + match self { + Present(value) => f(value), + Missing => default(), + } + } } impl FromArgs for OptionalArg diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index ee7ad3d3a8..85bedd3017 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -93,6 +93,18 @@ pub fn normalize_encoding(encoding: &str) -> String { res } +pub fn encode_to_vec(value: &str, encoding: &str, vm: &VirtualMachine) -> PyResult> { + let encoding = normalize_encoding(encoding); + if encoding == "utf_8" || encoding == "u8" { + Ok(value.as_bytes().to_vec()) + } else { + // TODO: different encoding + return Err( + vm.new_value_error(format!("unknown encoding: {}", encoding)), //should be lookup error + ); + } +} + impl ByteInnerNewOptions { pub fn get_value(self, vm: &VirtualMachine) -> PyResult { // First handle bytes(string, encoding[, errors]) @@ -100,17 +112,9 @@ impl ByteInnerNewOptions { if let OptionalArg::Present(eval) = self.val_option { if let Ok(input) = eval.downcast::() { let encoding = enc.as_str(); - if encoding.to_lowercase() == "utf8" || encoding.to_lowercase() == "utf-8" - // TODO: different encoding - { - return Ok(PyByteInner { - elements: input.value.as_bytes().to_vec(), - }); - } else { - return Err( - vm.new_value_error(format!("unknown encoding: {}", encoding)), //should be lookup error - ); - } + return Ok(PyByteInner { + elements: encode_to_vec(&input.value, &encoding, vm)?, + }); } else { return Err(vm.new_type_error("encoding without a string argument".to_string())); } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 6545c99858..827b109f03 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -19,6 +19,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; +use super::objbyteinner; use super::objdict::PyDict; use super::objint::{self, PyInt}; use super::objnone::PyNone; @@ -957,6 +958,31 @@ impl PyString { } } } + + #[pymethod] + fn encode( + &self, + encoding: OptionalArg, + _errors: OptionalArg, + vm: &VirtualMachine, + ) -> PyResult { + let encoding = encoding.map_or_else( + || Ok("utf-8".to_string()), + |v| { + if objtype::isinstance(&v, &vm.ctx.str_type()) { + Ok(get_value(&v)) + } else { + Err(vm.new_type_error(format!( + "encode() argument 1 must be str, not {}", + v.class().name + ))) + } + }, + )?; + + let encoded = objbyteinner::encode_to_vec(&self.value, &encoding, vm)?; + Ok(vm.ctx.new_bytes(encoded)) + } } impl PyValue for PyString { From 59476c65bb19a151dd0e5e795e8119f5aadeef64 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 7 May 2019 02:05:46 +0900 Subject: [PATCH 672/884] PyBytes::from_string --- vm/src/obj/objbyteinner.rs | 32 ++++++++++++++++---------------- vm/src/obj/objbytes.rs | 7 +++++++ vm/src/obj/objstr.rs | 6 +++--- 3 files changed, 26 insertions(+), 19 deletions(-) diff --git a/vm/src/obj/objbyteinner.rs b/vm/src/obj/objbyteinner.rs index 85bedd3017..32a58255f8 100644 --- a/vm/src/obj/objbyteinner.rs +++ b/vm/src/obj/objbyteinner.rs @@ -93,28 +93,14 @@ pub fn normalize_encoding(encoding: &str) -> String { res } -pub fn encode_to_vec(value: &str, encoding: &str, vm: &VirtualMachine) -> PyResult> { - let encoding = normalize_encoding(encoding); - if encoding == "utf_8" || encoding == "u8" { - Ok(value.as_bytes().to_vec()) - } else { - // TODO: different encoding - return Err( - vm.new_value_error(format!("unknown encoding: {}", encoding)), //should be lookup error - ); - } -} - impl ByteInnerNewOptions { pub fn get_value(self, vm: &VirtualMachine) -> PyResult { // First handle bytes(string, encoding[, errors]) if let OptionalArg::Present(enc) = self.encoding { if let OptionalArg::Present(eval) = self.val_option { if let Ok(input) = eval.downcast::() { - let encoding = enc.as_str(); - return Ok(PyByteInner { - elements: encode_to_vec(&input.value, &encoding, vm)?, - }); + let inner = PyByteInner::from_string(&input.value, enc.as_str(), vm)?; + return Ok(inner); } else { return Err(vm.new_type_error("encoding without a string argument".to_string())); } @@ -334,6 +320,20 @@ impl ByteInnerSplitlinesOptions { } impl PyByteInner { + pub fn from_string(value: &str, encoding: &str, vm: &VirtualMachine) -> PyResult { + let normalized = normalize_encoding(encoding); + if normalized == "utf_8" || normalized == "utf8" || normalized == "u8" { + Ok(PyByteInner { + elements: value.as_bytes().to_vec(), + }) + } else { + // TODO: different encoding + Err( + vm.new_value_error(format!("unknown encoding: {}", encoding)), // should be lookup error + ) + } + } + pub fn repr(&self) -> PyResult { let mut res = String::with_capacity(self.elements.len()); for i in self.elements.iter() { diff --git a/vm/src/obj/objbytes.rs b/vm/src/obj/objbytes.rs index dd2e1518c0..33016e511a 100644 --- a/vm/src/obj/objbytes.rs +++ b/vm/src/obj/objbytes.rs @@ -45,6 +45,13 @@ impl PyBytes { inner: PyByteInner { elements }, } } + + pub fn from_string(value: &str, encoding: &str, vm: &VirtualMachine) -> PyResult { + Ok(PyBytes { + inner: PyByteInner::from_string(value, encoding, vm)?, + }) + } + pub fn get_value(&self) -> &[u8] { &self.inner.elements } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 827b109f03..b0d19e9c3f 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -19,7 +19,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; -use super::objbyteinner; +use super::objbytes::PyBytes; use super::objdict::PyDict; use super::objint::{self, PyInt}; use super::objnone::PyNone; @@ -980,8 +980,8 @@ impl PyString { }, )?; - let encoded = objbyteinner::encode_to_vec(&self.value, &encoding, vm)?; - Ok(vm.ctx.new_bytes(encoded)) + let encoded = PyBytes::from_string(&self.value, &encoding, vm)?; + Ok(encoded.into_pyobject(vm)?) } } From 89729c3155791fceb562d1cf592e6f1d6f34f6c5 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Tue, 14 May 2019 04:22:13 +0900 Subject: [PATCH 673/884] PyClassImpl for PySet, PyFrozenSet --- vm/src/obj/objset.rs | 307 ++++++++++++++++++++++--------------------- 1 file changed, 159 insertions(+), 148 deletions(-) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index e404fa9249..d59bf388d0 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -8,8 +8,8 @@ use std::fmt; use crate::dictdatatype; use crate::function::OptionalArg; use crate::pyobject::{ - PyContext, PyIterable, PyObject, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, - TypeProtocol, + PyClassImpl, PyContext, PyIterable, PyObject, PyObjectRef, PyRef, PyResult, PyValue, + TryFromObject, TypeProtocol, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -19,12 +19,22 @@ use super::objtype::PyClassRef; pub type SetContentType = dictdatatype::Dict<()>; +/// set() -> new empty set object +/// set(iterable) -> new set object +/// +/// Build an unordered collection of unique elements. +#[pyclass] #[derive(Default)] pub struct PySet { inner: RefCell, } pub type PySetRef = PyRef; +/// frozenset() -> empty frozenset object +/// frozenset(iterable) -> frozenset object +/// +/// Build an immutable unordered collection of unique elements. +#[pyclass] #[derive(Default)] pub struct PyFrozenSet { inner: PySetInner, @@ -293,7 +303,9 @@ impl PySetInner { } } -impl PySetRef { +#[pyimpl] +impl PySet { + #[pymethod(name = "__new__")] fn new( cls: PyClassRef, iterable: OptionalArg, @@ -305,21 +317,25 @@ impl PySetRef { .into_ref_with_type(vm, cls) } - fn len(self, _vm: &VirtualMachine) -> usize { + #[pymethod(name = "__len__")] + fn len(&self, _vm: &VirtualMachine) -> usize { self.inner.borrow().len() } - fn copy(self, _vm: &VirtualMachine) -> PySet { + #[pymethod] + fn copy(&self, _vm: &VirtualMachine) -> PySet { PySet { inner: RefCell::new(self.inner.borrow().copy()), } } - fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__contains__")] + fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.inner.borrow().contains(&needle, vm) } - fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__eq__")] + fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, set @ PySet => self.inner.borrow().eq(&set.inner.borrow(), vm), frozen @ PyFrozenSet => self.inner.borrow().eq(&frozen.inner, vm), @@ -327,7 +343,8 @@ impl PySetRef { ) } - fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__ge__")] + fn ge(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, set @ PySet => self.inner.borrow().ge(&set.inner.borrow(), vm), frozen @ PyFrozenSet => self.inner.borrow().ge(&frozen.inner, vm), @@ -335,7 +352,8 @@ impl PySetRef { ) } - fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__gt__")] + fn gt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, set @ PySet => self.inner.borrow().gt(&set.inner.borrow(), vm), frozen @ PyFrozenSet => self.inner.borrow().gt(&frozen.inner, vm), @@ -343,7 +361,8 @@ impl PySetRef { ) } - fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__le__")] + fn le(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, set @ PySet => self.inner.borrow().le(&set.inner.borrow(), vm), frozen @ PyFrozenSet => self.inner.borrow().le(&frozen.inner, vm), @@ -351,7 +370,8 @@ impl PySetRef { ) } - fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__lt__")] + fn lt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, set @ PySet => self.inner.borrow().lt(&set.inner.borrow(), vm), frozen @ PyFrozenSet => self.inner.borrow().lt(&frozen.inner, vm), @@ -359,7 +379,8 @@ impl PySetRef { ) } - fn union(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn union(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { inner: RefCell::new(self.inner.borrow().union(other, vm)?), @@ -369,7 +390,8 @@ impl PySetRef { )) } - fn intersection(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn intersection(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { inner: RefCell::new(self.inner.borrow().intersection(other, vm)?), @@ -379,7 +401,8 @@ impl PySetRef { )) } - fn difference(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { inner: RefCell::new(self.inner.borrow().difference(other, vm)?), @@ -389,7 +412,8 @@ impl PySetRef { )) } - fn symmetric_difference(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn symmetric_difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PySet { inner: RefCell::new(self.inner.borrow().symmetric_difference(other, vm)?), @@ -399,35 +423,42 @@ impl PySetRef { )) } - fn isdisjoint(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn isdisjoint(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner.borrow().isdisjoint(other, vm) } - fn or(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__or__")] + fn or(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.union(other.iterable, vm) } - fn and(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__and__")] + fn and(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.intersection(other.iterable, vm) } - fn sub(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__sub__")] + fn sub(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.difference(other.iterable, vm) } - fn xor(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__xor__")] + fn xor(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.symmetric_difference(other.iterable, vm) } - fn iter(self, vm: &VirtualMachine) -> PyListIterator { + #[pymethod(name = "__iter__")] + fn iter(&self, vm: &VirtualMachine) -> PyListIterator { self.inner.borrow().iter(vm) } - fn repr(self, vm: &VirtualMachine) -> PyResult { - let inner = self.inner.borrow(); + #[pymethod(name = "__repr__")] + fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + let inner = zelf.inner.borrow(); let s = if inner.len() == 0 { "set()".to_string() - } else if let Some(_guard) = ReprGuard::enter(self.as_object()) { + } else if let Some(_guard) = ReprGuard::enter(zelf.as_object()) { inner.repr(vm)? } else { "set(...)".to_string() @@ -435,78 +466,108 @@ impl PySetRef { Ok(vm.new_str(s)) } - pub fn add(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + #[pymethod] + pub fn add(&self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self.inner.borrow_mut().add(&item, vm)?; Ok(()) } - fn remove(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + #[pymethod] + fn remove(&self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self.inner.borrow_mut().remove(&item, vm) } - fn discard(self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { + #[pymethod] + fn discard(&self, item: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> { self.inner.borrow_mut().discard(&item, vm)?; Ok(()) } - fn clear(self, _vm: &VirtualMachine) { + #[pymethod] + fn clear(&self, _vm: &VirtualMachine) { self.inner.borrow_mut().clear() } - fn pop(self, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn pop(&self, vm: &VirtualMachine) -> PyResult { self.inner.borrow_mut().pop(vm) } - fn ior(self, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { - self.inner.borrow_mut().update(iterable.iterable, vm)?; - Ok(self.as_object().clone()) + #[pymethod(name = "__ior__")] + fn ior(zelf: PyRef, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { + zelf.inner.borrow_mut().update(iterable.iterable, vm)?; + Ok(zelf.as_object().clone()) } - fn update(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn update(&self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner.borrow_mut().update(iterable, vm)?; Ok(vm.get_none()) } - fn intersection_update(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn intersection_update(&self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner.borrow_mut().intersection_update(iterable, vm)?; Ok(vm.get_none()) } - fn iand(self, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { - self.inner + #[pymethod(name = "__iand__")] + fn iand(zelf: PyRef, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { + zelf.inner .borrow_mut() .intersection_update(iterable.iterable, vm)?; - Ok(self.as_object().clone()) + Ok(zelf.as_object().clone()) } - fn difference_update(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn difference_update(&self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner.borrow_mut().difference_update(iterable, vm)?; Ok(vm.get_none()) } - fn isub(self, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { - self.inner + #[pymethod(name = "__isub__")] + fn isub(zelf: PyRef, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { + zelf.inner .borrow_mut() .difference_update(iterable.iterable, vm)?; - Ok(self.as_object().clone()) + Ok(zelf.as_object().clone()) } - fn symmetric_difference_update(self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn symmetric_difference_update(&self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner .borrow_mut() .symmetric_difference_update(iterable, vm)?; Ok(vm.get_none()) } - fn ixor(self, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { - self.inner + #[pymethod(name = "__ixor__")] + fn ixor(zelf: PyRef, iterable: SetIterable, vm: &VirtualMachine) -> PyResult { + zelf.inner .borrow_mut() .symmetric_difference_update(iterable.iterable, vm)?; - Ok(self.as_object().clone()) + Ok(zelf.as_object().clone()) + } + + #[pymethod] + fn issubset(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.le(other, vm) + } + + #[pymethod] + fn issuperset(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.ge(other, vm) + } + + #[pymethod(name = "__hash__")] + fn hash(&self, vm: &VirtualMachine) -> PyResult<()> { + Err(vm.new_type_error("unhashable type".to_string())) } } -impl PyFrozenSetRef { +#[pyimpl] +impl PyFrozenSet { + #[pymethod(name = "__new__")] fn new( cls: PyClassRef, iterable: OptionalArg, @@ -518,21 +579,25 @@ impl PyFrozenSetRef { .into_ref_with_type(vm, cls) } - fn len(self, _vm: &VirtualMachine) -> usize { + #[pymethod(name = "__len__")] + fn len(&self, _vm: &VirtualMachine) -> usize { self.inner.len() } - fn copy(self, _vm: &VirtualMachine) -> PyFrozenSet { + #[pymethod] + fn copy(&self, _vm: &VirtualMachine) -> PyFrozenSet { PyFrozenSet { inner: self.inner.copy(), } } - fn contains(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__contains__")] + fn contains(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { self.inner.contains(&needle, vm) } - fn eq(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__eq__")] + fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, set @ PySet => self.inner.eq(&set.inner.borrow(), vm), frozen @ PyFrozenSet => self.inner.eq(&frozen.inner, vm), @@ -540,7 +605,8 @@ impl PyFrozenSetRef { ) } - fn ge(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__ge__")] + fn ge(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, set @ PySet => self.inner.ge(&set.inner.borrow(), vm), frozen @ PyFrozenSet => self.inner.ge(&frozen.inner, vm), @@ -548,7 +614,8 @@ impl PyFrozenSetRef { ) } - fn gt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__gt__")] + fn gt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, set @ PySet => self.inner.gt(&set.inner.borrow(), vm), frozen @ PyFrozenSet => self.inner.gt(&frozen.inner, vm), @@ -556,7 +623,8 @@ impl PyFrozenSetRef { ) } - fn le(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__le__")] + fn le(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, set @ PySet => self.inner.le(&set.inner.borrow(), vm), frozen @ PyFrozenSet => self.inner.le(&frozen.inner, vm), @@ -564,7 +632,8 @@ impl PyFrozenSetRef { ) } - fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__lt__")] + fn lt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { match_class!(other, set @ PySet => self.inner.lt(&set.inner.borrow(), vm), frozen @ PyFrozenSet => self.inner.lt(&frozen.inner, vm), @@ -572,7 +641,8 @@ impl PyFrozenSetRef { ) } - fn union(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn union(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { inner: self.inner.union(other, vm)?, @@ -582,7 +652,8 @@ impl PyFrozenSetRef { )) } - fn intersection(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn intersection(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { inner: self.inner.intersection(other, vm)?, @@ -592,7 +663,8 @@ impl PyFrozenSetRef { )) } - fn difference(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { inner: self.inner.difference(other, vm)?, @@ -602,7 +674,8 @@ impl PyFrozenSetRef { )) } - fn symmetric_difference(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn symmetric_difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { Ok(PyObject::new( PyFrozenSet { inner: self.inner.symmetric_difference(other, vm)?, @@ -612,41 +685,58 @@ impl PyFrozenSetRef { )) } - fn isdisjoint(self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod] + fn isdisjoint(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner.isdisjoint(other, vm) } - fn or(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__or__")] + fn or(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.union(other.iterable, vm) } - fn and(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__and__")] + fn and(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.intersection(other.iterable, vm) } - fn sub(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__sub__")] + fn sub(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.difference(other.iterable, vm) } - fn xor(self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__xor__")] + fn xor(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.symmetric_difference(other.iterable, vm) } - fn iter(self, vm: &VirtualMachine) -> PyListIterator { + #[pymethod(name = "__iter__")] + fn iter(&self, vm: &VirtualMachine) -> PyListIterator { self.inner.iter(vm) } - fn repr(self, vm: &VirtualMachine) -> PyResult { - let inner = &self.inner; + #[pymethod(name = "__repr__")] + fn repr(zelf: PyRef, vm: &VirtualMachine) -> PyResult { + let inner = &zelf.inner; let s = if inner.len() == 0 { "frozenset()".to_string() - } else if let Some(_guard) = ReprGuard::enter(self.as_object()) { + } else if let Some(_guard) = ReprGuard::enter(zelf.as_object()) { format!("frozenset({})", inner.repr(vm)?) } else { "frozenset(...)".to_string() }; Ok(vm.new_str(s)) } + + #[pymethod] + fn issubset(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.le(other, vm) + } + + #[pymethod] + fn issuperset(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + self.ge(other, vm) + } } struct SetIterable { @@ -670,86 +760,7 @@ impl TryFromObject for SetIterable { } } -fn set_hash(_zelf: PySetRef, vm: &VirtualMachine) -> PyResult<()> { - Err(vm.new_type_error("unhashable type".to_string())) -} - pub fn init(context: &PyContext) { - let set_type = &context.set_type; - - let set_doc = "set() -> new empty set object\n\ - set(iterable) -> new set object\n\n\ - Build an unordered collection of unique elements."; - - extend_class!(context, set_type, { - "__hash__" => context.new_rustfunc(set_hash), - "__contains__" => context.new_rustfunc(PySetRef::contains), - "__len__" => context.new_rustfunc(PySetRef::len), - "__new__" => context.new_rustfunc(PySetRef::new), - "__repr__" => context.new_rustfunc(PySetRef::repr), - "__eq__" => context.new_rustfunc(PySetRef::eq), - "__ge__" => context.new_rustfunc(PySetRef::ge), - "__gt__" => context.new_rustfunc(PySetRef::gt), - "__le__" => context.new_rustfunc(PySetRef::le), - "__lt__" => context.new_rustfunc(PySetRef::lt), - "issubset" => context.new_rustfunc(PySetRef::le), - "issuperset" => context.new_rustfunc(PySetRef::ge), - "union" => context.new_rustfunc(PySetRef::union), - "__or__" => context.new_rustfunc(PySetRef::or), - "intersection" => context.new_rustfunc(PySetRef::intersection), - "__and__" => context.new_rustfunc(PySetRef::and), - "difference" => context.new_rustfunc(PySetRef::difference), - "__sub__" => context.new_rustfunc(PySetRef::sub), - "symmetric_difference" => context.new_rustfunc(PySetRef::symmetric_difference), - "__xor__" => context.new_rustfunc(PySetRef::xor), - "__doc__" => context.new_str(set_doc.to_string()), - "add" => context.new_rustfunc(PySetRef::add), - "remove" => context.new_rustfunc(PySetRef::remove), - "discard" => context.new_rustfunc(PySetRef::discard), - "clear" => context.new_rustfunc(PySetRef::clear), - "copy" => context.new_rustfunc(PySetRef::copy), - "pop" => context.new_rustfunc(PySetRef::pop), - "update" => context.new_rustfunc(PySetRef::update), - "__ior__" => context.new_rustfunc(PySetRef::ior), - "intersection_update" => context.new_rustfunc(PySetRef::intersection_update), - "__iand__" => context.new_rustfunc(PySetRef::iand), - "difference_update" => context.new_rustfunc(PySetRef::difference_update), - "__isub__" => context.new_rustfunc(PySetRef::isub), - "symmetric_difference_update" => context.new_rustfunc(PySetRef::symmetric_difference_update), - "__ixor__" => context.new_rustfunc(PySetRef::ixor), - "__iter__" => context.new_rustfunc(PySetRef::iter), - "isdisjoint" => context.new_rustfunc(PySetRef::isdisjoint), - }); - - let frozenset_type = &context.frozenset_type; - - let frozenset_doc = "frozenset() -> empty frozenset object\n\ - frozenset(iterable) -> frozenset object\n\n\ - Build an immutable unordered collection of unique elements."; - - extend_class!(context, frozenset_type, { - "__new__" => context.new_rustfunc(PyFrozenSetRef::new), - "__eq__" => context.new_rustfunc(PyFrozenSetRef::eq), - "__ge__" => context.new_rustfunc(PyFrozenSetRef::ge), - "__gt__" => context.new_rustfunc(PyFrozenSetRef::gt), - "__le__" => context.new_rustfunc(PyFrozenSetRef::le), - "__lt__" => context.new_rustfunc(PyFrozenSetRef::lt), - "issubset" => context.new_rustfunc(PyFrozenSetRef::le), - "issuperset" => context.new_rustfunc(PyFrozenSetRef::ge), - "union" => context.new_rustfunc(PyFrozenSetRef::union), - "__or__" => context.new_rustfunc(PyFrozenSetRef::or), - "intersection" => context.new_rustfunc(PyFrozenSetRef::intersection), - "__and__" => context.new_rustfunc(PyFrozenSetRef::and), - "difference" => context.new_rustfunc(PyFrozenSetRef::difference), - "__sub__" => context.new_rustfunc(PyFrozenSetRef::sub), - "symmetric_difference" => context.new_rustfunc(PyFrozenSetRef::symmetric_difference), - "__xor__" => context.new_rustfunc(PyFrozenSetRef::xor), - "__contains__" => context.new_rustfunc(PyFrozenSetRef::contains), - "__len__" => context.new_rustfunc(PyFrozenSetRef::len), - "__doc__" => context.new_str(frozenset_doc.to_string()), - "__repr__" => context.new_rustfunc(PyFrozenSetRef::repr), - "copy" => context.new_rustfunc(PyFrozenSetRef::copy), - "__iter__" => context.new_rustfunc(PyFrozenSetRef::iter), - "isdisjoint" => context.new_rustfunc(PyFrozenSetRef::isdisjoint), - }); + PySet::extend_class(context, &context.set_type); + PyFrozenSet::extend_class(context, &context.frozenset_type); } From 31c88721e6513c5bd9ef6c83e03b8a592027ce6c Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 16 May 2019 03:06:39 +0900 Subject: [PATCH 674/884] Refactor PySet with try_set_inner! --- vm/src/obj/objset.rs | 70 +++++++++++++------------------------------- 1 file changed, 20 insertions(+), 50 deletions(-) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index d59bf388d0..4aeacc0c0c 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -303,6 +303,16 @@ impl PySetInner { } } +macro_rules! try_set_inner { + ($vm:expr, $other:expr, $op:expr) => { + match_class!($other, + set @ PySet => $op(&*set.inner.borrow()), + frozen @ PyFrozenSet => $op(&frozen.inner), + other => Err($vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class()))), + ); + }; +} + #[pyimpl] impl PySet { #[pymethod(name = "__new__")] @@ -336,47 +346,27 @@ impl PySet { #[pymethod(name = "__eq__")] fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - set @ PySet => self.inner.borrow().eq(&set.inner.borrow(), vm), - frozen @ PyFrozenSet => self.inner.borrow().eq(&frozen.inner, vm), - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ) + try_set_inner!(vm, other, |other| self.inner.borrow().eq(other, vm)) } #[pymethod(name = "__ge__")] fn ge(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - set @ PySet => self.inner.borrow().ge(&set.inner.borrow(), vm), - frozen @ PyFrozenSet => self.inner.borrow().ge(&frozen.inner, vm), - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ) + try_set_inner!(vm, other, |other| self.inner.borrow().ge(other, vm)) } #[pymethod(name = "__gt__")] fn gt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - set @ PySet => self.inner.borrow().gt(&set.inner.borrow(), vm), - frozen @ PyFrozenSet => self.inner.borrow().gt(&frozen.inner, vm), - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ) + try_set_inner!(vm, other, |other| self.inner.borrow().gt(other, vm)) } #[pymethod(name = "__le__")] fn le(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - set @ PySet => self.inner.borrow().le(&set.inner.borrow(), vm), - frozen @ PyFrozenSet => self.inner.borrow().le(&frozen.inner, vm), - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ) + try_set_inner!(vm, other, |other| self.inner.borrow().le(other, vm)) } #[pymethod(name = "__lt__")] fn lt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - set @ PySet => self.inner.borrow().lt(&set.inner.borrow(), vm), - frozen @ PyFrozenSet => self.inner.borrow().lt(&frozen.inner, vm), - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ) + try_set_inner!(vm, other, |other| self.inner.borrow().lt(other, vm)) } #[pymethod] @@ -598,47 +588,27 @@ impl PyFrozenSet { #[pymethod(name = "__eq__")] fn eq(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - set @ PySet => self.inner.eq(&set.inner.borrow(), vm), - frozen @ PyFrozenSet => self.inner.eq(&frozen.inner, vm), - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ) + try_set_inner!(vm, other, |other| self.inner.eq(other, vm)) } #[pymethod(name = "__ge__")] fn ge(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - set @ PySet => self.inner.ge(&set.inner.borrow(), vm), - frozen @ PyFrozenSet => self.inner.ge(&frozen.inner, vm), - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ) + try_set_inner!(vm, other, |other| self.inner.ge(other, vm)) } #[pymethod(name = "__gt__")] fn gt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - set @ PySet => self.inner.gt(&set.inner.borrow(), vm), - frozen @ PyFrozenSet => self.inner.gt(&frozen.inner, vm), - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ) + try_set_inner!(vm, other, |other| self.inner.gt(other, vm)) } #[pymethod(name = "__le__")] fn le(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - set @ PySet => self.inner.le(&set.inner.borrow(), vm), - frozen @ PyFrozenSet => self.inner.le(&frozen.inner, vm), - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ) + try_set_inner!(vm, other, |other| self.inner.le(other, vm)) } #[pymethod(name = "__lt__")] fn lt(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match_class!(other, - set @ PySet => self.inner.lt(&set.inner.borrow(), vm), - frozen @ PyFrozenSet => self.inner.lt(&frozen.inner, vm), - other => {return Err(vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class())));}, - ) + try_set_inner!(vm, other, |other| self.inner.lt(other, vm)) } #[pymethod] From 979e1253ae046b8e5756fe0cfd0914bf32ee10a8 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 16 May 2019 04:08:05 +0900 Subject: [PATCH 675/884] Fix set/frozenset comparison --- tests/snippets/set.py | 20 +++++++++ vm/src/obj/objset.rs | 98 +++++++++++++++++++++++-------------------- 2 files changed, 73 insertions(+), 45 deletions(-) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index 65a568f9d9..6e7413e56b 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -27,6 +27,26 @@ assert not set([1,2]) < set([1,2]) assert not set([1,3]) < set([1,2]) +assert (set() == []) is False +assert set().__eq__([]) == NotImplemented +assert_raises(TypeError, lambda: set() < [], "'<' not supported between instances of 'set' and 'list'") +assert_raises(TypeError, lambda: set() <= [], "'<=' not supported between instances of 'set' and 'list'") +assert_raises(TypeError, lambda: set() > [], "'>' not supported between instances of 'set' and 'list'") +assert_raises(TypeError, lambda: set() >= [], "'>=' not supported between instances of 'set' and 'list'") +assert set().issuperset([]) +assert set().issubset([]) +assert not set().issuperset([1, 2, 3]) +assert set().issubset([1, 2]) + +assert (set() == 3) is False +assert set().__eq__(3) == NotImplemented +assert_raises(TypeError, lambda: set() < 3, "'int' object is not iterable") +assert_raises(TypeError, lambda: set() <= 3, "'int' object is not iterable") +assert_raises(TypeError, lambda: set() > 3, "'int' object is not iterable") +assert_raises(TypeError, lambda: set() >= 3, "'int' object is not iterable") +assert_raises(TypeError, lambda: set().issuperset(3), "'int' object is not iterable") +assert_raises(TypeError, lambda: set().issubset(3), "'int' object is not iterable") + class Hashable(object): def __init__(self, obj): self.obj = obj diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 4aeacc0c0c..ce97e861cf 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -73,16 +73,22 @@ struct PySetInner { } impl PySetInner { - fn new(iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { + fn new(iterable: PyIterable, vm: &VirtualMachine) -> PyResult { let mut set = PySetInner::default(); - if let OptionalArg::Present(iterable) = iterable { - for item in iterable.iter(vm)? { - set.add(&item?, vm)?; - } + for item in iterable.iter(vm)? { + set.add(&item?, vm)?; } Ok(set) } + fn from_arg(iterable: OptionalArg, vm: &VirtualMachine) -> PyResult { + if let OptionalArg::Present(iterable) = iterable { + Self::new(iterable, vm) + } else { + Ok(PySetInner::default()) + } + } + fn len(&self) -> usize { self.content.len() } @@ -97,6 +103,7 @@ impl PySetInner { self.content.contains(vm, needle) } + #[inline] fn _compare_inner( &self, other: &PySetInner, @@ -104,26 +111,13 @@ impl PySetInner { swap: bool, vm: &VirtualMachine, ) -> PyResult { - let get_zelf = |swap: bool| -> &PySetInner { - if swap { - other - } else { - self - } - }; - let get_other = |swap: bool| -> &PySetInner { - if swap { - self - } else { - other - } - }; + let (zelf, other) = if swap { (other, self) } else { (self, other) }; - if size_func(get_zelf(swap).len(), get_other(swap).len()) { + if size_func(zelf.len(), other.len()) { return Ok(vm.new_bool(false)); } - for key in get_other(swap).content.keys() { - if !get_zelf(swap).contains(&key, vm)? { + for key in other.content.keys() { + if !zelf.contains(&key, vm)? { return Ok(vm.new_bool(false)); } } @@ -213,6 +207,20 @@ impl PySetInner { Ok(new_inner) } + fn issuperset(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + for item in other.iter(vm)? { + if !self.contains(&item?, vm)? { + return Ok(false); + } + } + Ok(true) + } + + fn issubset(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + let other_set = PySetInner::new(other, vm)?; + self.le(&other_set, vm) + } + fn isdisjoint(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { for item in other.iter(vm)? { if self.contains(&item?, vm)? { @@ -308,7 +316,7 @@ macro_rules! try_set_inner { match_class!($other, set @ PySet => $op(&*set.inner.borrow()), frozen @ PyFrozenSet => $op(&frozen.inner), - other => Err($vm.new_type_error(format!("{} is not a subtype of set or frozenset", other.class()))), + _ => Ok($vm.ctx.not_implemented()), ); }; } @@ -322,7 +330,7 @@ impl PySet { vm: &VirtualMachine, ) -> PyResult { PySet { - inner: RefCell::new(PySetInner::new(iterable, vm)?), + inner: RefCell::new(PySetInner::from_arg(iterable, vm)?), } .into_ref_with_type(vm, cls) } @@ -413,6 +421,16 @@ impl PySet { )) } + #[pymethod] + fn issubset(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().issubset(other, vm) + } + + #[pymethod] + fn issuperset(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner.borrow().issuperset(other, vm) + } + #[pymethod] fn isdisjoint(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner.borrow().isdisjoint(other, vm) @@ -539,16 +557,6 @@ impl PySet { Ok(zelf.as_object().clone()) } - #[pymethod] - fn issubset(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.le(other, vm) - } - - #[pymethod] - fn issuperset(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.ge(other, vm) - } - #[pymethod(name = "__hash__")] fn hash(&self, vm: &VirtualMachine) -> PyResult<()> { Err(vm.new_type_error("unhashable type".to_string())) @@ -564,7 +572,7 @@ impl PyFrozenSet { vm: &VirtualMachine, ) -> PyResult { PyFrozenSet { - inner: PySetInner::new(iterable, vm)?, + inner: PySetInner::from_arg(iterable, vm)?, } .into_ref_with_type(vm, cls) } @@ -655,6 +663,16 @@ impl PyFrozenSet { )) } + #[pymethod] + fn issubset(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner.issubset(other, vm) + } + + #[pymethod] + fn issuperset(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + self.inner.issuperset(other, vm) + } + #[pymethod] fn isdisjoint(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { self.inner.isdisjoint(other, vm) @@ -697,16 +715,6 @@ impl PyFrozenSet { }; Ok(vm.new_str(s)) } - - #[pymethod] - fn issubset(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.le(other, vm) - } - - #[pymethod] - fn issuperset(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { - self.ge(other, vm) - } } struct SetIterable { From ba4ac902ebb0d14b208f6c2a0a6313c25c1a2b69 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 16 May 2019 04:18:20 +0900 Subject: [PATCH 676/884] PySet/PyFrozenSet doesn't call PyObject::new --- vm/src/obj/objset.rs | 140 +++++++++++++++++-------------------------- 1 file changed, 54 insertions(+), 86 deletions(-) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index ce97e861cf..444d4cd141 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -8,8 +8,8 @@ use std::fmt; use crate::dictdatatype; use crate::function::OptionalArg; use crate::pyobject::{ - PyClassImpl, PyContext, PyIterable, PyObject, PyObjectRef, PyRef, PyResult, PyValue, - TryFromObject, TypeProtocol, + PyClassImpl, PyContext, PyIterable, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, + TypeProtocol, }; use crate::vm::{ReprGuard, VirtualMachine}; @@ -280,7 +280,7 @@ impl PySetInner { Ok(()) } - fn intersection_update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + fn intersection_update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult<()> { let temp_inner = self.copy(); self.clear(); for item in iterable.iter(vm)? { @@ -289,25 +289,25 @@ impl PySetInner { self.add(&obj, vm)?; } } - Ok(vm.get_none()) + Ok(()) } - fn difference_update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult { + fn difference_update(&mut self, iterable: PyIterable, vm: &VirtualMachine) -> PyResult<()> { for item in iterable.iter(vm)? { self.content.delete_if_exists(vm, &item?)?; } - Ok(vm.get_none()) + Ok(()) } fn symmetric_difference_update( &mut self, iterable: PyIterable, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult<()> { for item in iterable.iter(vm)? { self.content.delete_or_insert(vm, &item?, ())?; } - Ok(vm.get_none()) + Ok(()) } } @@ -329,7 +329,7 @@ impl PySet { iterable: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - PySet { + Self { inner: RefCell::new(PySetInner::from_arg(iterable, vm)?), } .into_ref_with_type(vm, cls) @@ -341,8 +341,8 @@ impl PySet { } #[pymethod] - fn copy(&self, _vm: &VirtualMachine) -> PySet { - PySet { + fn copy(&self, _vm: &VirtualMachine) -> Self { + Self { inner: RefCell::new(self.inner.borrow().copy()), } } @@ -378,47 +378,31 @@ impl PySet { } #[pymethod] - fn union(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new( - PySet { - inner: RefCell::new(self.inner.borrow().union(other, vm)?), - }, - PySet::class(vm), - None, - )) + fn union(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + Ok(Self { + inner: RefCell::new(self.inner.borrow().union(other, vm)?), + }) } #[pymethod] - fn intersection(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new( - PySet { - inner: RefCell::new(self.inner.borrow().intersection(other, vm)?), - }, - PySet::class(vm), - None, - )) + fn intersection(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + Ok(Self { + inner: RefCell::new(self.inner.borrow().intersection(other, vm)?), + }) } #[pymethod] - fn difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new( - PySet { - inner: RefCell::new(self.inner.borrow().difference(other, vm)?), - }, - PySet::class(vm), - None, - )) + fn difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + Ok(Self { + inner: RefCell::new(self.inner.borrow().difference(other, vm)?), + }) } #[pymethod] - fn symmetric_difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new( - PySet { - inner: RefCell::new(self.inner.borrow().symmetric_difference(other, vm)?), - }, - PySet::class(vm), - None, - )) + fn symmetric_difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + Ok(Self { + inner: RefCell::new(self.inner.borrow().symmetric_difference(other, vm)?), + }) } #[pymethod] @@ -437,22 +421,22 @@ impl PySet { } #[pymethod(name = "__or__")] - fn or(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + fn or(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.union(other.iterable, vm) } #[pymethod(name = "__and__")] - fn and(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + fn and(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.intersection(other.iterable, vm) } #[pymethod(name = "__sub__")] - fn sub(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + fn sub(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.difference(other.iterable, vm) } #[pymethod(name = "__xor__")] - fn xor(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + fn xor(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.symmetric_difference(other.iterable, vm) } @@ -571,7 +555,7 @@ impl PyFrozenSet { iterable: OptionalArg, vm: &VirtualMachine, ) -> PyResult { - PyFrozenSet { + Self { inner: PySetInner::from_arg(iterable, vm)?, } .into_ref_with_type(vm, cls) @@ -583,8 +567,8 @@ impl PyFrozenSet { } #[pymethod] - fn copy(&self, _vm: &VirtualMachine) -> PyFrozenSet { - PyFrozenSet { + fn copy(&self, _vm: &VirtualMachine) -> Self { + Self { inner: self.inner.copy(), } } @@ -620,47 +604,31 @@ impl PyFrozenSet { } #[pymethod] - fn union(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new( - PyFrozenSet { - inner: self.inner.union(other, vm)?, - }, - PyFrozenSet::class(vm), - None, - )) + fn union(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + Ok(Self { + inner: self.inner.union(other, vm)?, + }) } #[pymethod] - fn intersection(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new( - PyFrozenSet { - inner: self.inner.intersection(other, vm)?, - }, - PyFrozenSet::class(vm), - None, - )) + fn intersection(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + Ok(Self { + inner: self.inner.intersection(other, vm)?, + }) } #[pymethod] - fn difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new( - PyFrozenSet { - inner: self.inner.difference(other, vm)?, - }, - PyFrozenSet::class(vm), - None, - )) + fn difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + Ok(Self { + inner: self.inner.difference(other, vm)?, + }) } #[pymethod] - fn symmetric_difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { - Ok(PyObject::new( - PyFrozenSet { - inner: self.inner.symmetric_difference(other, vm)?, - }, - PyFrozenSet::class(vm), - None, - )) + fn symmetric_difference(&self, other: PyIterable, vm: &VirtualMachine) -> PyResult { + Ok(Self { + inner: self.inner.symmetric_difference(other, vm)?, + }) } #[pymethod] @@ -679,22 +647,22 @@ impl PyFrozenSet { } #[pymethod(name = "__or__")] - fn or(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + fn or(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.union(other.iterable, vm) } #[pymethod(name = "__and__")] - fn and(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + fn and(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.intersection(other.iterable, vm) } #[pymethod(name = "__sub__")] - fn sub(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + fn sub(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.difference(other.iterable, vm) } #[pymethod(name = "__xor__")] - fn xor(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + fn xor(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.symmetric_difference(other.iterable, vm) } From c349f77207d37429c7bb86720be2df21b5f47e00 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Thu, 16 May 2019 04:33:01 +0900 Subject: [PATCH 677/884] Add {set,frozenset}.__r*__ --- tests/snippets/set.py | 6 ++++++ vm/src/obj/objset.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/tests/snippets/set.py b/tests/snippets/set.py index 6e7413e56b..50b8f97d84 100644 --- a/tests/snippets/set.py +++ b/tests/snippets/set.py @@ -103,6 +103,12 @@ def __hash__(self): assert set([1,2,3]).isdisjoint(set([2,5,6])) == False assert set([1,2,3]).isdisjoint([5,6]) == True +assert_raises(TypeError, lambda: set() & []) +assert_raises(TypeError, lambda: set() | []) +assert_raises(TypeError, lambda: set() ^ []) +assert_raises(TypeError, lambda: set() + []) +assert_raises(TypeError, lambda: set() - []) + assert_raises(TypeError, lambda: set([[]])) assert_raises(TypeError, lambda: set().add([])) diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index 444d4cd141..b7f0276ced 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -425,21 +425,41 @@ impl PySet { self.union(other.iterable, vm) } + #[pymethod(name = "__ror__")] + fn ror(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.or(other, vm) + } + #[pymethod(name = "__and__")] fn and(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.intersection(other.iterable, vm) } + #[pymethod(name = "__rand__")] + fn rand(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.and(other, vm) + } + #[pymethod(name = "__sub__")] fn sub(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.difference(other.iterable, vm) } + #[pymethod(name = "__rsub__")] + fn rsub(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.sub(other, vm) + } + #[pymethod(name = "__xor__")] fn xor(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.symmetric_difference(other.iterable, vm) } + #[pymethod(name = "__rxor__")] + fn rxor(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.xor(other, vm) + } + #[pymethod(name = "__iter__")] fn iter(&self, vm: &VirtualMachine) -> PyListIterator { self.inner.borrow().iter(vm) @@ -651,21 +671,41 @@ impl PyFrozenSet { self.union(other.iterable, vm) } + #[pymethod(name = "__ror__")] + fn ror(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.or(other, vm) + } + #[pymethod(name = "__and__")] fn and(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.intersection(other.iterable, vm) } + #[pymethod(name = "__rand__")] + fn rand(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.and(other, vm) + } + #[pymethod(name = "__sub__")] fn sub(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.difference(other.iterable, vm) } + #[pymethod(name = "__rsub__")] + fn rsub(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.sub(other, vm) + } + #[pymethod(name = "__xor__")] fn xor(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { self.symmetric_difference(other.iterable, vm) } + #[pymethod(name = "__rxor__")] + fn rxor(&self, other: SetIterable, vm: &VirtualMachine) -> PyResult { + self.xor(other, vm) + } + #[pymethod(name = "__iter__")] fn iter(&self, vm: &VirtualMachine) -> PyListIterator { self.inner.iter(vm) From 80b1f545e28b4e7966c8ef10b76d26138353a9d3 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Wed, 29 May 2019 23:25:10 +0300 Subject: [PATCH 678/884] Add `itertools.islice` --- tests/snippets/stdlib_itertools.py | 29 +++++++ vm/src/stdlib/itertools.rs | 134 ++++++++++++++++++++++++++++- 2 files changed, 162 insertions(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_itertools.py b/tests/snippets/stdlib_itertools.py index c8921cb912..e8285fac95 100644 --- a/tests/snippets/stdlib_itertools.py +++ b/tests/snippets/stdlib_itertools.py @@ -154,3 +154,32 @@ next(t) with assertRaises(StopIteration): next(t) + + +# itertools.islice tests + +def assert_matches_seq(it, seq): + assert list(it) == list(seq) + +i = itertools.islice + +it = i([1, 2, 3, 4, 5], 3) +assert_matches_seq(it, [1, 2, 3]) + +it = i([0.5, 1, 1.5, 2, 2.5, 3, 4, 5], 1, 6, 2) +assert_matches_seq(it, [1, 2, 3]) + +it = i([1, 2], None) +assert_matches_seq(it, [1, 2]) + +it = i([1, 2, 3], None, None, None) +assert_matches_seq(it, [1, 2, 3]) + +it = i([1, 2, 3], 1, None, None) +assert_matches_seq(it, [2, 3]) + +it = i([1, 2, 3], None, 2, None) +assert_matches_seq(it, [1, 2]) + +it = i([1, 2, 3], None, None, 3) +assert_matches_seq(it, [1]) diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index 7380a76774..a8c333701a 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -3,14 +3,16 @@ use std::cmp::Ordering; use std::ops::{AddAssign, SubAssign}; use num_bigint::BigInt; +use num_traits::ToPrimitive; use crate::function::{OptionalArg, PyFuncArgs}; use crate::obj::objbool; +use crate::obj::objint; use crate::obj::objint::{PyInt, PyIntRef}; use crate::obj::objiter::{call_next, get_iter, new_stop_iteration}; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{IdProtocol, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; #[pyclass(name = "chain")] @@ -284,6 +286,133 @@ impl PyItertoolsTakewhile { } } +#[pyclass(name = "islice")] +#[derive(Debug)] +struct PyItertoolsIslice { + iterable: PyObjectRef, + cur: RefCell, + next: RefCell, + stop: Option, + step: usize, +} + +impl PyValue for PyItertoolsIslice { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("itertools", "islice") + } +} + +fn pyobject_to_opt_usize(obj: PyObjectRef, vm: &VirtualMachine) -> Option { + let is_int = objtype::isinstance(&obj, &vm.ctx.int_type()); + if is_int { + objint::get_value(&obj).to_usize() + } else { + None + } +} + +#[pyimpl] +impl PyItertoolsIslice { + #[pymethod(name = "__new__")] + fn new(_cls: PyClassRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { + let (iter, start, stop, step) = match args.args.len() { + 0 | 1 => { + return Err(vm.new_type_error(format!( + "islice expected at least 2 arguments, got {}", + args.args.len() + ))); + } + + 2 => { + let (iter, stop): (PyObjectRef, PyObjectRef) = args.bind(vm)?; + + (iter, 0usize, stop, 1usize) + } + _ => { + let (iter, start, stop, step): ( + PyObjectRef, + PyObjectRef, + PyObjectRef, + PyObjectRef, + ) = args.bind(vm)?; + + let start = if !start.is(&vm.get_none()) { + pyobject_to_opt_usize(start, &vm).ok_or_else(|| { + vm.new_value_error( + "Indices for islice() must be None or an integer: 0 <= x <= sys.maxsize.".to_string(), + ) + })? + } else { + 0usize + }; + + let step = if !step.is(&vm.get_none()) { + pyobject_to_opt_usize(step, &vm).ok_or_else(|| { + vm.new_value_error( + "Step for islice() must be a positive integer or None.".to_string(), + ) + })? + } else { + 1usize + }; + + (iter, start, stop, step) + } + }; + + let stop = if !stop.is(&vm.get_none()) { + Some(pyobject_to_opt_usize(stop, &vm).ok_or_else(|| { + vm.new_value_error( + "Stop argument for islice() must be None or an integer: 0 <= x <= sys.maxsize." + .to_string(), + ) + })?) + } else { + None + }; + + let iter = get_iter(vm, &iter)?; + + Ok(PyItertoolsIslice { + iterable: iter, + cur: RefCell::new(0), + next: RefCell::new(start), + stop: stop, + step: step, + } + .into_ref(vm) + .into_object()) + } + + #[pymethod(name = "__next__")] + fn next(&self, vm: &VirtualMachine) -> PyResult { + while *self.cur.borrow() < *self.next.borrow() { + call_next(vm, &self.iterable)?; + *self.cur.borrow_mut() += 1; + } + + if let Some(stop) = self.stop { + if *self.cur.borrow() >= stop { + return Err(new_stop_iteration(vm)); + } + } + + let obj = call_next(vm, &self.iterable)?; + *self.cur.borrow_mut() += 1; + + // TODO is this overflow check required? attempts to copy CPython. + let (next, ovf) = (*self.next.borrow()).overflowing_add(self.step); + *self.next.borrow_mut() = if ovf { self.stop.unwrap() } else { next }; + + Ok(obj) + } + + #[pymethod(name = "__iter__")] + fn iter(zelf: PyRef, _vm: &VirtualMachine) -> PyRef { + zelf + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -300,11 +429,14 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let takewhile = ctx.new_class("takewhile", ctx.object()); PyItertoolsTakewhile::extend_class(ctx, &takewhile); + let islice = PyItertoolsIslice::make_class(ctx); + py_module!(vm, "itertools", { "chain" => chain, "count" => count, "repeat" => repeat, "starmap" => starmap, "takewhile" => takewhile, + "islice" => islice, }) } From 7ec3cef48e888e269d01e956e5acf63fa72300e7 Mon Sep 17 00:00:00 2001 From: Yonatan Goldschmidt Date: Wed, 29 May 2019 23:29:00 +0300 Subject: [PATCH 679/884] Add `Lib/reprlib.py` from CPython Generated with: `git -C ../cpython show 1bf9cc509326bc42cd8cb1650eb9bf64550d817e:Lib/reprlib.py > Lib/reprlib.py` Required for `functools`. --- Lib/reprlib.py | 161 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 Lib/reprlib.py diff --git a/Lib/reprlib.py b/Lib/reprlib.py new file mode 100644 index 0000000000..616b3439b5 --- /dev/null +++ b/Lib/reprlib.py @@ -0,0 +1,161 @@ +"""Redo the builtin repr() (representation) but with limits on most sizes.""" + +__all__ = ["Repr", "repr", "recursive_repr"] + +import builtins +from itertools import islice +from _thread import get_ident + +def recursive_repr(fillvalue='...'): + 'Decorator to make a repr function return fillvalue for a recursive call' + + def decorating_function(user_function): + repr_running = set() + + def wrapper(self): + key = id(self), get_ident() + if key in repr_running: + return fillvalue + repr_running.add(key) + try: + result = user_function(self) + finally: + repr_running.discard(key) + return result + + # Can't use functools.wraps() here because of bootstrap issues + wrapper.__module__ = getattr(user_function, '__module__') + wrapper.__doc__ = getattr(user_function, '__doc__') + wrapper.__name__ = getattr(user_function, '__name__') + wrapper.__qualname__ = getattr(user_function, '__qualname__') + wrapper.__annotations__ = getattr(user_function, '__annotations__', {}) + return wrapper + + return decorating_function + +class Repr: + + def __init__(self): + self.maxlevel = 6 + self.maxtuple = 6 + self.maxlist = 6 + self.maxarray = 5 + self.maxdict = 4 + self.maxset = 6 + self.maxfrozenset = 6 + self.maxdeque = 6 + self.maxstring = 30 + self.maxlong = 40 + self.maxother = 30 + + def repr(self, x): + return self.repr1(x, self.maxlevel) + + def repr1(self, x, level): + typename = type(x).__name__ + if ' ' in typename: + parts = typename.split() + typename = '_'.join(parts) + if hasattr(self, 'repr_' + typename): + return getattr(self, 'repr_' + typename)(x, level) + else: + return self.repr_instance(x, level) + + def _repr_iterable(self, x, level, left, right, maxiter, trail=''): + n = len(x) + if level <= 0 and n: + s = '...' + else: + newlevel = level - 1 + repr1 = self.repr1 + pieces = [repr1(elem, newlevel) for elem in islice(x, maxiter)] + if n > maxiter: pieces.append('...') + s = ', '.join(pieces) + if n == 1 and trail: right = trail + right + return '%s%s%s' % (left, s, right) + + def repr_tuple(self, x, level): + return self._repr_iterable(x, level, '(', ')', self.maxtuple, ',') + + def repr_list(self, x, level): + return self._repr_iterable(x, level, '[', ']', self.maxlist) + + def repr_array(self, x, level): + if not x: + return "array('%s')" % x.typecode + header = "array('%s', [" % x.typecode + return self._repr_iterable(x, level, header, '])', self.maxarray) + + def repr_set(self, x, level): + if not x: + return 'set()' + x = _possibly_sorted(x) + return self._repr_iterable(x, level, '{', '}', self.maxset) + + def repr_frozenset(self, x, level): + if not x: + return 'frozenset()' + x = _possibly_sorted(x) + return self._repr_iterable(x, level, 'frozenset({', '})', + self.maxfrozenset) + + def repr_deque(self, x, level): + return self._repr_iterable(x, level, 'deque([', '])', self.maxdeque) + + def repr_dict(self, x, level): + n = len(x) + if n == 0: return '{}' + if level <= 0: return '{...}' + newlevel = level - 1 + repr1 = self.repr1 + pieces = [] + for key in islice(_possibly_sorted(x), self.maxdict): + keyrepr = repr1(key, newlevel) + valrepr = repr1(x[key], newlevel) + pieces.append('%s: %s' % (keyrepr, valrepr)) + if n > self.maxdict: pieces.append('...') + s = ', '.join(pieces) + return '{%s}' % (s,) + + def repr_str(self, x, level): + s = builtins.repr(x[:self.maxstring]) + if len(s) > self.maxstring: + i = max(0, (self.maxstring-3)//2) + j = max(0, self.maxstring-3-i) + s = builtins.repr(x[:i] + x[len(x)-j:]) + s = s[:i] + '...' + s[len(s)-j:] + return s + + def repr_int(self, x, level): + s = builtins.repr(x) # XXX Hope this isn't too slow... + if len(s) > self.maxlong: + i = max(0, (self.maxlong-3)//2) + j = max(0, self.maxlong-3-i) + s = s[:i] + '...' + s[len(s)-j:] + return s + + def repr_instance(self, x, level): + try: + s = builtins.repr(x) + # Bugs in x.__repr__() can cause arbitrary + # exceptions -- then make up something + except Exception: + return '<%s instance at %#x>' % (x.__class__.__name__, id(x)) + if len(s) > self.maxother: + i = max(0, (self.maxother-3)//2) + j = max(0, self.maxother-3-i) + s = s[:i] + '...' + s[len(s)-j:] + return s + + +def _possibly_sorted(x): + # Since not all sequences of items can be sorted and comparison + # functions may raise arbitrary exceptions, return an unsorted + # sequence in that case. + try: + return sorted(x) + except Exception: + return list(x) + +aRepr = Repr() +repr = aRepr.repr From 11892816a765d87ed7402aa1b35b5fcc5b3a63d3 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 29 May 2019 16:11:22 -0500 Subject: [PATCH 680/884] Don't check env variables on wasm --- vm/src/sysmodule.rs | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index dd52a189a8..f2f123c71b 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -54,27 +54,31 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectR "cache_tag" => ctx.new_str("rustpython-01".to_string()), }); - fn get_paths<'a>( - paths: &'a Option, - ) -> impl Iterator + 'a { - match paths { - Some(paths) => env::split_paths(paths), - None => env::split_paths(""), + let path_list = if cfg!(target_arch = "wasm32") { + vec![] + } else { + fn get_paths<'a>( + paths: &'a Option, + ) -> impl Iterator + 'a { + match paths { + Some(paths) => env::split_paths(paths), + None => env::split_paths(""), + } } - } - let rustpy_path = env::var_os("RUSTPYTHONPATH"); - let py_path = env::var_os("PYTHONPATH"); - let path_list = get_paths(&rustpy_path) - .chain(get_paths(&py_path)) - .map(|path| { - ctx.new_str( - path.to_str() - .expect("PYTHONPATH isn't valid unicode") - .to_string(), - ) - }) - .collect(); + let rustpy_path = env::var_os("RUSTPYTHONPATH"); + let py_path = env::var_os("PYTHONPATH"); + get_paths(&rustpy_path) + .chain(get_paths(&py_path)) + .map(|path| { + ctx.new_str( + path.to_str() + .expect("PYTHONPATH isn't valid unicode") + .to_string(), + ) + }) + .collect() + }; let path = ctx.new_list(path_list); let platform = if cfg!(target_os = "linux") { From c78dc520ad929b472b06d35a2b62ac21e63ca426 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 29 May 2019 16:53:13 -0500 Subject: [PATCH 681/884] Don't clone PathBufs --- vm/src/sysmodule.rs | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index f2f123c71b..3c5a58f13f 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -57,24 +57,20 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectR let path_list = if cfg!(target_arch = "wasm32") { vec![] } else { - fn get_paths<'a>( - paths: &'a Option, - ) -> impl Iterator + 'a { - match paths { - Some(paths) => env::split_paths(paths), - None => env::split_paths(""), - } - } + let get_paths = |paths| match paths { + Some(paths) => env::split_paths(paths), + None => env::split_paths(""), + }; let rustpy_path = env::var_os("RUSTPYTHONPATH"); let py_path = env::var_os("PYTHONPATH"); - get_paths(&rustpy_path) - .chain(get_paths(&py_path)) + get_paths(rustpy_path.as_ref()) + .chain(get_paths(py_path.as_ref())) .map(|path| { ctx.new_str( - path.to_str() - .expect("PYTHONPATH isn't valid unicode") - .to_string(), + path.into_os_string() + .into_string() + .expect("PYTHONPATH isn't valid unicode"), ) }) .collect() From 1dc2d097135ed5d66a8595db8b97bb1bf65228aa Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 29 May 2019 22:46:12 -0500 Subject: [PATCH 682/884] Add __setattr__ to weakproxy --- vm/src/obj/objweakproxy.rs | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/vm/src/obj/objweakproxy.rs b/vm/src/obj/objweakproxy.rs index 735670f4e4..5d4f68d9d7 100644 --- a/vm/src/obj/objweakproxy.rs +++ b/vm/src/obj/objweakproxy.rs @@ -1,9 +1,10 @@ use super::objweakref::PyWeak; use crate::function::OptionalArg; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; +#[pyclass] #[derive(Debug)] pub struct PyWeakProxy { weak: PyWeak, @@ -17,21 +18,27 @@ impl PyValue for PyWeakProxy { pub type PyWeakProxyRef = PyRef; -impl PyWeakProxyRef { - // TODO callbacks +#[pyimpl] +impl PyWeakProxy { + // TODO: callbacks + #[pymethod(name = "__new__")] fn create( cls: PyClassRef, referent: PyObjectRef, - _callback: OptionalArg, + callback: OptionalArg, vm: &VirtualMachine, - ) -> PyResult { + ) -> PyResult { + if callback.is_present() { + panic!("Passed a callback to weakproxy, but weakproxy does not yet support proxies."); + } PyWeakProxy { weak: PyWeak::downgrade(&referent), } .into_ref_with_type(vm, cls) } - fn getattr(self, attr_name: PyObjectRef, vm: &VirtualMachine) -> PyResult { + #[pymethod(name = "__getattr__")] + fn getattr(&self, attr_name: PyObjectRef, vm: &VirtualMachine) -> PyResult { match self.weak.upgrade() { Some(obj) => vm.get_attribute(obj, attr_name), None => Err(vm.new_exception( @@ -40,11 +47,19 @@ impl PyWeakProxyRef { )), } } + + #[pymethod(name = "__setattr__")] + fn setattr(&self, attr_name: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -> PyResult { + match self.weak.upgrade() { + Some(obj) => vm.set_attr(&obj, attr_name, value), + None => Err(vm.new_exception( + vm.ctx.exceptions.reference_error.clone(), + "weakly-referenced object no longer exists".to_string(), + )), + } + } } pub fn init(context: &PyContext) { - extend_class!(context, &context.weakproxy_type, { - "__new__" => context.new_rustfunc(PyWeakProxyRef::create), - "__getattr__" => context.new_rustfunc(PyWeakProxyRef::getattr), - }); + PyWeakProxy::extend_class(&context, &context.weakproxy_type); } From d2757a2680c323ebb74d4b583bb614834e9fc1ff Mon Sep 17 00:00:00 2001 From: ben Date: Thu, 30 May 2019 21:22:30 +1200 Subject: [PATCH 683/884] Fix merge issue with PyTuple::fast_getitem --- vm/src/obj/objtuple.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 829a992de3..03596fbd25 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -39,7 +39,7 @@ impl PyValue for PyTuple { impl PyTuple { pub fn fast_getitem(&self, idx: usize) -> PyObjectRef { - self.elements.borrow()[idx].clone() + self.elements[idx].clone() } } From 9e88b808dde3a51b828849f280dca9da1ff4624c Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 30 May 2019 18:50:46 +0300 Subject: [PATCH 684/884] Add _imp.is_frozen --- tests/snippets/imp.py | 3 +++ vm/src/stdlib/imp.rs | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/tests/snippets/imp.py b/tests/snippets/imp.py index 45332a9b96..a9d827b63d 100644 --- a/tests/snippets/imp.py +++ b/tests/snippets/imp.py @@ -3,3 +3,6 @@ assert _imp.is_builtin("time") == True assert _imp.is_builtin("os") == False assert _imp.is_builtin("not existing module") == False + +assert _imp.is_frozen("__hello__") == True +assert _imp.is_frozen("os") == False diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index f5d889852a..5b249a1356 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -25,6 +25,10 @@ fn imp_is_builtin(name: PyStringRef, vm: &VirtualMachine) -> bool { vm.stdlib_inits.borrow().contains_key(name.as_str()) } +fn imp_is_frozen(name: PyStringRef, vm: &VirtualMachine) -> bool { + vm.frozen.borrow().contains_key(name.as_str()) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let module = py_module!(vm, "_imp", { @@ -33,6 +37,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "release_lock" => ctx.new_rustfunc(imp_release_lock), "lock_held" => ctx.new_rustfunc(imp_lock_held), "is_builtin" => ctx.new_rustfunc(imp_is_builtin), + "is_frozen" => ctx.new_rustfunc(imp_is_frozen), }); module From 39c63ef1395da891b824ac88dccdb92e4e0c0272 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 30 May 2019 23:03:58 -0500 Subject: [PATCH 685/884] Update Lib/operator.py and add Lib/heapq.py from cpython --- Lib/heapq.py | 601 ++++++++++++++++++++++++++++++++++++++++++++++++ Lib/operator.py | 455 +++++++++++++++++++++++++++++++++++- 2 files changed, 1053 insertions(+), 3 deletions(-) create mode 100644 Lib/heapq.py diff --git a/Lib/heapq.py b/Lib/heapq.py new file mode 100644 index 0000000000..0e3555cf91 --- /dev/null +++ b/Lib/heapq.py @@ -0,0 +1,601 @@ +"""Heap queue algorithm (a.k.a. priority queue). + +Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for +all k, counting elements from 0. For the sake of comparison, +non-existing elements are considered to be infinite. The interesting +property of a heap is that a[0] is always its smallest element. + +Usage: + +heap = [] # creates an empty heap +heappush(heap, item) # pushes a new item on the heap +item = heappop(heap) # pops the smallest item from the heap +item = heap[0] # smallest item on the heap without popping it +heapify(x) # transforms list into a heap, in-place, in linear time +item = heapreplace(heap, item) # pops and returns smallest item, and adds + # new item; the heap size is unchanged + +Our API differs from textbook heap algorithms as follows: + +- We use 0-based indexing. This makes the relationship between the + index for a node and the indexes for its children slightly less + obvious, but is more suitable since Python uses 0-based indexing. + +- Our heappop() method returns the smallest item, not the largest. + +These two make it possible to view the heap as a regular Python list +without surprises: heap[0] is the smallest item, and heap.sort() +maintains the heap invariant! +""" + +# Original code by Kevin O'Connor, augmented by Tim Peters and Raymond Hettinger + +__about__ = """Heap queues + +[explanation by François Pinard] + +Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for +all k, counting elements from 0. For the sake of comparison, +non-existing elements are considered to be infinite. The interesting +property of a heap is that a[0] is always its smallest element. + +The strange invariant above is meant to be an efficient memory +representation for a tournament. The numbers below are `k', not a[k]: + + 0 + + 1 2 + + 3 4 5 6 + + 7 8 9 10 11 12 13 14 + + 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 + + +In the tree above, each cell `k' is topping `2*k+1' and `2*k+2'. In +a usual binary tournament we see in sports, each cell is the winner +over the two cells it tops, and we can trace the winner down the tree +to see all opponents s/he had. However, in many computer applications +of such tournaments, we do not need to trace the history of a winner. +To be more memory efficient, when a winner is promoted, we try to +replace it by something else at a lower level, and the rule becomes +that a cell and the two cells it tops contain three different items, +but the top cell "wins" over the two topped cells. + +If this heap invariant is protected at all time, index 0 is clearly +the overall winner. The simplest algorithmic way to remove it and +find the "next" winner is to move some loser (let's say cell 30 in the +diagram above) into the 0 position, and then percolate this new 0 down +the tree, exchanging values, until the invariant is re-established. +This is clearly logarithmic on the total number of items in the tree. +By iterating over all items, you get an O(n ln n) sort. + +A nice feature of this sort is that you can efficiently insert new +items while the sort is going on, provided that the inserted items are +not "better" than the last 0'th element you extracted. This is +especially useful in simulation contexts, where the tree holds all +incoming events, and the "win" condition means the smallest scheduled +time. When an event schedule other events for execution, they are +scheduled into the future, so they can easily go into the heap. So, a +heap is a good structure for implementing schedulers (this is what I +used for my MIDI sequencer :-). + +Various structures for implementing schedulers have been extensively +studied, and heaps are good for this, as they are reasonably speedy, +the speed is almost constant, and the worst case is not much different +than the average case. However, there are other representations which +are more efficient overall, yet the worst cases might be terrible. + +Heaps are also very useful in big disk sorts. You most probably all +know that a big sort implies producing "runs" (which are pre-sorted +sequences, which size is usually related to the amount of CPU memory), +followed by a merging passes for these runs, which merging is often +very cleverly organised[1]. It is very important that the initial +sort produces the longest runs possible. Tournaments are a good way +to that. If, using all the memory available to hold a tournament, you +replace and percolate items that happen to fit the current run, you'll +produce runs which are twice the size of the memory for random input, +and much better for input fuzzily ordered. + +Moreover, if you output the 0'th item on disk and get an input which +may not fit in the current tournament (because the value "wins" over +the last output value), it cannot fit in the heap, so the size of the +heap decreases. The freed memory could be cleverly reused immediately +for progressively building a second heap, which grows at exactly the +same rate the first heap is melting. When the first heap completely +vanishes, you switch heaps and start a new run. Clever and quite +effective! + +In a word, heaps are useful memory structures to know. I use them in +a few applications, and I think it is good to keep a `heap' module +around. :-) + +-------------------- +[1] The disk balancing algorithms which are current, nowadays, are +more annoying than clever, and this is a consequence of the seeking +capabilities of the disks. On devices which cannot seek, like big +tape drives, the story was quite different, and one had to be very +clever to ensure (far in advance) that each tape movement will be the +most effective possible (that is, will best participate at +"progressing" the merge). Some tapes were even able to read +backwards, and this was also used to avoid the rewinding time. +Believe me, real good tape sorts were quite spectacular to watch! +From all times, sorting has always been a Great Art! :-) +""" + +__all__ = ['heappush', 'heappop', 'heapify', 'heapreplace', 'merge', + 'nlargest', 'nsmallest', 'heappushpop'] + +def heappush(heap, item): + """Push item onto heap, maintaining the heap invariant.""" + heap.append(item) + _siftdown(heap, 0, len(heap)-1) + +def heappop(heap): + """Pop the smallest item off the heap, maintaining the heap invariant.""" + lastelt = heap.pop() # raises appropriate IndexError if heap is empty + if heap: + returnitem = heap[0] + heap[0] = lastelt + _siftup(heap, 0) + return returnitem + return lastelt + +def heapreplace(heap, item): + """Pop and return the current smallest value, and add the new item. + + This is more efficient than heappop() followed by heappush(), and can be + more appropriate when using a fixed-size heap. Note that the value + returned may be larger than item! That constrains reasonable uses of + this routine unless written as part of a conditional replacement: + + if item > heap[0]: + item = heapreplace(heap, item) + """ + returnitem = heap[0] # raises appropriate IndexError if heap is empty + heap[0] = item + _siftup(heap, 0) + return returnitem + +def heappushpop(heap, item): + """Fast version of a heappush followed by a heappop.""" + if heap and heap[0] < item: + item, heap[0] = heap[0], item + _siftup(heap, 0) + return item + +def heapify(x): + """Transform list into a heap, in-place, in O(len(x)) time.""" + n = len(x) + # Transform bottom-up. The largest index there's any point to looking at + # is the largest with a child index in-range, so must have 2*i + 1 < n, + # or i < (n-1)/2. If n is even = 2*j, this is (2*j-1)/2 = j-1/2 so + # j-1 is the largest, which is n//2 - 1. If n is odd = 2*j+1, this is + # (2*j+1-1)/2 = j so j-1 is the largest, and that's again n//2-1. + for i in reversed(range(n//2)): + _siftup(x, i) + +def _heappop_max(heap): + """Maxheap version of a heappop.""" + lastelt = heap.pop() # raises appropriate IndexError if heap is empty + if heap: + returnitem = heap[0] + heap[0] = lastelt + _siftup_max(heap, 0) + return returnitem + return lastelt + +def _heapreplace_max(heap, item): + """Maxheap version of a heappop followed by a heappush.""" + returnitem = heap[0] # raises appropriate IndexError if heap is empty + heap[0] = item + _siftup_max(heap, 0) + return returnitem + +def _heapify_max(x): + """Transform list into a maxheap, in-place, in O(len(x)) time.""" + n = len(x) + for i in reversed(range(n//2)): + _siftup_max(x, i) + +# 'heap' is a heap at all indices >= startpos, except possibly for pos. pos +# is the index of a leaf with a possibly out-of-order value. Restore the +# heap invariant. +def _siftdown(heap, startpos, pos): + newitem = heap[pos] + # Follow the path to the root, moving parents down until finding a place + # newitem fits. + while pos > startpos: + parentpos = (pos - 1) >> 1 + parent = heap[parentpos] + if newitem < parent: + heap[pos] = parent + pos = parentpos + continue + break + heap[pos] = newitem + +# The child indices of heap index pos are already heaps, and we want to make +# a heap at index pos too. We do this by bubbling the smaller child of +# pos up (and so on with that child's children, etc) until hitting a leaf, +# then using _siftdown to move the oddball originally at index pos into place. +# +# We *could* break out of the loop as soon as we find a pos where newitem <= +# both its children, but turns out that's not a good idea, and despite that +# many books write the algorithm that way. During a heap pop, the last array +# element is sifted in, and that tends to be large, so that comparing it +# against values starting from the root usually doesn't pay (= usually doesn't +# get us out of the loop early). See Knuth, Volume 3, where this is +# explained and quantified in an exercise. +# +# Cutting the # of comparisons is important, since these routines have no +# way to extract "the priority" from an array element, so that intelligence +# is likely to be hiding in custom comparison methods, or in array elements +# storing (priority, record) tuples. Comparisons are thus potentially +# expensive. +# +# On random arrays of length 1000, making this change cut the number of +# comparisons made by heapify() a little, and those made by exhaustive +# heappop() a lot, in accord with theory. Here are typical results from 3 +# runs (3 just to demonstrate how small the variance is): +# +# Compares needed by heapify Compares needed by 1000 heappops +# -------------------------- -------------------------------- +# 1837 cut to 1663 14996 cut to 8680 +# 1855 cut to 1659 14966 cut to 8678 +# 1847 cut to 1660 15024 cut to 8703 +# +# Building the heap by using heappush() 1000 times instead required +# 2198, 2148, and 2219 compares: heapify() is more efficient, when +# you can use it. +# +# The total compares needed by list.sort() on the same lists were 8627, +# 8627, and 8632 (this should be compared to the sum of heapify() and +# heappop() compares): list.sort() is (unsurprisingly!) more efficient +# for sorting. + +def _siftup(heap, pos): + endpos = len(heap) + startpos = pos + newitem = heap[pos] + # Bubble up the smaller child until hitting a leaf. + childpos = 2*pos + 1 # leftmost child position + while childpos < endpos: + # Set childpos to index of smaller child. + rightpos = childpos + 1 + if rightpos < endpos and not heap[childpos] < heap[rightpos]: + childpos = rightpos + # Move the smaller child up. + heap[pos] = heap[childpos] + pos = childpos + childpos = 2*pos + 1 + # The leaf at pos is empty now. Put newitem there, and bubble it up + # to its final resting place (by sifting its parents down). + heap[pos] = newitem + _siftdown(heap, startpos, pos) + +def _siftdown_max(heap, startpos, pos): + 'Maxheap variant of _siftdown' + newitem = heap[pos] + # Follow the path to the root, moving parents down until finding a place + # newitem fits. + while pos > startpos: + parentpos = (pos - 1) >> 1 + parent = heap[parentpos] + if parent < newitem: + heap[pos] = parent + pos = parentpos + continue + break + heap[pos] = newitem + +def _siftup_max(heap, pos): + 'Maxheap variant of _siftup' + endpos = len(heap) + startpos = pos + newitem = heap[pos] + # Bubble up the larger child until hitting a leaf. + childpos = 2*pos + 1 # leftmost child position + while childpos < endpos: + # Set childpos to index of larger child. + rightpos = childpos + 1 + if rightpos < endpos and not heap[rightpos] < heap[childpos]: + childpos = rightpos + # Move the larger child up. + heap[pos] = heap[childpos] + pos = childpos + childpos = 2*pos + 1 + # The leaf at pos is empty now. Put newitem there, and bubble it up + # to its final resting place (by sifting its parents down). + heap[pos] = newitem + _siftdown_max(heap, startpos, pos) + +def merge(*iterables, key=None, reverse=False): + '''Merge multiple sorted inputs into a single sorted output. + + Similar to sorted(itertools.chain(*iterables)) but returns a generator, + does not pull the data into memory all at once, and assumes that each of + the input streams is already sorted (smallest to largest). + + >>> list(merge([1,3,5,7], [0,2,4,8], [5,10,15,20], [], [25])) + [0, 1, 2, 3, 4, 5, 5, 7, 8, 10, 15, 20, 25] + + If *key* is not None, applies a key function to each element to determine + its sort order. + + >>> list(merge(['dog', 'horse'], ['cat', 'fish', 'kangaroo'], key=len)) + ['dog', 'cat', 'fish', 'horse', 'kangaroo'] + + ''' + + h = [] + h_append = h.append + + if reverse: + _heapify = _heapify_max + _heappop = _heappop_max + _heapreplace = _heapreplace_max + direction = -1 + else: + _heapify = heapify + _heappop = heappop + _heapreplace = heapreplace + direction = 1 + + if key is None: + for order, it in enumerate(map(iter, iterables)): + try: + next = it.__next__ + h_append([next(), order * direction, next]) + except StopIteration: + pass + _heapify(h) + while len(h) > 1: + try: + while True: + value, order, next = s = h[0] + yield value + s[0] = next() # raises StopIteration when exhausted + _heapreplace(h, s) # restore heap condition + except StopIteration: + _heappop(h) # remove empty iterator + if h: + # fast case when only a single iterator remains + value, order, next = h[0] + yield value + yield from next.__self__ + return + + for order, it in enumerate(map(iter, iterables)): + try: + next = it.__next__ + value = next() + h_append([key(value), order * direction, value, next]) + except StopIteration: + pass + _heapify(h) + while len(h) > 1: + try: + while True: + key_value, order, value, next = s = h[0] + yield value + value = next() + s[0] = key(value) + s[2] = value + _heapreplace(h, s) + except StopIteration: + _heappop(h) + if h: + key_value, order, value, next = h[0] + yield value + yield from next.__self__ + + +# Algorithm notes for nlargest() and nsmallest() +# ============================================== +# +# Make a single pass over the data while keeping the k most extreme values +# in a heap. Memory consumption is limited to keeping k values in a list. +# +# Measured performance for random inputs: +# +# number of comparisons +# n inputs k-extreme values (average of 5 trials) % more than min() +# ------------- ---------------- --------------------- ----------------- +# 1,000 100 3,317 231.7% +# 10,000 100 14,046 40.5% +# 100,000 100 105,749 5.7% +# 1,000,000 100 1,007,751 0.8% +# 10,000,000 100 10,009,401 0.1% +# +# Theoretical number of comparisons for k smallest of n random inputs: +# +# Step Comparisons Action +# ---- -------------------------- --------------------------- +# 1 1.66 * k heapify the first k-inputs +# 2 n - k compare remaining elements to top of heap +# 3 k * (1 + lg2(k)) * ln(n/k) replace the topmost value on the heap +# 4 k * lg2(k) - (k/2) final sort of the k most extreme values +# +# Combining and simplifying for a rough estimate gives: +# +# comparisons = n + k * (log(k, 2) * log(n/k) + log(k, 2) + log(n/k)) +# +# Computing the number of comparisons for step 3: +# ----------------------------------------------- +# * For the i-th new value from the iterable, the probability of being in the +# k most extreme values is k/i. For example, the probability of the 101st +# value seen being in the 100 most extreme values is 100/101. +# * If the value is a new extreme value, the cost of inserting it into the +# heap is 1 + log(k, 2). +# * The probability times the cost gives: +# (k/i) * (1 + log(k, 2)) +# * Summing across the remaining n-k elements gives: +# sum((k/i) * (1 + log(k, 2)) for i in range(k+1, n+1)) +# * This reduces to: +# (H(n) - H(k)) * k * (1 + log(k, 2)) +# * Where H(n) is the n-th harmonic number estimated by: +# gamma = 0.5772156649 +# H(n) = log(n, e) + gamma + 1 / (2 * n) +# http://en.wikipedia.org/wiki/Harmonic_series_(mathematics)#Rate_of_divergence +# * Substituting the H(n) formula: +# comparisons = k * (1 + log(k, 2)) * (log(n/k, e) + (1/n - 1/k) / 2) +# +# Worst-case for step 3: +# ---------------------- +# In the worst case, the input data is reversed sorted so that every new element +# must be inserted in the heap: +# +# comparisons = 1.66 * k + log(k, 2) * (n - k) +# +# Alternative Algorithms +# ---------------------- +# Other algorithms were not used because they: +# 1) Took much more auxiliary memory, +# 2) Made multiple passes over the data. +# 3) Made more comparisons in common cases (small k, large n, semi-random input). +# See the more detailed comparison of approach at: +# http://code.activestate.com/recipes/577573-compare-algorithms-for-heapqsmallest + +def nsmallest(n, iterable, key=None): + """Find the n smallest elements in a dataset. + + Equivalent to: sorted(iterable, key=key)[:n] + """ + + # Short-cut for n==1 is to use min() + if n == 1: + it = iter(iterable) + sentinel = object() + result = min(it, default=sentinel, key=key) + return [] if result is sentinel else [result] + + # When n>=size, it's faster to use sorted() + try: + size = len(iterable) + except (TypeError, AttributeError): + pass + else: + if n >= size: + return sorted(iterable, key=key)[:n] + + # When key is none, use simpler decoration + if key is None: + it = iter(iterable) + # put the range(n) first so that zip() doesn't + # consume one too many elements from the iterator + result = [(elem, i) for i, elem in zip(range(n), it)] + if not result: + return result + _heapify_max(result) + top = result[0][0] + order = n + _heapreplace = _heapreplace_max + for elem in it: + if elem < top: + _heapreplace(result, (elem, order)) + top, _order = result[0] + order += 1 + result.sort() + return [elem for (elem, order) in result] + + # General case, slowest method + it = iter(iterable) + result = [(key(elem), i, elem) for i, elem in zip(range(n), it)] + if not result: + return result + _heapify_max(result) + top = result[0][0] + order = n + _heapreplace = _heapreplace_max + for elem in it: + k = key(elem) + if k < top: + _heapreplace(result, (k, order, elem)) + top, _order, _elem = result[0] + order += 1 + result.sort() + return [elem for (k, order, elem) in result] + +def nlargest(n, iterable, key=None): + """Find the n largest elements in a dataset. + + Equivalent to: sorted(iterable, key=key, reverse=True)[:n] + """ + + # Short-cut for n==1 is to use max() + if n == 1: + it = iter(iterable) + sentinel = object() + result = max(it, default=sentinel, key=key) + return [] if result is sentinel else [result] + + # When n>=size, it's faster to use sorted() + try: + size = len(iterable) + except (TypeError, AttributeError): + pass + else: + if n >= size: + return sorted(iterable, key=key, reverse=True)[:n] + + # When key is none, use simpler decoration + if key is None: + it = iter(iterable) + result = [(elem, i) for i, elem in zip(range(0, -n, -1), it)] + if not result: + return result + heapify(result) + top = result[0][0] + order = -n + _heapreplace = heapreplace + for elem in it: + if top < elem: + _heapreplace(result, (elem, order)) + top, _order = result[0] + order -= 1 + result.sort(reverse=True) + return [elem for (elem, order) in result] + + # General case, slowest method + it = iter(iterable) + result = [(key(elem), i, elem) for i, elem in zip(range(0, -n, -1), it)] + if not result: + return result + heapify(result) + top = result[0][0] + order = -n + _heapreplace = heapreplace + for elem in it: + k = key(elem) + if top < k: + _heapreplace(result, (k, order, elem)) + top, _order, _elem = result[0] + order -= 1 + result.sort(reverse=True) + return [elem for (k, order, elem) in result] + +# If available, use C implementation +try: + from _heapq import * +except ImportError: + pass +try: + from _heapq import _heapreplace_max +except ImportError: + pass +try: + from _heapq import _heapify_max +except ImportError: + pass +try: + from _heapq import _heappop_max +except ImportError: + pass + + +if __name__ == "__main__": + + import doctest + print(doctest.testmod()) diff --git a/Lib/operator.py b/Lib/operator.py index f219200641..0e2e53efc6 100644 --- a/Lib/operator.py +++ b/Lib/operator.py @@ -1,15 +1,464 @@ +""" +Operator Interface +This module exports a set of functions corresponding to the intrinsic +operators of Python. For example, operator.add(x, y) is equivalent +to the expression x+y. The function names are those used for special +methods; variants without leading and trailing '__' are also provided +for convenience. -# Comparison operations: +This is the pure Python implementation of the module. +""" + +__all__ = ['abs', 'add', 'and_', 'attrgetter', 'concat', 'contains', 'countOf', + 'delitem', 'eq', 'floordiv', 'ge', 'getitem', 'gt', 'iadd', 'iand', + 'iconcat', 'ifloordiv', 'ilshift', 'imatmul', 'imod', 'imul', + 'index', 'indexOf', 'inv', 'invert', 'ior', 'ipow', 'irshift', + 'is_', 'is_not', 'isub', 'itemgetter', 'itruediv', 'ixor', 'le', + 'length_hint', 'lshift', 'lt', 'matmul', 'methodcaller', 'mod', + 'mul', 'ne', 'neg', 'not_', 'or_', 'pos', 'pow', 'rshift', + 'setitem', 'sub', 'truediv', 'truth', 'xor'] + +from builtins import abs as _abs + + +# Comparison Operations *******************************************************# + +def lt(a, b): + "Same as a < b." + return a < b + +def le(a, b): + "Same as a <= b." + return a <= b def eq(a, b): + "Same as a == b." return a == b - def ne(a, b): + "Same as a != b." return a != b - def ge(a, b): + "Same as a >= b." return a >= b +def gt(a, b): + "Same as a > b." + return a > b + +# Logical Operations **********************************************************# + +def not_(a): + "Same as not a." + return not a + +def truth(a): + "Return True if a is true, False otherwise." + return True if a else False + +def is_(a, b): + "Same as a is b." + return a is b + +def is_not(a, b): + "Same as a is not b." + return a is not b + +# Mathematical/Bitwise Operations *********************************************# + +def abs(a): + "Same as abs(a)." + return _abs(a) + +def add(a, b): + "Same as a + b." + return a + b + +def and_(a, b): + "Same as a & b." + return a & b + +def floordiv(a, b): + "Same as a // b." + return a // b + +def index(a): + "Same as a.__index__()." + return a.__index__() + +def inv(a): + "Same as ~a." + return ~a +invert = inv + +def lshift(a, b): + "Same as a << b." + return a << b + +def mod(a, b): + "Same as a % b." + return a % b + +def mul(a, b): + "Same as a * b." + return a * b + +def matmul(a, b): + "Same as a @ b." + return a @ b + +def neg(a): + "Same as -a." + return -a + +def or_(a, b): + "Same as a | b." + return a | b + +def pos(a): + "Same as +a." + return +a + +def pow(a, b): + "Same as a ** b." + return a ** b + +def rshift(a, b): + "Same as a >> b." + return a >> b + +def sub(a, b): + "Same as a - b." + return a - b + +def truediv(a, b): + "Same as a / b." + return a / b + +def xor(a, b): + "Same as a ^ b." + return a ^ b + +# Sequence Operations *********************************************************# + +def concat(a, b): + "Same as a + b, for a and b sequences." + if not hasattr(a, '__getitem__'): + msg = "'%s' object can't be concatenated" % type(a).__name__ + raise TypeError(msg) + return a + b + +def contains(a, b): + "Same as b in a (note reversed operands)." + return b in a + +def countOf(a, b): + "Return the number of times b occurs in a." + count = 0 + for i in a: + if i == b: + count += 1 + return count + +def delitem(a, b): + "Same as del a[b]." + del a[b] + +def getitem(a, b): + "Same as a[b]." + return a[b] + +def indexOf(a, b): + "Return the first index of b in a." + for i, j in enumerate(a): + if j == b: + return i + else: + raise ValueError('sequence.index(x): x not in sequence') + +def setitem(a, b, c): + "Same as a[b] = c." + a[b] = c + +def length_hint(obj, default=0): + """ + Return an estimate of the number of items in obj. + This is useful for presizing containers when building from an iterable. + + If the object supports len(), the result will be exact. Otherwise, it may + over- or under-estimate by an arbitrary amount. The result will be an + integer >= 0. + """ + if not isinstance(default, int): + msg = ("'%s' object cannot be interpreted as an integer" % + type(default).__name__) + raise TypeError(msg) + + try: + return len(obj) + except TypeError: + pass + + try: + hint = type(obj).__length_hint__ + except AttributeError: + return default + + try: + val = hint(obj) + except TypeError: + return default + if val is NotImplemented: + return default + if not isinstance(val, int): + msg = ('__length_hint__ must be integer, not %s' % + type(val).__name__) + raise TypeError(msg) + if val < 0: + msg = '__length_hint__() should return >= 0' + raise ValueError(msg) + return val + +# Generalized Lookup Objects **************************************************# + +class attrgetter: + """ + Return a callable object that fetches the given attribute(s) from its operand. + After f = attrgetter('name'), the call f(r) returns r.name. + After g = attrgetter('name', 'date'), the call g(r) returns (r.name, r.date). + After h = attrgetter('name.first', 'name.last'), the call h(r) returns + (r.name.first, r.name.last). + """ + __slots__ = ('_attrs', '_call') + + def __init__(self, attr, *attrs): + if not attrs: + if not isinstance(attr, str): + raise TypeError('attribute name must be a string') + self._attrs = (attr,) + names = attr.split('.') + def func(obj): + for name in names: + obj = getattr(obj, name) + return obj + self._call = func + else: + self._attrs = (attr,) + attrs + getters = tuple(map(attrgetter, self._attrs)) + def func(obj): + return tuple(getter(obj) for getter in getters) + self._call = func + + def __call__(self, obj): + return self._call(obj) + + def __repr__(self): + return '%s.%s(%s)' % (self.__class__.__module__, + self.__class__.__qualname__, + ', '.join(map(repr, self._attrs))) + + def __reduce__(self): + return self.__class__, self._attrs + +class itemgetter: + """ + Return a callable object that fetches the given item(s) from its operand. + After f = itemgetter(2), the call f(r) returns r[2]. + After g = itemgetter(2, 5, 3), the call g(r) returns (r[2], r[5], r[3]) + """ + __slots__ = ('_items', '_call') + + def __init__(self, item, *items): + if not items: + self._items = (item,) + def func(obj): + return obj[item] + self._call = func + else: + self._items = items = (item,) + items + def func(obj): + return tuple(obj[i] for i in items) + self._call = func + + def __call__(self, obj): + return self._call(obj) + + def __repr__(self): + return '%s.%s(%s)' % (self.__class__.__module__, + self.__class__.__name__, + ', '.join(map(repr, self._items))) + + def __reduce__(self): + return self.__class__, self._items + +class methodcaller: + """ + Return a callable object that calls the given method on its operand. + After f = methodcaller('name'), the call f(r) returns r.name(). + After g = methodcaller('name', 'date', foo=1), the call g(r) returns + r.name('date', foo=1). + """ + __slots__ = ('_name', '_args', '_kwargs') + + def __init__(*args, **kwargs): + if len(args) < 2: + msg = "methodcaller needs at least one argument, the method name" + raise TypeError(msg) + self = args[0] + self._name = args[1] + if not isinstance(self._name, str): + raise TypeError('method name must be a string') + self._args = args[2:] + self._kwargs = kwargs + + def __call__(self, obj): + return getattr(obj, self._name)(*self._args, **self._kwargs) + + def __repr__(self): + args = [repr(self._name)] + args.extend(map(repr, self._args)) + args.extend('%s=%r' % (k, v) for k, v in self._kwargs.items()) + return '%s.%s(%s)' % (self.__class__.__module__, + self.__class__.__name__, + ', '.join(args)) + + def __reduce__(self): + if not self._kwargs: + return self.__class__, (self._name,) + self._args + else: + from functools import partial + return partial(self.__class__, self._name, **self._kwargs), self._args + + +# In-place Operations *********************************************************# + +def iadd(a, b): + "Same as a += b." + a += b + return a + +def iand(a, b): + "Same as a &= b." + a &= b + return a + +def iconcat(a, b): + "Same as a += b, for a and b sequences." + if not hasattr(a, '__getitem__'): + msg = "'%s' object can't be concatenated" % type(a).__name__ + raise TypeError(msg) + a += b + return a + +def ifloordiv(a, b): + "Same as a //= b." + a //= b + return a + +def ilshift(a, b): + "Same as a <<= b." + a <<= b + return a + +def imod(a, b): + "Same as a %= b." + a %= b + return a + +def imul(a, b): + "Same as a *= b." + a *= b + return a + +def imatmul(a, b): + "Same as a @= b." + a @= b + return a + +def ior(a, b): + "Same as a |= b." + a |= b + return a + +def ipow(a, b): + "Same as a **= b." + a **=b + return a + +def irshift(a, b): + "Same as a >>= b." + a >>= b + return a + +def isub(a, b): + "Same as a -= b." + a -= b + return a + +def itruediv(a, b): + "Same as a /= b." + a /= b + return a + +def ixor(a, b): + "Same as a ^= b." + a ^= b + return a + + +try: + from _operator import * +except ImportError: + pass +else: + from _operator import __doc__ + +# All of these "__func__ = func" assignments have to happen after importing +# from _operator to make sure they're set to the right function +__lt__ = lt +__le__ = le +__eq__ = eq +__ne__ = ne +__ge__ = ge +__gt__ = gt +__not__ = not_ +__abs__ = abs +__add__ = add +__and__ = and_ +__floordiv__ = floordiv +__index__ = index +__inv__ = inv +__invert__ = invert +__lshift__ = lshift +__mod__ = mod +__mul__ = mul +__matmul__ = matmul +__neg__ = neg +__or__ = or_ +__pos__ = pos +__pow__ = pow +__rshift__ = rshift +__sub__ = sub +__truediv__ = truediv +__xor__ = xor +__concat__ = concat +__contains__ = contains +__delitem__ = delitem +__getitem__ = getitem +__setitem__ = setitem +__iadd__ = iadd +__iand__ = iand +__iconcat__ = iconcat +__ifloordiv__ = ifloordiv +__ilshift__ = ilshift +__imod__ = imod +__imul__ = imul +__imatmul__ = imatmul +__ior__ = ior +__ipow__ = ipow +__irshift__ = irshift +__isub__ = isub +__itruediv__ = itruediv +__ixor__ = ixor From e452d5523e8f029d43c2f13253d3b594c4a43174 Mon Sep 17 00:00:00 2001 From: Shitong Wen Date: Fri, 31 May 2019 14:16:31 +0800 Subject: [PATCH 686/884] use crate crc to replace crc table --- Cargo.lock | 16 ++++++++++++++ vm/Cargo.toml | 1 + vm/src/stdlib/binascii.rs | 46 +++++---------------------------------- 3 files changed, 22 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 272786ef3c..26521b2598 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,6 +124,11 @@ dependencies = [ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "build_const" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bumpalo" version = "2.3.0" @@ -195,6 +200,14 @@ dependencies = [ "python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crc" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "diff" version = "0.1.11" @@ -886,6 +899,7 @@ dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1429,6 +1443,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" "checksum block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49665c62e0e700857531fa5d3763e91b539ff1abeebd56808d378b495870d60d" "checksum block-padding 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d75255892aeb580d3c566f213a2b6fdc1c66667839f45719ee1d30ebf2aea591" +"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" "checksum bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f6a6bc2ba935b1e2f810787ceba8dfe86cbc164cee55925cae715541186673c4" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" "checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" @@ -1439,6 +1454,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b489034e723e7f5109fecd19b719e664f89ef925be785885252469e9822fa940" +"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 5ee8e81c44..91c6988225 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -30,6 +30,7 @@ lexical = "2.0.0" itertools = "^0.8.0" hexf = "0.1.0" indexmap = "1.0.2" +crc = "^1.0.0" # TODO: release and publish to crates.io [dependencies.unicode-casing] diff --git a/vm/src/stdlib/binascii.rs b/vm/src/stdlib/binascii.rs index f0c6cc8ca5..049f69dfe6 100644 --- a/vm/src/stdlib/binascii.rs +++ b/vm/src/stdlib/binascii.rs @@ -3,6 +3,7 @@ use crate::obj::objbytes; use crate::obj::objint; use crate::pyobject::{PyObjectRef, PyResult}; use crate::vm::VirtualMachine; +use crc::{crc32, Hasher32}; use num_traits::ToPrimitive; fn hex_nibble(n: u8) -> u8 { @@ -58,40 +59,6 @@ fn binascii_unhexlify(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bytes(unhex)) } -static CRC32_TABLE: [u32; 256] = [ - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, -]; fn binascii_crc32(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -106,13 +73,10 @@ fn binascii_crc32(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Some(value) => objint::get_value(&value).to_u32().unwrap(), }; - crc = !crc; - for b in bytes.iter() { - let b = *b as u32; - crc = CRC32_TABLE[((crc ^ b) & 0xff) as usize] ^ (crc >> 8); - } - crc = crc ^ 0xffffffff; - Ok(vm.ctx.new_int(crc)) + let mut digest = crc32::Digest::new_with_initial(crc32::IEEE, crc); + digest.write(&bytes); + + Ok(vm.ctx.new_int(digest.sum32())) } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { From 1949239592d6a02870b00c6fe63120863472ede6 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Fri, 31 May 2019 21:56:10 +0300 Subject: [PATCH 687/884] cleanups, type fixes and renames --- src/main.rs | 2 +- vm/src/frame.rs | 26 +++++++++++++------------- vm/src/stdlib/binascii.rs | 2 +- vm/src/symboltable.rs | 16 +++++++--------- vm/src/vm.rs | 23 +++++++++++------------ 5 files changed, 33 insertions(+), 36 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2100226c2f..cd20462ecc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -159,7 +159,7 @@ fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), Com Ok(value) => { // Save non-None values as "_" - use rustpython_vm::pyobject::{IdProtocol, IntoPyObject, ItemProtocol}; + use rustpython_vm::pyobject::{IdProtocol, IntoPyObject}; if !value.is(&vm.get_none()) { let key = objstr::PyString::from("_").into_pyobject(vm); diff --git a/vm/src/frame.rs b/vm/src/frame.rs index c5e3b53ca9..fef36bfecc 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -124,15 +124,15 @@ impl Scope { self.locals.iter().next().cloned() } - pub fn child_scope_with_locals(&self, locals: PyDictRef) -> Scope { + pub fn new_child_scope_with_locals(&self, locals: PyDictRef) -> Scope { Scope { locals: self.locals.clone().insert(locals), globals: self.globals.clone(), } } - pub fn child_scope(&self, ctx: &PyContext) -> Scope { - self.child_scope_with_locals(ctx.new_dict()) + pub fn new_child_scope(&self, ctx: &PyContext) -> Scope { + self.new_child_scope_with_locals(ctx.new_dict()) } } @@ -651,22 +651,22 @@ impl Frame { // pop argc arguments // argument: name, args, globals let scope = self.scope.clone(); - let obj = vm + let func_obj = vm .ctx .new_function(code_obj, scope, defaults, kw_only_defaults); let name = qualified_name.value.split('.').next_back().unwrap(); - vm.set_attr(&obj, "__name__", vm.new_str(name.to_string()))?; - vm.set_attr(&obj, "__qualname__", qualified_name)?; + vm.set_attr(&func_obj, "__name__", vm.new_str(name.to_string()))?; + vm.set_attr(&func_obj, "__qualname__", qualified_name)?; let module = self .scope .globals .get_item_option("__name__", vm)? .unwrap_or_else(|| vm.get_none()); - vm.set_attr(&obj, "__module__", module)?; - vm.set_attr(&obj, "__annotations__", annotations)?; + vm.set_attr(&func_obj, "__module__", module)?; + vm.set_attr(&func_obj, "__annotations__", annotations)?; - self.push_value(obj); + self.push_value(func_obj); Ok(None) } bytecode::Instruction::CallFunction { typ } => { @@ -1318,14 +1318,14 @@ impl Frame { || objtype::isinstance(&exception, &vm.ctx.exceptions.base_exception_type) { Ok(exception) - } else if let Ok(exception) = PyClassRef::try_from_object(vm, exception) { - if objtype::issubclass(&exception, &vm.ctx.exceptions.base_exception_type) { - let exception = vm.new_empty_exception(exception)?; + } else if let Ok(exc_type) = PyClassRef::try_from_object(vm, exception) { + if objtype::issubclass(&exc_type, &vm.ctx.exceptions.base_exception_type) { + let exception = vm.new_empty_exception(exc_type)?; Ok(exception) } else { let msg = format!( "Can only raise BaseException derived types, not {}", - exception + exc_type ); Err(vm.new_type_error(msg)) } diff --git a/vm/src/stdlib/binascii.rs b/vm/src/stdlib/binascii.rs index 049f69dfe6..ff748aa013 100644 --- a/vm/src/stdlib/binascii.rs +++ b/vm/src/stdlib/binascii.rs @@ -68,7 +68,7 @@ fn binascii_crc32(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { ); let bytes = objbytes::get_value(data); - let mut crc = match value { + let crc = match value { None => 0u32, Some(value) => objint::get_value(&value).to_u32().unwrap(), }; diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs index ea9ae52a0a..24407c7512 100644 --- a/vm/src/symboltable.rs +++ b/vm/src/symboltable.rs @@ -16,7 +16,8 @@ pub fn make_symbol_table(program: &ast::Program) -> Result, @@ -101,7 +102,6 @@ fn analyze_symbol_table( symbol_scope: &SymbolScope, parent_symbol_scope: Option<&SymbolScope>, ) -> SymbolTableResult { - // println!("Analyzing {:?}, parent={:?} symbols={:?}", symbol_scope, parent_symbol_scope, symbol_scope.symbols); // Analyze sub scopes: for sub_scope in &symbol_scope.sub_scopes { analyze_symbol_table(&sub_scope, Some(symbol_scope))?; @@ -157,7 +157,6 @@ impl SymbolTableBuilder { } pub fn enter_scope(&mut self) { - // Create new scope and push into scope stack. let scope = SymbolScope::new(); self.scopes.push(scope); } @@ -494,7 +493,7 @@ impl SymbolTableBuilder { } fn enter_function(&mut self, args: &ast::Parameters) -> SymbolTableResult { - // Evaulate eventual default parameters: + // Evaluate eventual default parameters: self.scan_expressions(&args.defaults)?; for kw_default in &args.kw_defaults { if let Some(expression) = kw_default { @@ -545,9 +544,8 @@ impl SymbolTableBuilder { let scope_depth = self.scopes.len(); let current_scope = self.scopes.last_mut().unwrap(); let location = Default::default(); - if let Some(_old_role) = current_scope.symbols.get(name) { + if current_scope.symbols.contains_key(name) { // Role already set.. - // debug!("TODO: {:?}", old_role); match role { SymbolRole::Global => { return Err(SymbolTableError { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 17d1caffb9..7c2e804625 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -171,14 +171,13 @@ impl VirtualMachine { self.invoke(exc_type.into_object(), args) } + /// Create Python instance of `exc_type` with message pub fn new_exception(&self, exc_type: PyClassRef, msg: String) -> PyObjectRef { // TODO: exc_type may be user-defined exception, so we should return PyResult // TODO: maybe there is a clearer way to create an instance: info!("New exception created: {}", msg); let pymsg = self.new_str(msg); let args: Vec = vec![pymsg]; - - // Call function: self.invoke(exc_type.into_object(), args).unwrap() } @@ -402,13 +401,13 @@ impl VirtualMachine { scope: &Scope, defaults: &Option, kw_only_defaults: &Option, - args: PyFuncArgs, + func_args: PyFuncArgs, ) -> PyResult { - let scope = scope.child_scope(&self.ctx); + let scope = scope.new_child_scope(&self.ctx); self.fill_locals_from_args( &code.code, &scope.get_locals(), - args, + func_args, defaults, kw_only_defaults, )?; @@ -432,8 +431,8 @@ impl VirtualMachine { ) -> PyResult { if let Some(PyFunction { code, scope, .. }) = &function.payload() { let scope = scope - .child_scope_with_locals(cells) - .child_scope_with_locals(locals); + .new_child_scope_with_locals(cells) + .new_child_scope_with_locals(locals); let frame = Frame::new(code.clone(), scope).into_ref(self); return self.run_frame_full(frame); } @@ -447,11 +446,11 @@ impl VirtualMachine { &self, code_object: &bytecode::CodeObject, locals: &PyDictRef, - args: PyFuncArgs, + func_args: PyFuncArgs, defaults: &Option, kw_only_defaults: &Option, ) -> PyResult<()> { - let nargs = args.args.len(); + let nargs = func_args.args.len(); let nexpected_args = code_object.arg_names.len(); // This parses the arguments from args and kwargs into @@ -469,7 +468,7 @@ impl VirtualMachine { // Copy positional arguments into local variables for i in 0..n { let arg_name = &code_object.arg_names[i]; - let arg = &args.args[i]; + let arg = &func_args.args[i]; locals.set_item(arg_name, arg.clone(), self)?; } @@ -478,7 +477,7 @@ impl VirtualMachine { bytecode::Varargs::Named(ref vararg_name) => { let mut last_args = vec![]; for i in n..nargs { - let arg = &args.args[i]; + let arg = &func_args.args[i]; last_args.push(arg.clone()); } let vararg_value = self.ctx.new_tuple(last_args); @@ -508,7 +507,7 @@ impl VirtualMachine { }; // Handle keyword arguments - for (name, value) in args.kwargs { + for (name, value) in func_args.kwargs { // Check if we have a parameter with this name: if code_object.arg_names.contains(&name) || code_object.kwonlyarg_names.contains(&name) { From bc35a64e5402566de8e8e9b4f01dfda87851429f Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Sat, 1 Jun 2019 03:19:22 +0300 Subject: [PATCH 688/884] fix most of clippy warnings --- parser/src/ast.rs | 1 + vm/src/builtins.rs | 11 +++++------ vm/src/dictdatatype.rs | 2 +- vm/src/frame.rs | 3 +-- vm/src/obj/objbytearray.rs | 2 +- vm/src/obj/objcode.rs | 1 + vm/src/obj/objcomplex.rs | 4 ++-- vm/src/obj/objfloat.rs | 2 +- vm/src/obj/objframe.rs | 1 + vm/src/obj/objint.rs | 10 +++++++--- vm/src/obj/objstr.rs | 14 +++++++------- vm/src/obj/objtuple.rs | 2 +- vm/src/pyhash.rs | 6 +++--- vm/src/pyobject.rs | 1 + vm/src/stdlib/ast.rs | 2 +- vm/src/stdlib/itertools.rs | 25 ++++++++++++++----------- vm/src/stdlib/os.rs | 13 ++++++++----- vm/src/stdlib/socket.rs | 4 ++-- vm/src/symboltable.rs | 14 +++++++++----- vm/src/vm.rs | 2 +- 20 files changed, 68 insertions(+), 52 deletions(-) diff --git a/parser/src/ast.rs b/parser/src/ast.rs index 1b985db2db..941e041316 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -44,6 +44,7 @@ pub struct Located { pub type LocatedStatement = Located; /// Abstract syntax tree nodes for python statements. +#[allow(clippy::large_enum_variant)] #[derive(Debug, PartialEq)] pub enum Statement { Break, diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index d467201a17..9afcd0e2ca 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -612,13 +612,12 @@ impl Printer for std::io::StdoutLock<'_> { } } -pub fn builtin_exit(object: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { - match object { - OptionalArg::Present(object) => match i32::try_from_object(&vm, object.clone()) { +pub fn builtin_exit(exit_code_arg: OptionalArg, vm: &VirtualMachine) -> PyResult<()> { + if let OptionalArg::Present(exit_code_obj) = exit_code_arg { + match i32::try_from_object(&vm, exit_code_obj.clone()) { Ok(code) => std::process::exit(code), - _ => println!("{}", vm.to_str(&object)?.as_str()), - }, - _ => {} + _ => println!("{}", vm.to_str(&exit_code_obj)?.as_str()), + } } std::process::exit(0); } diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index 7885872667..ca20f20127 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -193,7 +193,7 @@ impl Dict { Box::new( self.entries .iter() - .filter_map(|v| v.as_ref().map_or(None, |v| Some(v.key.clone()))), + .filter_map(|v| v.as_ref().and_then(|v| Some(v.key.clone()))), ) } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index fef36bfecc..adc7bc46af 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -173,8 +173,7 @@ impl NameProtocol for Scope { fn store_cell(&self, vm: &VirtualMachine, name: &str, value: PyObjectRef) { self.locals .iter() - .skip(1) - .next() + .nth(1) .expect("no outer scope for non-local") .set_item(name, value, vm) .unwrap(); diff --git a/vm/src/obj/objbytearray.rs b/vm/src/obj/objbytearray.rs index 2c72a3f462..51d608b095 100644 --- a/vm/src/obj/objbytearray.rs +++ b/vm/src/obj/objbytearray.rs @@ -340,7 +340,7 @@ impl PyByteArrayRef { let pos = bytes .iter() .position(|b| *b == x) - .ok_or(vm.new_value_error("value not found in bytearray".to_string()))?; + .ok_or_else(|| vm.new_value_error("value not found in bytearray".to_string()))?; bytes.remove(pos); diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 60549cd3ac..51a73d6141 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -34,6 +34,7 @@ impl PyValue for PyCode { } impl PyCodeRef { + #[allow(clippy::new_ret_no_self)] fn new(_cls: PyClassRef, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error("Cannot directly create code object".to_string())) } diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index a349354a23..7a6df87056 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -126,12 +126,12 @@ impl PyComplex { #[pymethod(name = "__float__")] fn float(&self, vm: &VirtualMachine) -> PyResult { - return Err(vm.new_type_error(String::from("Can't convert complex to float"))); + Err(vm.new_type_error(String::from("Can't convert complex to float"))) } #[pymethod(name = "__int__")] fn int(&self, vm: &VirtualMachine) -> PyResult { - return Err(vm.new_type_error(String::from("Can't convert complex to int"))); + Err(vm.new_type_error(String::from("Can't convert complex to int"))) } #[pymethod(name = "__mul__")] diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index e300cf2ac8..2d6287d7b0 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -541,7 +541,7 @@ pub fn ufrexp(value: f64) -> (f64, i32) { } else { let bits = value.to_bits(); let exponent: i32 = ((bits >> 52) & 0x7ff) as i32 - 1022; - let mantissa_bits = bits & (0x000fffffffffffff) | (1022 << 52); + let mantissa_bits = bits & (0x000f_ffff_ffff_ffff) | (1022 << 52); (f64::from_bits(mantissa_bits), exponent) } } diff --git a/vm/src/obj/objframe.rs b/vm/src/obj/objframe.rs index b4ed15f74a..6669e3bb9a 100644 --- a/vm/src/obj/objframe.rs +++ b/vm/src/obj/objframe.rs @@ -18,6 +18,7 @@ pub fn init(context: &PyContext) { } impl FrameRef { + #[allow(clippy::new_ret_no_self)] fn new(_class: FrameRef, vm: &VirtualMachine) -> PyResult { Err(vm.new_type_error("Cannot directly create frame object".to_string())) } diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index f8af3553f6..2b703d30e8 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -112,8 +112,9 @@ impl_try_from_object_int!( (u64, to_u64), ); +#[allow(clippy::collapsible_if)] fn inner_pow(int1: &PyInt, int2: &PyInt, vm: &VirtualMachine) -> PyResult { - Ok(if int2.value.is_negative() { + let result = if int2.value.is_negative() { let v1 = int1.float(vm)?; let v2 = int2.float(vm)?; vm.ctx.new_float(v1.pow(v2)) @@ -133,7 +134,8 @@ fn inner_pow(int1: &PyInt, int2: &PyInt, vm: &VirtualMachine) -> PyResult { // practically, exp over u64 is not possible to calculate anyway vm.ctx.not_implemented() } - }) + }; + Ok(result) } #[pyimpl] @@ -494,6 +496,7 @@ impl PyInt { } #[pymethod] + #[allow(clippy::match_bool)] fn from_bytes( bytes: PyByteInner, byteorder: PyStringRef, @@ -529,6 +532,7 @@ impl PyInt { Ok(x) } #[pymethod] + #[allow(clippy::match_bool)] fn to_bytes( &self, length: PyIntRef, @@ -547,7 +551,7 @@ impl PyInt { ); } } - if value.sign() == Sign::Minus && signed == false { + if value.sign() == Sign::Minus && !signed { return Err(vm.new_overflow_error("can't convert negative int to unsigned".to_string())); } let byte_len; diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index b0d19e9c3f..40d1912968 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -863,7 +863,7 @@ impl PyString { None => false, }; // a string is not an identifier if it has whitespace or starts with a number - is_identifier_start && chars.all(|c| UnicodeXID::is_xid_continue(c)) + is_identifier_start && chars.all(UnicodeXID::is_xid_continue) } // https://docs.python.org/3/library/stdtypes.html#str.translate @@ -876,18 +876,18 @@ impl PyString { match table.get_item(c as u32, vm) { Ok(value) => { if let Some(text) = value.payload::() { - translated.extend(text.value.chars()); - } else if let Some(_) = value.payload::() { - // Do Nothing + translated.push_str(&text.value); } else if let Some(bigint) = value.payload::() { match bigint.as_bigint().to_u32().and_then(std::char::from_u32) { Some(ch) => translated.push(ch as char), None => { - return Err(vm.new_value_error(format!( - "character mapping must be in range(0x110000)" - ))); + return Err(vm.new_value_error( + "character mapping must be in range(0x110000)".to_owned(), + )); } } + } else if let Some(_) = value.payload::() { + // Do Nothing } else { return Err(vm.new_type_error( "character mapping must return integer, None or str".to_owned(), diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 03596fbd25..40a5749ee7 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -27,7 +27,7 @@ impl fmt::Debug for PyTuple { impl From> for PyTuple { fn from(elements: Vec) -> Self { - PyTuple { elements: elements } + PyTuple { elements } } } diff --git a/vm/src/pyhash.rs b/vm/src/pyhash.rs index e717daa39f..8bbd82168e 100644 --- a/vm/src/pyhash.rs +++ b/vm/src/pyhash.rs @@ -9,11 +9,11 @@ pub type PyHash = i64; pub type PyUHash = u64; /// Prime multiplier used in string and various other hashes. -pub const MULTIPLIER: PyHash = 1000003; // 0xf4243 +pub const MULTIPLIER: PyHash = 1_000_003; // 0xf4243 /// Numeric hashes are based on reduction modulo the prime 2**_BITS - 1 pub const BITS: usize = 61; pub const MODULUS: PyUHash = (1 << BITS) - 1; -pub const INF: PyHash = 314159; +pub const INF: PyHash = 314_159; pub const NAN: PyHash = 0; pub const IMAG: PyHash = MULTIPLIER; @@ -42,7 +42,7 @@ pub fn hash_float(value: f64) -> PyHash { let mut x: PyUHash = 0; while m != 0.0 { x = ((x << 28) & MODULUS) | x >> (BITS - 28); - m *= 268435456.0; // 2**28 + m *= 268_435_456.0; // 2**28 e -= 28; let y = m as PyUHash; // pull out integer part m -= y as f64; diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index d88991b9ef..70c8a84b49 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1238,6 +1238,7 @@ impl PyObject where T: Sized + PyObjectPayload, { + #[allow(clippy::new_ret_no_self)] pub fn new(payload: T, typ: PyClassRef, dict: Option) -> PyObjectRef { PyObject { typ, dict, payload }.into_ref() } diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 2ac36c81e2..2b79dd7069 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -457,7 +457,7 @@ fn parameter_to_ast(vm: &VirtualMachine, parameter: &ast::Parameter) -> PyResult fn map_ast( f: fn(vm: &VirtualMachine, &T) -> PyResult, vm: &VirtualMachine, - items: &Vec, + items: &[T], ) -> PyResult { let list: PyResult> = items.iter().map(|x| Ok(f(vm, x)?.into_object())).collect(); diff --git a/vm/src/stdlib/itertools.rs b/vm/src/stdlib/itertools.rs index a8c333701a..732020480f 100644 --- a/vm/src/stdlib/itertools.rs +++ b/vm/src/stdlib/itertools.rs @@ -31,6 +31,7 @@ impl PyValue for PyItertoolsChain { #[pyimpl] impl PyItertoolsChain { #[pymethod(name = "__new__")] + #[allow(clippy::new_ret_no_self)] fn new(_cls: PyClassRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { Ok(PyItertoolsChain { iterables: args.args, @@ -88,6 +89,7 @@ impl PyValue for PyItertoolsCount { #[pyimpl] impl PyItertoolsCount { #[pymethod(name = "__new__")] + #[allow(clippy::new_ret_no_self)] fn new( _cls: PyClassRef, start: OptionalArg, @@ -105,7 +107,7 @@ impl PyItertoolsCount { Ok(PyItertoolsCount { cur: RefCell::new(start), - step: step, + step, } .into_ref(vm) .into_object()) @@ -140,6 +142,7 @@ impl PyValue for PyItertoolsRepeat { #[pyimpl] impl PyItertoolsRepeat { #[pymethod(name = "__new__")] + #[allow(clippy::new_ret_no_self)] fn new( _cls: PyClassRef, object: PyObjectRef, @@ -153,7 +156,7 @@ impl PyItertoolsRepeat { Ok(PyItertoolsRepeat { object: object.clone(), - times: times, + times, } .into_ref(vm) .into_object()) @@ -198,6 +201,7 @@ impl PyValue for PyItertoolsStarmap { #[pyimpl] impl PyItertoolsStarmap { #[pymethod(name = "__new__")] + #[allow(clippy::new_ret_no_self)] fn new( _cls: PyClassRef, function: PyObjectRef, @@ -206,12 +210,9 @@ impl PyItertoolsStarmap { ) -> PyResult { let iter = get_iter(vm, &iterable)?; - Ok(PyItertoolsStarmap { - function: function, - iter: iter, - } - .into_ref(vm) - .into_object()) + Ok(PyItertoolsStarmap { function, iter } + .into_ref(vm) + .into_object()) } #[pymethod(name = "__next__")] @@ -244,6 +245,7 @@ impl PyValue for PyItertoolsTakewhile { #[pyimpl] impl PyItertoolsTakewhile { #[pymethod(name = "__new__")] + #[allow(clippy::new_ret_no_self)] fn new( _cls: PyClassRef, predicate: PyObjectRef, @@ -253,7 +255,7 @@ impl PyItertoolsTakewhile { let iter = get_iter(vm, &iterable)?; Ok(PyItertoolsTakewhile { - predicate: predicate, + predicate, iterable: iter, stop_flag: RefCell::new(false), } @@ -314,6 +316,7 @@ fn pyobject_to_opt_usize(obj: PyObjectRef, vm: &VirtualMachine) -> Option #[pyimpl] impl PyItertoolsIslice { #[pymethod(name = "__new__")] + #[allow(clippy::new_ret_no_self)] fn new(_cls: PyClassRef, args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { let (iter, start, stop, step) = match args.args.len() { 0 | 1 => { @@ -377,8 +380,8 @@ impl PyItertoolsIslice { iterable: iter, cur: RefCell::new(0), next: RefCell::new(start), - stop: stop, - step: step, + stop, + step, } .into_ref(vm) .into_object()) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index f5a6ced96d..0893c1e6da 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -259,6 +259,7 @@ impl DirEntryRef { self.entry.path().to_str().unwrap().to_string() } + #[allow(clippy::match_bool)] fn perform_on_metadata( self, follow_symlinks: FollowSymlinks, @@ -434,6 +435,7 @@ fn to_seconds_from_nanos(secs: i64, nanos: i64) -> f64 { #[cfg(unix)] macro_rules! os_unix_stat_inner { ( $path:expr, $follow_symlinks:expr, $vm:expr ) => {{ + #[allow(clippy::match_bool)] fn get_stats(path: &str, follow_symlinks: bool) -> io::Result { let meta = match follow_symlinks { true => fs::metadata(path)?, @@ -685,12 +687,13 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { where F: IntoPyNativeFunc, { + let func_obj = vm.ctx.new_rustfunc(func); Self { - name: name, - func_obj: vm.ctx.new_rustfunc(func), - fd: fd, - dir_fd: dir_fd, - follow_symlinks: follow_symlinks, + name, + func_obj, + fd, + dir_fd, + follow_symlinks, } } } diff --git a/vm/src/stdlib/socket.rs b/vm/src/stdlib/socket.rs index 4f7cab5a14..68934bd016 100644 --- a/vm/src/stdlib/socket.rs +++ b/vm/src/stdlib/socket.rs @@ -218,7 +218,7 @@ impl SocketRef { } } - fn listen(self, _num: PyIntRef, _vm: &VirtualMachine) -> () {} + fn listen(self, _num: PyIntRef, _vm: &VirtualMachine) {} fn accept(self, vm: &VirtualMachine) -> PyResult { let ret = match self.con.borrow_mut().as_mut() { @@ -310,7 +310,7 @@ impl SocketRef { } } - fn close(self, _vm: &VirtualMachine) -> () { + fn close(self, _vm: &VirtualMachine) { self.con.borrow_mut().take(); } diff --git a/vm/src/symboltable.rs b/vm/src/symboltable.rs index 24407c7512..9687b0ec76 100644 --- a/vm/src/symboltable.rs +++ b/vm/src/symboltable.rs @@ -115,6 +115,7 @@ fn analyze_symbol_table( Ok(()) } +#[allow(clippy::single_match)] fn analyze_symbol( symbol_name: &str, symbol_role: &SymbolRole, @@ -298,13 +299,15 @@ impl SymbolTableBuilder { ast::Statement::Import { import_parts } => { for part in import_parts { if let Some(alias) = &part.alias { + // `import mymodule as myalias` + // `from mymodule import myimportname as myalias` self.register_name(alias, SymbolRole::Assigned)?; + } else if let Some(symbol) = &part.symbol { + // `from mymodule import myimport` + self.register_name(symbol, SymbolRole::Assigned)?; } else { - if let Some(symbol) = &part.symbol { - self.register_name(symbol, SymbolRole::Assigned)?; - } else { - self.register_name(&part.module, SymbolRole::Assigned)?; - } + // `import module` + self.register_name(&part.module, SymbolRole::Assigned)?; } } } @@ -540,6 +543,7 @@ impl SymbolTableBuilder { Ok(()) } + #[allow(clippy::single_match)] fn register_name(&mut self, name: &str, role: SymbolRole) -> SymbolTableResult { let scope_depth = self.scopes.len(); let current_scope = self.scopes.last_mut().unwrap(); diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 7c2e804625..dfe89e6408 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -951,7 +951,7 @@ impl VirtualMachine { } } - pub fn push_exception(&self, exc: PyObjectRef) -> () { + pub fn push_exception(&self, exc: PyObjectRef) { self.exceptions.borrow_mut().push(exc) } From 1294b56cbe5a7a7689ed5782feae10e42de48341 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 1 Jun 2019 14:32:23 +0300 Subject: [PATCH 689/884] Add _imp.create_builtin --- tests/snippets/imp.py | 13 +++++++++++++ vm/src/stdlib/imp.rs | 20 +++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/tests/snippets/imp.py b/tests/snippets/imp.py index a9d827b63d..91b47daeb7 100644 --- a/tests/snippets/imp.py +++ b/tests/snippets/imp.py @@ -1,4 +1,5 @@ import _imp +import time as import_time assert _imp.is_builtin("time") == True assert _imp.is_builtin("os") == False @@ -6,3 +7,15 @@ assert _imp.is_frozen("__hello__") == True assert _imp.is_frozen("os") == False + +class FakeSpec: + def __init__(self, name): + self.name = name + +A = FakeSpec("time") + +imp_time = _imp.create_builtin(A) +assert imp_time.sleep == import_time.sleep + +B = FakeSpec("not existing module") +assert _imp.create_builtin(B) == None diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index 5b249a1356..f098d08fe0 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -1,5 +1,6 @@ +use crate::obj::objstr; use crate::obj::objstr::PyStringRef; -use crate::pyobject::{PyObjectRef, PyResult}; +use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult}; use crate::vm::VirtualMachine; fn imp_extension_suffixes(vm: &VirtualMachine) -> PyResult { @@ -29,6 +30,22 @@ fn imp_is_frozen(name: PyStringRef, vm: &VirtualMachine) -> bool { vm.frozen.borrow().contains_key(name.as_str()) } +fn imp_create_builtin(spec: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); + + let name = &objstr::get_value(&vm.get_attribute(spec.clone(), "name")?); + + if let Ok(module) = sys_modules.get_item(name, vm) { + Ok(module) + } else { + if let Some(make_module_func) = vm.stdlib_inits.borrow().get(name) { + Ok(make_module_func(vm)) + } else { + Ok(vm.get_none()) + } + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let module = py_module!(vm, "_imp", { @@ -38,6 +55,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "lock_held" => ctx.new_rustfunc(imp_lock_held), "is_builtin" => ctx.new_rustfunc(imp_is_builtin), "is_frozen" => ctx.new_rustfunc(imp_is_frozen), + "create_builtin" => ctx.new_rustfunc(imp_create_builtin), }); module From 9a168b10d152507c9fd864205c53b6b622347c84 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 1 Jun 2019 14:49:12 +0300 Subject: [PATCH 690/884] Add _imp.exec_builtin that does nothing --- tests/snippets/imp.py | 2 ++ vm/src/stdlib/imp.rs | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/tests/snippets/imp.py b/tests/snippets/imp.py index 91b47daeb7..fee4701e33 100644 --- a/tests/snippets/imp.py +++ b/tests/snippets/imp.py @@ -19,3 +19,5 @@ def __init__(self, name): B = FakeSpec("not existing module") assert _imp.create_builtin(B) == None + +_imp.exec_builtin(imp_time) == 0 diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index f098d08fe0..f285f9d606 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -1,3 +1,4 @@ +use crate::obj::objmodule::PyModuleRef; use crate::obj::objstr; use crate::obj::objstr::PyStringRef; use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult}; @@ -46,6 +47,11 @@ fn imp_create_builtin(spec: PyObjectRef, vm: &VirtualMachine) -> PyResult { } } +fn imp_exec_builtin(_mod: PyModuleRef, _vm: &VirtualMachine) -> i32 { + // TOOD: Should we do something here? + 0 +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let module = py_module!(vm, "_imp", { @@ -56,6 +62,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "is_builtin" => ctx.new_rustfunc(imp_is_builtin), "is_frozen" => ctx.new_rustfunc(imp_is_frozen), "create_builtin" => ctx.new_rustfunc(imp_create_builtin), + "exec_builtin" => ctx.new_rustfunc(imp_exec_builtin), }); module From 4faaf2d6ca653cfd7362d2f9d111c31c81644060 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 1 Jun 2019 15:03:45 +0300 Subject: [PATCH 691/884] Add _imp.get_frozen_object --- tests/snippets/imp.py | 2 ++ vm/src/stdlib/imp.rs | 12 ++++++++++++ vm/src/vm.rs | 5 +++++ 3 files changed, 19 insertions(+) diff --git a/tests/snippets/imp.py b/tests/snippets/imp.py index fee4701e33..0f60c80825 100644 --- a/tests/snippets/imp.py +++ b/tests/snippets/imp.py @@ -21,3 +21,5 @@ def __init__(self, name): assert _imp.create_builtin(B) == None _imp.exec_builtin(imp_time) == 0 + +_imp.get_frozen_object("__hello__") diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index f285f9d606..6aa233b8d3 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -1,3 +1,5 @@ +use crate::compile; +use crate::obj::objcode::PyCodeRef; use crate::obj::objmodule::PyModuleRef; use crate::obj::objstr; use crate::obj::objstr::PyStringRef; @@ -52,6 +54,15 @@ fn imp_exec_builtin(_mod: PyModuleRef, _vm: &VirtualMachine) -> i32 { 0 } +fn imp_get_frozen_object(name: PyStringRef, vm: &VirtualMachine) -> PyResult { + if let Some(frozen) = vm.frozen.borrow().get(name.as_str()) { + compile::compile(vm, frozen, &compile::Mode::Exec, "frozen".to_string()) + .map_err(|err| vm.new_syntax_error(&err)) + } else { + Err(vm.new_import_error(format!("No such frozen object named {}", name.as_str()))) + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let module = py_module!(vm, "_imp", { @@ -63,6 +74,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "is_frozen" => ctx.new_rustfunc(imp_is_frozen), "create_builtin" => ctx.new_rustfunc(imp_create_builtin), "exec_builtin" => ctx.new_rustfunc(imp_exec_builtin), + "get_frozen_object" => ctx.new_rustfunc(imp_get_frozen_object), }); module diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 17d1caffb9..f256b22795 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -256,6 +256,11 @@ impl VirtualMachine { syntax_error } + pub fn new_import_error(&self, msg: String) -> PyObjectRef { + let import_error = self.ctx.exceptions.import_error.clone(); + self.new_exception(import_error, msg) + } + pub fn new_scope_with_builtins(&self) -> Scope { Scope::with_builtins(None, self.ctx.new_dict(), self) } From 0acce07631ea8fcdea4c7c100105eaeca4047690 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 1 Jun 2019 15:11:55 +0300 Subject: [PATCH 692/884] Add _imp.init_frozen --- tests/snippets/imp.py | 3 +++ vm/src/stdlib/imp.rs | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/tests/snippets/imp.py b/tests/snippets/imp.py index 0f60c80825..44e7e10423 100644 --- a/tests/snippets/imp.py +++ b/tests/snippets/imp.py @@ -23,3 +23,6 @@ def __init__(self, name): _imp.exec_builtin(imp_time) == 0 _imp.get_frozen_object("__hello__") + +hello = _imp.init_frozen("__hello__") +assert hello.initialized == True diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index 6aa233b8d3..ca266aa272 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -1,4 +1,5 @@ use crate::compile; +use crate::import::import_file; use crate::obj::objcode::PyCodeRef; use crate::obj::objmodule::PyModuleRef; use crate::obj::objstr; @@ -63,6 +64,14 @@ fn imp_get_frozen_object(name: PyStringRef, vm: &VirtualMachine) -> PyResult PyResult { + if let Some(frozen) = vm.frozen.borrow().get(name.as_str()) { + import_file(vm, name.as_str(), "frozen".to_string(), frozen.to_string()) + } else { + Err(vm.new_import_error(format!("No such frozen object named {}", name.as_str()))) + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let module = py_module!(vm, "_imp", { @@ -75,6 +84,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "create_builtin" => ctx.new_rustfunc(imp_create_builtin), "exec_builtin" => ctx.new_rustfunc(imp_exec_builtin), "get_frozen_object" => ctx.new_rustfunc(imp_get_frozen_object), + "init_frozen" => ctx.new_rustfunc(imp_init_frozen), }); module From 106f5f7054e499a1312819a0a0392ccbe0b85f71 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 1 Jun 2019 15:14:17 +0300 Subject: [PATCH 693/884] Add _imp.is_frozen_package --- vm/src/stdlib/imp.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index ca266aa272..ae40c16c53 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -72,6 +72,11 @@ fn imp_init_frozen(name: PyStringRef, vm: &VirtualMachine) -> PyResult { } } +fn imp_is_frozen_package(_name: PyStringRef, _vm: &VirtualMachine) -> bool { + // TODO: Support frozen package. + false +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let module = py_module!(vm, "_imp", { @@ -85,6 +90,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "exec_builtin" => ctx.new_rustfunc(imp_exec_builtin), "get_frozen_object" => ctx.new_rustfunc(imp_get_frozen_object), "init_frozen" => ctx.new_rustfunc(imp_init_frozen), + "is_frozen_package" => ctx.new_rustfunc(imp_is_frozen_package), }); module From 3b5cc1ad2f19db30ce0fc1cd88da793014ba0058 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Sun, 2 Jun 2019 01:12:05 +0300 Subject: [PATCH 694/884] more doc comments, split with_exit() method for context manager __exit__ into two methods --- vm/src/bytecode.rs | 1 + vm/src/frame.rs | 79 ++++++++++++++++++++++--------------------- vm/src/obj/objbool.rs | 1 + vm/src/obj/objtype.rs | 2 +- vm/src/pyobject.rs | 2 ++ 5 files changed, 45 insertions(+), 40 deletions(-) diff --git a/vm/src/bytecode.rs b/vm/src/bytecode.rs index fb8cab98bc..5df445d748 100644 --- a/vm/src/bytecode.rs +++ b/vm/src/bytecode.rs @@ -17,6 +17,7 @@ use std::fmt; #[derive(Clone, PartialEq, Serialize, Deserialize)] pub struct CodeObject { pub instructions: Vec, + /// Jump targets. pub label_map: HashMap, pub locations: Vec, pub arg_names: Vec, // Names of positional arguments diff --git a/vm/src/frame.rs b/vm/src/frame.rs index adc7bc46af..88a1b31ada 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -243,7 +243,7 @@ pub enum ExecutionResult { Yield(PyObjectRef), } -// A valid execution result, or an exception +/// A valid execution result, or an exception pub type FrameResult = Result, PyObjectRef>; impl Frame { @@ -285,9 +285,11 @@ impl Frame { Ok(Some(value)) => { break Ok(value); } + // Instruction raised an exception Err(exception) => { - // unwind block stack on exception and find any handlers. - // Add an entry in the traceback: + // 1. Extract traceback from exception's '__traceback__' attr. + // 2. Add new entry with current execution position (filename, lineno, code_object) to traceback. + // 3. Unwind block stack till appropriate handler is found. assert!(objtype::isinstance( &exception, &vm.ctx.exceptions.base_exception_type @@ -296,13 +298,12 @@ impl Frame { .get_attribute(exception.clone(), "__traceback__") .unwrap(); trace!("Adding to traceback: {:?} {:?}", traceback, lineno); - let pos = vm.ctx.new_tuple(vec![ + let raise_location = vm.ctx.new_tuple(vec![ vm.ctx.new_str(filename.clone()), vm.ctx.new_int(lineno.get_row()), vm.ctx.new_str(run_obj_name.clone()), ]); - objlist::PyListRef::try_from_object(vm, traceback)?.append(pos, vm); - // exception.__trace + objlist::PyListRef::try_from_object(vm, traceback)?.append(raise_location, vm); match self.unwind_exception(vm, exception) { None => {} Some(exception) => { @@ -333,7 +334,7 @@ impl Frame { ins2 } - // Execute a single instruction: + /// Execute a single instruction. fn execute_instruction(&self, vm: &VirtualMachine) -> FrameResult { let instruction = self.fetch_instruction(); { @@ -565,9 +566,7 @@ impl Frame { } = &block.typ { debug_assert!(end1 == end2); - - // call exit now with no exception: - self.with_exit(vm, &context_manager, None)?; + self.call_context_manager_exit_no_exception(vm, &context_manager)?; } else { unreachable!("Block stack is incorrect, expected a with block"); } @@ -949,7 +948,7 @@ impl Frame { BlockType::With { context_manager, .. } => { - match self.with_exit(vm, &context_manager, None) { + match self.call_context_manager_exit_no_exception(vm, &context_manager) { Ok(..) => {} Err(exc) => { // __exit__ went wrong, @@ -976,7 +975,7 @@ impl Frame { } BlockType::With { context_manager, .. - } => match self.with_exit(vm, &context_manager, None) { + } => match self.call_context_manager_exit_no_exception(vm, &context_manager) { Ok(..) => {} Err(exc) => { panic!("Exception in with __exit__ {:?}", exc); @@ -1006,12 +1005,12 @@ impl Frame { end, context_manager, } => { - match self.with_exit(vm, &context_manager, Some(exc.clone())) { - Ok(exit_action) => { - match objbool::boolval(vm, exit_action) { - Ok(handle_exception) => { - if handle_exception { - // We handle the exception, so return! + match self.call_context_manager_exit(vm, &context_manager, exc.clone()) { + Ok(exit_result_obj) => { + match objbool::boolval(vm, exit_result_obj) { + // If __exit__ method returned True, suppress the exception and continue execution. + Ok(suppress_exception) => { + if suppress_exception { self.jump(end); return None; } else { @@ -1022,7 +1021,6 @@ impl Frame { return Some(exit_exc); } } - // if objtype::isinstance } Err(exit_exc) => { // TODO: what about original exception? @@ -1031,36 +1029,39 @@ impl Frame { } } BlockType::Loop { .. } => {} - // Exception was already poped on Raised. + // Exception was already popped on Raised. BlockType::ExceptHandler => {} } } Some(exc) } - fn with_exit( + fn call_context_manager_exit_no_exception( &self, vm: &VirtualMachine, context_manager: &PyObjectRef, - exc: Option, ) -> PyResult { - // Assume top of stack is __exit__ method: // TODO: do we want to put the exit call on the stack? - // let exit_method = self.pop_value(); - // let args = PyFuncArgs::default(); - // TODO: what happens when we got an error during handling exception? - let args = if let Some(exc) = exc { - let exc_type = exc.class().into_object(); - let exc_val = exc.clone(); - let exc_tb = vm.ctx.none(); // TODO: retrieve traceback? - vec![exc_type, exc_val, exc_tb] - } else { - let exc_type = vm.ctx.none(); - let exc_val = vm.ctx.none(); - let exc_tb = vm.ctx.none(); - vec![exc_type, exc_val, exc_tb] - }; - vm.call_method(context_manager, "__exit__", args) + // TODO: what happens when we got an error during execution of __exit__? + vm.call_method( + context_manager, + "__exit__", + vec![vm.ctx.none(), vm.ctx.none(), vm.ctx.none()], + ) + } + + fn call_context_manager_exit( + &self, + vm: &VirtualMachine, + context_manager: &PyObjectRef, + exc: PyObjectRef, + ) -> PyResult { + // TODO: do we want to put the exit call on the stack? + // TODO: what happens when we got an error during execution of __exit__? + let exc_type = exc.class().into_object(); + let exc_val = exc.clone(); + let exc_tb = vm.ctx.none(); // TODO: retrieve traceback? + vm.call_method(context_manager, "__exit__", vec![exc_type, exc_val, exc_tb]) } fn store_name( @@ -1131,7 +1132,7 @@ impl Frame { fn jump(&self, label: bytecode::Label) { let target_pc = self.code.label_map[&label]; - trace!("program counter from {:?} to {:?}", self.lasti, target_pc); + trace!("jump from {:?} to {:?}", self.lasti, target_pc); *self.lasti.borrow_mut() = target_pc; } diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index c1a999e457..8276a46f7d 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -19,6 +19,7 @@ impl TryFromObject for bool { } } +/// Convert Python bool into Rust bool. pub fn boolval(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { Ok(if let Ok(f) = vm.get_method(obj.clone(), "__bool__") { let bool_res = vm.invoke(f, PyFuncArgs::default())?; diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 29c66ffd19..0416d665b3 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -289,7 +289,7 @@ fn type_dict_setter(_instance: PyClassRef, _value: PyObjectRef, vm: &VirtualMach )) } -// This is the internal get_attr implementation for fast lookup on a class. +/// This is the internal get_attr implementation for fast lookup on a class. pub fn class_get_attr(class: &PyClassRef, attr_name: &str) -> Option { if let Some(item) = class.attributes.borrow().get(attr_name).cloned() { return Some(item); diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 70c8a84b49..3023573b0d 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -87,6 +87,7 @@ pub type PyResult = Result; // A valid value, o /// For attributes we do not use a dict, but a hashmap. This is probably /// faster, unordered, and only supports strings as keys. +/// TODO: class attributes should maintain insertion order (use IndexMap here) pub type PyAttributes = HashMap; impl fmt::Display for PyObject { @@ -833,6 +834,7 @@ impl PyRef { pub fn as_object(&self) -> &PyObjectRef { &self.obj } + pub fn into_object(self) -> PyObjectRef { self.obj } From 58b1f4a8ee66324fe53bb54d7c83df0c606f1fe5 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Sun, 2 Jun 2019 18:21:52 +0300 Subject: [PATCH 695/884] add indented source line in traceback --- vm/src/exceptions.rs | 96 +++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 32 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index e02cd2894f..464aea079b 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -4,6 +4,8 @@ use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{create_type, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; +use std::fs::File; +use std::io::{BufRead, BufReader}; fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let zelf = args.args[0].clone(); @@ -18,7 +20,7 @@ fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } -// print excption chain +/// Print exception chain pub fn print_exception(vm: &VirtualMachine, exc: &PyObjectRef) { let mut had_cause = false; if let Ok(cause) = vm.get_attribute(exc.clone(), "__cause__") { @@ -39,41 +41,71 @@ pub fn print_exception(vm: &VirtualMachine, exc: &PyObjectRef) { print_exception_inner(vm, exc) } -// Print exception including traceback: +fn print_source_line(filename: String, lineno: usize) { + // TODO: use io.open() method instead, when available, according to https://github.com/python/cpython/blob/master/Python/traceback.c#L393 + // TODO: support different encodings + let file = match File::open(filename) { + Ok(file) => file, + Err(_) => { + return; + } + }; + let file = BufReader::new(file); + + for (i, line) in file.lines().enumerate() { + if i + 1 == lineno { + if let Ok(line) = line { + // Indented with 4 spaces + println!(" {}", line.trim_start()); + } + return; + } + } +} + +/// Print exception occurrence location from traceback element +fn print_traceback_entry(vm: &VirtualMachine, tb_entry: &PyObjectRef) { + if objtype::isinstance(&tb_entry, &vm.ctx.tuple_type()) { + let location_attrs = objsequence::get_elements_tuple(&tb_entry); + let filename = if let Ok(x) = vm.to_str(&location_attrs[0]) { + x.value.clone() + } else { + "".to_string() + }; + + let lineno = if let Ok(x) = vm.to_str(&location_attrs[1]) { + x.value.clone() + } else { + "".to_string() + }; + + let obj_name = if let Ok(x) = vm.to_str(&location_attrs[2]) { + x.value.clone() + } else { + "".to_string() + }; + + println!( + r##" File "{}", line {}, in {}"##, + filename, lineno, obj_name + ); + print_source_line(filename, lineno.parse().unwrap()); + } else { + println!(" File ??"); + return; + } +} + +/// Print exception with traceback pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) { if let Ok(tb) = vm.get_attribute(exc.clone(), "__traceback__") { println!("Traceback (most recent call last):"); if objtype::isinstance(&tb, &vm.ctx.list_type()) { - let mut elements = objsequence::get_elements_list(&tb).to_vec(); - elements.reverse(); - for element in elements.iter() { - if objtype::isinstance(&element, &vm.ctx.tuple_type()) { - let element = objsequence::get_elements_tuple(&element); - let filename = if let Ok(x) = vm.to_str(&element[0]) { - x.value.clone() - } else { - "".to_string() - }; - - let lineno = if let Ok(x) = vm.to_str(&element[1]) { - x.value.clone() - } else { - "".to_string() - }; - - let obj_name = if let Ok(x) = vm.to_str(&element[2]) { - x.value.clone() - } else { - "".to_string() - }; - - println!( - r##" File "{}", line {}, in {}"##, - filename, lineno, obj_name - ); - } else { - println!(" File ??"); - } + let mut tb_entries = objsequence::get_elements_list(&tb).to_vec(); + tb_entries.reverse(); + + for exc_location in tb_entries.iter() { + print_traceback_entry(vm, exc_location); } } } else { From 1b9c054d034ce1b18202c9f2549098e40bf599ad Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Mon, 3 Jun 2019 17:27:58 +0300 Subject: [PATCH 696/884] make new exception logs a bit more descriptive --- vm/src/vm.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index dfe89e6408..12428123d5 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -166,7 +166,7 @@ impl VirtualMachine { } pub fn new_empty_exception(&self, exc_type: PyClassRef) -> PyResult { - info!("New exception created: no msg"); + info!("New exception created: {}", exc_type.name); let args = PyFuncArgs::default(); self.invoke(exc_type.into_object(), args) } @@ -175,7 +175,7 @@ impl VirtualMachine { pub fn new_exception(&self, exc_type: PyClassRef, msg: String) -> PyObjectRef { // TODO: exc_type may be user-defined exception, so we should return PyResult // TODO: maybe there is a clearer way to create an instance: - info!("New exception created: {}", msg); + info!("New exception created: {}('{}')", exc_type.name, msg); let pymsg = self.new_str(msg); let args: Vec = vec![pymsg]; self.invoke(exc_type.into_object(), args).unwrap() From 3bfd66bb89f89156499199e6767239c4460ee4a9 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Mon, 3 Jun 2019 19:46:32 +0300 Subject: [PATCH 697/884] make some exception messages more compatible with CPython, split get_method() into two methods and make raising TypeError more explicit for get_method() errors --- vm/src/builtins.rs | 40 +++++++++++++++++++--------------------- vm/src/obj/objbool.rs | 20 ++++++++++++-------- vm/src/obj/objdict.rs | 5 ++--- vm/src/obj/objfloat.rs | 18 ++++++++++++------ vm/src/obj/objint.rs | 23 +++++++++++------------ vm/src/obj/objiter.rs | 20 +++++++++++--------- vm/src/obj/objstr.rs | 8 ++++++-- vm/src/obj/objtype.rs | 5 +++-- vm/src/pyobject.rs | 12 ++++++++---- vm/src/stdlib/math.rs | 15 ++++++++------- vm/src/vm.rs | 23 ++++++++++++++++++----- 11 files changed, 110 insertions(+), 79 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 9afcd0e2ca..e69e63d9d5 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -31,10 +31,12 @@ use crate::vm::VirtualMachine; use crate::stdlib::io::io_open; fn builtin_abs(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { - match vm.get_method(x.clone(), "__abs__") { - Ok(attrib) => vm.invoke(attrib, PyFuncArgs::new(vec![], vec![])), - Err(..) => Err(vm.new_type_error("bad operand for abs".to_string())), - } + let method = vm.get_method_or_type_error( + x.clone(), + "__abs__", + format!("bad operand type for abs(): '{}'", x.class().name), + )?; + vm.invoke(method, PyFuncArgs::new(vec![], vec![])) } fn builtin_all(iterable: PyIterable, vm: &VirtualMachine) -> PyResult { @@ -367,15 +369,12 @@ fn builtin_iter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - let len_method_name = "__len__"; - match vm.get_method(obj.clone(), len_method_name) { - Ok(value) => vm.invoke(value, PyFuncArgs::default()), - Err(..) => Err(vm.new_type_error(format!( - "object of type '{}' has no method {:?}", - obj.class().name, - len_method_name - ))), - } + let method = vm.get_method_or_type_error( + obj.clone(), + "__len__", + format!("object of type '{}' has no len()", obj.class().name), + )?; + vm.invoke(method, PyFuncArgs::default()) } fn builtin_locals(vm: &VirtualMachine) -> PyDictRef { @@ -671,15 +670,14 @@ fn builtin_repr(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult fn builtin_reversed(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - match vm.get_method(obj.clone(), "__reversed__") { - Ok(value) => vm.invoke(value, PyFuncArgs::default()), - // TODO: fallback to using __len__ and __getitem__, if object supports sequence protocol - Err(..) => { - Err(vm.new_type_error(format!("'{}' object is not reversible", obj.class().name))) - } - } + // 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()) } -// builtin_reversed fn builtin_round(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( diff --git a/vm/src/obj/objbool.rs b/vm/src/obj/objbool.rs index 8276a46f7d..f294434b30 100644 --- a/vm/src/obj/objbool.rs +++ b/vm/src/obj/objbool.rs @@ -21,15 +21,19 @@ impl TryFromObject for bool { /// Convert Python bool into Rust bool. pub fn boolval(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - Ok(if let Ok(f) = vm.get_method(obj.clone(), "__bool__") { - let bool_res = vm.invoke(f, PyFuncArgs::default())?; - match bool_res.payload::() { - Some(i) => !i.as_bigint().is_zero(), - None => return Err(vm.new_type_error(String::from("TypeError"))), + let rs_bool = match vm.get_method(obj.clone(), "__bool__") { + Some(method_or_err) => { + // If descriptor returns Error, propagate it further + let method = method_or_err?; + let bool_obj = vm.invoke(method, PyFuncArgs::default())?; + match bool_obj.payload::() { + Some(int_obj) => !int_obj.as_bigint().is_zero(), + None => return Err(vm.new_type_error(String::from(""))), + } } - } else { - true - }) + None => true, + }; + Ok(rs_bool) } pub fn init(context: &PyContext) { diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 87e4b19191..8d2e0fdff8 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -206,11 +206,10 @@ impl PyDictRef { if let Some(value) = self.entries.borrow().get(vm, &key)? { return Ok(value); } - - if let Ok(method) = vm.get_method(self.clone().into_object(), "__missing__") { + if let Some(method_or_err) = vm.get_method(self.clone().into_object(), "__missing__") { + let method = method_or_err?; return vm.invoke(method, vec![key]); } - Err(vm.new_key_error(format!("Key not found: {}", vm.to_pystr(&key)?))) } diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 2d6287d7b0..78d1a8f4c8 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -262,7 +262,7 @@ impl PyFloat { Err(_) => { let arg_repr = vm.to_pystr(&arg)?; return Err(vm.new_value_error(format!( - "could not convert string to float: {}", + "could not convert string to float: '{}'", arg_repr ))); } @@ -273,7 +273,7 @@ impl PyFloat { Err(_) => { let arg_repr = vm.to_pystr(&arg)?; return Err(vm.new_value_error(format!( - "could not convert string to float: {}", + "could not convert string to float: '{}'", arg_repr ))); } @@ -556,11 +556,17 @@ pub fn get_value(obj: &PyObjectRef) -> f64 { pub fn make_float(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { if objtype::isinstance(obj, &vm.ctx.float_type()) { Ok(get_value(obj)) - } else if let Ok(method) = vm.get_method(obj.clone(), "__float__") { - let res = vm.invoke(method, vec![])?; - Ok(get_value(&res)) } else { - Err(vm.new_type_error(format!("Cannot cast {} to float", obj))) + let method = vm.get_method_or_type_error( + obj.clone(), + "__float__", + format!( + "float() argument must be a string or a number, not '{}'", + obj.class().name + ), + )?; + let result = vm.invoke(method, vec![])?; + Ok(get_value(&result)) } } diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 2b703d30e8..a9b5622c01 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -657,18 +657,17 @@ pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult { - if let Ok(f) = vm.get_method(obj.clone(), "__int__") { - let int_res = vm.invoke(f, PyFuncArgs::default())?; - match int_res.payload::() { - Some(i) => Ok(i.as_bigint().clone()), - None => Err(vm.new_type_error(format!( - "TypeError: __int__ returned non-int (type '{}')", int_res.class().name))), - } - } else { - Err(vm.new_type_error(format!( - "int() argument must be a string or a number, not '{}'", - obj.class().name - ))) + let method = vm.get_method_or_type_error( + obj.clone(), + "__int__", + format!("int() argument must be a string or a number, not '{}'", + obj.class().name) + )?; + let result = vm.invoke(method, PyFuncArgs::default())?; + match result.payload::() { + Some(int_obj) => Ok(int_obj.as_bigint().clone()), + None => Err(vm.new_type_error(format!( + "TypeError: __int__ returned non-int (type '{}')", result.class().name))), } } ) diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index efe672d62d..4eb2662369 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -18,18 +18,20 @@ use super::objtype::PyClassRef; * function 'iter' is called. */ pub fn get_iter(vm: &VirtualMachine, iter_target: &PyObjectRef) -> PyResult { - if let Ok(method) = vm.get_method(iter_target.clone(), "__iter__") { + if let Some(method_or_err) = vm.get_method(iter_target.clone(), "__iter__") { + let method = method_or_err?; vm.invoke(method, vec![]) - } else if vm.get_method(iter_target.clone(), "__getitem__").is_ok() { - Ok(PySequenceIterator { + } else { + vm.get_method_or_type_error( + iter_target.clone(), + "__getitem__", + format!("Cannot iterate over {}", iter_target.class().name), + )?; + let obj_iterator = PySequenceIterator { position: Cell::new(0), obj: iter_target.clone(), - } - .into_ref(vm) - .into_object()) - } else { - let message = format!("Cannot iterate over {}", iter_target.class().name); - return Err(vm.new_type_error(message)); + }; + Ok(obj_iterator.into_ref(vm).into_object()) } } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 40d1912968..9f03af12d9 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -869,9 +869,13 @@ impl PyString { // https://docs.python.org/3/library/stdtypes.html#str.translate #[pymethod] fn translate(&self, table: PyObjectRef, vm: &VirtualMachine) -> PyResult { + vm.get_method_or_type_error( + table.clone(), + "__getitem__", + format!("'{}' object is not subscriptable", table.class().name), + )?; + let mut translated = String::new(); - // It throws a type error if it is not subscribtable - vm.get_method(table.clone(), "__getitem__")?; for c in self.value.chars() { match table.get_item(c as u32, vm) { Ok(value) => { diff --git a/vm/src/obj/objtype.rs b/vm/src/obj/objtype.rs index 0416d665b3..90ec95ae8b 100644 --- a/vm/src/obj/objtype.rs +++ b/vm/src/obj/objtype.rs @@ -270,8 +270,9 @@ pub fn type_call(class: PyClassRef, args: Args, kwargs: KwArgs, vm: &VirtualMach let new_wrapped = vm.call_get_descriptor(new, class.into_object())?; let obj = vm.invoke(new_wrapped, (&args, &kwargs))?; - if let Ok(init) = vm.get_method(obj.clone(), "__init__") { - let res = vm.invoke(init, (&args, &kwargs))?; + if let Some(init_method_or_err) = vm.get_method(obj.clone(), "__init__") { + let init_method = init_method_or_err?; + let res = vm.invoke(init_method, (&args, &kwargs))?; if !res.is(&vm.get_none()) { return Err(vm.new_type_error("__init__ must return None".to_string())); } diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 3023573b0d..3bb2a26551 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1134,12 +1134,18 @@ where T: TryFromObject, { fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { - if let Ok(method) = vm.get_method(obj.clone(), "__iter__") { + if let Some(method_or_err) = vm.get_method(obj.clone(), "__iter__") { + let method = method_or_err?; Ok(PyIterable { method, _item: std::marker::PhantomData, }) - } else if vm.get_method(obj.clone(), "__getitem__").is_ok() { + } else { + vm.get_method_or_type_error( + obj.clone(), + "__getitem__", + format!("'{}' object is not iterable", obj.class().name), + )?; Self::try_from_object( vm, objiter::PySequenceIterator { @@ -1149,8 +1155,6 @@ where .into_ref(vm) .into_object(), ) - } else { - Err(vm.new_type_error(format!("'{}' object is not iterable", obj.class().name))) } } } diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index c8ee632478..bd1f5dab8f 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -173,15 +173,16 @@ fn math_lgamma(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn try_magic_method(func_name: &str, vm: &VirtualMachine, value: &PyObjectRef) -> PyResult { - if let Ok(method) = vm.get_method(value.clone(), func_name) { - vm.invoke(method, vec![]) - } else { - Err(vm.new_type_error(format!( - "TypeError: type {} doesn't define {} method", + let method = vm.get_method_or_type_error( + value.clone(), + func_name, + format!( + "type '{}' doesn't define '{}' method", value.class().name, func_name, - ))) - } + ), + )?; + vm.invoke(method, vec![]) } fn math_trunc(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 12428123d5..1e3f1b51ae 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -624,14 +624,26 @@ impl VirtualMachine { // get_method should be used for internal access to magic methods (by-passing // the full getattribute look-up. - pub fn get_method(&self, obj: PyObjectRef, method_name: &str) -> PyResult { + pub fn get_method_or_type_error( + &self, + obj: PyObjectRef, + method_name: &str, + err_msg: String, + ) -> PyResult { let cls = obj.class(); match objtype::class_get_attr(&cls, method_name) { Some(method) => self.call_get_descriptor(method, obj.clone()), - None => Err(self.new_type_error(format!("{} has no method {:?}", obj, method_name))), + None => Err(self.new_type_error(err_msg)), } } + /// May return exception, if `__get__` descriptor raises one + pub fn get_method(&self, obj: PyObjectRef, method_name: &str) -> Option { + let cls = obj.class(); + let method = objtype::class_get_attr(&cls, method_name)?; + Some(self.call_get_descriptor(method, obj.clone())) + } + /// Calls a method on `obj` passing `arg`, if the method exists. /// /// Otherwise, or if the result is the special `NotImplemented` built-in constant, @@ -646,13 +658,13 @@ impl VirtualMachine { where F: Fn(&VirtualMachine, PyObjectRef, PyObjectRef) -> PyResult, { - if let Ok(method) = self.get_method(obj.clone(), method) { + if let Some(method_or_err) = self.get_method(obj.clone(), method) { + let method = method_or_err?; let result = self.invoke(method, vec![arg.clone()])?; if !result.is(&self.ctx.not_implemented()) { return Ok(result); } } - unsupported(self, obj, arg) } @@ -944,7 +956,8 @@ impl VirtualMachine { } pub fn _membership(&self, haystack: PyObjectRef, needle: PyObjectRef) -> PyResult { - if let Ok(method) = self.get_method(haystack.clone(), "__contains__") { + if let Some(method_or_err) = self.get_method(haystack.clone(), "__contains__") { + let method = method_or_err?; self.invoke(method, vec![needle]) } else { self._membership_iter_search(haystack, needle) From bc33921fdb6ae7ddf75ae7c8f59375635fe05158 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Tue, 4 Jun 2019 14:56:44 +0300 Subject: [PATCH 698/884] make empty exception message format compatible with CPython --- vm/src/exceptions.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 464aea079b..4f1f947447 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -12,7 +12,8 @@ fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let msg = if args.args.len() > 1 { args.args[1].clone() } else { - vm.new_str("No msg".to_string()) + let empty_string = String::default(); + vm.new_str(empty_string) }; let traceback = vm.ctx.new_list(Vec::new()); vm.set_attr(&zelf, "msg", msg)?; @@ -132,8 +133,11 @@ fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } else { panic!("Error message must be set"); }; - let s = format!("{}: {}", exc.class().name, msg); - Ok(vm.new_str(s)) + let mut exc_repr = exc.class().name.clone(); + if !msg.is_empty() { + &exc_repr.push_str(&format!(": {}", msg)); + } + Ok(vm.new_str(exc_repr)) } #[derive(Debug)] From b2cdeab0c6942a7f035f966819a006ab675d0ac5 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Wed, 5 Jun 2019 01:43:32 +0300 Subject: [PATCH 699/884] Fix memoryview.obj misimplementation. The memoryview skeleton did not correctly assign the "obj" attribute; The "obj" attribute was assigned to the memoryview class and not the memryview object, leading to a funny bug that can be reproduced as follows: ```python m1 = memoryview(b'1234') m2 = memoryview(b'abcd') print(m1.obj) ``` the result would be the value inside `m2` instead that of `m1` --- vm/src/obj/objmemory.rs | 43 ++++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index 88040ae14a..8c2af26f05 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -1,18 +1,37 @@ use crate::obj::objbyteinner::try_as_byte; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyObjectRef, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyClassImpl, PyContext, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; -pub type PyMemoryViewRef = PyRef; - +#[pyclass(name = "memoryview")] #[derive(Debug)] pub struct PyMemoryView { - obj: PyObjectRef, + obj_ref: PyObjectRef, } +pub type PyMemoryViewRef = PyRef; + +#[pyimpl] impl PyMemoryView { pub fn get_obj_value(&self) -> Option> { - try_as_byte(&self.obj) + try_as_byte(&self.obj_ref) + } + + #[pymethod(name = "__new__")] + fn new( + cls: PyClassRef, + bytes_object: PyObjectRef, + vm: &VirtualMachine, + ) -> PyResult { + PyMemoryView { + obj_ref: bytes_object.clone(), + } + .into_ref_with_type(vm, cls) + } + + #[pyproperty] + fn obj(&self, __vm: &VirtualMachine) -> PyObjectRef { + self.obj_ref.clone() } } @@ -22,18 +41,6 @@ impl PyValue for PyMemoryView { } } -pub fn new_memory_view( - cls: PyClassRef, - bytes_object: PyObjectRef, - vm: &VirtualMachine, -) -> PyResult { - vm.set_attr(cls.as_object(), "obj", bytes_object.clone())?; - PyMemoryView { obj: bytes_object }.into_ref_with_type(vm, cls) -} - pub fn init(ctx: &PyContext) { - let memoryview_type = &ctx.memoryview_type; - extend_class!(ctx, memoryview_type, { - "__new__" => ctx.new_rustfunc(new_memory_view) - }); + PyMemoryView::extend_class(ctx, &ctx.memoryview_type) } From d2a82b1acdcb83fac8c7ab7f02482611ea29f66a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 15:14:27 +0300 Subject: [PATCH 700/884] Add _warnings --- tests/snippets/warnings.py | 3 +++ vm/src/stdlib/mod.rs | 2 ++ vm/src/stdlib/warnings.rs | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) create mode 100644 tests/snippets/warnings.py create mode 100644 vm/src/stdlib/warnings.rs diff --git a/tests/snippets/warnings.py b/tests/snippets/warnings.py new file mode 100644 index 0000000000..95092e5066 --- /dev/null +++ b/tests/snippets/warnings.py @@ -0,0 +1,3 @@ +import _warnings + +_warnings.warn("Test") diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index ef2d7f3a53..096d148458 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -16,6 +16,7 @@ mod string; mod thread; mod time_module; mod tokenize; +mod warnings; mod weakref; use std::collections::HashMap; @@ -55,6 +56,7 @@ pub fn get_module_inits() -> HashMap { modules.insert("tokenize".to_string(), Box::new(tokenize::make_module)); modules.insert("_weakref".to_string(), Box::new(weakref::make_module)); modules.insert("_imp".to_string(), Box::new(imp::make_module)); + modules.insert("_warnings".to_string(), Box::new(warnings::make_module)); // disable some modules on WASM #[cfg(not(target_arch = "wasm32"))] diff --git a/vm/src/stdlib/warnings.rs b/vm/src/stdlib/warnings.rs new file mode 100644 index 0000000000..bf0055d900 --- /dev/null +++ b/vm/src/stdlib/warnings.rs @@ -0,0 +1,37 @@ +use crate::function::OptionalArg; +use crate::obj::objstr::PyStringRef; +use crate::pyobject::PyObjectRef; +use crate::vm::VirtualMachine; + +#[derive(FromArgs)] +struct WarnArgs { + #[pyarg(positional_only, optional = false)] + message: PyStringRef, + #[pyarg(positional_or_keyword, optional = true)] + category: OptionalArg, + #[pyarg(positional_or_keyword, optional = true)] + stacklevel: OptionalArg, +} + +fn warnings_warn(args: WarnArgs, _vm: &VirtualMachine) { + // TODO: Implement correctly + let level = match args.stacklevel { + OptionalArg::Present(l) => l, + OptionalArg::Missing => 1, + }; + println!( + "Warning: {} , category: {:?}, level: {}", + args.message.as_str(), + args.category, + level + ) +} + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + let module = py_module!(vm, "_warnings", { + "warn" => ctx.new_rustfunc(warnings_warn), + }); + + module +} From 8a5182a8a19d160a6ab07071e2e3e2ebbf7b4721 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Fri, 7 Jun 2019 16:08:37 +0300 Subject: [PATCH 701/884] make get_method_or_type_error() error message lazy --- vm/src/builtins.rs | 24 +++++++++--------------- vm/src/obj/objfloat.rs | 8 +++----- vm/src/obj/objint.rs | 9 +++------ vm/src/obj/objiter.rs | 8 +++----- vm/src/obj/objstr.rs | 8 +++----- vm/src/pyobject.rs | 8 +++----- vm/src/stdlib/math.rs | 8 +++----- vm/src/vm.rs | 11 +++++++---- 8 files changed, 34 insertions(+), 50 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index e69e63d9d5..f44ad70112 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -31,11 +31,9 @@ use crate::vm::VirtualMachine; use crate::stdlib::io::io_open; fn builtin_abs(x: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let method = vm.get_method_or_type_error( - x.clone(), - "__abs__", - format!("bad operand type for abs(): '{}'", x.class().name), - )?; + let method = vm.get_method_or_type_error(x.clone(), "__abs__", || { + format!("bad operand type for abs(): '{}'", x.class().name) + })?; vm.invoke(method, PyFuncArgs::new(vec![], vec![])) } @@ -369,11 +367,9 @@ fn builtin_iter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn builtin_len(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(obj, None)]); - let method = vm.get_method_or_type_error( - obj.clone(), - "__len__", - format!("object of type '{}' has no len()", obj.class().name), - )?; + let method = vm.get_method_or_type_error(obj.clone(), "__len__", || { + format!("object of type '{}' has no len()", obj.class().name) + })?; vm.invoke(method, PyFuncArgs::default()) } @@ -671,11 +667,9 @@ 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"), - )?; + let method = vm.get_method_or_type_error(obj.clone(), "__reversed__", || { + format!("argument to reversed() must be a sequence") + })?; vm.invoke(method, PyFuncArgs::default()) } diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index 78d1a8f4c8..bd17147ad1 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -557,14 +557,12 @@ pub fn make_float(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { if objtype::isinstance(obj, &vm.ctx.float_type()) { Ok(get_value(obj)) } else { - let method = vm.get_method_or_type_error( - obj.clone(), - "__float__", + let method = vm.get_method_or_type_error(obj.clone(), "__float__", || { format!( "float() argument must be a string or a number, not '{}'", obj.class().name - ), - )?; + ) + })?; let result = vm.invoke(method, vec![])?; Ok(get_value(&result)) } diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index a9b5622c01..e0f29cefcb 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -657,12 +657,9 @@ pub fn to_int(vm: &VirtualMachine, obj: &PyObjectRef, base: u32) -> PyResult { - let method = vm.get_method_or_type_error( - obj.clone(), - "__int__", - format!("int() argument must be a string or a number, not '{}'", - obj.class().name) - )?; + let method = vm.get_method_or_type_error(obj.clone(), "__int__", || { + format!("int() argument must be a string or a number, not '{}'", obj.class().name) + })?; let result = vm.invoke(method, PyFuncArgs::default())?; match result.payload::() { Some(int_obj) => Ok(int_obj.as_bigint().clone()), diff --git a/vm/src/obj/objiter.rs b/vm/src/obj/objiter.rs index 4eb2662369..9aebc85f67 100644 --- a/vm/src/obj/objiter.rs +++ b/vm/src/obj/objiter.rs @@ -22,11 +22,9 @@ pub fn get_iter(vm: &VirtualMachine, iter_target: &PyObjectRef) -> PyResult { let method = method_or_err?; vm.invoke(method, vec![]) } else { - vm.get_method_or_type_error( - iter_target.clone(), - "__getitem__", - format!("Cannot iterate over {}", iter_target.class().name), - )?; + vm.get_method_or_type_error(iter_target.clone(), "__getitem__", || { + format!("Cannot iterate over {}", iter_target.class().name) + })?; let obj_iterator = PySequenceIterator { position: Cell::new(0), obj: iter_target.clone(), diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 9f03af12d9..2f5b2adf64 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -869,11 +869,9 @@ impl PyString { // https://docs.python.org/3/library/stdtypes.html#str.translate #[pymethod] fn translate(&self, table: PyObjectRef, vm: &VirtualMachine) -> PyResult { - vm.get_method_or_type_error( - table.clone(), - "__getitem__", - format!("'{}' object is not subscriptable", table.class().name), - )?; + vm.get_method_or_type_error(table.clone(), "__getitem__", || { + format!("'{}' object is not subscriptable", table.class().name) + })?; let mut translated = String::new(); for c in self.value.chars() { diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index 3bb2a26551..d0bccd856e 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1141,11 +1141,9 @@ where _item: std::marker::PhantomData, }) } else { - vm.get_method_or_type_error( - obj.clone(), - "__getitem__", - format!("'{}' object is not iterable", obj.class().name), - )?; + vm.get_method_or_type_error(obj.clone(), "__getitem__", || { + format!("'{}' object is not iterable", obj.class().name) + })?; Self::try_from_object( vm, objiter::PySequenceIterator { diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index bd1f5dab8f..01e3fe6c1d 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -173,15 +173,13 @@ fn math_lgamma(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn try_magic_method(func_name: &str, vm: &VirtualMachine, value: &PyObjectRef) -> PyResult { - let method = vm.get_method_or_type_error( - value.clone(), - func_name, + let method = vm.get_method_or_type_error(value.clone(), func_name, || { format!( "type '{}' doesn't define '{}' method", value.class().name, func_name, - ), - )?; + ) + })?; vm.invoke(method, vec![]) } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 1e3f1b51ae..77ea5ba734 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -624,16 +624,19 @@ impl VirtualMachine { // get_method should be used for internal access to magic methods (by-passing // the full getattribute look-up. - pub fn get_method_or_type_error( + pub fn get_method_or_type_error( &self, obj: PyObjectRef, method_name: &str, - err_msg: String, - ) -> PyResult { + err_msg: F, + ) -> PyResult + where + F: FnOnce() -> String, + { let cls = obj.class(); match objtype::class_get_attr(&cls, method_name) { Some(method) => self.call_get_descriptor(method, obj.clone()), - None => Err(self.new_type_error(err_msg)), + None => Err(self.new_type_error(err_msg())), } } From c0e1702869f21d3512307b4bb72195671ea73282 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Fri, 7 Jun 2019 16:34:16 +0300 Subject: [PATCH 702/884] split mode/type for io.open properly, fix open('r') case --- tests/snippets/builtin_open.py | 13 +- vm/src/stdlib/io.rs | 228 +++++++++++++++++++++++++-------- 2 files changed, 189 insertions(+), 52 deletions(-) diff --git a/tests/snippets/builtin_open.py b/tests/snippets/builtin_open.py index 41bcf2b800..d7c310a921 100644 --- a/tests/snippets/builtin_open.py +++ b/tests/snippets/builtin_open.py @@ -6,5 +6,14 @@ assert_raises(FileNotFoundError, lambda: open('DoesNotExist')) # Use open as a context manager -with open('README.md') as fp: - fp.read() +with open('README.md', 'rt') as fp: + contents = fp.read() + assert type(contents) == str, "type is " + str(type(contents)) + +with open('README.md', 'r') as fp: + contents = fp.read() + assert type(contents) == str, "type is " + str(type(contents)) + +with open('README.md', 'rb') as fp: + contents = fp.read() + assert type(contents) == bytes, "type is " + str(type(contents)) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 8b203cc781..80b9ffb7f3 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -1,9 +1,7 @@ /* * I/O core tools. */ - use std::cell::RefCell; -use std::collections::HashSet; use std::fs::File; use std::io::prelude::*; use std::io::BufReader; @@ -294,6 +292,63 @@ fn text_io_base_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } +fn split_mode_string(mode_string: String) -> Result<(String, String), String> { + let mut mode: char = '\0'; + let mut typ: char = '\0'; + let mut plus_is_set = false; + + for ch in mode_string.chars() { + match ch { + '+' => { + if plus_is_set { + return Err(format!("invalid mode: '{}'", mode_string)); + } + plus_is_set = true; + } + 't' | 'b' => { + if typ != '\0' { + if typ == ch { + // no duplicates allowed + return Err(format!("invalid mode: '{}'", mode_string)); + } else { + return Err("can't have text and binary mode at once".to_string()); + } + } + typ = ch; + } + 'a' | 'r' | 'w' => { + if mode != '\0' { + if mode == ch { + // no duplicates allowed + return Err(format!("invalid mode: '{}'", mode_string)); + } else { + return Err( + "must have exactly one of create/read/write/append mode".to_string() + ); + } + } + mode = ch; + } + _ => return Err(format!("invalid mode: '{}'", mode_string)), + } + } + + if mode == '\0' { + return Err( + "Must have exactly one of create/read/write/append mode and at most one plus" + .to_string(), + ); + } + let mut mode = mode.to_string(); + if plus_is_set { + mode.push('+'); + } + if typ == '\0' { + typ = 't'; + } + Ok((mode, typ.to_string())) +} + pub fn io_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -301,69 +356,55 @@ pub fn io_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(file, Some(vm.ctx.str_type()))], optional = [(mode, Some(vm.ctx.str_type()))] ); + // mode is optional: 'rt' is the default mode (open from reading text) + let mode_string = mode.map_or("rt".to_string(), |m| objstr::get_value(m)); + let (mode, typ) = match split_mode_string(mode_string) { + Ok((mode, typ)) => (mode, typ), + Err(error_message) => { + return Err(vm.new_value_error(error_message)); + } + }; let module = import::import_module(vm, PathBuf::default(), "io").unwrap(); - //mode is optional: 'rt' is the default mode (open from reading text) - let rust_mode = mode.map_or("rt".to_string(), |m| objstr::get_value(m)); - - let mut raw_modes = HashSet::new(); - - //add raw modes - raw_modes.insert("a".to_string()); - raw_modes.insert("r".to_string()); - raw_modes.insert("x".to_string()); - raw_modes.insert("w".to_string()); - - //This is not a terribly elegant way to separate the file mode from - //the "type" flag - this should be improved. The intention here is to - //match a valid flag for the file_io_init call: - //https://docs.python.org/3/library/io.html#io.FileIO - let modes: Vec = rust_mode - .chars() - .filter(|a| raw_modes.contains(&a.to_string())) - .collect(); - - if modes.is_empty() || modes.len() > 1 { - return Err(vm.new_value_error("Invalid Mode".to_string())); - } - - //Class objects (potentially) consumed by io.open - //RawIO: FileIO - //Buffered: BufferedWriter, BufferedReader - //Text: TextIOWrapper + // Class objects (potentially) consumed by io.open + // RawIO: FileIO + // Buffered: BufferedWriter, BufferedReader + // Text: TextIOWrapper let file_io_class = vm.get_attribute(module.clone(), "FileIO").unwrap(); let buffered_writer_class = vm.get_attribute(module.clone(), "BufferedWriter").unwrap(); let buffered_reader_class = vm.get_attribute(module.clone(), "BufferedReader").unwrap(); let text_io_wrapper_class = vm.get_attribute(module, "TextIOWrapper").unwrap(); - //Construct a FileIO (subclass of RawIOBase) - //This is subsequently consumed by a Buffered Class. - let file_args = vec![file.clone(), vm.ctx.new_str(modes[0].to_string())]; - let file_io = vm.invoke(file_io_class, file_args)?; + // Construct a FileIO (subclass of RawIOBase) + // This is subsequently consumed by a Buffered Class. + let file_args = vec![file.clone(), vm.ctx.new_str(mode.clone())]; + let file_io_obj = vm.invoke(file_io_class, file_args)?; - //Create Buffered class to consume FileIO. The type of buffered class depends on - //the operation in the mode. - //There are 3 possible classes here, each inheriting from the RawBaseIO + // Create Buffered class to consume FileIO. The type of buffered class depends on + // the operation in the mode. + // There are 3 possible classes here, each inheriting from the RawBaseIO // creating || writing || appending => BufferedWriter - let buffered = if rust_mode.contains('w') { - vm.invoke(buffered_writer_class, vec![file_io.clone()]) - // reading => BufferedReader - } else { - vm.invoke(buffered_reader_class, vec![file_io.clone()]) + let buffered = match mode.chars().next().unwrap() { + 'w' => vm.invoke(buffered_writer_class, vec![file_io_obj.clone()]), + 'r' => vm.invoke(buffered_reader_class, vec![file_io_obj.clone()]), //TODO: updating => PyBufferedRandom + _ => unimplemented!("'a' mode is not yet implemented"), }; - if rust_mode.contains('t') { - //If the mode is text this buffer type is consumed on construction of - //a TextIOWrapper which is subsequently returned. - vm.invoke(text_io_wrapper_class, vec![buffered.unwrap()]) - } else { + let io_obj = match typ.chars().next().unwrap() { + // If the mode is text this buffer type is consumed on construction of + // a TextIOWrapper which is subsequently returned. + 't' => vm.invoke(text_io_wrapper_class, vec![buffered.unwrap()]), + // If the mode is binary this Buffered class is returned directly at // this point. - //For Buffered class construct "raw" IO class e.g. FileIO and pass this into corresponding field - buffered - } + // For Buffered class construct "raw" IO class e.g. FileIO and pass this into corresponding field + 'b' => buffered, + + _ => unreachable!(), + }; + io_obj } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { @@ -439,3 +480,90 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "BytesIO" => bytes_io, }) } + +#[cfg(test)] +mod tests { + use super::*; + + fn assert_mode_split_into(mode_string: &str, expected_mode: &str, expected_typ: &str) { + let (mode, typ) = split_mode_string(mode_string.to_string()).unwrap(); + assert_eq!(mode, expected_mode); + assert_eq!(typ, expected_typ); + } + + #[test] + fn test_split_mode_valid_cases() { + assert_mode_split_into("r", "r", "t"); + assert_mode_split_into("rb", "r", "b"); + assert_mode_split_into("rt", "r", "t"); + assert_mode_split_into("r+t", "r+", "t"); + assert_mode_split_into("w+t", "w+", "t"); + assert_mode_split_into("r+b", "r+", "b"); + assert_mode_split_into("w+b", "w+", "b"); + } + + #[test] + fn test_invalid_mode() { + assert_eq!( + split_mode_string("rbsss".to_string()), + Err("invalid mode: 'rbsss'".to_string()) + ); + assert_eq!( + split_mode_string("rrb".to_string()), + Err("invalid mode: 'rrb'".to_string()) + ); + assert_eq!( + split_mode_string("rbb".to_string()), + Err("invalid mode: 'rbb'".to_string()) + ); + } + + #[test] + fn test_mode_not_specified() { + assert_eq!( + split_mode_string("".to_string()), + Err( + "Must have exactly one of create/read/write/append mode and at most one plus" + .to_string() + ) + ); + assert_eq!( + split_mode_string("b".to_string()), + Err( + "Must have exactly one of create/read/write/append mode and at most one plus" + .to_string() + ) + ); + assert_eq!( + split_mode_string("t".to_string()), + Err( + "Must have exactly one of create/read/write/append mode and at most one plus" + .to_string() + ) + ); + } + + #[test] + fn test_text_and_binary_at_once() { + assert_eq!( + split_mode_string("rbt".to_string()), + Err("can't have text and binary mode at once".to_string()) + ); + } + + #[test] + fn test_exactly_one_mode() { + assert_eq!( + split_mode_string("rwb".to_string()), + Err("must have exactly one of create/read/write/append mode".to_string()) + ); + } + + #[test] + fn test_at_most_one_plus() { + assert_eq!( + split_mode_string("a++".to_string()), + Err("invalid mode: 'a++'".to_string()) + ); + } +} From 8fccad1e09a5c1bf34ef292fee5379b5891c747e Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 18:59:41 +0300 Subject: [PATCH 703/884] Print warning to stderr Co-Authored-By: coolreader18 <33094578+coolreader18@users.noreply.github.com> --- vm/src/stdlib/warnings.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/stdlib/warnings.rs b/vm/src/stdlib/warnings.rs index bf0055d900..cb97ead480 100644 --- a/vm/src/stdlib/warnings.rs +++ b/vm/src/stdlib/warnings.rs @@ -19,7 +19,7 @@ fn warnings_warn(args: WarnArgs, _vm: &VirtualMachine) { OptionalArg::Present(l) => l, OptionalArg::Missing => 1, }; - println!( + eprintln!( "Warning: {} , category: {:?}, level: {}", args.message.as_str(), args.category, From 50252b4e809f7277670b284d168157299572fd87 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 7 Jun 2019 11:50:15 -0500 Subject: [PATCH 704/884] Disable the pwd module on android --- vm/Cargo.toml | 2 +- vm/src/stdlib/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index ddc8ee20c0..922163d014 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -38,5 +38,5 @@ bincode = "1.1.4" git = "https://github.com/OddCoincidence/unicode-casing" rev = "90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb" -[target.'cfg(unix)'.dependencies] +[target.'cfg(all(unix, not(target_os = "android")))'.dependencies] pwd = "1" diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 096d148458..7c72a1b61d 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -26,7 +26,7 @@ use crate::vm::VirtualMachine; pub mod io; #[cfg(not(target_arch = "wasm32"))] mod os; -#[cfg(unix)] +#[cfg(all(unix, not(target_os = "android")))] mod pwd; use crate::pyobject::PyObjectRef; @@ -67,7 +67,7 @@ pub fn get_module_inits() -> HashMap { } // Unix-only - #[cfg(unix)] + #[cfg(all(unix, not(target_os = "android")))] { modules.insert("pwd".to_string(), Box::new(pwd::make_module)); } From baf62d56d2a128dd0cec9df8aaea416a47c3831e Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Sat, 8 Jun 2019 02:42:06 +0300 Subject: [PATCH 705/884] get io reader/writer classes only when needed --- vm/src/stdlib/io.rs | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 80b9ffb7f3..da8bedf537 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -365,29 +365,33 @@ pub fn io_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } }; - let module = import::import_module(vm, PathBuf::default(), "io").unwrap(); - - // Class objects (potentially) consumed by io.open - // RawIO: FileIO - // Buffered: BufferedWriter, BufferedReader - // Text: TextIOWrapper - let file_io_class = vm.get_attribute(module.clone(), "FileIO").unwrap(); - let buffered_writer_class = vm.get_attribute(module.clone(), "BufferedWriter").unwrap(); - let buffered_reader_class = vm.get_attribute(module.clone(), "BufferedReader").unwrap(); - let text_io_wrapper_class = vm.get_attribute(module, "TextIOWrapper").unwrap(); + let io_module = import::import_module(vm, PathBuf::default(), "io").unwrap(); // Construct a FileIO (subclass of RawIOBase) // This is subsequently consumed by a Buffered Class. - let file_args = vec![file.clone(), vm.ctx.new_str(mode.clone())]; - let file_io_obj = vm.invoke(file_io_class, file_args)?; + let file_io_class = vm.get_attribute(io_module.clone(), "FileIO").unwrap(); + let file_io_obj = vm.invoke( + file_io_class, + vec![file.clone(), vm.ctx.new_str(mode.clone())], + )?; // Create Buffered class to consume FileIO. The type of buffered class depends on // the operation in the mode. // There are 3 possible classes here, each inheriting from the RawBaseIO // creating || writing || appending => BufferedWriter let buffered = match mode.chars().next().unwrap() { - 'w' => vm.invoke(buffered_writer_class, vec![file_io_obj.clone()]), - 'r' => vm.invoke(buffered_reader_class, vec![file_io_obj.clone()]), + 'w' => { + let buffered_writer_class = vm + .get_attribute(io_module.clone(), "BufferedWriter") + .unwrap(); + vm.invoke(buffered_writer_class, vec![file_io_obj.clone()]) + } + 'r' => { + let buffered_reader_class = vm + .get_attribute(io_module.clone(), "BufferedReader") + .unwrap(); + vm.invoke(buffered_reader_class, vec![file_io_obj.clone()]) + } //TODO: updating => PyBufferedRandom _ => unimplemented!("'a' mode is not yet implemented"), }; @@ -395,13 +399,14 @@ pub fn io_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let io_obj = match typ.chars().next().unwrap() { // If the mode is text this buffer type is consumed on construction of // a TextIOWrapper which is subsequently returned. - 't' => vm.invoke(text_io_wrapper_class, vec![buffered.unwrap()]), - + 't' => { + let text_io_wrapper_class = vm.get_attribute(io_module, "TextIOWrapper").unwrap(); + vm.invoke(text_io_wrapper_class, vec![buffered.unwrap()]) + } // If the mode is binary this Buffered class is returned directly at // this point. // For Buffered class construct "raw" IO class e.g. FileIO and pass this into corresponding field 'b' => buffered, - _ => unreachable!(), }; io_obj From 9592e94920a5bdbda24acbd56202a1b7a451231f Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 8 Jun 2019 16:23:51 +0200 Subject: [PATCH 706/884] Isolate determination of indentation into seperate method. --- parser/src/lexer.rs | 114 +++++++++++++++++++++++--------------------- 1 file changed, 59 insertions(+), 55 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 88bbe079c7..3b0585ee0c 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -649,6 +649,64 @@ where self.location.column = 1; } + /// Given we are at the start of a line, count the number of spaces and/or tabs until the first character. + fn determine_indentation(&mut self) -> Result { + // Determine indentation: + let mut spaces: usize = 0; + let mut tabs: usize = 0; + loop { + match self.chr0 { + Some(' ') => { + /* + if tabs != 0 { + // Don't allow spaces after tabs as part of indentation. + // This is technically stricter than python3 but spaces after + // tabs is even more insane than mixing spaces and tabs. + return Some(Err(LexicalError { + error: LexicalErrorType::OtherError("Spaces not allowed as part of indentation after tabs".to_string()), + location: self.get_pos(), + })); + } + */ + self.next_char(); + spaces += 1; + } + Some('\t') => { + if spaces != 0 { + // Don't allow tabs after spaces as part of indentation. + // This is technically stricter than python3 but spaces before + // tabs is even more insane than mixing spaces and tabs. + return Err(LexicalError { + error: LexicalErrorType::OtherError( + "Tabs not allowed as part of indentation after spaces".to_string(), + ), + location: self.get_pos(), + }); + } + self.next_char(); + tabs += 1; + } + Some('#') => { + self.lex_comment(); + spaces = 0; + tabs = 0; + } + Some('\n') => { + // Empty line! + self.next_char(); + self.new_line(); + spaces = 0; + tabs = 0; + } + _ => { + break; + } + } + } + + Ok(IndentationLevel { spaces, tabs }) + } + fn inner_next(&mut self) -> LexResult { if !self.pending.is_empty() { return self.pending.remove(0); @@ -659,61 +717,7 @@ where if self.at_begin_of_line { self.at_begin_of_line = false; - // Determine indentation: - let mut spaces: usize = 0; - let mut tabs: usize = 0; - loop { - match self.chr0 { - Some(' ') => { - /* - if tabs != 0 { - // Don't allow spaces after tabs as part of indentation. - // This is technically stricter than python3 but spaces after - // tabs is even more insane than mixing spaces and tabs. - return Some(Err(LexicalError { - error: LexicalErrorType::OtherError("Spaces not allowed as part of indentation after tabs".to_string()), - location: self.get_pos(), - })); - } - */ - self.next_char(); - spaces += 1; - } - Some('\t') => { - if spaces != 0 { - // Don't allow tabs after spaces as part of indentation. - // This is technically stricter than python3 but spaces before - // tabs is even more insane than mixing spaces and tabs. - return Err(LexicalError { - error: LexicalErrorType::OtherError( - "Tabs not allowed as part of indentation after spaces" - .to_string(), - ), - location: self.get_pos(), - }); - } - self.next_char(); - tabs += 1; - } - Some('#') => { - self.lex_comment(); - self.at_begin_of_line = true; - continue 'top_loop; - } - Some('\n') => { - // Empty line! - self.next_char(); - self.at_begin_of_line = true; - self.new_line(); - continue 'top_loop; - } - _ => { - break; - } - } - } - - let indentation_level = IndentationLevel { spaces, tabs }; + let indentation_level = self.determine_indentation()?; if self.nesting == 0 { // Determine indent or dedent: From 9d9fcfc034d8ba4a912348b08248fffb47f1f169 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 8 Jun 2019 16:38:15 +0200 Subject: [PATCH 707/884] Removal of walrus operator. --- parser/src/lexer.rs | 9 +-------- parser/src/token.rs | 1 - 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 3b0585ee0c..1c20df038b 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -1045,14 +1045,7 @@ where return result; } ':' => { - let tok_start = self.get_pos(); - if let Some('=') = self.chr0 { - self.next_char(); - let tok_end = self.get_pos(); - return Ok((tok_start, Tok::Walrus, tok_end)); - } else { - return self.eat_single_char(Tok::Colon); - } + return self.eat_single_char(Tok::Colon); } ';' => { return self.eat_single_char(Tok::Semi); diff --git a/parser/src/token.rs b/parser/src/token.rs index cc1a787714..982d8de924 100644 --- a/parser/src/token.rs +++ b/parser/src/token.rs @@ -23,7 +23,6 @@ pub enum Tok { Lsqb, Rsqb, Colon, - Walrus, // ':=' Comma, Semi, Plus, From b0db7c1096b5877fb670be6810fbeec6297c32d0 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 8 Jun 2019 18:28:18 -0500 Subject: [PATCH 708/884] Add RFC issue template Structure taken from #771 --- .github/ISSUE_TEMPLATE/rfc.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/rfc.md diff --git a/.github/ISSUE_TEMPLATE/rfc.md b/.github/ISSUE_TEMPLATE/rfc.md new file mode 100644 index 0000000000..84a7b19ce7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/rfc.md @@ -0,0 +1,24 @@ +--- +name: RFC +about: Make a suggestion in a Request for Comments format to RustPython +title: "[RFC] " +labels: RFC +assignees: '' + +--- + +## Summary + + + +## Detailed Explanation + + + +## Drawbacks, Rationale, and Alternatives + + + +## Unresolved Questions + + From d45c632f498caf5bc8d69bf9b11399e672edc0bb Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 8 Jun 2019 18:59:02 -0500 Subject: [PATCH 709/884] Add incompatibility issue template --- .github/ISSUE_TEMPLATE/report-incompatibility.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/report-incompatibility.md diff --git a/.github/ISSUE_TEMPLATE/report-incompatibility.md b/.github/ISSUE_TEMPLATE/report-incompatibility.md new file mode 100644 index 0000000000..e917e94326 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/report-incompatibility.md @@ -0,0 +1,16 @@ +--- +name: Report incompatibility +about: Report an incompatibility between RustPython and CPython +title: '' +labels: feat +assignees: '' + +--- + +## Feature + + + +## Python Documentation + + From 6767b4efb195d21954786f28d18af0c0c3c865cb Mon Sep 17 00:00:00 2001 From: rmliddle Date: Sun, 9 Jun 2019 21:33:16 +1000 Subject: [PATCH 710/884] BytesIO First Pass --- README.md | 2 +- tests/snippets/bytes_io.py | 24 ++++++++++++++ tests/snippets/string_io.py | 22 +++++++++++++ tests/snippets/test_io.py | 14 -------- vm/src/stdlib/io.rs | 65 ++++++++++++++++++++++++++++++------- 5 files changed, 100 insertions(+), 27 deletions(-) create mode 100644 tests/snippets/bytes_io.py create mode 100644 tests/snippets/string_io.py delete mode 100644 tests/snippets/test_io.py diff --git a/README.md b/README.md index 3c64397b81..1bfa960ed0 100644 --- a/README.md +++ b/README.md @@ -197,7 +197,7 @@ crate and webpack. # Code style -The code style used is the default rustfmt codestyle. Please format your code accordingly. +The code style used is the default [rustfmt](https://github.com/rust-lang/rustfmt) codestyle. Please format your code accordingly. # Community diff --git a/tests/snippets/bytes_io.py b/tests/snippets/bytes_io.py new file mode 100644 index 0000000000..c269ecbcc9 --- /dev/null +++ b/tests/snippets/bytes_io.py @@ -0,0 +1,24 @@ + +from io import BytesIO + +def test_01(): + bytes_string = b'Test String 1' + + f = BytesIO() + f.write(bytes_string) + + assert f.getvalue() == bytes_string + +def test_02(): + bytes_string = b'Test String 2' + + f = BytesIO() + f.write(bytes_string) + + assert f.read() == bytes_string + assert f.read() == b'' + assert f.getvalue() == b'' + +if __name__ == "__main__": + test_01() + test_02() diff --git a/tests/snippets/string_io.py b/tests/snippets/string_io.py new file mode 100644 index 0000000000..8655b71de4 --- /dev/null +++ b/tests/snippets/string_io.py @@ -0,0 +1,22 @@ + +from io import StringIO + +def test_01(): + string = 'Test String 1' + f = StringIO() + f.write(string) + + assert f.getvalue() == string + +def test_02(): + string = 'Test String 2' + f = StringIO() + f.write(string) + + assert f.read() == string + assert f.read() == '' + assert f.getvalue() == '' + +if __name__ == "__main__": + test_01() + test_02() diff --git a/tests/snippets/test_io.py b/tests/snippets/test_io.py deleted file mode 100644 index bc49b72870..0000000000 --- a/tests/snippets/test_io.py +++ /dev/null @@ -1,14 +0,0 @@ - - -import io - -f = io.StringIO() -f.write('bladibla') -assert f.getvalue() == 'bladibla' - -# TODO: -# print('fubar', file=f, end='') -print(f.getvalue()) - -# TODO: -# assert f.getvalue() == 'bladiblafubar' diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 80b9ffb7f3..292698d742 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -53,6 +53,12 @@ impl PyStringIORef { fn getvalue(self, _vm: &VirtualMachine) -> String { self.data.borrow().clone() } + + fn read(self, _vm: &VirtualMachine) -> String { + let data = self.data.borrow().clone(); + self.data.borrow_mut().clear(); + data + } } fn string_io_new(cls: PyClassRef, vm: &VirtualMachine) -> PyResult { @@ -62,15 +68,41 @@ fn string_io_new(cls: PyClassRef, vm: &VirtualMachine) -> PyResult PyResult { - // TODO - Ok(vm.get_none()) +#[derive(Debug, Default, Clone)] +struct PyBytesIO { + data: RefCell>, } -fn bytes_io_getvalue(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args); - // TODO - Ok(vm.get_none()) +type PyBytesIORef = PyRef; + +impl PyValue for PyBytesIO { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("io", "BytesIO") + } +} + +impl PyBytesIORef { + fn write(self, data: objbytes::PyBytesRef, _vm: &VirtualMachine) { + let data = data.get_value(); + self.data.borrow_mut().extend(data); + } + + fn getvalue(self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_bytes(self.data.borrow().clone())) + } + + fn read(self, vm: &VirtualMachine) -> PyResult { + let data = self.data.borrow().clone(); + self.data.borrow_mut().clear(); + Ok(vm.ctx.new_bytes(data)) + } +} + +fn bytes_io_new(cls: PyClassRef, vm: &VirtualMachine) -> PyResult { + PyBytesIO { + data: RefCell::new(Vec::new()), + } + .into_ref_with_type(vm, cls) } fn io_base_cm_enter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -420,9 +452,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { // IOBase Subclasses let raw_io_base = py_class!(ctx, "RawIOBase", io_base.clone(), {}); - let buffered_io_base = py_class!(ctx, "BufferedIOBase", io_base.clone(), { - "__init__" => ctx.new_rustfunc(buffered_io_base_init) - }); + let buffered_io_base = py_class!(ctx, "BufferedIOBase", io_base.clone(), {}); //TextIO Base has no public constructor let text_io_base = py_class!(ctx, "TextIOBase", io_base.clone(), { @@ -441,10 +471,18 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { // BufferedIOBase Subclasses let buffered_reader = py_class!(ctx, "BufferedReader", buffered_io_base.clone(), { + //workaround till the buffered classes can be fixed up to be more + //consistent with the python model + //For more info see: https://github.com/RustPython/RustPython/issues/547 + "__init__" => ctx.new_rustfunc(buffered_io_base_init), "read" => ctx.new_rustfunc(buffered_reader_read) }); let buffered_writer = py_class!(ctx, "BufferedWriter", buffered_io_base.clone(), { + //workaround till the buffered classes can be fixed up to be more + //consistent with the python model + //For more info see: https://github.com/RustPython/RustPython/issues/547 + "__init__" => ctx.new_rustfunc(buffered_io_base_init), "write" => ctx.new_rustfunc(buffered_writer_write) }); @@ -456,14 +494,17 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { //StringIO: in-memory text let string_io = py_class!(ctx, "StringIO", text_io_base.clone(), { "__new__" => ctx.new_rustfunc(string_io_new), + "read" => ctx.new_rustfunc(PyStringIORef::read), "write" => ctx.new_rustfunc(PyStringIORef::write), "getvalue" => ctx.new_rustfunc(PyStringIORef::getvalue) }); //BytesIO: in-memory bytes let bytes_io = py_class!(ctx, "BytesIO", buffered_io_base.clone(), { - "__init__" => ctx.new_rustfunc(bytes_io_init), - "getvalue" => ctx.new_rustfunc(bytes_io_getvalue) + "__new__" => ctx.new_rustfunc(bytes_io_new), + "read" => ctx.new_rustfunc(PyBytesIORef::read), + "write" => ctx.new_rustfunc(PyBytesIORef::write), + "getvalue" => ctx.new_rustfunc(PyBytesIORef::getvalue) }); py_module!(vm, "io", { From 6542d3586e02e8b4c030870b3244b540be81bb07 Mon Sep 17 00:00:00 2001 From: rmliddle Date: Mon, 10 Jun 2019 00:57:17 +1000 Subject: [PATCH 711/884] Optional args on new for Bytes/StringIO --- tests/snippets/bytes_io.py | 5 +---- tests/snippets/string_io.py | 4 +--- vm/src/stdlib/io.rs | 38 +++++++++++++++++++++++++++---------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/tests/snippets/bytes_io.py b/tests/snippets/bytes_io.py index c269ecbcc9..edb81b6f57 100644 --- a/tests/snippets/bytes_io.py +++ b/tests/snippets/bytes_io.py @@ -11,13 +11,10 @@ def test_01(): def test_02(): bytes_string = b'Test String 2' - - f = BytesIO() - f.write(bytes_string) + f = BytesIO(bytes_string) assert f.read() == bytes_string assert f.read() == b'' - assert f.getvalue() == b'' if __name__ == "__main__": test_01() diff --git a/tests/snippets/string_io.py b/tests/snippets/string_io.py index 8655b71de4..cb02f5a45b 100644 --- a/tests/snippets/string_io.py +++ b/tests/snippets/string_io.py @@ -10,12 +10,10 @@ def test_01(): def test_02(): string = 'Test String 2' - f = StringIO() - f.write(string) + f = StringIO(string) assert f.read() == string assert f.read() == '' - assert f.getvalue() == '' if __name__ == "__main__": test_01() diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 292698d742..f0b9bb4514 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -11,12 +11,12 @@ use num_bigint::ToBigInt; use num_traits::ToPrimitive; use super::os; -use crate::function::PyFuncArgs; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::import; use crate::obj::objbytearray::PyByteArray; use crate::obj::objbytes; use crate::obj::objint; -use crate::obj::objstr; +use crate::obj::objstr::{get_value, PyStringRef}; use crate::obj::objtype::PyClassRef; use crate::pyobject::{BufferProtocol, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; @@ -45,7 +45,7 @@ impl PyValue for PyStringIO { } impl PyStringIORef { - fn write(self, data: objstr::PyStringRef, _vm: &VirtualMachine) { + fn write(self, data: PyStringRef, _vm: &VirtualMachine) { let data = data.value.clone(); self.data.borrow_mut().push_str(&data); } @@ -61,9 +61,18 @@ impl PyStringIORef { } } -fn string_io_new(cls: PyClassRef, vm: &VirtualMachine) -> PyResult { +fn string_io_new( + cls: PyClassRef, + object: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let raw_string = match object { + OptionalArg::Present(ref input) => get_value(input), + OptionalArg::Missing => String::new(), + }; + PyStringIO { - data: RefCell::new(String::default()), + data: RefCell::new(raw_string), } .into_ref_with_type(vm, cls) } @@ -98,9 +107,18 @@ impl PyBytesIORef { } } -fn bytes_io_new(cls: PyClassRef, vm: &VirtualMachine) -> PyResult { +fn bytes_io_new( + cls: PyClassRef, + object: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let raw_bytes = match object { + OptionalArg::Present(ref input) => objbytes::get_value(input).to_vec(), + OptionalArg::Missing => vec![], + }; + PyBytesIO { - data: RefCell::new(Vec::new()), + data: RefCell::new(raw_bytes), } .into_ref_with_type(vm, cls) } @@ -172,7 +190,7 @@ fn file_io_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(mode, Some(vm.ctx.str_type()))] ); - let rust_mode = mode.map_or("r".to_string(), |m| objstr::get_value(m)); + let rust_mode = mode.map_or("r".to_string(), |m| get_value(m)); match compute_c_flag(&rust_mode).to_bigint() { Some(os_mode) => { @@ -193,7 +211,7 @@ fn file_io_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn file_io_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(file_io, None)]); let py_name = vm.get_attribute(file_io.clone(), "name")?; - let f = match File::open(objstr::get_value(&py_name)) { + let f = match File::open(get_value(&py_name)) { Ok(v) => Ok(v), Err(_) => Err(vm.new_type_error("Error opening file".to_string())), }; @@ -389,7 +407,7 @@ pub fn io_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(mode, Some(vm.ctx.str_type()))] ); // mode is optional: 'rt' is the default mode (open from reading text) - let mode_string = mode.map_or("rt".to_string(), |m| objstr::get_value(m)); + let mode_string = mode.map_or("rt".to_string(), |m| get_value(m)); let (mode, typ) = match split_mode_string(mode_string) { Ok((mode, typ)) => (mode, typ), Err(error_message) => { From f76be43d265862414fb6ca11383c5891d1026d17 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Sun, 9 Jun 2019 11:12:31 +0800 Subject: [PATCH 712/884] str.isprintable - check unicode type by unicode_categories - rm redundant check of empty string --- tests/snippets/strings.py | 7 +++++++ vm/Cargo.toml | 1 + vm/src/obj/objstr.rs | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index aaeaed5f3e..19cd98269a 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -216,3 +216,10 @@ def try_mutate_str(): for s, b, e in zip(ss, bs, ['u8', 'U8', 'utf-8', 'UTF-8', 'utf_8']): assert s.encode(e) == b # assert s.encode(encoding=e) == b + +# str.isisprintable +assert "".isprintable() +assert " ".isprintable() +assert "abcdefg".isprintable() +assert not "abcdefg\n".isprintable() +assert "ʹ".isprintable() diff --git a/vm/Cargo.toml b/vm/Cargo.toml index ddc8ee20c0..cc7fe23b6c 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -31,6 +31,7 @@ hexf = "0.1.0" indexmap = "1.0.2" crc = "^1.0.0" bincode = "1.1.4" +unicode_categories = "0.1.1" # TODO: release and publish to crates.io diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 40d1912968..8ea5cd6993 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1,3 +1,4 @@ +extern crate unicode_categories; extern crate unicode_xid; use std::fmt; @@ -27,6 +28,8 @@ use super::objsequence::PySliceableSequence; use super::objslice::PySlice; use super::objtype::{self, PyClassRef}; +use unicode_categories::UnicodeCategories; + /// str(object='') -> str /// str(bytes_or_buffer[, encoding[, errors]]) -> str /// @@ -519,6 +522,29 @@ impl PyString { } } + /// Return true if all characters in the string are printable or the string is empty, + /// false otherwise. Nonprintable characters are those characters defined in the + /// Unicode character database as `Other` or `Separator`, + /// excepting the ASCII space (0x20) which is considered printable. + /// + /// All characters except those characters defined in the Unicode character + /// database as following categories are considered printable. + /// * Cc (Other, Control) + /// * Cf (Other, Format) + /// * Cs (Other, Surrogate) + /// * Co (Other, Private Use) + /// * Cn (Other, Not Assigned) + /// * Zl Separator, Line ('\u2028', LINE SEPARATOR) + /// * Zp Separator, Paragraph ('\u2029', PARAGRAPH SEPARATOR) + /// * Zs (Separator, Space) other than ASCII space('\x20'). + #[pymethod] + fn isprintable(&self, _vm: &VirtualMachine) -> bool { + self.value.chars().all(|c| match c { + '\u{0020}' => true, + _ => !(c.is_other_control() | c.is_separator()), + }) + } + // cpython's isspace ignores whitespace, including \t and \n, etc, unless the whole string is empty // which is why isspace is using is_ascii_whitespace. Same for isupper & islower #[pymethod] From 65d009382213fbdd112679a97446ed3194a5530a Mon Sep 17 00:00:00 2001 From: Alan Justino Date: Sun, 9 Jun 2019 13:45:37 -0300 Subject: [PATCH 713/884] Dont fail on '{!r}'.format(...) --- tests/snippets/strings.py | 3 +++ vm/src/format.rs | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index aaeaed5f3e..3aa8e0b010 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -186,6 +186,9 @@ assert "{0} {1}".format(2,3) == "2 3" assert "--{:s>4}--".format(1) == "--sss1--" assert "{keyword} {0}".format(1, keyword=2) == "2 1" +assert "repr() shows quotes: {!r}; str() doesn't: {!s}".format( + 'test1', 'test2' +) == "repr() shows quotes: 'test1'; str() doesn't: test2", 'Output: {!r}, {!s}'.format('test1', 'test2') assert 'a' < 'b' assert 'a' <= 'b' diff --git a/vm/src/format.rs b/vm/src/format.rs index e89e4f731d..86372e7022 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -467,6 +467,17 @@ impl FormatString { String::new() }; + // On parts[0] can still be the preconversor (!r, !s, !a) + let parts: Vec<&str> = arg_part .splitn(2, '!').collect(); + // before the bang is a keyword or arg index, after the comma is maybe a conversor spec. + let arg_part = parts[0]; + + let preconversor_spec = if parts.len() > 1 { + parts[1].to_string() + } else { + String::new() + }; + if arg_part.is_empty() { return Ok(FormatPart::AutoSpec(format_spec)); } From e19b674abfb3f505967e2e16b47c42938449dafd Mon Sep 17 00:00:00 2001 From: Alan Justino Date: Sun, 9 Jun 2019 16:22:22 -0300 Subject: [PATCH 714/884] FormatSpec got a preconversor:FormatPreconversor to handle !r, !s and !a --- vm/src/format.rs | 43 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/vm/src/format.rs b/vm/src/format.rs index 86372e7022..fdeac812e3 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -3,6 +3,24 @@ use num_traits::Signed; use std::cmp; use std::str::FromStr; +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum FormatPreconversor { + Str, + Repr, + Ascii, +} + +impl FormatPreconversor { + fn from_char(c: char) -> Option { + match c { + 's' => Some(FormatPreconversor::Str), + 'r' => Some(FormatPreconversor::Repr), + 'a' => Some(FormatPreconversor::Ascii), + _ => None, + } + } +} + #[derive(Debug, Copy, Clone, PartialEq)] pub enum FormatAlign { Left, @@ -56,6 +74,7 @@ pub enum FormatType { #[derive(Debug, PartialEq)] pub struct FormatSpec { + preconversor: Option, fill: Option, align: Option, sign: Option, @@ -75,6 +94,23 @@ fn get_num_digits(text: &str) -> usize { text.len() } +fn parse_preconversor(text: &str) -> (Option, &str) { + let mut chars = text.chars(); + if chars.next() != Some('!') { + return (None, text); + } + + match chars.next() { + None => (None, text), // Should fail instead? + Some(c) => { + match FormatPreconversor::from_char(c) { + Some(preconversor) => (Some(preconversor), chars.as_str()), + None => (None, text), // Should fail instead? + } + }, + } +} + fn parse_align(text: &str) -> (Option, &str) { let mut chars = text.chars(); let maybe_align = chars.next().and_then(FormatAlign::from_char); @@ -186,7 +222,8 @@ fn parse_format_type(text: &str) -> (Option, &str) { } fn parse_format_spec(text: &str) -> FormatSpec { - let (fill, align, after_align) = parse_fill_and_align(text); + let (preconversor, after_preconversor) = parse_preconversor(text); + let (fill, align, after_align) = parse_fill_and_align(after_preconversor); let (sign, after_sign) = parse_sign(after_align); let (alternate_form, after_alternate_form) = parse_alternate_form(after_sign); let after_zero = parse_zero(after_alternate_form); @@ -196,6 +233,7 @@ fn parse_format_spec(text: &str) -> FormatSpec { let (format_type, _) = parse_format_type(after_precision); FormatSpec { + preconversor, fill, align, sign, @@ -473,10 +511,11 @@ impl FormatString { let arg_part = parts[0]; let preconversor_spec = if parts.len() > 1 { - parts[1].to_string() + "!".to_string() + parts[1] } else { String::new() }; + let format_spec = preconversor_spec + &format_spec; if arg_part.is_empty() { return Ok(FormatPart::AutoSpec(format_spec)); From f0a2b4c50bc0b638d5257f12bae2e9d3f2966934 Mon Sep 17 00:00:00 2001 From: Alan Justino Date: Sun, 9 Jun 2019 18:11:10 -0300 Subject: [PATCH 715/884] Apply the {!r} on str.format calls By the Python docs, the `!` forces a conversion of the argument before applying the normal formating logic described after the `:` marker. See: https://docs.python.org/3.4/library/string.html#format-string-syntax --- tests/snippets/strings.py | 18 ++++++++++++++-- vm/src/format.rs | 44 +++++++++++++++++++++++++-------------- vm/src/obj/objstr.rs | 12 +++++++++-- 3 files changed, 54 insertions(+), 20 deletions(-) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 3aa8e0b010..ef334b065c 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -182,14 +182,28 @@ assert not '123'.isidentifier() # String Formatting -assert "{} {}".format(1,2) == "1 2" -assert "{0} {1}".format(2,3) == "2 3" +assert "{} {}".format(1, 2) == "1 2" +assert "{0} {1}".format(2, 3) == "2 3" assert "--{:s>4}--".format(1) == "--sss1--" assert "{keyword} {0}".format(1, keyword=2) == "2 1" assert "repr() shows quotes: {!r}; str() doesn't: {!s}".format( 'test1', 'test2' ) == "repr() shows quotes: 'test1'; str() doesn't: test2", 'Output: {!r}, {!s}'.format('test1', 'test2') + +class Foo: + def __str__(self): + return 'str(Foo)' + + def __repr__(self): + return 'repr(Foo)' + + +f = Foo() +assert "{} {!s} {!r} {!a}".format(f, f, f, f) == 'str(Foo) str(Foo) repr(Foo) repr(Foo)' +assert "{foo} {foo!s} {foo!r} {foo!a}".format(foo=f) == 'str(Foo) str(Foo) repr(Foo) repr(Foo)' +# assert '{} {!r} {:10} {!r:10} {foo!r:10} {foo!r} {foo}'.format('txt1', 'txt2', 'txt3', 'txt4', 'txt5', foo='bar') + assert 'a' < 'b' assert 'a' <= 'b' assert 'a' <= 'a' diff --git a/vm/src/format.rs b/vm/src/format.rs index fdeac812e3..dc4645165e 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -11,7 +11,7 @@ pub enum FormatPreconversor { } impl FormatPreconversor { - fn from_char(c: char) -> Option { + pub fn from_char(c: char) -> Option { match c { 's' => Some(FormatPreconversor::Str), 'r' => Some(FormatPreconversor::Repr), @@ -19,6 +19,31 @@ impl FormatPreconversor { _ => None, } } + + pub fn from_str(text: &str) -> Option { + let mut chars = text.chars(); + if chars.next() != Some('!') { + return None; + } + + match chars.next() { + None => None, // Should fail instead? + Some(c) => FormatPreconversor::from_char(c), + } + } + + pub fn parse_and_consume(text: &str) -> (Option, &str) { + let preconversor = FormatPreconversor::from_str(text); + match preconversor { + None => (None, text), + Some(_) => { + let mut chars = text.chars(); + chars.next(); // Consume the bang + chars.next(); // Consume one r,s,a char + (preconversor, chars.as_str()) + } + } + } } #[derive(Debug, Copy, Clone, PartialEq)] @@ -95,20 +120,7 @@ fn get_num_digits(text: &str) -> usize { } fn parse_preconversor(text: &str) -> (Option, &str) { - let mut chars = text.chars(); - if chars.next() != Some('!') { - return (None, text); - } - - match chars.next() { - None => (None, text), // Should fail instead? - Some(c) => { - match FormatPreconversor::from_char(c) { - Some(preconversor) => (Some(preconversor), chars.as_str()), - None => (None, text), // Should fail instead? - } - }, - } + FormatPreconversor::parse_and_consume(text) } fn parse_align(text: &str) -> (Option, &str) { @@ -506,7 +518,7 @@ impl FormatString { }; // On parts[0] can still be the preconversor (!r, !s, !a) - let parts: Vec<&str> = arg_part .splitn(2, '!').collect(); + let parts: Vec<&str> = arg_part.splitn(2, '!').collect(); // before the bang is a keyword or arg index, after the comma is maybe a conversor spec. let arg_part = parts[0]; diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 40d1912968..ce9625cdf0 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -10,7 +10,7 @@ use unicode_casing::CharExt; use unicode_segmentation::UnicodeSegmentation; use unicode_xid::UnicodeXID; -use crate::format::{FormatParseError, FormatPart, FormatString}; +use crate::format::{FormatParseError, FormatPart, FormatPreconversor, FormatString}; use crate::function::{single_or_tuple_any, OptionalArg, PyFuncArgs}; use crate::pyhash; use crate::pyobject::{ @@ -1026,7 +1026,15 @@ fn count_char(s: &str, c: char) -> usize { } fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: &str) -> PyResult { - let returned_type = vm.ctx.new_str(format_spec.to_string()); + let (preconversor, new_format_spec) = FormatPreconversor::parse_and_consume(format_spec); + let argument = match preconversor { + Some(FormatPreconversor::Str) => vm.call_method(&argument, "__str__", vec![])?, + Some(FormatPreconversor::Repr) => vm.call_method(&argument, "__repr__", vec![])?, + Some(FormatPreconversor::Ascii) => vm.call_method(&argument, "__repr__", vec![])?, + None => argument, + }; + let returned_type = vm.ctx.new_str(new_format_spec.to_string()); + let result = vm.call_method(&argument, "__format__", vec![returned_type])?; if !objtype::isinstance(&result, &vm.ctx.str_type()) { let result_type = result.class(); From 854bacf45293b06e7be4a44d980765294600976a Mon Sep 17 00:00:00 2001 From: Alan Justino Date: Sun, 9 Jun 2019 18:45:08 -0300 Subject: [PATCH 716/884] Fix the Rust tests --- vm/src/format.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vm/src/format.rs b/vm/src/format.rs index dc4645165e..eb59bf84ac 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -613,6 +613,7 @@ mod tests { #[test] fn test_width_only() { let expected = FormatSpec { + preconversor: None, fill: None, align: None, sign: None, @@ -628,6 +629,7 @@ mod tests { #[test] fn test_fill_and_width() { let expected = FormatSpec { + preconversor: None, fill: Some('<'), align: Some(FormatAlign::Right), sign: None, @@ -643,6 +645,7 @@ mod tests { #[test] fn test_all() { let expected = FormatSpec { + preconversor: None, fill: Some('<'), align: Some(FormatAlign::Right), sign: Some(FormatSign::Minus), From 33885a8334a327d708253668f4fa95d7ec5756c0 Mon Sep 17 00:00:00 2001 From: rmliddle Date: Mon, 10 Jun 2019 22:48:28 +1000 Subject: [PATCH 717/884] Namespace added for objstr: get_value, PyStringRef --- vm/src/stdlib/io.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index f0b9bb4514..a2347ed024 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -16,7 +16,7 @@ use crate::import; use crate::obj::objbytearray::PyByteArray; use crate::obj::objbytes; use crate::obj::objint; -use crate::obj::objstr::{get_value, PyStringRef}; +use crate::obj::objstr; use crate::obj::objtype::PyClassRef; use crate::pyobject::{BufferProtocol, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; @@ -45,7 +45,7 @@ impl PyValue for PyStringIO { } impl PyStringIORef { - fn write(self, data: PyStringRef, _vm: &VirtualMachine) { + fn write(self, data: objstr::PyStringRef, _vm: &VirtualMachine) { let data = data.value.clone(); self.data.borrow_mut().push_str(&data); } @@ -63,12 +63,12 @@ impl PyStringIORef { fn string_io_new( cls: PyClassRef, - object: OptionalArg, + object: OptionalArg>, vm: &VirtualMachine, ) -> PyResult { let raw_string = match object { - OptionalArg::Present(ref input) => get_value(input), - OptionalArg::Missing => String::new(), + OptionalArg::Present(Some(ref input)) => objstr::get_value(input), + _ => String::new(), }; PyStringIO { @@ -109,12 +109,12 @@ impl PyBytesIORef { fn bytes_io_new( cls: PyClassRef, - object: OptionalArg, + object: OptionalArg>, vm: &VirtualMachine, ) -> PyResult { let raw_bytes = match object { - OptionalArg::Present(ref input) => objbytes::get_value(input).to_vec(), - OptionalArg::Missing => vec![], + OptionalArg::Present(Some(ref input)) => objbytes::get_value(input).to_vec(), + _ => vec![], }; PyBytesIO { @@ -190,7 +190,7 @@ fn file_io_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { optional = [(mode, Some(vm.ctx.str_type()))] ); - let rust_mode = mode.map_or("r".to_string(), |m| get_value(m)); + let rust_mode = mode.map_or("r".to_string(), objstr::get_value); match compute_c_flag(&rust_mode).to_bigint() { Some(os_mode) => { @@ -211,7 +211,7 @@ fn file_io_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn file_io_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(file_io, None)]); let py_name = vm.get_attribute(file_io.clone(), "name")?; - let f = match File::open(get_value(&py_name)) { + let f = match File::open(objstr::get_value(&py_name)) { Ok(v) => Ok(v), Err(_) => Err(vm.new_type_error("Error opening file".to_string())), }; @@ -406,8 +406,10 @@ pub fn io_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { required = [(file, Some(vm.ctx.str_type()))], optional = [(mode, Some(vm.ctx.str_type()))] ); + // mode is optional: 'rt' is the default mode (open from reading text) - let mode_string = mode.map_or("rt".to_string(), |m| get_value(m)); + let mode_string = mode.map_or("rt".to_string(), objstr::get_value); + let (mode, typ) = match split_mode_string(mode_string) { Ok((mode, typ)) => (mode, typ), Err(error_message) => { From 7ff59b27d77941465226c4157614f5b210bf25de Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 1 Jun 2019 17:33:27 +0300 Subject: [PATCH 718/884] Add _bootsrap.py as frozen module --- vm/src/frozen.rs | 2 + vm/src/importlib.rs | 1175 +++++++++++++++++++++++++++++++++++++++++++ vm/src/lib.rs | 1 + 3 files changed, 1178 insertions(+) create mode 100644 vm/src/importlib.rs diff --git a/vm/src/frozen.rs b/vm/src/frozen.rs index cc6eb85fe5..36fd08a4ad 100644 --- a/vm/src/frozen.rs +++ b/vm/src/frozen.rs @@ -1,3 +1,4 @@ +use crate::importlib::IMPORTLIB_BOOTSTRAP; use std::collections::hash_map::HashMap; const HELLO: &str = "initialized = True @@ -7,5 +8,6 @@ print(\"Hello world!\") pub fn get_module_inits() -> HashMap { let mut modules = HashMap::new(); modules.insert("__hello__".to_string(), HELLO); + modules.insert("_frozen_importlib".to_string(), IMPORTLIB_BOOTSTRAP); modules } diff --git a/vm/src/importlib.rs b/vm/src/importlib.rs new file mode 100644 index 0000000000..7b8b4e81ba --- /dev/null +++ b/vm/src/importlib.rs @@ -0,0 +1,1175 @@ +pub const IMPORTLIB_BOOTSTRAP: &str = r#" +"""Core implementation of import. + +This module is NOT meant to be directly imported! It has been designed such +that it can be bootstrapped into Python as the implementation of import. As +such it requires the injection of specific modules and attributes in order to +work. One should use importlib as the public-facing version of this module. + +""" +# +# IMPORTANT: Whenever making changes to this module, be sure to run a top-level +# `make regen-importlib` followed by `make` in order to get the frozen version +# of the module updated. Not doing so will result in the Makefile to fail for +# all others who don't have a ./python around to freeze the module +# in the early stages of compilation. +# + +# See importlib._setup() for what is injected into the global namespace. + +# When editing this code be aware that code executed at import time CANNOT +# reference any injected objects! This includes not only global code but also +# anything specified at the class level. + +# Bootstrap-related code ###################################################### + +_bootstrap_external = None + +def _wrap(new, old): + """Simple substitute for functools.update_wrapper.""" + for replace in ['__module__', '__name__', '__qualname__', '__doc__']: + if hasattr(old, replace): + setattr(new, replace, getattr(old, replace)) + new.__dict__.update(old.__dict__) + + +def _new_module(name): + return type(sys)(name) + + +# Module-level locking ######################################################## + +# A dict mapping module names to weakrefs of _ModuleLock instances +# Dictionary protected by the global import lock +_module_locks = {} +# A dict mapping thread ids to _ModuleLock instances +_blocking_on = {} + + +class _DeadlockError(RuntimeError): + pass + + +class _ModuleLock: + """A recursive lock implementation which is able to detect deadlocks + (e.g. thread 1 trying to take locks A then B, and thread 2 trying to + take locks B then A). + """ + + def __init__(self, name): + self.lock = _thread.allocate_lock() + self.wakeup = _thread.allocate_lock() + self.name = name + self.owner = None + self.count = 0 + self.waiters = 0 + + def has_deadlock(self): + # Deadlock avoidance for concurrent circular imports. + me = _thread.get_ident() + tid = self.owner + while True: + lock = _blocking_on.get(tid) + if lock is None: + return False + tid = lock.owner + if tid == me: + return True + + def acquire(self): + """ + Acquire the module lock. If a potential deadlock is detected, + a _DeadlockError is raised. + Otherwise, the lock is always acquired and True is returned. + """ + tid = _thread.get_ident() + _blocking_on[tid] = self + try: + while True: + with self.lock: + if self.count == 0 or self.owner == tid: + self.owner = tid + self.count += 1 + return True + if self.has_deadlock(): + raise _DeadlockError('deadlock detected by %r' % self) + if self.wakeup.acquire(False): + self.waiters += 1 + # Wait for a release() call + self.wakeup.acquire() + self.wakeup.release() + finally: + del _blocking_on[tid] + + def release(self): + tid = _thread.get_ident() + with self.lock: + if self.owner != tid: + raise RuntimeError('cannot release un-acquired lock') + assert self.count > 0 + self.count -= 1 + if self.count == 0: + self.owner = None + if self.waiters: + self.waiters -= 1 + self.wakeup.release() + + def __repr__(self): + return '_ModuleLock({!r}) at {}'.format(self.name, id(self)) + + +class _DummyModuleLock: + """A simple _ModuleLock equivalent for Python builds without + multi-threading support.""" + + def __init__(self, name): + self.name = name + self.count = 0 + + def acquire(self): + self.count += 1 + return True + + def release(self): + if self.count == 0: + raise RuntimeError('cannot release un-acquired lock') + self.count -= 1 + + def __repr__(self): + return '_DummyModuleLock({!r}) at {}'.format(self.name, id(self)) + + +class _ModuleLockManager: + + def __init__(self, name): + self._name = name + self._lock = None + + def __enter__(self): + self._lock = _get_module_lock(self._name) + self._lock.acquire() + + def __exit__(self, *args, **kwargs): + self._lock.release() + + +# The following two functions are for consumption by Python/import.c. + +def _get_module_lock(name): + """Get or create the module lock for a given module name. + + Acquire/release internally the global import lock to protect + _module_locks.""" + + _imp.acquire_lock() + try: + try: + lock = _module_locks[name]() + except KeyError: + lock = None + + if lock is None: + if _thread is None: + lock = _DummyModuleLock(name) + else: + lock = _ModuleLock(name) + + def cb(ref, name=name): + _imp.acquire_lock() + try: + # bpo-31070: Check if another thread created a new lock + # after the previous lock was destroyed + # but before the weakref callback was called. + if _module_locks.get(name) is ref: + del _module_locks[name] + finally: + _imp.release_lock() + + _module_locks[name] = _weakref.ref(lock, cb) + finally: + _imp.release_lock() + + return lock + + +def _lock_unlock_module(name): + """Acquires then releases the module lock for a given module name. + + This is used to ensure a module is completely initialized, in the + event it is being imported by another thread. + """ + lock = _get_module_lock(name) + try: + lock.acquire() + except _DeadlockError: + # Concurrent circular import, we'll accept a partially initialized + # module object. + pass + else: + lock.release() + +# Frame stripping magic ############################################### +def _call_with_frames_removed(f, *args, **kwds): + """remove_importlib_frames in import.c will always remove sequences + of importlib frames that end with a call to this function + + Use it instead of a normal call in places where including the importlib + frames introduces unwanted noise into the traceback (e.g. when executing + module code) + """ + return f(*args, **kwds) + + +def _verbose_message(message, *args, verbosity=1): + """Print the message to stderr if -v/PYTHONVERBOSE is turned on.""" + if sys.flags.verbose >= verbosity: + if not message.startswith(('#', 'import ')): + message = '# ' + message + print(message.format(*args), file=sys.stderr) + + +def _requires_builtin(fxn): + """Decorator to verify the named module is built-in.""" + def _requires_builtin_wrapper(self, fullname): + if fullname not in sys.builtin_module_names: + raise ImportError('{!r} is not a built-in module'.format(fullname), + name=fullname) + return fxn(self, fullname) + _wrap(_requires_builtin_wrapper, fxn) + return _requires_builtin_wrapper + + +def _requires_frozen(fxn): + """Decorator to verify the named module is frozen.""" + def _requires_frozen_wrapper(self, fullname): + if not _imp.is_frozen(fullname): + raise ImportError('{!r} is not a frozen module'.format(fullname), + name=fullname) + return fxn(self, fullname) + _wrap(_requires_frozen_wrapper, fxn) + return _requires_frozen_wrapper + + +# Typically used by loader classes as a method replacement. +def _load_module_shim(self, fullname): + """Load the specified module into sys.modules and return it. + + This method is deprecated. Use loader.exec_module instead. + + """ + spec = spec_from_loader(fullname, self) + if fullname in sys.modules: + module = sys.modules[fullname] + _exec(spec, module) + return sys.modules[fullname] + else: + return _load(spec) + +# Module specifications ####################################################### + +def _module_repr(module): + # The implementation of ModuleType.__repr__(). + loader = getattr(module, '__loader__', None) + if hasattr(loader, 'module_repr'): + # As soon as BuiltinImporter, FrozenImporter, and NamespaceLoader + # drop their implementations for module_repr. we can add a + # deprecation warning here. + try: + return loader.module_repr(module) + except Exception: + pass + try: + spec = module.__spec__ + except AttributeError: + pass + else: + if spec is not None: + return _module_repr_from_spec(spec) + + # We could use module.__class__.__name__ instead of 'module' in the + # various repr permutations. + try: + name = module.__name__ + except AttributeError: + name = '?' + try: + filename = module.__file__ + except AttributeError: + if loader is None: + return ''.format(name) + else: + return ''.format(name, loader) + else: + return ''.format(name, filename) + + +class ModuleSpec: + """The specification for a module, used for loading. + + A module's spec is the source for information about the module. For + data associated with the module, including source, use the spec's + loader. + + `name` is the absolute name of the module. `loader` is the loader + to use when loading the module. `parent` is the name of the + package the module is in. The parent is derived from the name. + + `is_package` determines if the module is considered a package or + not. On modules this is reflected by the `__path__` attribute. + + `origin` is the specific location used by the loader from which to + load the module, if that information is available. When filename is + set, origin will match. + + `has_location` indicates that a spec's "origin" reflects a location. + When this is True, `__file__` attribute of the module is set. + + `cached` is the location of the cached bytecode file, if any. It + corresponds to the `__cached__` attribute. + + `submodule_search_locations` is the sequence of path entries to + search when importing submodules. If set, is_package should be + True--and False otherwise. + + Packages are simply modules that (may) have submodules. If a spec + has a non-None value in `submodule_search_locations`, the import + system will consider modules loaded from the spec as packages. + + Only finders (see importlib.abc.MetaPathFinder and + importlib.abc.PathEntryFinder) should modify ModuleSpec instances. + + """ + + def __init__(self, name, loader, *, origin=None, loader_state=None, + is_package=None): + self.name = name + self.loader = loader + self.origin = origin + self.loader_state = loader_state + self.submodule_search_locations = [] if is_package else None + + # file-location attributes + self._set_fileattr = False + self._cached = None + + def __repr__(self): + args = ['name={!r}'.format(self.name), + 'loader={!r}'.format(self.loader)] + if self.origin is not None: + args.append('origin={!r}'.format(self.origin)) + if self.submodule_search_locations is not None: + args.append('submodule_search_locations={}' + .format(self.submodule_search_locations)) + return '{}({})'.format(self.__class__.__name__, ', '.join(args)) + + def __eq__(self, other): + smsl = self.submodule_search_locations + try: + return (self.name == other.name and + self.loader == other.loader and + self.origin == other.origin and + smsl == other.submodule_search_locations and + self.cached == other.cached and + self.has_location == other.has_location) + except AttributeError: + return False + + @property + def cached(self): + if self._cached is None: + if self.origin is not None and self._set_fileattr: + if _bootstrap_external is None: + raise NotImplementedError + self._cached = _bootstrap_external._get_cached(self.origin) + return self._cached + + @cached.setter + def cached(self, cached): + self._cached = cached + + @property + def parent(self): + """The name of the module's parent.""" + if self.submodule_search_locations is None: + return self.name.rpartition('.')[0] + else: + return self.name + + @property + def has_location(self): + return self._set_fileattr + + @has_location.setter + def has_location(self, value): + self._set_fileattr = bool(value) + + +def spec_from_loader(name, loader, *, origin=None, is_package=None): + """Return a module spec based on various loader methods.""" + if hasattr(loader, 'get_filename'): + if _bootstrap_external is None: + raise NotImplementedError + spec_from_file_location = _bootstrap_external.spec_from_file_location + + if is_package is None: + return spec_from_file_location(name, loader=loader) + search = [] if is_package else None + return spec_from_file_location(name, loader=loader, + submodule_search_locations=search) + + if is_package is None: + if hasattr(loader, 'is_package'): + try: + is_package = loader.is_package(name) + except ImportError: + is_package = None # aka, undefined + else: + # the default + is_package = False + + return ModuleSpec(name, loader, origin=origin, is_package=is_package) + + +def _spec_from_module(module, loader=None, origin=None): + # This function is meant for use in _setup(). + try: + spec = module.__spec__ + except AttributeError: + pass + else: + if spec is not None: + return spec + + name = module.__name__ + if loader is None: + try: + loader = module.__loader__ + except AttributeError: + # loader will stay None. + pass + try: + location = module.__file__ + except AttributeError: + location = None + if origin is None: + if location is None: + try: + origin = loader._ORIGIN + except AttributeError: + origin = None + else: + origin = location + try: + cached = module.__cached__ + except AttributeError: + cached = None + try: + submodule_search_locations = list(module.__path__) + except AttributeError: + submodule_search_locations = None + + spec = ModuleSpec(name, loader, origin=origin) + spec._set_fileattr = False if location is None else True + spec.cached = cached + spec.submodule_search_locations = submodule_search_locations + return spec + + +def _init_module_attrs(spec, module, *, override=False): + # The passed-in module may be not support attribute assignment, + # in which case we simply don't set the attributes. + # __name__ + if (override or getattr(module, '__name__', None) is None): + try: + module.__name__ = spec.name + except AttributeError: + pass + # __loader__ + if override or getattr(module, '__loader__', None) is None: + loader = spec.loader + if loader is None: + # A backward compatibility hack. + if spec.submodule_search_locations is not None: + if _bootstrap_external is None: + raise NotImplementedError + _NamespaceLoader = _bootstrap_external._NamespaceLoader + + loader = _NamespaceLoader.__new__(_NamespaceLoader) + loader._path = spec.submodule_search_locations + spec.loader = loader + # While the docs say that module.__file__ is not set for + # built-in modules, and the code below will avoid setting it if + # spec.has_location is false, this is incorrect for namespace + # packages. Namespace packages have no location, but their + # __spec__.origin is None, and thus their module.__file__ + # should also be None for consistency. While a bit of a hack, + # this is the best place to ensure this consistency. + # + # See # https://docs.python.org/3/library/importlib.html#importlib.abc.Loader.load_module + # and bpo-32305 + module.__file__ = None + try: + module.__loader__ = loader + except AttributeError: + pass + # __package__ + if override or getattr(module, '__package__', None) is None: + try: + module.__package__ = spec.parent + except AttributeError: + pass + # __spec__ + try: + module.__spec__ = spec + except AttributeError: + pass + # __path__ + if override or getattr(module, '__path__', None) is None: + if spec.submodule_search_locations is not None: + try: + module.__path__ = spec.submodule_search_locations + except AttributeError: + pass + # __file__/__cached__ + if spec.has_location: + if override or getattr(module, '__file__', None) is None: + try: + module.__file__ = spec.origin + except AttributeError: + pass + + if override or getattr(module, '__cached__', None) is None: + if spec.cached is not None: + try: + module.__cached__ = spec.cached + except AttributeError: + pass + return module + + +def module_from_spec(spec): + """Create a module based on the provided spec.""" + # Typically loaders will not implement create_module(). + module = None + if hasattr(spec.loader, 'create_module'): + # If create_module() returns `None` then it means default + # module creation should be used. + module = spec.loader.create_module(spec) + elif hasattr(spec.loader, 'exec_module'): + raise ImportError('loaders that define exec_module() ' + 'must also define create_module()') + if module is None: + module = _new_module(spec.name) + _init_module_attrs(spec, module) + return module + + +def _module_repr_from_spec(spec): + """Return the repr to use for the module.""" + # We mostly replicate _module_repr() using the spec attributes. + name = '?' if spec.name is None else spec.name + if spec.origin is None: + if spec.loader is None: + return ''.format(name) + else: + return ''.format(name, spec.loader) + else: + if spec.has_location: + return ''.format(name, spec.origin) + else: + return ''.format(spec.name, spec.origin) + + +# Used by importlib.reload() and _load_module_shim(). +def _exec(spec, module): + """Execute the spec's specified module in an existing module's namespace.""" + name = spec.name + with _ModuleLockManager(name): + if sys.modules.get(name) is not module: + msg = 'module {!r} not in sys.modules'.format(name) + raise ImportError(msg, name=name) + try: + if spec.loader is None: + if spec.submodule_search_locations is None: + raise ImportError('missing loader', name=spec.name) + # Namespace package. + _init_module_attrs(spec, module, override=True) + else: + _init_module_attrs(spec, module, override=True) + if not hasattr(spec.loader, 'exec_module'): + # (issue19713) Once BuiltinImporter and ExtensionFileLoader + # have exec_module() implemented, we can add a deprecation + # warning here. + spec.loader.load_module(name) + else: + spec.loader.exec_module(module) + finally: + # Update the order of insertion into sys.modules for module + # clean-up at shutdown. + module = sys.modules.pop(spec.name) + sys.modules[spec.name] = module + return module + + +def _load_backward_compatible(spec): + # (issue19713) Once BuiltinImporter and ExtensionFileLoader + # have exec_module() implemented, we can add a deprecation + # warning here. + try: + spec.loader.load_module(spec.name) + except: + if spec.name in sys.modules: + module = sys.modules.pop(spec.name) + sys.modules[spec.name] = module + raise + # The module must be in sys.modules at this point! + # Move it to the end of sys.modules. + module = sys.modules.pop(spec.name) + sys.modules[spec.name] = module + if getattr(module, '__loader__', None) is None: + try: + module.__loader__ = spec.loader + except AttributeError: + pass + if getattr(module, '__package__', None) is None: + try: + # Since module.__path__ may not line up with + # spec.submodule_search_paths, we can't necessarily rely + # on spec.parent here. + module.__package__ = module.__name__ + if not hasattr(module, '__path__'): + module.__package__ = spec.name.rpartition('.')[0] + except AttributeError: + pass + if getattr(module, '__spec__', None) is None: + try: + module.__spec__ = spec + except AttributeError: + pass + return module + +def _load_unlocked(spec): + # A helper for direct use by the import system. + if spec.loader is not None: + # Not a namespace package. + if not hasattr(spec.loader, 'exec_module'): + return _load_backward_compatible(spec) + + module = module_from_spec(spec) + + # This must be done before putting the module in sys.modules + # (otherwise an optimization shortcut in import.c becomes + # wrong). + spec._initializing = True + try: + sys.modules[spec.name] = module + try: + if spec.loader is None: + if spec.submodule_search_locations is None: + raise ImportError('missing loader', name=spec.name) + # A namespace package so do nothing. + else: + spec.loader.exec_module(module) + except: + try: + del sys.modules[spec.name] + except KeyError: + pass + raise + # Move the module to the end of sys.modules. + # We don't ensure that the import-related module attributes get + # set in the sys.modules replacement case. Such modules are on + # their own. + module = sys.modules.pop(spec.name) + sys.modules[spec.name] = module + _verbose_message('import {!r} # {!r}', spec.name, spec.loader) + finally: + spec._initializing = False + + return module + +# A method used during testing of _load_unlocked() and by +# _load_module_shim(). +def _load(spec): + """Return a new module object, loaded by the spec's loader. + + The module is not added to its parent. + + If a module is already in sys.modules, that existing module gets + clobbered. + + """ + with _ModuleLockManager(spec.name): + return _load_unlocked(spec) + + +# Loaders ##################################################################### + +class BuiltinImporter: + + """Meta path import for built-in modules. + + All methods are either class or static methods to avoid the need to + instantiate the class. + + """ + + @staticmethod + def module_repr(module): + """Return repr for the module. + + The method is deprecated. The import machinery does the job itself. + + """ + return ''.format(module.__name__) + + @classmethod + def find_spec(cls, fullname, path=None, target=None): + if path is not None: + return None + if _imp.is_builtin(fullname): + return spec_from_loader(fullname, cls, origin='built-in') + else: + return None + + @classmethod + def find_module(cls, fullname, path=None): + """Find the built-in module. + + If 'path' is ever specified then the search is considered a failure. + + This method is deprecated. Use find_spec() instead. + + """ + spec = cls.find_spec(fullname, path) + return spec.loader if spec is not None else None + + @classmethod + def create_module(self, spec): + """Create a built-in module""" + if spec.name not in sys.builtin_module_names: + raise ImportError('{!r} is not a built-in module'.format(spec.name), + name=spec.name) + return _call_with_frames_removed(_imp.create_builtin, spec) + + @classmethod + def exec_module(self, module): + """Exec a built-in module""" + _call_with_frames_removed(_imp.exec_builtin, module) + + @classmethod + @_requires_builtin + def get_code(cls, fullname): + """Return None as built-in modules do not have code objects.""" + return None + + @classmethod + @_requires_builtin + def get_source(cls, fullname): + """Return None as built-in modules do not have source code.""" + return None + + @classmethod + @_requires_builtin + def is_package(cls, fullname): + """Return False as built-in modules are never packages.""" + return False + + load_module = classmethod(_load_module_shim) + + +class FrozenImporter: + + """Meta path import for frozen modules. + + All methods are either class or static methods to avoid the need to + instantiate the class. + + """ + + _ORIGIN = "frozen" + + @staticmethod + def module_repr(m): + """Return repr for the module. + + The method is deprecated. The import machinery does the job itself. + + """ + return ''.format(m.__name__, FrozenImporter._ORIGIN) + + @classmethod + def find_spec(cls, fullname, path=None, target=None): + if _imp.is_frozen(fullname): + return spec_from_loader(fullname, cls, origin=cls._ORIGIN) + else: + return None + + @classmethod + def find_module(cls, fullname, path=None): + """Find a frozen module. + + This method is deprecated. Use find_spec() instead. + + """ + return cls if _imp.is_frozen(fullname) else None + + @classmethod + def create_module(cls, spec): + """Use default semantics for module creation.""" + + @staticmethod + def exec_module(module): + name = module.__spec__.name + if not _imp.is_frozen(name): + raise ImportError('{!r} is not a frozen module'.format(name), + name=name) + code = _call_with_frames_removed(_imp.get_frozen_object, name) + exec(code, module.__dict__) + + @classmethod + def load_module(cls, fullname): + """Load a frozen module. + + This method is deprecated. Use exec_module() instead. + + """ + return _load_module_shim(cls, fullname) + + @classmethod + @_requires_frozen + def get_code(cls, fullname): + """Return the code object for the frozen module.""" + return _imp.get_frozen_object(fullname) + + @classmethod + @_requires_frozen + def get_source(cls, fullname): + """Return None as frozen modules do not have source code.""" + return None + + @classmethod + @_requires_frozen + def is_package(cls, fullname): + """Return True if the frozen module is a package.""" + return _imp.is_frozen_package(fullname) + + +# Import itself ############################################################### + +class _ImportLockContext: + + """Context manager for the import lock.""" + + def __enter__(self): + """Acquire the import lock.""" + _imp.acquire_lock() + + def __exit__(self, exc_type, exc_value, exc_traceback): + """Release the import lock regardless of any raised exceptions.""" + _imp.release_lock() + + +def _resolve_name(name, package, level): + """Resolve a relative module name to an absolute one.""" + bits = package.rsplit('.', level - 1) + if len(bits) < level: + raise ValueError('attempted relative import beyond top-level package') + base = bits[0] + return '{}.{}'.format(base, name) if name else base + + +def _find_spec_legacy(finder, name, path): + # This would be a good place for a DeprecationWarning if + # we ended up going that route. + loader = finder.find_module(name, path) + if loader is None: + return None + return spec_from_loader(name, loader) + + +def _find_spec(name, path, target=None): + """Find a module's spec.""" + meta_path = sys.meta_path + if meta_path is None: + # PyImport_Cleanup() is running or has been called. + raise ImportError("sys.meta_path is None, Python is likely " + "shutting down") + + if not meta_path: + _warnings.warn('sys.meta_path is empty', ImportWarning) + + # We check sys.modules here for the reload case. While a passed-in + # target will usually indicate a reload there is no guarantee, whereas + # sys.modules provides one. + is_reload = name in sys.modules + for finder in meta_path: + with _ImportLockContext(): + try: + find_spec = finder.find_spec + except AttributeError: + spec = _find_spec_legacy(finder, name, path) + if spec is None: + continue + else: + spec = find_spec(name, path, target) + if spec is not None: + # The parent import may have already imported this module. + if not is_reload and name in sys.modules: + module = sys.modules[name] + try: + __spec__ = module.__spec__ + except AttributeError: + # We use the found spec since that is the one that + # we would have used if the parent module hadn't + # beaten us to the punch. + return spec + else: + if __spec__ is None: + return spec + else: + return __spec__ + else: + return spec + else: + return None + + +def _sanity_check(name, package, level): + """Verify arguments are "sane".""" + if not isinstance(name, str): + raise TypeError('module name must be str, not {}'.format(type(name))) + if level < 0: + raise ValueError('level must be >= 0') + if level > 0: + if not isinstance(package, str): + raise TypeError('__package__ not set to a string') + elif not package: + raise ImportError('attempted relative import with no known parent ' + 'package') + if not name and level == 0: + raise ValueError('Empty module name') + + +_ERR_MSG_PREFIX = 'No module named ' +_ERR_MSG = _ERR_MSG_PREFIX + '{!r}' + +def _find_and_load_unlocked(name, import_): + path = None + parent = name.rpartition('.')[0] + if parent: + if parent not in sys.modules: + _call_with_frames_removed(import_, parent) + # Crazy side-effects! + if name in sys.modules: + return sys.modules[name] + parent_module = sys.modules[parent] + try: + path = parent_module.__path__ + except AttributeError: + msg = (_ERR_MSG + '; {!r} is not a package').format(name, parent) + raise ModuleNotFoundError(msg, name=name) from None + spec = _find_spec(name, path) + if spec is None: + raise ModuleNotFoundError(_ERR_MSG.format(name), name=name) + else: + module = _load_unlocked(spec) + if parent: + # Set the module as an attribute on its parent. + parent_module = sys.modules[parent] + setattr(parent_module, name.rpartition('.')[2], module) + return module + + +_NEEDS_LOADING = object() + + +def _find_and_load(name, import_): + """Find and load the module.""" + with _ModuleLockManager(name): + module = sys.modules.get(name, _NEEDS_LOADING) + if module is _NEEDS_LOADING: + return _find_and_load_unlocked(name, import_) + + if module is None: + message = ('import of {} halted; ' + 'None in sys.modules'.format(name)) + raise ModuleNotFoundError(message, name=name) + + _lock_unlock_module(name) + return module + + +def _gcd_import(name, package=None, level=0): + """Import and return the module based on its name, the package the call is + being made from, and the level adjustment. + + This function represents the greatest common denominator of functionality + between import_module and __import__. This includes setting __package__ if + the loader did not. + + """ + _sanity_check(name, package, level) + if level > 0: + name = _resolve_name(name, package, level) + return _find_and_load(name, _gcd_import) + + +def _handle_fromlist(module, fromlist, import_, *, recursive=False): + """Figure out what __import__ should return. + + The import_ parameter is a callable which takes the name of module to + import. It is required to decouple the function from assuming importlib's + import implementation is desired. + + """ + # The hell that is fromlist ... + # If a package was imported, try to import stuff from fromlist. + for x in fromlist: + if not isinstance(x, str): + if recursive: + where = module.__name__ + '.__all__' + else: + where = "``from list''" + raise TypeError(f"Item in {where} must be str, " + f"not {type(x).__name__}") + elif x == '*': + if not recursive and hasattr(module, '__all__'): + _handle_fromlist(module, module.__all__, import_, + recursive=True) + elif not hasattr(module, x): + from_name = '{}.{}'.format(module.__name__, x) + try: + _call_with_frames_removed(import_, from_name) + except ModuleNotFoundError as exc: + # Backwards-compatibility dictates we ignore failed + # imports triggered by fromlist for modules that don't + # exist. + if (exc.name == from_name and + sys.modules.get(from_name, _NEEDS_LOADING) is not None): + continue + raise + return module + + +def _calc___package__(globals): + """Calculate what __package__ should be. + + __package__ is not guaranteed to be defined or could be set to None + to represent that its proper value is unknown. + + """ + package = globals.get('__package__') + spec = globals.get('__spec__') + if package is not None: + if spec is not None and package != spec.parent: + _warnings.warn("__package__ != __spec__.parent " + f"({package!r} != {spec.parent!r})", + ImportWarning, stacklevel=3) + return package + elif spec is not None: + return spec.parent + else: + _warnings.warn("can't resolve package from __spec__ or __package__, " + "falling back on __name__ and __path__", + ImportWarning, stacklevel=3) + package = globals['__name__'] + if '__path__' not in globals: + package = package.rpartition('.')[0] + return package + + +def __import__(name, globals=None, locals=None, fromlist=(), level=0): + """Import a module. + + The 'globals' argument is used to infer where the import is occurring from + to handle relative imports. The 'locals' argument is ignored. The + 'fromlist' argument specifies what should exist as attributes on the module + being imported (e.g. ``from module import ``). The 'level' + argument represents the package location to import from in a relative + import (e.g. ``from ..pkg import mod`` would have a 'level' of 2). + + """ + if level == 0: + module = _gcd_import(name) + else: + globals_ = globals if globals is not None else {} + package = _calc___package__(globals_) + module = _gcd_import(name, package, level) + if not fromlist: + # Return up to the first dot in 'name'. This is complicated by the fact + # that 'name' may be relative. + if level == 0: + return _gcd_import(name.partition('.')[0]) + elif not name: + return module + else: + # Figure out where to slice the module's name up to the first dot + # in 'name'. + cut_off = len(name) - len(name.partition('.')[0]) + # Slice end needs to be positive to alleviate need to special-case + # when ``'.' not in name``. + return sys.modules[module.__name__[:len(module.__name__)-cut_off]] + elif hasattr(module, '__path__'): + return _handle_fromlist(module, fromlist, _gcd_import) + else: + return module + + +def _builtin_from_name(name): + spec = BuiltinImporter.find_spec(name) + if spec is None: + raise ImportError('no built-in module named ' + name) + return _load_unlocked(spec) + + +def _setup(sys_module, _imp_module): + """Setup importlib by importing needed built-in modules and injecting them + into the global namespace. + + As sys is needed for sys.modules access and _imp is needed to load built-in + modules, those two modules must be explicitly passed in. + + """ + global _imp, sys + _imp = _imp_module + sys = sys_module + + # Set up the spec for existing builtin/frozen modules. + module_type = type(sys) + for name, module in sys.modules.items(): + if isinstance(module, module_type): + if name in sys.builtin_module_names: + loader = BuiltinImporter + elif _imp.is_frozen(name): + loader = FrozenImporter + else: + continue + spec = _spec_from_module(module, loader) + _init_module_attrs(spec, module) + + # Directly load built-in modules needed during bootstrap. + self_module = sys.modules[__name__] + for builtin_name in ('_thread', '_warnings', '_weakref'): + if builtin_name not in sys.modules: + builtin_module = _builtin_from_name(builtin_name) + else: + builtin_module = sys.modules[builtin_name] + setattr(self_module, builtin_name, builtin_module) + + +def _install(sys_module, _imp_module): + """Install importers for builtin and frozen modules""" + _setup(sys_module, _imp_module) + + sys.meta_path.append(BuiltinImporter) + sys.meta_path.append(FrozenImporter) + + +def _install_external_importers(): + """Install importers that require external filesystem access""" + global _bootstrap_external + import _frozen_importlib_external + _bootstrap_external = _frozen_importlib_external + _frozen_importlib_external._install(sys.modules[__name__]) +"#; diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 24f28709ea..d52823c3de 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -48,6 +48,7 @@ pub mod frame; mod frozen; pub mod function; pub mod import; +mod importlib; pub mod obj; mod pyhash; pub mod pyobject; From e88d6ac1ef5f7e683f9a993b5a613bd16e868afe Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Wed, 5 Jun 2019 22:35:30 +0300 Subject: [PATCH 719/884] Add init_importlib --- src/main.rs | 3 +++ vm/src/import.rs | 36 ++++++++++++++++++++++++++++++------ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/src/main.rs b/src/main.rs index cd20462ecc..9b0711ce45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,6 +54,9 @@ fn main() { // Construct vm: let vm = VirtualMachine::new(); + let res = import::init_importlib(&vm); + handle_exception(&vm, res); + // Figure out if a -c option was given: let result = if let Some(command) = matches.value_of("c") { run_command(&vm, command.to_string()) diff --git a/vm/src/import.rs b/vm/src/import.rs index 0e8b7ba535..9bfeb59361 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -11,6 +11,32 @@ use crate::pyobject::{ItemProtocol, PyResult}; use crate::util; use crate::vm::VirtualMachine; +pub fn init_importlib(vm: &VirtualMachine) -> PyResult { + let importlib = import_frozen(vm, "_frozen_importlib")?; + let impmod = import_builtin(vm, "_imp")?; + let install = vm.get_attribute(importlib, "_install")?; + vm.invoke(install, vec![vm.sys_module.clone(), impmod]) +} + +fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { + if let Some(frozen) = vm.frozen.borrow().get(module_name) { + import_file(vm, module_name, "frozen".to_string(), frozen.to_string()) + } else { + Err(vm.new_import_error(format!("Cannot import frozen module {}", module_name))) + } +} + +fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult { + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); + if let Some(make_module_func) = vm.stdlib_inits.borrow().get(module_name) { + let module = make_module_func(vm); + sys_modules.set_item(module_name, module.clone(), vm)?; + Ok(module) + } else { + Err(vm.new_import_error(format!("Cannot import bultin module {}", module_name))) + } +} + pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &str) -> PyResult { // Cached modules: let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); @@ -18,12 +44,10 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s // First, see if we already loaded the module: if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) { Ok(module) - } else if let Some(frozen) = vm.frozen.borrow().get(module_name) { - import_file(vm, module_name, "frozen".to_string(), frozen.to_string()) - } else if let Some(make_module_func) = vm.stdlib_inits.borrow().get(module_name) { - let module = make_module_func(vm); - sys_modules.set_item(module_name, module.clone(), vm)?; - Ok(module) + } else if vm.frozen.borrow().contains_key(module_name) { + import_frozen(vm, module_name) + } else if vm.stdlib_inits.borrow().contains_key(module_name) { + import_builtin(vm, module_name) } else { let notfound_error = vm.context().exceptions.module_not_found_error.clone(); let import_error = vm.context().exceptions.import_error.clone(); From 698044b12822e43bf15cc65ed20cb4e325a821cf Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Wed, 5 Jun 2019 22:41:48 +0300 Subject: [PATCH 720/884] Add Module.__name__ --- tests/snippets/import.py | 2 ++ vm/src/obj/objmodule.rs | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/tests/snippets/import.py b/tests/snippets/import.py index 4f1164043a..99322a2ea9 100644 --- a/tests/snippets/import.py +++ b/tests/snippets/import.py @@ -7,6 +7,8 @@ assert import_target.X == import_target.func() assert import_target.X == func() +assert import_mutual1.__name__ == "import_mutual1" + assert import_target.Y == other_func() assert import_target.X == aliased.X diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs index 904487e4d9..ae1563d3a6 100644 --- a/vm/src/obj/objmodule.rs +++ b/vm/src/obj/objmodule.rs @@ -23,10 +23,15 @@ impl PyModuleRef { panic!("Modules should definitely have a dict."); } } + + fn name(self: PyModuleRef, _vm: &VirtualMachine) -> String { + self.name.clone() + } } pub fn init(context: &PyContext) { extend_class!(&context, &context.module_type, { "__dir__" => context.new_rustfunc(PyModuleRef::dir), + "__name__" => context.new_rustfunc(PyModuleRef::name) }); } From f1af6b1f4056ff3523ee8d4e064febbedd2d4764 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 10:28:20 +0300 Subject: [PATCH 721/884] Include _bootstrap.py as str --- vm/src/importlib.rs => Lib/importlib/_bootstrap.py | 2 -- vm/src/frozen.rs | 13 ++++++++++++- vm/src/lib.rs | 1 - 3 files changed, 12 insertions(+), 4 deletions(-) rename vm/src/importlib.rs => Lib/importlib/_bootstrap.py (99%) diff --git a/vm/src/importlib.rs b/Lib/importlib/_bootstrap.py similarity index 99% rename from vm/src/importlib.rs rename to Lib/importlib/_bootstrap.py index 7b8b4e81ba..32deef10af 100644 --- a/vm/src/importlib.rs +++ b/Lib/importlib/_bootstrap.py @@ -1,4 +1,3 @@ -pub const IMPORTLIB_BOOTSTRAP: &str = r#" """Core implementation of import. This module is NOT meant to be directly imported! It has been designed such @@ -1172,4 +1171,3 @@ def _install_external_importers(): import _frozen_importlib_external _bootstrap_external = _frozen_importlib_external _frozen_importlib_external._install(sys.modules[__name__]) -"#; diff --git a/vm/src/frozen.rs b/vm/src/frozen.rs index 36fd08a4ad..41848001d7 100644 --- a/vm/src/frozen.rs +++ b/vm/src/frozen.rs @@ -1,10 +1,21 @@ -use crate::importlib::IMPORTLIB_BOOTSTRAP; use std::collections::hash_map::HashMap; const HELLO: &str = "initialized = True print(\"Hello world!\") "; +const IMPORTLIB_BOOTSTRAP: &'static str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/", + "..", + "/", + "Lib", + "/", + "importlib", + "/", + "_bootstrap.py" +)); + pub fn get_module_inits() -> HashMap { let mut modules = HashMap::new(); modules.insert("__hello__".to_string(), HELLO); diff --git a/vm/src/lib.rs b/vm/src/lib.rs index d52823c3de..24f28709ea 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -48,7 +48,6 @@ pub mod frame; mod frozen; pub mod function; pub mod import; -mod importlib; pub mod obj; mod pyhash; pub mod pyobject; From f2145880fa151325eab22f1c48698dfef9191a0a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 11:09:31 +0300 Subject: [PATCH 722/884] Don't set __file__ for frozen modules --- vm/src/import.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index 9bfeb59361..33335583ef 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -80,7 +80,10 @@ pub fn import_file( let attrs = vm.ctx.new_dict(); attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; - attrs.set_item("__file__", vm.new_str(file_path), vm)?; + if file_path != "frozen".to_string() { + // TODO: Should be removed after precompiling frozen modules. + attrs.set_item("__file__", vm.new_str(file_path), vm)?; + } let module = vm.ctx.new_module(module_name, attrs.clone()); // Store module in cache to prevent infinite loop with mutual importing libs: From 0d9a066712998e6dd30366efaff4227645d75201 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 19:03:27 +0300 Subject: [PATCH 723/884] Add sys.meta_path --- vm/src/sysmodule.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index f1cc0afcb4..594a4f8a65 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -242,6 +242,7 @@ settrace() -- set the global debug tracing function "modules" => modules.clone(), "warnoptions" => ctx.new_list(vec![]), "platform" => ctx.new_str(platform), + "meta_path" => ctx.new_list(vec![]), }); modules.set_item("sys", module.clone(), vm).unwrap(); From 37b40c55dabc4997ed4e5bd558fbd7dc780611d1 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 19:15:16 +0300 Subject: [PATCH 724/884] Add frozen _bootstrap_external.py --- Lib/importlib/_bootstrap_external.py | 1616 ++++++++++++++++++++++++++ vm/src/frozen.rs | 16 + 2 files changed, 1632 insertions(+) create mode 100644 Lib/importlib/_bootstrap_external.py diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py new file mode 100644 index 0000000000..f8ff5f4f2c --- /dev/null +++ b/Lib/importlib/_bootstrap_external.py @@ -0,0 +1,1616 @@ +"""Core implementation of path-based import. + +This module is NOT meant to be directly imported! It has been designed such +that it can be bootstrapped into Python as the implementation of import. As +such it requires the injection of specific modules and attributes in order to +work. One should use importlib as the public-facing version of this module. + +""" +# IMPORTANT: Whenever making changes to this module, be sure to run a top-level +# `make regen-importlib` followed by `make` in order to get the frozen version +# of the module updated. Not doing so will result in the Makefile to fail for +# all others who don't have a ./python around to freeze the module in the early +# stages of compilation. +# + +# See importlib._setup() for what is injected into the global namespace. + +# When editing this code be aware that code executed at import time CANNOT +# reference any injected objects! This includes not only global code but also +# anything specified at the class level. + +# Bootstrap-related code ###################################################### +_CASE_INSENSITIVE_PLATFORMS_STR_KEY = 'win', +_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY = 'cygwin', 'darwin' +_CASE_INSENSITIVE_PLATFORMS = (_CASE_INSENSITIVE_PLATFORMS_BYTES_KEY + + _CASE_INSENSITIVE_PLATFORMS_STR_KEY) + + +def _make_relax_case(): + if sys.platform.startswith(_CASE_INSENSITIVE_PLATFORMS): + if sys.platform.startswith(_CASE_INSENSITIVE_PLATFORMS_STR_KEY): + key = 'PYTHONCASEOK' + else: + key = b'PYTHONCASEOK' + + def _relax_case(): + """True if filenames must be checked case-insensitively.""" + return key in _os.environ + else: + def _relax_case(): + """True if filenames must be checked case-insensitively.""" + return False + return _relax_case + + +def _pack_uint32(x): + """Convert a 32-bit integer to little-endian.""" + return (int(x) & 0xFFFFFFFF).to_bytes(4, 'little') + + +def _unpack_uint32(data): + """Convert 4 bytes in little-endian to an integer.""" + assert len(data) == 4 + return int.from_bytes(data, 'little') + +def _unpack_uint16(data): + """Convert 2 bytes in little-endian to an integer.""" + assert len(data) == 2 + return int.from_bytes(data, 'little') + + +def _path_join(*path_parts): + """Replacement for os.path.join().""" + return path_sep.join([part.rstrip(path_separators) + for part in path_parts if part]) + + +def _path_split(path): + """Replacement for os.path.split().""" + if len(path_separators) == 1: + front, _, tail = path.rpartition(path_sep) + return front, tail + for x in reversed(path): + if x in path_separators: + front, tail = path.rsplit(x, maxsplit=1) + return front, tail + return '', path + + +def _path_stat(path): + """Stat the path. + + Made a separate function to make it easier to override in experiments + (e.g. cache stat results). + + """ + return _os.stat(path) + + +def _path_is_mode_type(path, mode): + """Test whether the path is the specified mode type.""" + try: + stat_info = _path_stat(path) + except OSError: + return False + return (stat_info.st_mode & 0o170000) == mode + + +def _path_isfile(path): + """Replacement for os.path.isfile.""" + return _path_is_mode_type(path, 0o100000) + + +def _path_isdir(path): + """Replacement for os.path.isdir.""" + if not path: + path = _os.getcwd() + return _path_is_mode_type(path, 0o040000) + + +def _path_isabs(path): + """Replacement for os.path.isabs. + + Considers a Windows drive-relative path (no drive, but starts with slash) to + still be "absolute". + """ + return path.startswith(path_separators) or path[1:3] in _pathseps_with_colon + + +def _write_atomic(path, data, mode=0o666): + """Best-effort function to write data to a path atomically. + Be prepared to handle a FileExistsError if concurrent writing of the + temporary file is attempted.""" + # id() is used to generate a pseudo-random filename. + path_tmp = '{}.{}'.format(path, id(path)) + fd = _os.open(path_tmp, + _os.O_EXCL | _os.O_CREAT | _os.O_WRONLY, mode & 0o666) + try: + # We first write data to a temporary file, and then use os.replace() to + # perform an atomic rename. + with _io.FileIO(fd, 'wb') as file: + file.write(data) + _os.replace(path_tmp, path) + except OSError: + try: + _os.unlink(path_tmp) + except OSError: + pass + raise + + +_code_type = type(_write_atomic.__code__) + + +# Finder/loader utility code ############################################### + +# Magic word to reject .pyc files generated by other Python versions. +# It should change for each incompatible change to the bytecode. +# +# The value of CR and LF is incorporated so if you ever read or write +# a .pyc file in text mode the magic number will be wrong; also, the +# Apple MPW compiler swaps their values, botching string constants. +# +# There were a variety of old schemes for setting the magic number. +# The current working scheme is to increment the previous value by +# 10. +# +# Starting with the adoption of PEP 3147 in Python 3.2, every bump in magic +# number also includes a new "magic tag", i.e. a human readable string used +# to represent the magic number in __pycache__ directories. When you change +# the magic number, you must also set a new unique magic tag. Generally this +# can be named after the Python major version of the magic number bump, but +# it can really be anything, as long as it's different than anything else +# that's come before. The tags are included in the following table, starting +# with Python 3.2a0. +# +# Known values: +# Python 1.5: 20121 +# Python 1.5.1: 20121 +# Python 1.5.2: 20121 +# Python 1.6: 50428 +# Python 2.0: 50823 +# Python 2.0.1: 50823 +# Python 2.1: 60202 +# Python 2.1.1: 60202 +# Python 2.1.2: 60202 +# Python 2.2: 60717 +# Python 2.3a0: 62011 +# Python 2.3a0: 62021 +# Python 2.3a0: 62011 (!) +# Python 2.4a0: 62041 +# Python 2.4a3: 62051 +# Python 2.4b1: 62061 +# Python 2.5a0: 62071 +# Python 2.5a0: 62081 (ast-branch) +# Python 2.5a0: 62091 (with) +# Python 2.5a0: 62092 (changed WITH_CLEANUP opcode) +# Python 2.5b3: 62101 (fix wrong code: for x, in ...) +# Python 2.5b3: 62111 (fix wrong code: x += yield) +# Python 2.5c1: 62121 (fix wrong lnotab with for loops and +# storing constants that should have been removed) +# Python 2.5c2: 62131 (fix wrong code: for x, in ... in listcomp/genexp) +# Python 2.6a0: 62151 (peephole optimizations and STORE_MAP opcode) +# Python 2.6a1: 62161 (WITH_CLEANUP optimization) +# Python 2.7a0: 62171 (optimize list comprehensions/change LIST_APPEND) +# Python 2.7a0: 62181 (optimize conditional branches: +# introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE) +# Python 2.7a0 62191 (introduce SETUP_WITH) +# Python 2.7a0 62201 (introduce BUILD_SET) +# Python 2.7a0 62211 (introduce MAP_ADD and SET_ADD) +# Python 3000: 3000 +# 3010 (removed UNARY_CONVERT) +# 3020 (added BUILD_SET) +# 3030 (added keyword-only parameters) +# 3040 (added signature annotations) +# 3050 (print becomes a function) +# 3060 (PEP 3115 metaclass syntax) +# 3061 (string literals become unicode) +# 3071 (PEP 3109 raise changes) +# 3081 (PEP 3137 make __file__ and __name__ unicode) +# 3091 (kill str8 interning) +# 3101 (merge from 2.6a0, see 62151) +# 3103 (__file__ points to source file) +# Python 3.0a4: 3111 (WITH_CLEANUP optimization). +# Python 3.0b1: 3131 (lexical exception stacking, including POP_EXCEPT + #3021) +# Python 3.1a1: 3141 (optimize list, set and dict comprehensions: +# change LIST_APPEND and SET_ADD, add MAP_ADD #2183) +# Python 3.1a1: 3151 (optimize conditional branches: +# introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE + #4715) +# Python 3.2a1: 3160 (add SETUP_WITH #6101) +# tag: cpython-32 +# Python 3.2a2: 3170 (add DUP_TOP_TWO, remove DUP_TOPX and ROT_FOUR #9225) +# tag: cpython-32 +# Python 3.2a3 3180 (add DELETE_DEREF #4617) +# Python 3.3a1 3190 (__class__ super closure changed) +# Python 3.3a1 3200 (PEP 3155 __qualname__ added #13448) +# Python 3.3a1 3210 (added size modulo 2**32 to the pyc header #13645) +# Python 3.3a2 3220 (changed PEP 380 implementation #14230) +# Python 3.3a4 3230 (revert changes to implicit __class__ closure #14857) +# Python 3.4a1 3250 (evaluate positional default arguments before +# keyword-only defaults #16967) +# Python 3.4a1 3260 (add LOAD_CLASSDEREF; allow locals of class to override +# free vars #17853) +# Python 3.4a1 3270 (various tweaks to the __class__ closure #12370) +# Python 3.4a1 3280 (remove implicit class argument) +# Python 3.4a4 3290 (changes to __qualname__ computation #19301) +# Python 3.4a4 3300 (more changes to __qualname__ computation #19301) +# Python 3.4rc2 3310 (alter __qualname__ computation #20625) +# Python 3.5a1 3320 (PEP 465: Matrix multiplication operator #21176) +# Python 3.5b1 3330 (PEP 448: Additional Unpacking Generalizations #2292) +# Python 3.5b2 3340 (fix dictionary display evaluation order #11205) +# Python 3.5b3 3350 (add GET_YIELD_FROM_ITER opcode #24400) +# Python 3.5.2 3351 (fix BUILD_MAP_UNPACK_WITH_CALL opcode #27286) +# Python 3.6a0 3360 (add FORMAT_VALUE opcode #25483) +# Python 3.6a1 3361 (lineno delta of code.co_lnotab becomes signed #26107) +# Python 3.6a2 3370 (16 bit wordcode #26647) +# Python 3.6a2 3371 (add BUILD_CONST_KEY_MAP opcode #27140) +# Python 3.6a2 3372 (MAKE_FUNCTION simplification, remove MAKE_CLOSURE +# #27095) +# Python 3.6b1 3373 (add BUILD_STRING opcode #27078) +# Python 3.6b1 3375 (add SETUP_ANNOTATIONS and STORE_ANNOTATION opcodes +# #27985) +# Python 3.6b1 3376 (simplify CALL_FUNCTIONs & BUILD_MAP_UNPACK_WITH_CALL + #27213) +# Python 3.6b1 3377 (set __class__ cell from type.__new__ #23722) +# Python 3.6b2 3378 (add BUILD_TUPLE_UNPACK_WITH_CALL #28257) +# Python 3.6rc1 3379 (more thorough __class__ validation #23722) +# Python 3.7a1 3390 (add LOAD_METHOD and CALL_METHOD opcodes #26110) +# Python 3.7a2 3391 (update GET_AITER #31709) +# Python 3.7a4 3392 (PEP 552: Deterministic pycs #31650) +# Python 3.7b1 3393 (remove STORE_ANNOTATION opcode #32550) +# Python 3.7b5 3394 (restored docstring as the firts stmt in the body; +# this might affected the first line number #32911) +# Python 3.8a1 3400 (move frame block handling to compiler #17611) +# Python 3.8a1 3401 (add END_ASYNC_FOR #33041) +# Python 3.8a1 3410 (PEP570 Python Positional-Only Parameters #36540) +# +# MAGIC must change whenever the bytecode emitted by the compiler may no +# longer be understood by older implementations of the eval loop (usually +# due to the addition of new opcodes). +# +# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array +# in PC/launcher.c must also be updated. + +MAGIC_NUMBER = (3410).to_bytes(2, 'little') + b'\r\n' +_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c + +_PYCACHE = '__pycache__' +_OPT = 'opt-' + +SOURCE_SUFFIXES = ['.py'] # _setup() adds .pyw as needed. + +BYTECODE_SUFFIXES = ['.pyc'] +# Deprecated. +DEBUG_BYTECODE_SUFFIXES = OPTIMIZED_BYTECODE_SUFFIXES = BYTECODE_SUFFIXES + +def cache_from_source(path, debug_override=None, *, optimization=None): + """Given the path to a .py file, return the path to its .pyc file. + + The .py file does not need to exist; this simply returns the path to the + .pyc file calculated as if the .py file were imported. + + The 'optimization' parameter controls the presumed optimization level of + the bytecode file. If 'optimization' is not None, the string representation + of the argument is taken and verified to be alphanumeric (else ValueError + is raised). + + The debug_override parameter is deprecated. If debug_override is not None, + a True value is the same as setting 'optimization' to the empty string + while a False value is equivalent to setting 'optimization' to '1'. + + If sys.implementation.cache_tag is None then NotImplementedError is raised. + + """ + if debug_override is not None: + _warnings.warn('the debug_override parameter is deprecated; use ' + "'optimization' instead", DeprecationWarning) + if optimization is not None: + message = 'debug_override or optimization must be set to None' + raise TypeError(message) + optimization = '' if debug_override else 1 + path = _os.fspath(path) + head, tail = _path_split(path) + base, sep, rest = tail.rpartition('.') + tag = sys.implementation.cache_tag + if tag is None: + raise NotImplementedError('sys.implementation.cache_tag is None') + almost_filename = ''.join([(base if base else rest), sep, tag]) + if optimization is None: + if sys.flags.optimize == 0: + optimization = '' + else: + optimization = sys.flags.optimize + optimization = str(optimization) + if optimization != '': + if not optimization.isalnum(): + raise ValueError('{!r} is not alphanumeric'.format(optimization)) + almost_filename = '{}.{}{}'.format(almost_filename, _OPT, optimization) + filename = almost_filename + BYTECODE_SUFFIXES[0] + if sys.pycache_prefix is not None: + # We need an absolute path to the py file to avoid the possibility of + # collisions within sys.pycache_prefix, if someone has two different + # `foo/bar.py` on their system and they import both of them using the + # same sys.pycache_prefix. Let's say sys.pycache_prefix is + # `C:\Bytecode`; the idea here is that if we get `Foo\Bar`, we first + # make it absolute (`C:\Somewhere\Foo\Bar`), then make it root-relative + # (`Somewhere\Foo\Bar`), so we end up placing the bytecode file in an + # unambiguous `C:\Bytecode\Somewhere\Foo\Bar\`. + if not _path_isabs(head): + head = _path_join(_os.getcwd(), head) + + # Strip initial drive from a Windows path. We know we have an absolute + # path here, so the second part of the check rules out a POSIX path that + # happens to contain a colon at the second character. + if head[1] == ':' and head[0] not in path_separators: + head = head[2:] + + # Strip initial path separator from `head` to complete the conversion + # back to a root-relative path before joining. + return _path_join( + sys.pycache_prefix, + head.lstrip(path_separators), + filename, + ) + return _path_join(head, _PYCACHE, filename) + + +def source_from_cache(path): + """Given the path to a .pyc. file, return the path to its .py file. + + The .pyc file does not need to exist; this simply returns the path to + the .py file calculated to correspond to the .pyc file. If path does + not conform to PEP 3147/488 format, ValueError will be raised. If + sys.implementation.cache_tag is None then NotImplementedError is raised. + + """ + if sys.implementation.cache_tag is None: + raise NotImplementedError('sys.implementation.cache_tag is None') + path = _os.fspath(path) + head, pycache_filename = _path_split(path) + found_in_pycache_prefix = False + if sys.pycache_prefix is not None: + stripped_path = sys.pycache_prefix.rstrip(path_separators) + if head.startswith(stripped_path + path_sep): + head = head[len(stripped_path):] + found_in_pycache_prefix = True + if not found_in_pycache_prefix: + head, pycache = _path_split(head) + if pycache != _PYCACHE: + raise ValueError(f'{_PYCACHE} not bottom-level directory in ' + f'{path!r}') + dot_count = pycache_filename.count('.') + if dot_count not in {2, 3}: + raise ValueError(f'expected only 2 or 3 dots in {pycache_filename!r}') + elif dot_count == 3: + optimization = pycache_filename.rsplit('.', 2)[-2] + if not optimization.startswith(_OPT): + raise ValueError("optimization portion of filename does not start " + f"with {_OPT!r}") + opt_level = optimization[len(_OPT):] + if not opt_level.isalnum(): + raise ValueError(f"optimization level {optimization!r} is not an " + "alphanumeric value") + base_filename = pycache_filename.partition('.')[0] + return _path_join(head, base_filename + SOURCE_SUFFIXES[0]) + + +def _get_sourcefile(bytecode_path): + """Convert a bytecode file path to a source path (if possible). + + This function exists purely for backwards-compatibility for + PyImport_ExecCodeModuleWithFilenames() in the C API. + + """ + if len(bytecode_path) == 0: + return None + rest, _, extension = bytecode_path.rpartition('.') + if not rest or extension.lower()[-3:-1] != 'py': + return bytecode_path + try: + source_path = source_from_cache(bytecode_path) + except (NotImplementedError, ValueError): + source_path = bytecode_path[:-1] + return source_path if _path_isfile(source_path) else bytecode_path + + +def _get_cached(filename): + if filename.endswith(tuple(SOURCE_SUFFIXES)): + try: + return cache_from_source(filename) + except NotImplementedError: + pass + elif filename.endswith(tuple(BYTECODE_SUFFIXES)): + return filename + else: + return None + + +def _calc_mode(path): + """Calculate the mode permissions for a bytecode file.""" + try: + mode = _path_stat(path).st_mode + except OSError: + mode = 0o666 + # We always ensure write access so we can update cached files + # later even when the source files are read-only on Windows (#6074) + mode |= 0o200 + return mode + + +def _check_name(method): + """Decorator to verify that the module being requested matches the one the + loader can handle. + + The first argument (self) must define _name which the second argument is + compared against. If the comparison fails then ImportError is raised. + + """ + def _check_name_wrapper(self, name=None, *args, **kwargs): + if name is None: + name = self.name + elif self.name != name: + raise ImportError('loader for %s cannot handle %s' % + (self.name, name), name=name) + return method(self, name, *args, **kwargs) + try: + _wrap = _bootstrap._wrap + except NameError: + # XXX yuck + def _wrap(new, old): + for replace in ['__module__', '__name__', '__qualname__', '__doc__']: + if hasattr(old, replace): + setattr(new, replace, getattr(old, replace)) + new.__dict__.update(old.__dict__) + _wrap(_check_name_wrapper, method) + return _check_name_wrapper + + +def _find_module_shim(self, fullname): + """Try to find a loader for the specified module by delegating to + self.find_loader(). + + This method is deprecated in favor of finder.find_spec(). + + """ + # Call find_loader(). If it returns a string (indicating this + # is a namespace package portion), generate a warning and + # return None. + loader, portions = self.find_loader(fullname) + if loader is None and len(portions): + msg = 'Not importing directory {}: missing __init__' + _warnings.warn(msg.format(portions[0]), ImportWarning) + return loader + + +def _classify_pyc(data, name, exc_details): + """Perform basic validity checking of a pyc header and return the flags field, + which determines how the pyc should be further validated against the source. + + *data* is the contents of the pyc file. (Only the first 16 bytes are + required, though.) + + *name* is the name of the module being imported. It is used for logging. + + *exc_details* is a dictionary passed to ImportError if it raised for + improved debugging. + + ImportError is raised when the magic number is incorrect or when the flags + field is invalid. EOFError is raised when the data is found to be truncated. + + """ + magic = data[:4] + if magic != MAGIC_NUMBER: + message = f'bad magic number in {name!r}: {magic!r}' + _bootstrap._verbose_message('{}', message) + raise ImportError(message, **exc_details) + if len(data) < 16: + message = f'reached EOF while reading pyc header of {name!r}' + _bootstrap._verbose_message('{}', message) + raise EOFError(message) + flags = _unpack_uint32(data[4:8]) + # Only the first two flags are defined. + if flags & ~0b11: + message = f'invalid flags {flags!r} in {name!r}' + raise ImportError(message, **exc_details) + return flags + + +def _validate_timestamp_pyc(data, source_mtime, source_size, name, + exc_details): + """Validate a pyc against the source last-modified time. + + *data* is the contents of the pyc file. (Only the first 16 bytes are + required.) + + *source_mtime* is the last modified timestamp of the source file. + + *source_size* is None or the size of the source file in bytes. + + *name* is the name of the module being imported. It is used for logging. + + *exc_details* is a dictionary passed to ImportError if it raised for + improved debugging. + + An ImportError is raised if the bytecode is stale. + + """ + if _unpack_uint32(data[8:12]) != (source_mtime & 0xFFFFFFFF): + message = f'bytecode is stale for {name!r}' + _bootstrap._verbose_message('{}', message) + raise ImportError(message, **exc_details) + if (source_size is not None and + _unpack_uint32(data[12:16]) != (source_size & 0xFFFFFFFF)): + raise ImportError(f'bytecode is stale for {name!r}', **exc_details) + + +def _validate_hash_pyc(data, source_hash, name, exc_details): + """Validate a hash-based pyc by checking the real source hash against the one in + the pyc header. + + *data* is the contents of the pyc file. (Only the first 16 bytes are + required.) + + *source_hash* is the importlib.util.source_hash() of the source file. + + *name* is the name of the module being imported. It is used for logging. + + *exc_details* is a dictionary passed to ImportError if it raised for + improved debugging. + + An ImportError is raised if the bytecode is stale. + + """ + if data[8:16] != source_hash: + raise ImportError( + f'hash in bytecode doesn\'t match hash of source {name!r}', + **exc_details, + ) + + +def _compile_bytecode(data, name=None, bytecode_path=None, source_path=None): + """Compile bytecode as found in a pyc.""" + code = marshal.loads(data) + if isinstance(code, _code_type): + _bootstrap._verbose_message('code object from {!r}', bytecode_path) + if source_path is not None: + _imp._fix_co_filename(code, source_path) + return code + else: + raise ImportError('Non-code object in {!r}'.format(bytecode_path), + name=name, path=bytecode_path) + + +def _code_to_timestamp_pyc(code, mtime=0, source_size=0): + "Produce the data for a timestamp-based pyc." + data = bytearray(MAGIC_NUMBER) + data.extend(_pack_uint32(0)) + data.extend(_pack_uint32(mtime)) + data.extend(_pack_uint32(source_size)) + data.extend(marshal.dumps(code)) + return data + + +def _code_to_hash_pyc(code, source_hash, checked=True): + "Produce the data for a hash-based pyc." + data = bytearray(MAGIC_NUMBER) + flags = 0b1 | checked << 1 + data.extend(_pack_uint32(flags)) + assert len(source_hash) == 8 + data.extend(source_hash) + data.extend(marshal.dumps(code)) + return data + + +def decode_source(source_bytes): + """Decode bytes representing source code and return the string. + + Universal newline support is used in the decoding. + """ + import tokenize # To avoid bootstrap issues. + source_bytes_readline = _io.BytesIO(source_bytes).readline + encoding = tokenize.detect_encoding(source_bytes_readline) + newline_decoder = _io.IncrementalNewlineDecoder(None, True) + return newline_decoder.decode(source_bytes.decode(encoding[0])) + + +# Module specifications ####################################################### + +_POPULATE = object() + + +def spec_from_file_location(name, location=None, *, loader=None, + submodule_search_locations=_POPULATE): + """Return a module spec based on a file location. + + To indicate that the module is a package, set + submodule_search_locations to a list of directory paths. An + empty list is sufficient, though its not otherwise useful to the + import system. + + The loader must take a spec as its only __init__() arg. + + """ + if location is None: + # The caller may simply want a partially populated location- + # oriented spec. So we set the location to a bogus value and + # fill in as much as we can. + location = '' + if hasattr(loader, 'get_filename'): + # ExecutionLoader + try: + location = loader.get_filename(name) + except ImportError: + pass + else: + location = _os.fspath(location) + + # If the location is on the filesystem, but doesn't actually exist, + # we could return None here, indicating that the location is not + # valid. However, we don't have a good way of testing since an + # indirect location (e.g. a zip file or URL) will look like a + # non-existent file relative to the filesystem. + + spec = _bootstrap.ModuleSpec(name, loader, origin=location) + spec._set_fileattr = True + + # Pick a loader if one wasn't provided. + if loader is None: + for loader_class, suffixes in _get_supported_file_loaders(): + if location.endswith(tuple(suffixes)): + loader = loader_class(name, location) + spec.loader = loader + break + else: + return None + + # Set submodule_search_paths appropriately. + if submodule_search_locations is _POPULATE: + # Check the loader. + if hasattr(loader, 'is_package'): + try: + is_package = loader.is_package(name) + except ImportError: + pass + else: + if is_package: + spec.submodule_search_locations = [] + else: + spec.submodule_search_locations = submodule_search_locations + if spec.submodule_search_locations == []: + if location: + dirname = _path_split(location)[0] + spec.submodule_search_locations.append(dirname) + + return spec + + +# Loaders ##################################################################### + +class WindowsRegistryFinder: + + """Meta path finder for modules declared in the Windows registry.""" + + REGISTRY_KEY = ( + 'Software\\Python\\PythonCore\\{sys_version}' + '\\Modules\\{fullname}') + REGISTRY_KEY_DEBUG = ( + 'Software\\Python\\PythonCore\\{sys_version}' + '\\Modules\\{fullname}\\Debug') + DEBUG_BUILD = False # Changed in _setup() + + @classmethod + def _open_registry(cls, key): + try: + return _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, key) + except OSError: + return _winreg.OpenKey(_winreg.HKEY_LOCAL_MACHINE, key) + + @classmethod + def _search_registry(cls, fullname): + if cls.DEBUG_BUILD: + registry_key = cls.REGISTRY_KEY_DEBUG + else: + registry_key = cls.REGISTRY_KEY + key = registry_key.format(fullname=fullname, + sys_version='%d.%d' % sys.version_info[:2]) + try: + with cls._open_registry(key) as hkey: + filepath = _winreg.QueryValue(hkey, '') + except OSError: + return None + return filepath + + @classmethod + def find_spec(cls, fullname, path=None, target=None): + filepath = cls._search_registry(fullname) + if filepath is None: + return None + try: + _path_stat(filepath) + except OSError: + return None + for loader, suffixes in _get_supported_file_loaders(): + if filepath.endswith(tuple(suffixes)): + spec = _bootstrap.spec_from_loader(fullname, + loader(fullname, filepath), + origin=filepath) + return spec + + @classmethod + def find_module(cls, fullname, path=None): + """Find module named in the registry. + + This method is deprecated. Use exec_module() instead. + + """ + spec = cls.find_spec(fullname, path) + if spec is not None: + return spec.loader + else: + return None + + +class _LoaderBasics: + + """Base class of common code needed by both SourceLoader and + SourcelessFileLoader.""" + + def is_package(self, fullname): + """Concrete implementation of InspectLoader.is_package by checking if + the path returned by get_filename has a filename of '__init__.py'.""" + filename = _path_split(self.get_filename(fullname))[1] + filename_base = filename.rsplit('.', 1)[0] + tail_name = fullname.rpartition('.')[2] + return filename_base == '__init__' and tail_name != '__init__' + + def create_module(self, spec): + """Use default semantics for module creation.""" + + def exec_module(self, module): + """Execute the module.""" + code = self.get_code(module.__name__) + if code is None: + raise ImportError('cannot load module {!r} when get_code() ' + 'returns None'.format(module.__name__)) + _bootstrap._call_with_frames_removed(exec, code, module.__dict__) + + def load_module(self, fullname): + """This module is deprecated.""" + return _bootstrap._load_module_shim(self, fullname) + + +class SourceLoader(_LoaderBasics): + + def path_mtime(self, path): + """Optional method that returns the modification time (an int) for the + specified path (a str). + + Raises OSError when the path cannot be handled. + """ + raise OSError + + def path_stats(self, path): + """Optional method returning a metadata dict for the specified + path (a str). + + Possible keys: + - 'mtime' (mandatory) is the numeric timestamp of last source + code modification; + - 'size' (optional) is the size in bytes of the source code. + + Implementing this method allows the loader to read bytecode files. + Raises OSError when the path cannot be handled. + """ + return {'mtime': self.path_mtime(path)} + + def _cache_bytecode(self, source_path, cache_path, data): + """Optional method which writes data (bytes) to a file path (a str). + + Implementing this method allows for the writing of bytecode files. + + The source path is needed in order to correctly transfer permissions + """ + # For backwards compatibility, we delegate to set_data() + return self.set_data(cache_path, data) + + def set_data(self, path, data): + """Optional method which writes data (bytes) to a file path (a str). + + Implementing this method allows for the writing of bytecode files. + """ + + + def get_source(self, fullname): + """Concrete implementation of InspectLoader.get_source.""" + path = self.get_filename(fullname) + try: + source_bytes = self.get_data(path) + except OSError as exc: + raise ImportError('source not available through get_data()', + name=fullname) from exc + return decode_source(source_bytes) + + def source_to_code(self, data, path, *, _optimize=-1): + """Return the code object compiled from source. + + The 'data' argument can be any object type that compile() supports. + """ + return _bootstrap._call_with_frames_removed(compile, data, path, 'exec', + dont_inherit=True, optimize=_optimize) + + def get_code(self, fullname): + """Concrete implementation of InspectLoader.get_code. + + Reading of bytecode requires path_stats to be implemented. To write + bytecode, set_data must also be implemented. + + """ + source_path = self.get_filename(fullname) + source_mtime = None + source_bytes = None + source_hash = None + hash_based = False + check_source = True + try: + bytecode_path = cache_from_source(source_path) + except NotImplementedError: + bytecode_path = None + else: + try: + st = self.path_stats(source_path) + except OSError: + pass + else: + source_mtime = int(st['mtime']) + try: + data = self.get_data(bytecode_path) + except OSError: + pass + else: + exc_details = { + 'name': fullname, + 'path': bytecode_path, + } + try: + flags = _classify_pyc(data, fullname, exc_details) + bytes_data = memoryview(data)[16:] + hash_based = flags & 0b1 != 0 + if hash_based: + check_source = flags & 0b10 != 0 + if (_imp.check_hash_based_pycs != 'never' and + (check_source or + _imp.check_hash_based_pycs == 'always')): + source_bytes = self.get_data(source_path) + source_hash = _imp.source_hash( + _RAW_MAGIC_NUMBER, + source_bytes, + ) + _validate_hash_pyc(data, source_hash, fullname, + exc_details) + else: + _validate_timestamp_pyc( + data, + source_mtime, + st['size'], + fullname, + exc_details, + ) + except (ImportError, EOFError): + pass + else: + _bootstrap._verbose_message('{} matches {}', bytecode_path, + source_path) + return _compile_bytecode(bytes_data, name=fullname, + bytecode_path=bytecode_path, + source_path=source_path) + if source_bytes is None: + source_bytes = self.get_data(source_path) + code_object = self.source_to_code(source_bytes, source_path) + _bootstrap._verbose_message('code object from {}', source_path) + if (not sys.dont_write_bytecode and bytecode_path is not None and + source_mtime is not None): + if hash_based: + if source_hash is None: + source_hash = _imp.source_hash(source_bytes) + data = _code_to_hash_pyc(code_object, source_hash, check_source) + else: + data = _code_to_timestamp_pyc(code_object, source_mtime, + len(source_bytes)) + try: + self._cache_bytecode(source_path, bytecode_path, data) + except NotImplementedError: + pass + return code_object + + +class FileLoader: + + """Base file loader class which implements the loader protocol methods that + require file system usage.""" + + def __init__(self, fullname, path): + """Cache the module name and the path to the file found by the + finder.""" + self.name = fullname + self.path = path + + def __eq__(self, other): + return (self.__class__ == other.__class__ and + self.__dict__ == other.__dict__) + + def __hash__(self): + return hash(self.name) ^ hash(self.path) + + @_check_name + def load_module(self, fullname): + """Load a module from a file. + + This method is deprecated. Use exec_module() instead. + + """ + # The only reason for this method is for the name check. + # Issue #14857: Avoid the zero-argument form of super so the implementation + # of that form can be updated without breaking the frozen module + return super(FileLoader, self).load_module(fullname) + + @_check_name + def get_filename(self, fullname): + """Return the path to the source file as found by the finder.""" + return self.path + + def get_data(self, path): + """Return the data from path as raw bytes.""" + with _io.FileIO(path, 'r') as file: + return file.read() + + # ResourceReader ABC API. + + @_check_name + def get_resource_reader(self, module): + if self.is_package(module): + return self + return None + + def open_resource(self, resource): + path = _path_join(_path_split(self.path)[0], resource) + return _io.FileIO(path, 'r') + + def resource_path(self, resource): + if not self.is_resource(resource): + raise FileNotFoundError + path = _path_join(_path_split(self.path)[0], resource) + return path + + def is_resource(self, name): + if path_sep in name: + return False + path = _path_join(_path_split(self.path)[0], name) + return _path_isfile(path) + + def contents(self): + return iter(_os.listdir(_path_split(self.path)[0])) + + +class SourceFileLoader(FileLoader, SourceLoader): + + """Concrete implementation of SourceLoader using the file system.""" + + def path_stats(self, path): + """Return the metadata for the path.""" + st = _path_stat(path) + return {'mtime': st.st_mtime, 'size': st.st_size} + + def _cache_bytecode(self, source_path, bytecode_path, data): + # Adapt between the two APIs + mode = _calc_mode(source_path) + return self.set_data(bytecode_path, data, _mode=mode) + + def set_data(self, path, data, *, _mode=0o666): + """Write bytes data to a file.""" + parent, filename = _path_split(path) + path_parts = [] + # Figure out what directories are missing. + while parent and not _path_isdir(parent): + parent, part = _path_split(parent) + path_parts.append(part) + # Create needed directories. + for part in reversed(path_parts): + parent = _path_join(parent, part) + try: + _os.mkdir(parent) + except FileExistsError: + # Probably another Python process already created the dir. + continue + except OSError as exc: + # Could be a permission error, read-only filesystem: just forget + # about writing the data. + _bootstrap._verbose_message('could not create {!r}: {!r}', + parent, exc) + return + try: + _write_atomic(path, data, _mode) + _bootstrap._verbose_message('created {!r}', path) + except OSError as exc: + # Same as above: just don't write the bytecode. + _bootstrap._verbose_message('could not create {!r}: {!r}', path, + exc) + + +class SourcelessFileLoader(FileLoader, _LoaderBasics): + + """Loader which handles sourceless file imports.""" + + def get_code(self, fullname): + path = self.get_filename(fullname) + data = self.get_data(path) + # Call _classify_pyc to do basic validation of the pyc but ignore the + # result. There's no source to check against. + exc_details = { + 'name': fullname, + 'path': path, + } + _classify_pyc(data, fullname, exc_details) + return _compile_bytecode( + memoryview(data)[16:], + name=fullname, + bytecode_path=path, + ) + + def get_source(self, fullname): + """Return None as there is no source code.""" + return None + + +# Filled in by _setup(). +EXTENSION_SUFFIXES = [] + + +class ExtensionFileLoader(FileLoader, _LoaderBasics): + + """Loader for extension modules. + + The constructor is designed to work with FileFinder. + + """ + + def __init__(self, name, path): + self.name = name + self.path = path + + def __eq__(self, other): + return (self.__class__ == other.__class__ and + self.__dict__ == other.__dict__) + + def __hash__(self): + return hash(self.name) ^ hash(self.path) + + def create_module(self, spec): + """Create an unitialized extension module""" + module = _bootstrap._call_with_frames_removed( + _imp.create_dynamic, spec) + _bootstrap._verbose_message('extension module {!r} loaded from {!r}', + spec.name, self.path) + return module + + def exec_module(self, module): + """Initialize an extension module""" + _bootstrap._call_with_frames_removed(_imp.exec_dynamic, module) + _bootstrap._verbose_message('extension module {!r} executed from {!r}', + self.name, self.path) + + def is_package(self, fullname): + """Return True if the extension module is a package.""" + file_name = _path_split(self.path)[1] + return any(file_name == '__init__' + suffix + for suffix in EXTENSION_SUFFIXES) + + def get_code(self, fullname): + """Return None as an extension module cannot create a code object.""" + return None + + def get_source(self, fullname): + """Return None as extension modules have no source code.""" + return None + + @_check_name + def get_filename(self, fullname): + """Return the path to the source file as found by the finder.""" + return self.path + + +class _NamespacePath: + """Represents a namespace package's path. It uses the module name + to find its parent module, and from there it looks up the parent's + __path__. When this changes, the module's own path is recomputed, + using path_finder. For top-level modules, the parent module's path + is sys.path.""" + + def __init__(self, name, path, path_finder): + self._name = name + self._path = path + self._last_parent_path = tuple(self._get_parent_path()) + self._path_finder = path_finder + + def _find_parent_path_names(self): + """Returns a tuple of (parent-module-name, parent-path-attr-name)""" + parent, dot, me = self._name.rpartition('.') + if dot == '': + # This is a top-level module. sys.path contains the parent path. + return 'sys', 'path' + # Not a top-level module. parent-module.__path__ contains the + # parent path. + return parent, '__path__' + + def _get_parent_path(self): + parent_module_name, path_attr_name = self._find_parent_path_names() + return getattr(sys.modules[parent_module_name], path_attr_name) + + def _recalculate(self): + # If the parent's path has changed, recalculate _path + parent_path = tuple(self._get_parent_path()) # Make a copy + if parent_path != self._last_parent_path: + spec = self._path_finder(self._name, parent_path) + # Note that no changes are made if a loader is returned, but we + # do remember the new parent path + if spec is not None and spec.loader is None: + if spec.submodule_search_locations: + self._path = spec.submodule_search_locations + self._last_parent_path = parent_path # Save the copy + return self._path + + def __iter__(self): + return iter(self._recalculate()) + + def __getitem__(self, index): + return self._recalculate()[index] + + def __setitem__(self, index, path): + self._path[index] = path + + def __len__(self): + return len(self._recalculate()) + + def __repr__(self): + return '_NamespacePath({!r})'.format(self._path) + + def __contains__(self, item): + return item in self._recalculate() + + def append(self, item): + self._path.append(item) + + +# We use this exclusively in module_from_spec() for backward-compatibility. +class _NamespaceLoader: + def __init__(self, name, path, path_finder): + self._path = _NamespacePath(name, path, path_finder) + + @classmethod + def module_repr(cls, module): + """Return repr for the module. + + The method is deprecated. The import machinery does the job itself. + + """ + return ''.format(module.__name__) + + def is_package(self, fullname): + return True + + def get_source(self, fullname): + return '' + + def get_code(self, fullname): + return compile('', '', 'exec', dont_inherit=True) + + def create_module(self, spec): + """Use default semantics for module creation.""" + + def exec_module(self, module): + pass + + def load_module(self, fullname): + """Load a namespace module. + + This method is deprecated. Use exec_module() instead. + + """ + # The import system never calls this method. + _bootstrap._verbose_message('namespace module loaded with path {!r}', + self._path) + return _bootstrap._load_module_shim(self, fullname) + + +# Finders ##################################################################### + +class PathFinder: + + """Meta path finder for sys.path and package __path__ attributes.""" + + @classmethod + def invalidate_caches(cls): + """Call the invalidate_caches() method on all path entry finders + stored in sys.path_importer_caches (where implemented).""" + for name, finder in list(sys.path_importer_cache.items()): + if finder is None: + del sys.path_importer_cache[name] + elif hasattr(finder, 'invalidate_caches'): + finder.invalidate_caches() + + @classmethod + def _path_hooks(cls, path): + """Search sys.path_hooks for a finder for 'path'.""" + if sys.path_hooks is not None and not sys.path_hooks: + _warnings.warn('sys.path_hooks is empty', ImportWarning) + for hook in sys.path_hooks: + try: + return hook(path) + except ImportError: + continue + else: + return None + + @classmethod + def _path_importer_cache(cls, path): + """Get the finder for the path entry from sys.path_importer_cache. + + If the path entry is not in the cache, find the appropriate finder + and cache it. If no finder is available, store None. + + """ + if path == '': + try: + path = _os.getcwd() + except FileNotFoundError: + # Don't cache the failure as the cwd can easily change to + # a valid directory later on. + return None + try: + finder = sys.path_importer_cache[path] + except KeyError: + finder = cls._path_hooks(path) + sys.path_importer_cache[path] = finder + return finder + + @classmethod + def _legacy_get_spec(cls, fullname, finder): + # This would be a good place for a DeprecationWarning if + # we ended up going that route. + if hasattr(finder, 'find_loader'): + loader, portions = finder.find_loader(fullname) + else: + loader = finder.find_module(fullname) + portions = [] + if loader is not None: + return _bootstrap.spec_from_loader(fullname, loader) + spec = _bootstrap.ModuleSpec(fullname, None) + spec.submodule_search_locations = portions + return spec + + @classmethod + def _get_spec(cls, fullname, path, target=None): + """Find the loader or namespace_path for this module/package name.""" + # If this ends up being a namespace package, namespace_path is + # the list of paths that will become its __path__ + namespace_path = [] + for entry in path: + if not isinstance(entry, (str, bytes)): + continue + finder = cls._path_importer_cache(entry) + if finder is not None: + if hasattr(finder, 'find_spec'): + spec = finder.find_spec(fullname, target) + else: + spec = cls._legacy_get_spec(fullname, finder) + if spec is None: + continue + if spec.loader is not None: + return spec + portions = spec.submodule_search_locations + if portions is None: + raise ImportError('spec missing loader') + # This is possibly part of a namespace package. + # Remember these path entries (if any) for when we + # create a namespace package, and continue iterating + # on path. + namespace_path.extend(portions) + else: + spec = _bootstrap.ModuleSpec(fullname, None) + spec.submodule_search_locations = namespace_path + return spec + + @classmethod + def find_spec(cls, fullname, path=None, target=None): + """Try to find a spec for 'fullname' on sys.path or 'path'. + + The search is based on sys.path_hooks and sys.path_importer_cache. + """ + if path is None: + path = sys.path + spec = cls._get_spec(fullname, path, target) + if spec is None: + return None + elif spec.loader is None: + namespace_path = spec.submodule_search_locations + if namespace_path: + # We found at least one namespace path. Return a spec which + # can create the namespace package. + spec.origin = None + spec.submodule_search_locations = _NamespacePath(fullname, namespace_path, cls._get_spec) + return spec + else: + return None + else: + return spec + + @classmethod + def find_module(cls, fullname, path=None): + """find the module on sys.path or 'path' based on sys.path_hooks and + sys.path_importer_cache. + + This method is deprecated. Use find_spec() instead. + + """ + spec = cls.find_spec(fullname, path) + if spec is None: + return None + return spec.loader + + +class FileFinder: + + """File-based finder. + + Interactions with the file system are cached for performance, being + refreshed when the directory the finder is handling has been modified. + + """ + + def __init__(self, path, *loader_details): + """Initialize with the path to search on and a variable number of + 2-tuples containing the loader and the file suffixes the loader + recognizes.""" + loaders = [] + for loader, suffixes in loader_details: + loaders.extend((suffix, loader) for suffix in suffixes) + self._loaders = loaders + # Base (directory) path + self.path = path or '.' + self._path_mtime = -1 + self._path_cache = set() + self._relaxed_path_cache = set() + + def invalidate_caches(self): + """Invalidate the directory mtime.""" + self._path_mtime = -1 + + find_module = _find_module_shim + + def find_loader(self, fullname): + """Try to find a loader for the specified module, or the namespace + package portions. Returns (loader, list-of-portions). + + This method is deprecated. Use find_spec() instead. + + """ + spec = self.find_spec(fullname) + if spec is None: + return None, [] + return spec.loader, spec.submodule_search_locations or [] + + def _get_spec(self, loader_class, fullname, path, smsl, target): + loader = loader_class(fullname, path) + return spec_from_file_location(fullname, path, loader=loader, + submodule_search_locations=smsl) + + def find_spec(self, fullname, target=None): + """Try to find a spec for the specified module. + + Returns the matching spec, or None if not found. + """ + is_namespace = False + tail_module = fullname.rpartition('.')[2] + try: + mtime = _path_stat(self.path or _os.getcwd()).st_mtime + except OSError: + mtime = -1 + if mtime != self._path_mtime: + self._fill_cache() + self._path_mtime = mtime + # tail_module keeps the original casing, for __file__ and friends + if _relax_case(): + cache = self._relaxed_path_cache + cache_module = tail_module.lower() + else: + cache = self._path_cache + cache_module = tail_module + # Check if the module is the name of a directory (and thus a package). + if cache_module in cache: + base_path = _path_join(self.path, tail_module) + for suffix, loader_class in self._loaders: + init_filename = '__init__' + suffix + full_path = _path_join(base_path, init_filename) + if _path_isfile(full_path): + return self._get_spec(loader_class, fullname, full_path, [base_path], target) + else: + # If a namespace package, return the path if we don't + # find a module in the next section. + is_namespace = _path_isdir(base_path) + # Check for a file w/ a proper suffix exists. + for suffix, loader_class in self._loaders: + full_path = _path_join(self.path, tail_module + suffix) + _bootstrap._verbose_message('trying {}', full_path, verbosity=2) + if cache_module + suffix in cache: + if _path_isfile(full_path): + return self._get_spec(loader_class, fullname, full_path, + None, target) + if is_namespace: + _bootstrap._verbose_message('possible namespace for {}', base_path) + spec = _bootstrap.ModuleSpec(fullname, None) + spec.submodule_search_locations = [base_path] + return spec + return None + + def _fill_cache(self): + """Fill the cache of potential modules and packages for this directory.""" + path = self.path + try: + contents = _os.listdir(path or _os.getcwd()) + except (FileNotFoundError, PermissionError, NotADirectoryError): + # Directory has either been removed, turned into a file, or made + # unreadable. + contents = [] + # We store two cached versions, to handle runtime changes of the + # PYTHONCASEOK environment variable. + if not sys.platform.startswith('win'): + self._path_cache = set(contents) + else: + # Windows users can import modules with case-insensitive file + # suffixes (for legacy reasons). Make the suffix lowercase here + # so it's done once instead of for every import. This is safe as + # the specified suffixes to check against are always specified in a + # case-sensitive manner. + lower_suffix_contents = set() + for item in contents: + name, dot, suffix = item.partition('.') + if dot: + new_name = '{}.{}'.format(name, suffix.lower()) + else: + new_name = name + lower_suffix_contents.add(new_name) + self._path_cache = lower_suffix_contents + if sys.platform.startswith(_CASE_INSENSITIVE_PLATFORMS): + self._relaxed_path_cache = {fn.lower() for fn in contents} + + @classmethod + def path_hook(cls, *loader_details): + """A class method which returns a closure to use on sys.path_hook + which will return an instance using the specified loaders and the path + called on the closure. + + If the path called on the closure is not a directory, ImportError is + raised. + + """ + def path_hook_for_FileFinder(path): + """Path hook for importlib.machinery.FileFinder.""" + if not _path_isdir(path): + raise ImportError('only directories are supported', path=path) + return cls(path, *loader_details) + + return path_hook_for_FileFinder + + def __repr__(self): + return 'FileFinder({!r})'.format(self.path) + + +# Import setup ############################################################### + +def _fix_up_module(ns, name, pathname, cpathname=None): + # This function is used by PyImport_ExecCodeModuleObject(). + loader = ns.get('__loader__') + spec = ns.get('__spec__') + if not loader: + if spec: + loader = spec.loader + elif pathname == cpathname: + loader = SourcelessFileLoader(name, pathname) + else: + loader = SourceFileLoader(name, pathname) + if not spec: + spec = spec_from_file_location(name, pathname, loader=loader) + try: + ns['__spec__'] = spec + ns['__loader__'] = loader + ns['__file__'] = pathname + ns['__cached__'] = cpathname + except Exception: + # Not important enough to report. + pass + + +def _get_supported_file_loaders(): + """Returns a list of file-based module loaders. + + Each item is a tuple (loader, suffixes). + """ + extensions = ExtensionFileLoader, _imp.extension_suffixes() + source = SourceFileLoader, SOURCE_SUFFIXES + bytecode = SourcelessFileLoader, BYTECODE_SUFFIXES + return [extensions, source, bytecode] + + +def _setup(_bootstrap_module): + """Setup the path-based importers for importlib by importing needed + built-in modules and injecting them into the global namespace. + + Other components are extracted from the core bootstrap module. + + """ + global sys, _imp, _bootstrap + _bootstrap = _bootstrap_module + sys = _bootstrap.sys + _imp = _bootstrap._imp + + # Directly load built-in modules needed during bootstrap. + self_module = sys.modules[__name__] + for builtin_name in ('_io', '_warnings', 'builtins', 'marshal'): + if builtin_name not in sys.modules: + builtin_module = _bootstrap._builtin_from_name(builtin_name) + else: + builtin_module = sys.modules[builtin_name] + setattr(self_module, builtin_name, builtin_module) + + # Directly load the os module (needed during bootstrap). + os_details = ('posix', ['/']), ('nt', ['\\', '/']) + for builtin_os, path_separators in os_details: + # Assumption made in _path_join() + assert all(len(sep) == 1 for sep in path_separators) + path_sep = path_separators[0] + if builtin_os in sys.modules: + os_module = sys.modules[builtin_os] + break + else: + try: + os_module = _bootstrap._builtin_from_name(builtin_os) + break + except ImportError: + continue + else: + raise ImportError('importlib requires posix or nt') + setattr(self_module, '_os', os_module) + setattr(self_module, 'path_sep', path_sep) + setattr(self_module, 'path_separators', ''.join(path_separators)) + setattr(self_module, '_pathseps_with_colon', {f':{s}' for s in path_separators}) + + # Directly load the _thread module (needed during bootstrap). + thread_module = _bootstrap._builtin_from_name('_thread') + setattr(self_module, '_thread', thread_module) + + # Directly load the _weakref module (needed during bootstrap). + weakref_module = _bootstrap._builtin_from_name('_weakref') + setattr(self_module, '_weakref', weakref_module) + + # Directly load the winreg module (needed during bootstrap). + if builtin_os == 'nt': + winreg_module = _bootstrap._builtin_from_name('winreg') + setattr(self_module, '_winreg', winreg_module) + + # Constants + setattr(self_module, '_relax_case', _make_relax_case()) + EXTENSION_SUFFIXES.extend(_imp.extension_suffixes()) + if builtin_os == 'nt': + SOURCE_SUFFIXES.append('.pyw') + if '_d.pyd' in EXTENSION_SUFFIXES: + WindowsRegistryFinder.DEBUG_BUILD = True + + +def _install(_bootstrap_module): + """Install the path-based import components.""" + _setup(_bootstrap_module) + supported_loaders = _get_supported_file_loaders() + sys.path_hooks.extend([FileFinder.path_hook(*supported_loaders)]) + sys.meta_path.append(PathFinder) diff --git a/vm/src/frozen.rs b/vm/src/frozen.rs index 41848001d7..92f272e1a7 100644 --- a/vm/src/frozen.rs +++ b/vm/src/frozen.rs @@ -16,9 +16,25 @@ const IMPORTLIB_BOOTSTRAP: &'static str = include_str!(concat!( "_bootstrap.py" )); +const IMPORTLIB_BOOTSTRAP_EXTERNAL: &'static str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/", + "..", + "/", + "Lib", + "/", + "importlib", + "/", + "_bootstrap_external.py" +)); + pub fn get_module_inits() -> HashMap { let mut modules = HashMap::new(); modules.insert("__hello__".to_string(), HELLO); modules.insert("_frozen_importlib".to_string(), IMPORTLIB_BOOTSTRAP); + modules.insert( + "_frozen_importlib_external".to_string(), + IMPORTLIB_BOOTSTRAP_EXTERNAL, + ); modules } From fbaff7fd50aaacdfa90a8b2bd7f4eba15ccf665a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 19:16:12 +0300 Subject: [PATCH 725/884] Install external importers on init_importlib --- vm/src/import.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index 33335583ef..c91371de7d 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -14,8 +14,10 @@ use crate::vm::VirtualMachine; pub fn init_importlib(vm: &VirtualMachine) -> PyResult { let importlib = import_frozen(vm, "_frozen_importlib")?; let impmod = import_builtin(vm, "_imp")?; - let install = vm.get_attribute(importlib, "_install")?; - vm.invoke(install, vec![vm.sys_module.clone(), impmod]) + let install = vm.get_attribute(importlib.clone(), "_install")?; + vm.invoke(install, vec![vm.sys_module.clone(), impmod])?; + let install_external = vm.get_attribute(importlib, "_install_external_importers")?; + vm.invoke(install_external, vec![]) } fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { From a57f38b07f4d8e2728ed0cd42e73b2d232730545 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 19:26:59 +0300 Subject: [PATCH 726/884] Rename builtin io to _io --- Lib/io.py | 1 + vm/src/stdlib/io.rs | 2 +- vm/src/stdlib/mod.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 Lib/io.py diff --git a/Lib/io.py b/Lib/io.py new file mode 100644 index 0000000000..5536a308c3 --- /dev/null +++ b/Lib/io.py @@ -0,0 +1 @@ +from _io import * diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index da8bedf537..2dafc71377 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -471,7 +471,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "getvalue" => ctx.new_rustfunc(bytes_io_getvalue) }); - py_module!(vm, "io", { + py_module!(vm, "_io", { "open" => ctx.new_rustfunc(io_open), "IOBase" => io_base, "RawIOBase" => raw_io_base, diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 7c72a1b61d..75e554cd63 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -61,7 +61,7 @@ pub fn get_module_inits() -> HashMap { // disable some modules on WASM #[cfg(not(target_arch = "wasm32"))] { - modules.insert("io".to_string(), Box::new(io::make_module)); + modules.insert("_io".to_string(), Box::new(io::make_module)); modules.insert("_os".to_string(), Box::new(os::make_module)); modules.insert("socket".to_string(), Box::new(socket::make_module)); } From b0cccf35adc61b222a8ad327512e301435f0f1e9 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 19:32:02 +0300 Subject: [PATCH 727/884] Change os_details --- Lib/importlib/_bootstrap_external.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index f8ff5f4f2c..11b8723a45 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -1565,7 +1565,7 @@ def _setup(_bootstrap_module): setattr(self_module, builtin_name, builtin_module) # Directly load the os module (needed during bootstrap). - os_details = ('posix', ['/']), ('nt', ['\\', '/']) + os_details = ('_os', ['/']), ('_os', ['\\', '/']) # Changed by palaviv to fit RustPython!!! for builtin_os, path_separators in os_details: # Assumption made in _path_join() assert all(len(sep) == 1 for sep in path_separators) From 58d9d9deebb2ae673eb1a18dbd887f2995614a72 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 19:32:58 +0300 Subject: [PATCH 728/884] Add sys.path_hooks --- vm/src/sysmodule.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 594a4f8a65..a16b80745b 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -243,6 +243,7 @@ settrace() -- set the global debug tracing function "warnoptions" => ctx.new_list(vec![]), "platform" => ctx.new_str(platform), "meta_path" => ctx.new_list(vec![]), + "path_hooks" => ctx.new_list(vec![]), }); modules.set_item("sys", module.clone(), vm).unwrap(); From 7f61125866487a8426cf3dca88d65d7e2e075ba8 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 20:00:34 +0300 Subject: [PATCH 729/884] Use _bootstrap.py __import__ --- vm/src/builtins.rs | 22 +--------------------- vm/src/import.rs | 7 +++++-- vm/src/vm.rs | 3 +++ 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 9afcd0e2ca..28ab42bd20 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -732,27 +732,7 @@ fn builtin_sum(iterable: PyIterable, start: OptionalArg, vm: &VirtualMachine) -> // Should be renamed to builtin___import__? fn builtin_import(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(name, Some(vm.ctx.str_type()))], - optional = [ - (_globals, Some(vm.ctx.dict_type())), - (_locals, Some(vm.ctx.dict_type())) - ] - ); - let current_path = { - match vm.current_frame() { - Some(frame) => { - let mut source_pathbuf = PathBuf::from(&frame.code.source_path); - source_pathbuf.pop(); - source_pathbuf - } - None => PathBuf::new(), - } - }; - - import_module(vm, current_path, &objstr::get_value(name)) + vm.invoke(vm.import_func.borrow().clone(), args) } // builtin_vars diff --git a/vm/src/import.rs b/vm/src/import.rs index c91371de7d..5c21123c01 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -16,8 +16,11 @@ pub fn init_importlib(vm: &VirtualMachine) -> PyResult { let impmod = import_builtin(vm, "_imp")?; let install = vm.get_attribute(importlib.clone(), "_install")?; vm.invoke(install, vec![vm.sys_module.clone(), impmod])?; - let install_external = vm.get_attribute(importlib, "_install_external_importers")?; - vm.invoke(install_external, vec![]) + vm.import_func + .replace(vm.get_attribute(importlib.clone(), "__import__")?); + let install_external = vm.get_attribute(importlib.clone(), "_install_external_importers")?; + vm.invoke(install_external, vec![])?; + Ok(vm.get_none()) } fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index eeba4a10d3..7efbd1c195 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -55,6 +55,7 @@ pub struct VirtualMachine { pub wasm_id: Option, pub exceptions: RefCell>, pub frozen: RefCell>, + pub import_func: RefCell, } impl VirtualMachine { @@ -68,6 +69,7 @@ impl VirtualMachine { let stdlib_inits = RefCell::new(stdlib::get_module_inits()); let frozen = RefCell::new(frozen::get_module_inits()); + let import_func = RefCell::new(ctx.none()); let vm = VirtualMachine { builtins: builtins.clone(), sys_module: sysmod.clone(), @@ -77,6 +79,7 @@ impl VirtualMachine { wasm_id: None, exceptions: RefCell::new(vec![]), frozen, + import_func, }; builtins::make_module(&vm, builtins.clone()); From 2817214c88ffc701e58d68f7522837d26ec950b3 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 20:20:12 +0300 Subject: [PATCH 730/884] Remove unused imports --- vm/src/builtins.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 28ab42bd20..04d83de4eb 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -4,13 +4,11 @@ use std::char; use std::io::{self, Write}; -use std::path::PathBuf; use num_bigint::Sign; use num_traits::{Signed, Zero}; use crate::compile; -use crate::import::import_module; use crate::obj::objbool; use crate::obj::objcode::PyCodeRef; use crate::obj::objdict::PyDictRef; From 0e76dbb749d4256fa1837bbe622ed15903f3297b Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 21:28:19 +0300 Subject: [PATCH 731/884] Add needed methods to _thread --- vm/src/stdlib/thread.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/thread.rs b/vm/src/stdlib/thread.rs index 8de881893a..6155365490 100644 --- a/vm/src/stdlib/thread.rs +++ b/vm/src/stdlib/thread.rs @@ -2,8 +2,10 @@ /// support threading use super::super::pyobject::PyObjectRef; use crate::function::PyFuncArgs; +use crate::import; use crate::pyobject::PyResult; use crate::vm::VirtualMachine; +use std::path::PathBuf; fn rlock_acquire(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) @@ -11,20 +13,49 @@ fn rlock_acquire(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { fn rlock_release(_zelf: PyObjectRef, _vm: &VirtualMachine) {} +fn rlock_enter(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!(vm, args, required = [(instance, None)]); + Ok(instance.clone()) +} + +fn rlock_exit(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + // The context manager protocol requires these, but we don't use them + required = [ + (_instance, None), + (_exception_type, None), + (_exception_value, None), + (_traceback, None) + ] + ); + Ok(vm.get_none()) +} + fn get_ident(_vm: &VirtualMachine) -> u32 { 1 } +fn allocate_lock(vm: &VirtualMachine) -> PyResult { + let module = import::import_module(vm, PathBuf::default(), "_thread")?; + let lock_class = vm.get_attribute(module.clone(), "RLock")?; + vm.invoke(lock_class, vec![]) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let rlock_type = py_class!(ctx, "_thread.RLock", ctx.object(), { "acquire" => ctx.new_rustfunc(rlock_acquire), "release" => ctx.new_rustfunc(rlock_release), + "__enter__" => ctx.new_rustfunc(rlock_enter), + "__exit__" => ctx.new_rustfunc(rlock_exit), }); py_module!(vm, "_thread", { "RLock" => rlock_type, - "get_ident" => ctx.new_rustfunc(get_ident) + "get_ident" => ctx.new_rustfunc(get_ident), + "allocate_lock" => ctx.new_rustfunc(allocate_lock), }) } From 5c53e585470c3f2ad0edfa0e49b426978b353e42 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 7 Jun 2019 21:58:43 +0300 Subject: [PATCH 732/884] Print frozen import file name in stacktrace --- vm/src/import.rs | 13 +++++++++---- vm/src/stdlib/imp.rs | 20 +++++++++++--------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index 5c21123c01..554d5e15ee 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -23,15 +23,20 @@ pub fn init_importlib(vm: &VirtualMachine) -> PyResult { Ok(vm.get_none()) } -fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { +pub fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { if let Some(frozen) = vm.frozen.borrow().get(module_name) { - import_file(vm, module_name, "frozen".to_string(), frozen.to_string()) + import_file( + vm, + module_name, + format!("frozen {}", module_name), + frozen.to_string(), + ) } else { Err(vm.new_import_error(format!("Cannot import frozen module {}", module_name))) } } -fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult { +pub fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult { let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); if let Some(make_module_func) = vm.stdlib_inits.borrow().get(module_name) { let module = make_module_func(vm); @@ -85,7 +90,7 @@ pub fn import_file( let attrs = vm.ctx.new_dict(); attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; - if file_path != "frozen".to_string() { + if !file_path.starts_with("frozen") { // TODO: Should be removed after precompiling frozen modules. attrs.set_item("__file__", vm.new_str(file_path), vm)?; } diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index ae40c16c53..fbf0247a05 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -1,5 +1,5 @@ use crate::compile; -use crate::import::import_file; +use crate::import; use crate::obj::objcode::PyCodeRef; use crate::obj::objmodule::PyModuleRef; use crate::obj::objstr; @@ -56,20 +56,22 @@ fn imp_exec_builtin(_mod: PyModuleRef, _vm: &VirtualMachine) -> i32 { } fn imp_get_frozen_object(name: PyStringRef, vm: &VirtualMachine) -> PyResult { - if let Some(frozen) = vm.frozen.borrow().get(name.as_str()) { - compile::compile(vm, frozen, &compile::Mode::Exec, "frozen".to_string()) - .map_err(|err| vm.new_syntax_error(&err)) + let name_str = name.as_str(); + if let Some(frozen) = vm.frozen.borrow().get(name_str) { + compile::compile( + vm, + frozen, + &compile::Mode::Exec, + format!("frozen {}", name_str), + ) + .map_err(|err| vm.new_syntax_error(&err)) } else { Err(vm.new_import_error(format!("No such frozen object named {}", name.as_str()))) } } fn imp_init_frozen(name: PyStringRef, vm: &VirtualMachine) -> PyResult { - if let Some(frozen) = vm.frozen.borrow().get(name.as_str()) { - import_file(vm, name.as_str(), "frozen".to_string(), frozen.to_string()) - } else { - Err(vm.new_import_error(format!("No such frozen object named {}", name.as_str()))) - } + import::import_frozen(vm, name.as_str()) } fn imp_is_frozen_package(_name: PyStringRef, _vm: &VirtualMachine) -> bool { From 6615e811c088c0c8369cf8e2fb98f0e7f27ec716 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 8 Jun 2019 09:58:50 +0300 Subject: [PATCH 733/884] Collapse concat --- vm/src/frozen.rs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/vm/src/frozen.rs b/vm/src/frozen.rs index 92f272e1a7..42b42c65b2 100644 --- a/vm/src/frozen.rs +++ b/vm/src/frozen.rs @@ -6,26 +6,11 @@ print(\"Hello world!\") const IMPORTLIB_BOOTSTRAP: &'static str = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), - "/", - "..", - "/", - "Lib", - "/", - "importlib", - "/", - "_bootstrap.py" + "/../Lib/importlib/_bootstrap.py" )); - const IMPORTLIB_BOOTSTRAP_EXTERNAL: &'static str = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), - "/", - "..", - "/", - "Lib", - "/", - "importlib", - "/", - "_bootstrap_external.py" + "/../Lib/importlib/_bootstrap_external.py" )); pub fn get_module_inits() -> HashMap { From fe0284aa05ae1de55bb49b808f4f4290bb0f90fb Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 8 Jun 2019 10:49:02 +0300 Subject: [PATCH 734/884] Add new to objmodule and change __name__ to property --- vm/src/obj/objmodule.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs index ae1563d3a6..c6b1f3bd02 100644 --- a/vm/src/obj/objmodule.rs +++ b/vm/src/obj/objmodule.rs @@ -1,3 +1,5 @@ +use crate::obj::objproperty::PropertyBuilder; +use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyContext, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; @@ -15,6 +17,12 @@ impl PyValue for PyModule { } impl PyModuleRef { + fn new(cls: PyClassRef, name: PyStringRef, vm: &VirtualMachine) -> PyResult { + PyModule { + name: name.as_str().to_string(), + } + .into_ref_with_type(vm, cls) + } fn dir(self: PyModuleRef, vm: &VirtualMachine) -> PyResult { if let Some(dict) = &self.into_object().dict { let keys = dict.into_iter().map(|(k, _v)| k.clone()).collect(); @@ -24,7 +32,7 @@ impl PyModuleRef { } } - fn name(self: PyModuleRef, _vm: &VirtualMachine) -> String { + fn name(self, _vm: &VirtualMachine) -> String { self.name.clone() } } @@ -32,6 +40,9 @@ impl PyModuleRef { pub fn init(context: &PyContext) { extend_class!(&context, &context.module_type, { "__dir__" => context.new_rustfunc(PyModuleRef::dir), - "__name__" => context.new_rustfunc(PyModuleRef::name) + "__name__" => PropertyBuilder::new(context) + .add_getter(PyModuleRef::name) + .create(), + "__new__" => context.new_rustfunc(PyModuleRef::new), }); } From 375790e142af29a3bc501ea2d63e436234fb594c Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 8 Jun 2019 11:05:45 +0300 Subject: [PATCH 735/884] objmodule should have a dict --- vm/src/obj/objmodule.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs index c6b1f3bd02..69f5c8b1f4 100644 --- a/vm/src/obj/objmodule.rs +++ b/vm/src/obj/objmodule.rs @@ -11,6 +11,8 @@ pub struct PyModule { pub type PyModuleRef = PyRef; impl PyValue for PyModule { + const HAVE_DICT: bool = true; + fn class(vm: &VirtualMachine) -> PyClassRef { vm.ctx.module_type() } From c8248c321101067ea3c272bc3fee90dd0ff020be Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 9 Jun 2019 10:49:23 +0300 Subject: [PATCH 736/884] Expose __name__ in __dict__ --- vm/src/obj/objmodule.rs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs index 69f5c8b1f4..183a096efc 100644 --- a/vm/src/obj/objmodule.rs +++ b/vm/src/obj/objmodule.rs @@ -1,7 +1,8 @@ +use crate::obj::objdict::PyDictRef; use crate::obj::objproperty::PropertyBuilder; use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{PyContext, PyRef, PyResult, PyValue}; +use crate::pyobject::{ItemProtocol, PyContext, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; #[derive(Debug)] @@ -25,6 +26,7 @@ impl PyModuleRef { } .into_ref_with_type(vm, cls) } + fn dir(self: PyModuleRef, vm: &VirtualMachine) -> PyResult { if let Some(dict) = &self.into_object().dict { let keys = dict.into_iter().map(|(k, _v)| k.clone()).collect(); @@ -34,6 +36,17 @@ impl PyModuleRef { } } + fn dict(self, vm: &VirtualMachine) -> PyResult { + let name_obj = vm.new_str(self.name.clone()); + if let Some(ref dict) = &self.into_object().dict { + let mod_dict = dict.clone(); + mod_dict.set_item("__name__", name_obj, vm)?; + Ok(mod_dict) + } else { + panic!("Modules should definitely have a dict."); + } + } + fn name(self, _vm: &VirtualMachine) -> String { self.name.clone() } @@ -45,6 +58,10 @@ pub fn init(context: &PyContext) { "__name__" => PropertyBuilder::new(context) .add_getter(PyModuleRef::name) .create(), + "__dict__" => + PropertyBuilder::new(context) + .add_getter(PyModuleRef::dict) + .create(), "__new__" => context.new_rustfunc(PyModuleRef::new), }); } From 5584733cdaf9b91fb6bfcfe95816f9d6eec5272e Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 9 Jun 2019 10:56:52 +0300 Subject: [PATCH 737/884] Add sys.path_importer_cache --- vm/src/sysmodule.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index a16b80745b..f1860c52be 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -244,6 +244,7 @@ settrace() -- set the global debug tracing function "platform" => ctx.new_str(platform), "meta_path" => ctx.new_list(vec![]), "path_hooks" => ctx.new_list(vec![]), + "path_importer_cache" => ctx.new_dict(), }); modules.set_item("sys", module.clone(), vm).unwrap(); From d9d0ea18341f282a26f76a5a515dd0ec3d1cd51d Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 9 Jun 2019 22:15:00 +0300 Subject: [PATCH 738/884] Fix os to _os in class --- vm/src/stdlib/os.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 0893c1e6da..14d720df6b 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -234,7 +234,7 @@ type DirEntryRef = PyRef; impl PyValue for DirEntry { fn class(vm: &VirtualMachine) -> PyClassRef { - vm.class("os", "DirEntry") + vm.class("_os", "DirEntry") } } @@ -316,7 +316,7 @@ struct ScandirIterator { impl PyValue for ScandirIterator { fn class(vm: &VirtualMachine) -> PyClassRef { - vm.class("os", "ScandirIter") + vm.class("_os", "ScandirIter") } } @@ -366,7 +366,7 @@ struct StatResult { impl PyValue for StatResult { fn class(vm: &VirtualMachine) -> PyClassRef { - vm.class("os", "stat_result") + vm.class("_os", "stat_result") } } From 5df05d4f95f26cf4edfa91b75d0cd03a6db79c1e Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 9 Jun 2019 22:59:24 +0300 Subject: [PATCH 739/884] Add script dir to sys.path --- src/main.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main.rs b/src/main.rs index 9b0711ce45..84d2af51f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -129,6 +129,10 @@ fn run_script(vm: &VirtualMachine, script_file: &str) -> PyResult { std::process::exit(1); }; + let dir = file_path.parent().unwrap().to_str().unwrap().to_string(); + let sys_path = vm.get_attribute(vm.sys_module.clone(), "path").unwrap(); + vm.call_method(&sys_path, "insert", vec![vm.new_int(0), vm.new_str(dir)])?; + match util::read_file(&file_path) { Ok(source) => _run_string(vm, &source, file_path.to_str().unwrap().to_string()), Err(err) => { From b567464378e0d4dafe608020d815e23676134e4c Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 9 Jun 2019 22:59:53 +0300 Subject: [PATCH 740/884] Add ModuleNotFoundError to builtins --- vm/src/builtins.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 04d83de4eb..cfdd6732b3 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -838,6 +838,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone(), "KeyError" => ctx.exceptions.key_error.clone(), "OSError" => ctx.exceptions.os_error.clone(), + "ModuleNotFoundError" => ctx.exceptions.module_not_found_error.clone(), // Warnings "Warning" => ctx.exceptions.warning.clone(), From 10828e01fb5f5e86be6a80a789cc8bee2ecd6e17 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 9 Jun 2019 23:06:09 +0300 Subject: [PATCH 741/884] Set sys.pycache_prefix to None --- vm/src/sysmodule.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index f1860c52be..977f2dc93c 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -245,6 +245,7 @@ settrace() -- set the global debug tracing function "meta_path" => ctx.new_list(vec![]), "path_hooks" => ctx.new_list(vec![]), "path_importer_cache" => ctx.new_dict(), + "pycache_prefix" => vm.get_none(), }); modules.set_item("sys", module.clone(), vm).unwrap(); From 8dec522f96c9fabdea077d4bd7e825cddb9c9c69 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 9 Jun 2019 23:17:57 +0300 Subject: [PATCH 742/884] compile source may be bytes --- vm/src/builtins.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index cfdd6732b3..d41eb58746 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -4,12 +4,14 @@ use std::char; use std::io::{self, Write}; +use std::str; use num_bigint::Sign; use num_traits::{Signed, Zero}; use crate::compile; use crate::obj::objbool; +use crate::obj::objbytes::PyBytesRef; use crate::obj::objcode::PyCodeRef; use crate::obj::objdict::PyDictRef; use crate::obj::objint::{self, PyIntRef}; @@ -20,7 +22,7 @@ use crate::obj::objtype::{self, PyClassRef}; use crate::frame::Scope; use crate::function::{single_or_tuple_any, Args, KwArgs, OptionalArg, PyFuncArgs}; use crate::pyobject::{ - IdProtocol, IntoPyObject, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, + Either, IdProtocol, IntoPyObject, ItemProtocol, PyIterable, PyObjectRef, PyResult, PyValue, TryFromObject, TypeProtocol, }; use crate::vm::VirtualMachine; @@ -78,13 +80,19 @@ fn builtin_chr(i: u32, vm: &VirtualMachine) -> PyResult { } fn builtin_compile( - source: PyStringRef, + source: Either, filename: PyStringRef, mode: PyStringRef, vm: &VirtualMachine, ) -> PyResult { + // TODO: compile::compile should probably get bytes + let source = match source { + Either::A(string) => string.value.to_string(), + Either::B(bytes) => str::from_utf8(&bytes).unwrap().to_string(), + }; + // TODO: fix this newline bug: - let source = format!("{}\n", &source.value); + let source = format!("{}\n", source); let mode = { let mode = &mode.value; From 03735a6d264ce82baa60b9f7c63600ca58ec5aa1 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 10 Jun 2019 19:07:27 +0300 Subject: [PATCH 743/884] Add optional parameters to compile --- vm/src/builtins.rs | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index d41eb58746..87d39ff544 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -79,14 +79,26 @@ fn builtin_chr(i: u32, vm: &VirtualMachine) -> PyResult { } } -fn builtin_compile( +#[derive(FromArgs)] +#[allow(dead_code)] +struct CompileArgs { + #[pyarg(positional_only, optional = false)] source: Either, + #[pyarg(positional_only, optional = false)] filename: PyStringRef, + #[pyarg(positional_only, optional = false)] mode: PyStringRef, - vm: &VirtualMachine, -) -> PyResult { + #[pyarg(positional_or_keyword, optional = true)] + flags: OptionalArg, + #[pyarg(positional_or_keyword, optional = true)] + dont_inherit: OptionalArg, + #[pyarg(positional_or_keyword, optional = true)] + optimize: OptionalArg, +} + +fn builtin_compile(args: CompileArgs, vm: &VirtualMachine) -> PyResult { // TODO: compile::compile should probably get bytes - let source = match source { + let source = match args.source { Either::A(string) => string.value.to_string(), Either::B(bytes) => str::from_utf8(&bytes).unwrap().to_string(), }; @@ -95,7 +107,7 @@ fn builtin_compile( let source = format!("{}\n", source); let mode = { - let mode = &mode.value; + let mode = &args.mode.value; if mode == "exec" { compile::Mode::Exec } else if mode == "eval" { @@ -109,7 +121,7 @@ fn builtin_compile( } }; - compile::compile(vm, &source, &mode, filename.value.to_string()) + compile::compile(vm, &source, &mode, args.filename.value.to_string()) .map_err(|err| vm.new_syntax_error(&err)) } From e1472f2277c74f0db9ed4b4ec80dcc7a5516eed4 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 10 Jun 2019 19:09:09 +0300 Subject: [PATCH 744/884] Add sys.dont_write_bytecode --- vm/src/sysmodule.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 977f2dc93c..3701da240c 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -246,6 +246,7 @@ settrace() -- set the global debug tracing function "path_hooks" => ctx.new_list(vec![]), "path_importer_cache" => ctx.new_dict(), "pycache_prefix" => vm.get_none(), + "dont_write_bytecode" => vm.new_bool(true), }); modules.set_item("sys", module.clone(), vm).unwrap(); From 603ef1ad0516b1d318e17f99e1da80adffea8291 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 10 Jun 2019 21:45:05 +0300 Subject: [PATCH 745/884] Support from_list --- vm/src/frame.rs | 8 ++++++-- vm/src/vm.rs | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 88a1b31ada..2506aa4553 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -908,7 +908,11 @@ impl Frame { } fn import(&self, vm: &VirtualMachine, module: &str, symbol: &Option) -> FrameResult { - let module = vm.import(module)?; + let from_list = match symbol { + Some(symbol) => vm.ctx.new_tuple(vec![vm.ctx.new_str(symbol.to_string())]), + None => vm.ctx.new_tuple(vec![]), + }; + let module = vm.import(module, &from_list)?; // If we're importing a symbol, look it up and use it, otherwise construct a module and return // that @@ -926,7 +930,7 @@ impl Frame { } fn import_star(&self, vm: &VirtualMachine, module: &str) -> FrameResult { - let module = vm.import(module)?; + let module = vm.import(module, &vm.ctx.new_tuple(vec![]))?; // Grab all the names from the module and put them in the context if let Some(dict) = &module.dict { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 7efbd1c195..3356cc450d 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -137,7 +137,7 @@ impl VirtualMachine { pub fn try_class(&self, module: &str, class: &str) -> PyResult { let class = self - .get_attribute(self.import(module)?, class)? + .get_attribute(self.import(module, &self.ctx.new_tuple(vec![]))?, class)? .downcast() .expect("not a class"); Ok(class) @@ -145,7 +145,7 @@ impl VirtualMachine { pub fn class(&self, module: &str, class: &str) -> PyClassRef { let module = self - .import(module) + .import(module, &self.ctx.new_tuple(vec![])) .unwrap_or_else(|_| panic!("unable to import {}", module)); let class = self .get_attribute(module.clone(), class) @@ -303,9 +303,17 @@ impl VirtualMachine { TryFromObject::try_from_object(self, repr) } - pub fn import(&self, module: &str) -> PyResult { + pub fn import(&self, module: &str, from_list: &PyObjectRef) -> PyResult { match self.get_attribute(self.builtins.clone(), "__import__") { - Ok(func) => self.invoke(func, vec![self.ctx.new_str(module.to_string())]), + Ok(func) => self.invoke( + func, + vec![ + self.ctx.new_str(module.to_string()), + self.get_none(), + self.get_none(), + from_list.clone(), + ], + ), Err(_) => Err(self.new_exception( self.ctx.exceptions.import_error.clone(), "__import__ not found".to_string(), From 0e320f9af6ff363e07acb8afa56caabb40db8d42 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Mon, 10 Jun 2019 21:50:37 +0300 Subject: [PATCH 746/884] Change comment to XXX --- Lib/importlib/_bootstrap_external.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/importlib/_bootstrap_external.py b/Lib/importlib/_bootstrap_external.py index 11b8723a45..124948ef48 100644 --- a/Lib/importlib/_bootstrap_external.py +++ b/Lib/importlib/_bootstrap_external.py @@ -1565,7 +1565,7 @@ def _setup(_bootstrap_module): setattr(self_module, builtin_name, builtin_module) # Directly load the os module (needed during bootstrap). - os_details = ('_os', ['/']), ('_os', ['\\', '/']) # Changed by palaviv to fit RustPython!!! + os_details = ('_os', ['/']), ('_os', ['\\', '/']) # XXX Changed to fit RustPython!!! for builtin_os, path_separators in os_details: # Assumption made in _path_join() assert all(len(sep) == 1 for sep in path_separators) From ea8e28026b474269c7f86f5e41fb1890fed1ee20 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 11 Jun 2019 13:10:29 +0300 Subject: [PATCH 747/884] Simplify objmodule --- vm/src/macros.rs | 1 + vm/src/obj/objmodule.rs | 46 +++++------------------------------------ vm/src/sysmodule.rs | 1 + 3 files changed, 7 insertions(+), 41 deletions(-) diff --git a/vm/src/macros.rs b/vm/src/macros.rs index c3267ff157..3e5d3262f0 100644 --- a/vm/src/macros.rs +++ b/vm/src/macros.rs @@ -118,6 +118,7 @@ macro_rules! no_kwargs { macro_rules! py_module { ( $vm:expr, $module_name:expr, { $($name:expr => $value:expr),* $(,)* }) => {{ let module = $vm.ctx.new_module($module_name, $vm.ctx.new_dict()); + $vm.set_attr(&module, "__name__", $vm.ctx.new_str($module_name.to_string())).unwrap(); $( $vm.set_attr(&module, $name, $value).unwrap(); )* diff --git a/vm/src/obj/objmodule.rs b/vm/src/obj/objmodule.rs index 183a096efc..99dff3b267 100644 --- a/vm/src/obj/objmodule.rs +++ b/vm/src/obj/objmodule.rs @@ -1,8 +1,6 @@ -use crate::obj::objdict::PyDictRef; -use crate::obj::objproperty::PropertyBuilder; use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; -use crate::pyobject::{ItemProtocol, PyContext, PyRef, PyResult, PyValue}; +use crate::pyobject::{PyContext, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; #[derive(Debug)] @@ -20,48 +18,14 @@ impl PyValue for PyModule { } impl PyModuleRef { - fn new(cls: PyClassRef, name: PyStringRef, vm: &VirtualMachine) -> PyResult { - PyModule { - name: name.as_str().to_string(), - } - .into_ref_with_type(vm, cls) - } - - fn dir(self: PyModuleRef, vm: &VirtualMachine) -> PyResult { - if let Some(dict) = &self.into_object().dict { - let keys = dict.into_iter().map(|(k, _v)| k.clone()).collect(); - Ok(vm.ctx.new_list(keys)) - } else { - panic!("Modules should definitely have a dict."); - } - } - - fn dict(self, vm: &VirtualMachine) -> PyResult { - let name_obj = vm.new_str(self.name.clone()); - if let Some(ref dict) = &self.into_object().dict { - let mod_dict = dict.clone(); - mod_dict.set_item("__name__", name_obj, vm)?; - Ok(mod_dict) - } else { - panic!("Modules should definitely have a dict."); - } - } - - fn name(self, _vm: &VirtualMachine) -> String { - self.name.clone() + fn init(self, name: PyStringRef, vm: &VirtualMachine) -> PyResult { + vm.set_attr(&self.into_object(), "__name__", name)?; + Ok(vm.get_none()) } } pub fn init(context: &PyContext) { extend_class!(&context, &context.module_type, { - "__dir__" => context.new_rustfunc(PyModuleRef::dir), - "__name__" => PropertyBuilder::new(context) - .add_getter(PyModuleRef::name) - .create(), - "__dict__" => - PropertyBuilder::new(context) - .add_getter(PyModuleRef::dict) - .create(), - "__new__" => context.new_rustfunc(PyModuleRef::new), + "__init__" => context.new_rustfunc(PyModuleRef::init), }); } diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 3701da240c..8ce352d32d 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -224,6 +224,7 @@ settrace() -- set the global debug tracing function module_names.sort(); let modules = ctx.new_dict(); extend_module!(vm, module, { + "__name__" => ctx.new_str(String::from("sys")), "argv" => argv(ctx), "builtin_module_names" => ctx.new_tuple(module_names.iter().map(|v| v.into_pyobject(vm).unwrap()).collect()), "flags" => flags, From 1de9f73bd076b559189841de72446a96e1c7ffd2 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 11 Jun 2019 22:54:54 +0300 Subject: [PATCH 748/884] Optimize already loaded modules --- vm/src/vm.rs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 3356cc450d..96fb081f49 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -304,20 +304,27 @@ impl VirtualMachine { } pub fn import(&self, module: &str, from_list: &PyObjectRef) -> PyResult { - match self.get_attribute(self.builtins.clone(), "__import__") { - Ok(func) => self.invoke( - func, - vec![ - self.ctx.new_str(module.to_string()), - self.get_none(), - self.get_none(), - from_list.clone(), - ], - ), - Err(_) => Err(self.new_exception( - self.ctx.exceptions.import_error.clone(), - "__import__ not found".to_string(), - )), + let sys_modules = self + .get_attribute(self.sys_module.clone(), "modules") + .unwrap(); + if let Ok(module) = sys_modules.get_item(module.to_string(), self) { + Ok(module) + } else { + match self.get_attribute(self.builtins.clone(), "__import__") { + Ok(func) => self.invoke( + func, + vec![ + self.ctx.new_str(module.to_string()), + self.get_none(), + self.get_none(), + from_list.clone(), + ], + ), + Err(_) => Err(self.new_exception( + self.ctx.exceptions.import_error.clone(), + "__import__ not found".to_string(), + )), + } } } From 07c6a900451204a880f5ad5adf508784a1a097e7 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 11 Jun 2019 16:00:35 -0500 Subject: [PATCH 749/884] Update cargo dependencies --- Cargo.lock | 543 ++++++++++++++++++++++++++++------------------------- 1 file changed, 288 insertions(+), 255 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9430a9f46f..f54307d02c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,12 +8,20 @@ dependencies = [ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "aho-corasick" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -46,27 +54,26 @@ name = "atty" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "autocfg" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "backtrace" -version = "0.3.14" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -74,8 +81,8 @@ name = "backtrace-sys" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cc 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -83,9 +90,9 @@ name = "bincode" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -103,7 +110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bitflags" -version = "1.0.4" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -117,18 +124,18 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "block-padding 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "block-padding" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -141,7 +148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bumpalo" -version = "2.3.0" +version = "2.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -151,7 +158,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "byteorder" -version = "1.3.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -159,30 +166,30 @@ name = "caseless" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cc" -version = "1.0.32" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cfg-if" -version = "0.1.7" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "clap" -version = "2.32.0" +version = "2.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -192,7 +199,7 @@ name = "cloudabi" version = "0.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -205,8 +212,8 @@ name = "cpython" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -236,26 +243,25 @@ name = "dirs" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "docopt" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "either" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -274,8 +280,8 @@ dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -286,8 +292,8 @@ dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -295,7 +301,7 @@ name = "failure" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -304,10 +310,10 @@ name = "failure_derive" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", + "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -327,7 +333,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "futures" -version = "0.1.25" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -343,7 +349,7 @@ name = "heck" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -389,20 +395,20 @@ name = "itertools" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "either 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "itoa" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "js-sys" -version = "0.3.17" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -423,15 +429,15 @@ dependencies = [ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", - "docopt 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "ena 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "lalrpop-util 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -453,7 +459,7 @@ name = "lexical" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "lexical-core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -462,15 +468,15 @@ name = "lexical-core" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "stackvector 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "libc" -version = "0.2.50" +version = "0.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -486,7 +492,7 @@ name = "log" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -501,13 +507,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "nix" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -530,41 +536,52 @@ name = "num-bigint" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-complex" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-integer" -version = "0.1.39" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-rational" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.2.6" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "numtoa" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -623,7 +640,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "proc-macro2" -version = "0.4.27" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -635,7 +652,7 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -643,8 +660,8 @@ name = "python3-sys" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -659,10 +676,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "quote" -version = "0.6.11" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -672,9 +689,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -682,17 +699,17 @@ name = "rand" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -700,7 +717,7 @@ name = "rand_chacha" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -735,12 +752,12 @@ dependencies = [ [[package]] name = "rand_jitter" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -750,10 +767,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -761,7 +778,7 @@ name = "rand_pcg" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -783,7 +800,7 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.1.51" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -791,7 +808,7 @@ name = "redox_termios" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -802,7 +819,7 @@ dependencies = [ "argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -814,19 +831,19 @@ dependencies = [ "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "regex" -version = "1.1.2" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", - "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -839,7 +856,7 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.5" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -847,7 +864,7 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.13" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -871,7 +888,7 @@ dependencies = [ name = "rustpython" version = "0.0.1" dependencies = [ - "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -885,9 +902,9 @@ dependencies = [ name = "rustpython_derive" version = "0.1.0" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -898,9 +915,9 @@ dependencies = [ "lalrpop-util 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "unic-emoji-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -910,8 +927,8 @@ name = "rustpython_vm" version = "0.1.0" dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -921,37 +938,38 @@ dependencies = [ "lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "num-complex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "pwd 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_derive 0.1.0", "rustpython_parser 0.0.1", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb)", - "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rustpython_wasm" version = "0.1.0-pre-alpha.2" dependencies = [ - "cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", - "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-futures 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "web-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -960,19 +978,19 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "ryu" -version = "0.2.7" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -995,20 +1013,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "serde" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "serde_derive" -version = "1.0.89" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1016,9 +1034,9 @@ name = "serde_json" version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", - "ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1026,7 +1044,7 @@ name = "sha2" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1039,7 +1057,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "smallvec" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1077,7 +1095,7 @@ dependencies = [ "new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", "precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1089,8 +1107,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "phf_generator 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", "phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1101,7 +1119,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "strsim" -version = "0.7.0" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1116,11 +1139,11 @@ dependencies = [ [[package]] name = "syn" -version = "0.15.29" +version = "0.15.35" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1134,12 +1157,12 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1154,7 +1177,7 @@ dependencies = [ [[package]] name = "termcolor" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1162,17 +1185,18 @@ dependencies = [ [[package]] name = "termion" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)", + "numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)", "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "textwrap" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1242,12 +1266,12 @@ name = "unicode-normalization" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "unicode-segmentation" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1265,6 +1289,11 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unreachable" version = "1.0.0" @@ -1275,7 +1304,7 @@ dependencies = [ [[package]] name = "utf8-ranges" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1300,93 +1329,93 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "wasm-bindgen" -version = "0.2.40" +version = "0.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "wasm-bindgen-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.40" +version = "0.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bumpalo 2.4.3 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-futures" -version = "0.3.17" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.40" +version = "0.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-macro-support 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-macro-support 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.40" +version = "0.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-shared 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.40" +version = "0.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "wasm-bindgen-webidl" -version = "0.2.40" +version = "0.2.45" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-backend 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "weedle 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "web-sys" -version = "0.3.17" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", - "wasm-bindgen-webidl 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen-webidl 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "weedle" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1399,7 +1428,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1421,7 +1450,7 @@ name = "winapi-util" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1434,7 +1463,7 @@ name = "wincolor" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1445,29 +1474,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [metadata] "checksum aho-corasick 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" +"checksum aho-corasick 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6f484ae0c99fec2e858eb6134949117399f222608d84cadb3f58c1f97c2364c" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" "checksum argon2rs 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3f67b0b6a86dae6e67ff4ca2b6201396074996379fba2b92ff649126f37cb392" "checksum arrayvec 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" "checksum ascii-canvas 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b385d69402821a1c254533a011a312531cbcc0e3e24f19bbb4747a5a2daf37e2" "checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652" -"checksum autocfg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" -"checksum backtrace 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "cd5a90e2b463010cd0e0ce9a11d4a9d5d58d9f41d4a6ba3dcaf9e68b466e88b4" +"checksum autocfg 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0e49efa51329a5fd37e7c79db4621af617cd4e3e5bc224939808d076077077bf" +"checksum backtrace 0.3.30 (registry+https://github.com/rust-lang/crates.io-index)" = "ada4c783bb7e7443c14e0480f429ae2cc99da95065aeab7ee1b81ada0419404f" "checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6" "checksum bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9f04a5e50dc80b3d5d35320889053637d15011aed5e66b66b37ae798c65da6f7" "checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" "checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" -"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" +"checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" -"checksum block-buffer 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49665c62e0e700857531fa5d3763e91b539ff1abeebd56808d378b495870d60d" -"checksum block-padding 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d75255892aeb580d3c566f213a2b6fdc1c66667839f45719ee1d30ebf2aea591" +"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +"checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" "checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" -"checksum bumpalo 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f6a6bc2ba935b1e2f810787ceba8dfe86cbc164cee55925cae715541186673c4" +"checksum bumpalo 2.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "84dca3afd8e01b9526818b7963e5b4916063b3cdf9f10cf6b73ef0bd0ec37aa5" "checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" -"checksum byteorder 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" +"checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "808dab3318747be122cb31d36de18d4d1c81277a76f8332a02b81a3d73463d7f" -"checksum cc 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)" = "ad0daef304fa0b4238f5f7ed7178774b43b06f6a9b6509f6642bef4ff1f7b9b2" -"checksum cfg-if 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "11d43355396e872eefb45ce6342e4374ed7bc2b3a502d1b28e36d6e23c05d1f4" -"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)" = "39f75544d7bbaf57560d2168f28fd649ff9c76153874db88bdbdfd839b1a7e7d" +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b489034e723e7f5109fecd19b719e664f89ef925be785885252469e9822fa940" @@ -1475,8 +1505,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" -"checksum docopt 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "db2906c2579b5b7207fc1e328796a9a8835dc44e22dbe8e460b1d636f9a7b225" -"checksum either 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c67353c641dc847124ea1902d69bd753dee9bb3beff9aa3662ecf86c971d1fac" +"checksum docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" +"checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" "checksum ena 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f56c93cc076508c549d9bb747f79aa9b4eb098be7b8cad8830c3137ef52d1e00" "checksum env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)" = "15b0a4d2e39f8420210be8b27eeda28029729e2fd4291019455016c348240c38" "checksum env_logger 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b61fa891024a945da30a9581546e8cfaf5602c7b3f4c137a2805cf388f92075a" @@ -1485,7 +1515,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" +"checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e54653cc32d838771a36532647afad59c4bf7155745eeeec406f71fd5d7e7538" @@ -1494,27 +1524,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" "checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" -"checksum itoa 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" -"checksum js-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "d4bda84b98977341bb6ba1086ecbd9eab8bc929de0f2923c118baf76c21dd0c8" +"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" +"checksum js-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "9987e7c13a91d9cf0efe59cca48a3a7a70e2b11695d5a4640f85ae71e28f5e73" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lalrpop 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4e2e80bee40b22bca46665b4ef1f3cd88ed0fb043c971407eac17a0712c02572" "checksum lalrpop-util 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "33b27d8490dbe1f9704b0088d61e8d46edc10d5673a8829836c6ded26a9912c7" "checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" "checksum lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c106ed999697325a540c43d66a8d5175668cd96d7eb0bdba03a3bd98256cd698" "checksum lexical-core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3e82e023e062f1d25f807ad182008fba1b46538e999f908a08cc0c29e084462e" -"checksum libc 0.2.50 (registry+https://github.com/rust-lang/crates.io-index)" = "aab692d7759f5cd8c859e169db98ae5b52c924add2af5fbbca11d12fefb567c1" +"checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30" -"checksum nix 0.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46f0f3210768d796e8fa79ec70ee6af172dacbe7147f5e69be5240a47778302b" +"checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "57450397855d951f1a41305e54851b1a7b8f5d2e349543a02a2effe25459f718" -"checksum num-complex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "107b9be86cd2481930688277b675b0114578227f034674726605b8a482d8baf8" -"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" -"checksum num-rational 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e96f040177bb3da242b5b1ecf3f54b5d5af3efbbfb18608977a5d2767b22f10" -"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" +"checksum num-complex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7a9854fff8af065f715e116e1d61e2d25a41fee956e941b6940f11295199584c" +"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" +"checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454" +"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" +"checksum numtoa 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" "checksum opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "93f5bb2e8e8dec81642920ccff6b61f1eb94fa3020c5a325c9851ff604152409" "checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" "checksum petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)" = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f" @@ -1523,12 +1554,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" "checksum proc-macro-hack 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b773f824ff2a495833f85fcdddcf85e096949971decada2e93249fa2c6c3d32f" "checksum proc-macro-hack-impl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0f674ccc446da486175527473ec8aa064f980b0966bbf767ee743a5dff6244a7" -"checksum proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)" = "4d317f9caece796be1980837fd5cb3dfec5613ebdb04ad0956deea83ce168915" +"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum pwd 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5dd32d8bece608e144ca20251e714ed107cdecdabb20c2d383cfc687825106a5" "checksum python3-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "61e4aac43f833fd637e429506cb2ac9d7df672c4b68f2eaaa163649b7fdc0444" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cdd8e04bd9c52e0342b406469d494fcb033be4bdbe5c606016defbb1681411e1" +"checksum quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "faf4799c5d274f3868a4aae320a0a182cbd2baee377b378f080e16a23e9d80db" "checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" @@ -1536,32 +1567,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum rand_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" "checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" "checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" -"checksum rand_jitter 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" +"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" "checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" "checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -"checksum redox_syscall 0.1.51 (registry+https://github.com/rust-lang/crates.io-index)" = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" +"checksum redox_syscall 0.1.54 (registry+https://github.com/rust-lang/crates.io-index)" = "12229c14a0f65c4f1cb046a3b52047cdd9da1f4b30f8a39c5063c8bae515e252" "checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum redox_users 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3fe5204c3a17e97dde73f285d49be585df59ed84b50a872baf416e73b62c3828" "checksum regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" -"checksum regex 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53ee8cfdddb2e0291adfb9f13d31d3bbe0a03c9a402c01b1e24188d86c35b24f" +"checksum regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0b2f0808e7d7e4fb1cb07feb6ff2f4bc827938f24f8c2e6a3beb7370af544bdd" "checksum regex-syntax 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" -"checksum regex-syntax 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8c2f35eedad5295fdf00a63d7d4b238135723f92b434ec06774dad15c7ab0861" -"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619" +"checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48" +"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6de8ecd7fad7731f306f69b6e10ec5a3178c61e464dcc06979427aa4cc891145" "checksum rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0f47ea1ceb347d2deae482d655dc8eef4bd82363d3329baffa3818bd76fea48b" -"checksum ryu 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "eb9e9b8cde282a9fe6a42dd4681319bfb63f121b8a8ee9439c6f4107e58a46f7" +"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f" "checksum scoped_threadpool 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" -"checksum serde 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "92514fb95f900c9b5126e32d020f5c6d40564c27a5ea6d1d7d9f157a96623560" -"checksum serde_derive 1.0.89 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6eabf4b5914e88e24eea240bb7c9f9a2cbc1bbbe8d961d381975ec3c6b806c" +"checksum serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "32746bf0f26eab52f06af0d0aa1984f641341d06d8d673c693871da2d188c9be" +"checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" -"checksum smallvec 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c4488ae950c49d403731982257768f48fada354a5203fe81f9bb6f43ca9002be" +"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" "checksum stackvector 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c049c77bf85fbc036484c97b008276d539d9ebff9dfbde37b632ebcd5b8746b6" "checksum static_assertions 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" @@ -1569,15 +1600,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum string_cache 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "25d70109977172b127fe834e5449e5ab1740b9ba49fa18a2020f509174f25423" "checksum string_cache_codegen 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eea1eee654ef80933142157fdad9dd8bc43cf7c74e999e369263496f04ff4da" "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" -"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +"checksum strsim 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "032c03039aae92b350aad2e3779c352e104d919cb192ba2fabbd7b831ce4f0f6" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" -"checksum syn 0.15.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1825685f977249735d510a242a6727b46efe914bb67e38d30c071b1b72b1d5c2" +"checksum syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)" = "641e117d55514d6d918490e47102f7e08d096fdde360247e4a10f7a91a8478d3" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" +"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" -"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" -"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" -"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" +"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" +"checksum termion 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dde0593aeb8d47accea5392b39350015b5eccb12c0d98044d856983d89548dea" +"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" @@ -1588,27 +1620,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" "checksum unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb)" = "" "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" -"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" +"checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +"checksum unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" -"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" +"checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde" "checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" -"checksum wasm-bindgen 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "9742fc4860f47bede1090a5e4b0cfc33afcd70cfdf45dd28f2cfb02d4662b0dd" -"checksum wasm-bindgen-backend 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c6d7f35ecbb4180513cdb9b298543321bcb278670730415cbb3205ff2c66a477" -"checksum wasm-bindgen-futures 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "67bfbec85e48e8915b6acfa1c4d90b770ce033c96dc2067688f3bccd00d3b9e1" -"checksum wasm-bindgen-macro 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "e3c86b06bcd28e92e87d2c2ad208889b2f69ea33f79810b91ef660cc3de65a4c" -"checksum wasm-bindgen-macro-support 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "81d7338dd8c67e193d8ef18e5802dc03d8710456baa792c1c2e66847e57fd389" -"checksum wasm-bindgen-shared 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "0d57c3b66f2f3e4d96b50f49b7b7e2f4cfcddc88b15744433c98c5c105b26672" -"checksum wasm-bindgen-webidl 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "fc7052a577eefce17a439edfce5c2eedc8432fe7457bbe949624cdf946233fa7" -"checksum web-sys 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "72d02003924c77af871ad74216f828b6e32af776cbb3f5dab3dcde2f2713c012" -"checksum weedle 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "26a4c67f132386d965390b8a734d5d10adbcd30eb5cc74bd9229af8b83f10044" +"checksum wasm-bindgen 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "b7ccc7b93cfd13e26700a9e2e41e6305f1951b87e166599069f77d10358100e6" +"checksum wasm-bindgen-backend 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "1953f91b1608eb1522513623c7739f047bb0fed4128ce51a93f08e12cc314645" +"checksum wasm-bindgen-futures 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "fa1af11c73eca3dc8c51c76ea475a4416e912da6402064a49fc6c0214701866d" +"checksum wasm-bindgen-macro 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "0f69da5696545d7ca6607a2e4b1a0edf5a6b36b2c49dbb0f1df6ad1d92884047" +"checksum wasm-bindgen-macro-support 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "2d4246f3bc73223bbb846f4f2430a60725826a96c9389adf715ed1d5af46dec6" +"checksum wasm-bindgen-shared 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "c08381e07e7a79e5e229ad7c60d15833d19033542cc5dd91d085df59d235f4a6" +"checksum wasm-bindgen-webidl 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)" = "1f42ff7adb8102bf5ad8adbc45b1635c520c8175f9fdf6eb2c54479d485d435a" +"checksum web-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "540b8259eb242ff3a566fa0140bda03a4ece4e5c226e1284b5c95dddcd4341f6" +"checksum weedle 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bcc44aa200daee8b1f3a004beaf16554369746f1b4486f0cf93b0caf8a3c2d1e" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" +"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" From 45bb2bd26373f727011bda3d524752dfa1d2aaee Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 12 Jun 2019 21:43:43 -0500 Subject: [PATCH 750/884] Split off bytecode compilation into a separate crate --- Cargo.lock | 14 +++++++++++++- Cargo.toml | 2 +- compiler/Cargo.toml | 13 +++++++++++++ {vm => compiler}/src/bytecode.rs | 1 + {vm => compiler}/src/compile.rs | 13 ++----------- {vm => compiler}/src/error.rs | 0 compiler/src/lib.rs | 7 +++++++ {vm => compiler}/src/symboltable.rs | 0 src/main.rs | 5 +++-- vm/Cargo.toml | 2 +- vm/src/builtins.rs | 6 +++--- vm/src/eval.rs | 2 +- vm/src/import.rs | 3 ++- vm/src/lib.rs | 7 +------ vm/src/stdlib/imp.rs | 9 ++------- vm/src/vm.rs | 15 ++++++++++++--- wasm/lib/src/vm_class.rs | 2 +- 17 files changed, 63 insertions(+), 38 deletions(-) create mode 100644 compiler/Cargo.toml rename {vm => compiler}/src/bytecode.rs (99%) rename {vm => compiler}/src/compile.rs (99%) rename {vm => compiler}/src/error.rs (100%) create mode 100644 compiler/src/lib.rs rename {vm => compiler}/src/symboltable.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index f54307d02c..7bb6469d7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,6 +898,18 @@ dependencies = [ "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustpython_compiler" +version = "0.1.0" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-complex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustpython_parser 0.0.1", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustpython_derive" version = "0.1.0" @@ -927,7 +939,6 @@ name = "rustpython_vm" version = "0.1.0" dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -946,6 +957,7 @@ dependencies = [ "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rustpython_compiler 0.1.0", "rustpython_derive 0.1.0", "rustpython_parser 0.0.1", "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 71db1d8396..2a4fc23bfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Windel Bouwman", "Shing Lyu "] edition = "2018" [workspace] -members = [".", "derive", "vm", "wasm/lib", "parser"] +members = [".", "derive", "vm", "wasm/lib", "parser", "compiler"] [[bench]] name = "bench" diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml new file mode 100644 index 0000000000..fc0d195931 --- /dev/null +++ b/compiler/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "rustpython_compiler" +version = "0.1.0" +authors = ["coolreader18 <33094578+coolreader18@users.noreply.github.com>"] +edition = "2018" + +[dependencies] +bitflags = "1.1" +rustpython_parser = { path = "../parser" } +serde = { version = "1.0", features = ["derive"] } +num-complex = { version = "0.2", features = ["serde"] } +num-bigint = { version = "0.2", features = ["serde"] } +log = "0.3" diff --git a/vm/src/bytecode.rs b/compiler/src/bytecode.rs similarity index 99% rename from vm/src/bytecode.rs rename to compiler/src/bytecode.rs index 5df445d748..0fc226b5bc 100644 --- a/vm/src/bytecode.rs +++ b/compiler/src/bytecode.rs @@ -5,6 +5,7 @@ * Primitive instruction type, which can be encoded and decoded. */ +use bitflags::bitflags; use num_bigint::BigInt; use num_complex::Complex64; use rustpython_parser::ast; diff --git a/vm/src/compile.rs b/compiler/src/compile.rs similarity index 99% rename from vm/src/compile.rs rename to compiler/src/compile.rs index b1964ca557..dec158e9b8 100644 --- a/vm/src/compile.rs +++ b/compiler/src/compile.rs @@ -7,11 +7,7 @@ use crate::bytecode::{self, CallType, CodeObject, Instruction, Varargs}; use crate::error::{CompileError, CompileErrorType}; -use crate::obj::objcode; -use crate::obj::objcode::PyCodeRef; -use crate::pyobject::PyValue; use crate::symboltable::{make_symbol_table, statements_to_symbol_table, SymbolRole, SymbolScope}; -use crate::VirtualMachine; use num_complex::Complex64; use rustpython_parser::{ast, parser}; @@ -27,12 +23,7 @@ struct Compiler { } /// Compile a given sourcecode into a bytecode object. -pub fn compile( - vm: &VirtualMachine, - source: &str, - mode: &Mode, - source_path: String, -) -> Result { +pub fn compile(source: &str, mode: &Mode, source_path: String) -> Result { let mut compiler = Compiler::new(); compiler.source_path = Some(source_path); compiler.push_new_code_object("".to_string()); @@ -57,7 +48,7 @@ pub fn compile( let code = compiler.pop_code_object(); trace!("Compilation completed: {:?}", code); - Ok(objcode::PyCode::new(code).into_ref(vm)) + Ok(code) } pub enum Mode { diff --git a/vm/src/error.rs b/compiler/src/error.rs similarity index 100% rename from vm/src/error.rs rename to compiler/src/error.rs diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs new file mode 100644 index 0000000000..1f0805c651 --- /dev/null +++ b/compiler/src/lib.rs @@ -0,0 +1,7 @@ +#[macro_use] +extern crate log; + +pub mod bytecode; +pub mod compile; +pub mod error; +mod symboltable; diff --git a/vm/src/symboltable.rs b/compiler/src/symboltable.rs similarity index 100% rename from vm/src/symboltable.rs rename to compiler/src/symboltable.rs diff --git a/src/main.rs b/src/main.rs index 84d2af51f3..5d4d2b9e72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,7 +75,8 @@ fn main() { } fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResult { - let code_obj = compile::compile(vm, source, &compile::Mode::Exec, source_path.clone()) + let code_obj = vm + .compile(source, &compile::Mode::Exec, source_path.clone()) .map_err(|err| vm.new_syntax_error(&err))?; // trace!("Code object: {:?}", code_obj.borrow()); let attrs = vm.ctx.new_dict(); @@ -160,7 +161,7 @@ fn test_run_script() { } fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), CompileError> { - match compile::compile(vm, source, &compile::Mode::Single, "".to_string()) { + match vm.compile(source, &compile::Mode::Single, "".to_string()) { Ok(code) => { match vm.run_code_obj(code, scope.clone()) { Ok(value) => { diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 2868ab65ff..de53223ae9 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -5,7 +5,6 @@ authors = ["Shing Lyu "] edition = "2018" [dependencies] -bitflags = "1.0.4" num-complex = { version = "0.2", features = ["serde"] } num-bigint = { version = "0.2.1", features = ["serde"] } num-traits = "0.2" @@ -15,6 +14,7 @@ rand = "0.5" log = "0.3" rustpython_derive = {path = "../derive"} rustpython_parser = {path = "../parser"} +rustpython_compiler = {path = "../compiler"} serde = { version = "1.0.66", features = ["derive"] } serde_json = "1.0.26" byteorder = "1.2.6" diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 0673dd9089..9779a50547 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -121,7 +121,7 @@ fn builtin_compile(args: CompileArgs, vm: &VirtualMachine) -> PyResult PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - compile::compile(vm, &source, &mode, "".to_string()) + vm.compile(&source, &mode, "".to_string()) .map_err(|err| vm.new_syntax_error(&err))? } else { return Err(vm.new_type_error("code argument must be str or code object".to_string())); @@ -198,7 +198,7 @@ fn builtin_exec(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - compile::compile(vm, &source, &mode, "".to_string()) + vm.compile(&source, &mode, "".to_string()) .map_err(|err| vm.new_syntax_error(&err))? } else if let Ok(code_obj) = PyCodeRef::try_from_object(vm, source.clone()) { code_obj diff --git a/vm/src/eval.rs b/vm/src/eval.rs index a927f8f034..e326e300e5 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -6,7 +6,7 @@ use crate::pyobject::PyResult; use crate::vm::VirtualMachine; pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str) -> PyResult { - match compile::compile(vm, source, &compile::Mode::Eval, source_path.to_string()) { + match vm.compile(source, &compile::Mode::Eval, source_path.to_string()) { Ok(bytecode) => { debug!("Code object: {:?}", bytecode); vm.run_code_obj(bytecode, scope) diff --git a/vm/src/import.rs b/vm/src/import.rs index 554d5e15ee..a24e936dce 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -84,7 +84,8 @@ pub fn import_file( content: String, ) -> PyResult { let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); - let code_obj = compile::compile(vm, &content, &compile::Mode::Exec, file_path.clone()) + let code_obj = vm + .compile(&content, &compile::Mode::Exec, file_path.clone()) .map_err(|err| vm.new_syntax_error(&err))?; // trace!("Code object: {:?}", code_obj); diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 24f28709ea..269a0a3b5b 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -12,8 +12,6 @@ clippy::implicit_hasher )] -#[macro_use] -extern crate bitflags; #[macro_use] extern crate lazy_static; extern crate lexical; @@ -37,10 +35,7 @@ pub use rustpython_derive::*; pub mod macros; mod builtins; -pub mod bytecode; -pub mod compile; mod dictdatatype; -pub mod error; pub mod eval; mod exceptions; pub mod format; @@ -52,7 +47,6 @@ pub mod obj; mod pyhash; pub mod pyobject; pub mod stdlib; -mod symboltable; mod sysmodule; mod traceback; pub mod util; @@ -61,3 +55,4 @@ mod vm; // pub use self::pyobject::Executor; pub use self::exceptions::print_exception; pub use self::vm::VirtualMachine; +pub use rustpython_compiler::*; diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index fbf0247a05..fda6f11cda 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -58,13 +58,8 @@ fn imp_exec_builtin(_mod: PyModuleRef, _vm: &VirtualMachine) -> i32 { fn imp_get_frozen_object(name: PyStringRef, vm: &VirtualMachine) -> PyResult { let name_str = name.as_str(); if let Some(frozen) = vm.frozen.borrow().get(name_str) { - compile::compile( - vm, - frozen, - &compile::Mode::Exec, - format!("frozen {}", name_str), - ) - .map_err(|err| vm.new_syntax_error(&err)) + vm.compile(frozen, &compile::Mode::Exec, format!("frozen {}", name_str)) + .map_err(|err| vm.new_syntax_error(&err)) } else { Err(vm.new_import_error(format!("No such frozen object named {}", name.as_str()))) } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 91543532f1..3f23742a1f 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -4,8 +4,6 @@ //! https://github.com/ProgVal/pythonvm-rust/blob/master/src/processor/mod.rs //! -extern crate rustpython_parser; - use std::cell::{Ref, RefCell}; use std::collections::hash_map::HashMap; use std::collections::hash_set::HashSet; @@ -14,13 +12,14 @@ use std::sync::{Mutex, MutexGuard}; use crate::builtins; use crate::bytecode; +use crate::compile; use crate::error::CompileError; use crate::frame::{ExecutionResult, Frame, FrameRef, Scope}; use crate::frozen; use crate::function::PyFuncArgs; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; -use crate::obj::objcode::PyCodeRef; +use crate::obj::objcode::{PyCode, PyCodeRef}; use crate::obj::objdict::PyDictRef; use crate::obj::objfunction::{PyFunction, PyMethod}; use crate::obj::objgenerator::PyGenerator; @@ -736,6 +735,16 @@ impl VirtualMachine { ) } + pub fn compile( + &self, + source: &str, + mode: &compile::Mode, + source_path: String, + ) -> Result { + compile::compile(source, mode, source_path) + .map(|codeobj| PyCode::new(codeobj).into_ref(self)) + } + pub fn _sub(&self, a: PyObjectRef, b: PyObjectRef) -> PyResult { self.call_or_reflection(a, b, "__sub__", "__rsub__", |vm, a, b| { Err(vm.new_unsupported_operand_error(a, b, "-")) diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index f9040dfcbd..9526941ffd 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -263,7 +263,7 @@ impl WASMVirtualMachine { ref vm, ref scope, .. }| { source.push('\n'); - let code = compile::compile(vm, &source, &mode, "".to_string()); + let code = vm.compile(&source, &mode, "".to_string()); let code = code.map_err(|err| { let js_err = SyntaxError::new(&format!("Error parsing Python code: {}", err)); if let rustpython_vm::error::CompileErrorType::Parse(ref parse_error) = From c2502bf8efa6ed256df157437c254d4aa246fdfe Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 12 Jun 2019 22:04:59 -0500 Subject: [PATCH 751/884] Add doc comments and individual compile functions --- compiler/src/bytecode.rs | 4 --- compiler/src/compile.rs | 55 ++++++++++++++++++++++++++++++++-------- compiler/src/lib.rs | 3 +++ 3 files changed, 47 insertions(+), 15 deletions(-) diff --git a/compiler/src/bytecode.rs b/compiler/src/bytecode.rs index 0fc226b5bc..aede69c934 100644 --- a/compiler/src/bytecode.rs +++ b/compiler/src/bytecode.rs @@ -1,10 +1,6 @@ //! Implement python as a virtual machine with bytecodes. This module //! implements bytecode structure. -/* - * Primitive instruction type, which can be encoded and decoded. - */ - use bitflags::bitflags; use num_bigint::BigInt; use num_complex::Complex64; diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index dec158e9b8..30510c2008 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -24,33 +24,66 @@ struct Compiler { /// Compile a given sourcecode into a bytecode object. pub fn compile(source: &str, mode: &Mode, source_path: String) -> Result { - let mut compiler = Compiler::new(); - compiler.source_path = Some(source_path); - compiler.push_new_code_object("".to_string()); - match mode { Mode::Exec => { let ast = parser::parse_program(source)?; - let symbol_table = make_symbol_table(&ast)?; - compiler.compile_program(&ast, symbol_table) + compile_program(ast, source_path) } Mode::Eval => { let statement = parser::parse_statement(source)?; - let symbol_table = statements_to_symbol_table(&statement)?; - compiler.compile_statement_eval(&statement, symbol_table) + compile_statement_eval(statement, source_path) } Mode::Single => { let ast = parser::parse_program(source)?; - let symbol_table = make_symbol_table(&ast)?; - compiler.compile_program_single(&ast, symbol_table) + compile_program_single(ast, source_path) } - }?; + } +} +/// A helper function for the shared code of the different compile functions +fn with_compiler( + source_path: String, + f: impl FnOnce(&mut Compiler) -> Result<(), CompileError>, +) -> Result { + let mut compiler = Compiler::new(); + compiler.source_path = Some(source_path); + compiler.push_new_code_object("".to_string()); + f(&mut compiler)?; let code = compiler.pop_code_object(); trace!("Compilation completed: {:?}", code); Ok(code) } +/// Compile a standard Python program to bytecode +pub fn compile_program(ast: ast::Program, source_path: String) -> Result { + with_compiler(source_path, |compiler| { + let symbol_table = make_symbol_table(&ast)?; + compiler.compile_program(&ast, symbol_table) + }) +} + +/// Compile a single Python expression to bytecode +pub fn compile_statement_eval( + statement: Vec, + source_path: String, +) -> Result { + with_compiler(source_path, |compiler| { + let symbol_table = statements_to_symbol_table(&statement)?; + compiler.compile_statement_eval(&statement, symbol_table) + }) +} + +/// Compile a Python program to bytecode for the context of a REPL +pub fn compile_program_single( + ast: ast::Program, + source_path: String, +) -> Result { + with_compiler(source_path, |compiler| { + let symbol_table = make_symbol_table(&ast)?; + compiler.compile_program_single(&ast, symbol_table) + }) +} + pub enum Mode { Exec, Eval, diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 1f0805c651..382647d157 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -1,3 +1,6 @@ +//! Compile a Python AST or source code into bytecode consumable by RustPython or +//! (eventually) CPython. + #[macro_use] extern crate log; From bfd6145d5fccd63ae7e90b7b7f30699d4cdc8623 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 13 Jun 2019 12:34:30 -0500 Subject: [PATCH 752/884] Use RUSTPYTHONPATH in ouroboros instructions --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3c64397b81..c023ca421b 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ To do this, follow this method: ```shell $ cd ~/GIT $ git clone git@github.com:pybee/ouroboros.git -$ export PYTHONPATH=~/GIT/ouroboros/ouroboros +$ export RUSTPYTHONPATH=~/GIT/ouroboros/ouroboros $ cd RustPython $ cargo run -- -c 'import statistics' ``` From 12a30288062ee8c076f05ff29f8d2204ea42c5b9 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 13 Jun 2019 20:41:37 +0300 Subject: [PATCH 753/884] Import all from_list in one __import__ call --- compiler/src/bytecode.rs | 4 +-- compiler/src/compile.rs | 56 ++++++++++++++++++++++++++----------- compiler/src/symboltable.rs | 20 +++++++++---- parser/src/ast.rs | 9 ++++-- parser/src/python.lalrpop | 23 ++++++++------- vm/src/frame.rs | 38 ++++++++++++------------- 6 files changed, 93 insertions(+), 57 deletions(-) diff --git a/compiler/src/bytecode.rs b/compiler/src/bytecode.rs index aede69c934..c87bc0ae0b 100644 --- a/compiler/src/bytecode.rs +++ b/compiler/src/bytecode.rs @@ -50,7 +50,7 @@ pub enum NameScope { pub enum Instruction { Import { name: String, - symbol: Option, + symbols: Vec, }, ImportStar { name: String, @@ -330,7 +330,7 @@ impl Instruction { } match self { - Import { name, symbol } => w!(Import, name, format!("{:?}", symbol)), + Import { name, symbols } => w!(Import, name, format!("{:?}", symbols)), ImportStar { name } => w!(ImportStar, name), LoadName { name, scope } => w!(LoadName, name, format!("{:?}", scope)), StoreName { name, scope } => w!(StoreName, name, format!("{:?}", scope)), diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index 30510c2008..dc83fad2e8 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -250,29 +250,51 @@ impl Compiler { ast::Statement::Import { import_parts } => { for ast::SingleImport { module, - symbol, + symbols, alias, } in import_parts { - match symbol { - Some(name) if name == "*" => { - self.emit(Instruction::ImportStar { - name: module.clone(), - }); - } - _ => { + if let Some(alias) = alias { + self.emit(Instruction::Import { + name: module.clone(), + symbols: vec![], + }); + self.store_name(&alias); + } else { + if symbols.is_empty() { self.emit(Instruction::Import { name: module.clone(), - symbol: symbol.clone(), + symbols: vec![], }); - let name = match alias { - Some(alias) => alias.clone(), - None => match symbol { - Some(symbol) => symbol.clone(), - None => module.clone(), - }, - }; - self.store_name(&name); + self.store_name(&module.clone()); + } else { + let mut import_star = false; + let mut symbols_strings = vec![]; + let mut names = vec![]; + for ast::ImportSymbol { symbol, alias } in symbols { + if symbol == "*" { + import_star = true; + } + symbols_strings.push(symbol.to_string()); + names.insert( + 0, + match alias { + Some(alias) => alias, + None => symbol, + }, + ); + } + if import_star { + self.emit(Instruction::ImportStar { + name: module.clone(), + }); + } else { + self.emit(Instruction::Import { + name: module.clone(), + symbols: symbols_strings, + }); + names.iter().for_each(|name| self.store_name(&name)); + } } } } diff --git a/compiler/src/symboltable.rs b/compiler/src/symboltable.rs index 9687b0ec76..04e5d9ede5 100644 --- a/compiler/src/symboltable.rs +++ b/compiler/src/symboltable.rs @@ -300,14 +300,22 @@ impl SymbolTableBuilder { for part in import_parts { if let Some(alias) = &part.alias { // `import mymodule as myalias` - // `from mymodule import myimportname as myalias` self.register_name(alias, SymbolRole::Assigned)?; - } else if let Some(symbol) = &part.symbol { - // `from mymodule import myimport` - self.register_name(symbol, SymbolRole::Assigned)?; } else { - // `import module` - self.register_name(&part.module, SymbolRole::Assigned)?; + if part.symbols.is_empty() { + // `import module` + self.register_name(&part.module, SymbolRole::Assigned)?; + } else { + // `from mymodule import myimport` + for symbol in &part.symbols { + if let Some(alias) = &symbol.alias { + // `from mymodule import myimportname as myalias` + self.register_name(alias, SymbolRole::Assigned)?; + } else { + self.register_name(&symbol.symbol, SymbolRole::Assigned)?; + } + } + } } } } diff --git a/parser/src/ast.rs b/parser/src/ast.rs index 941e041316..8fc2ba53d4 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -27,12 +27,17 @@ pub struct Program { pub statements: Vec, } +#[derive(Debug, PartialEq)] +pub struct ImportSymbol { + pub symbol: String, + pub alias: Option, +} + #[derive(Debug, PartialEq)] pub struct SingleImport { pub module: String, - // (symbol name in module, name it should be assigned locally) - pub symbol: Option, pub alias: Option, + pub symbols: Vec, } #[derive(Debug, PartialEq)] diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index 8d6dd7cac6..ac8669b8eb 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -206,7 +206,7 @@ ImportStatement: ast::LocatedStatement = { .map(|(n, a)| ast::SingleImport { module: n.to_string(), - symbol: None, + symbols: vec![], alias: a.clone() }) .collect() @@ -217,15 +217,18 @@ ImportStatement: ast::LocatedStatement = { ast::LocatedStatement { location: loc, node: ast::Statement::Import { - import_parts: i - .iter() - .map(|(i, a)| - ast::SingleImport { - module: n.to_string(), - symbol: Some(i.to_string()), - alias: a.clone() - }) - .collect() + import_parts: vec![ + ast::SingleImport { + module: n.to_string(), + symbols: i.iter() + .map(|(i, a)| + ast::ImportSymbol { + symbol: i.to_string(), + alias: a.clone(), + }) + .collect(), + alias: None + }] }, } }, diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 2506aa4553..8aaed07081 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -357,8 +357,8 @@ impl Frame { } bytecode::Instruction::Import { ref name, - ref symbol, - } => self.import(vm, name, symbol), + ref symbols, + } => self.import(vm, name, symbols), bytecode::Instruction::ImportStar { ref name } => self.import_star(vm, name), bytecode::Instruction::LoadName { ref name, @@ -907,25 +907,23 @@ impl Frame { } } - fn import(&self, vm: &VirtualMachine, module: &str, symbol: &Option) -> FrameResult { - let from_list = match symbol { - Some(symbol) => vm.ctx.new_tuple(vec![vm.ctx.new_str(symbol.to_string())]), - None => vm.ctx.new_tuple(vec![]), - }; - let module = vm.import(module, &from_list)?; - - // If we're importing a symbol, look it up and use it, otherwise construct a module and return - // that - let obj = match symbol { - Some(symbol) => vm.get_attribute(module, symbol.as_str()).map_err(|_| { - let import_error = vm.context().exceptions.import_error.clone(); - vm.new_exception(import_error, format!("cannot import name '{}'", symbol)) - }), - None => Ok(module), - }; + fn import(&self, vm: &VirtualMachine, module: &str, symbols: &Vec) -> FrameResult { + let mut from_list = vec![]; + for symbol in symbols { + from_list.push(vm.ctx.new_str(symbol.to_string())); + } + let module = vm.import(module, &vm.ctx.new_tuple(from_list))?; - // Push module on stack: - self.push_value(obj?); + if symbols.is_empty() { + self.push_value(module); + } else { + for symbol in symbols { + let obj = vm + .get_attribute(module.clone(), symbol.as_str()) + .map_err(|_| vm.new_import_error(format!("cannot import name '{}'", symbol))); + self.push_value(obj?); + } + } Ok(None) } From 4938c03d6f1938f88692e910d06adbf39b3bf69f Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 14 Jun 2019 08:45:30 +0300 Subject: [PATCH 754/884] Improve compiler import --- compiler/src/compile.rs | 61 ++++++++++++++++++++--------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index dc83fad2e8..8b806b1d1a 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -255,46 +255,45 @@ impl Compiler { } in import_parts { if let Some(alias) = alias { + // import module as alias self.emit(Instruction::Import { name: module.clone(), symbols: vec![], }); self.store_name(&alias); + } else if symbols.is_empty() { + // import module + self.emit(Instruction::Import { + name: module.clone(), + symbols: vec![], + }); + self.store_name(&module.clone()); } else { - if symbols.is_empty() { - self.emit(Instruction::Import { + let import_star = symbols + .iter() + .any(|import_symbol| import_symbol.symbol == "*"); + if import_star { + // from module import * + self.emit(Instruction::ImportStar { name: module.clone(), - symbols: vec![], }); - self.store_name(&module.clone()); } else { - let mut import_star = false; - let mut symbols_strings = vec![]; - let mut names = vec![]; - for ast::ImportSymbol { symbol, alias } in symbols { - if symbol == "*" { - import_star = true; - } - symbols_strings.push(symbol.to_string()); - names.insert( - 0, - match alias { - Some(alias) => alias, - None => symbol, - }, - ); - } - if import_star { - self.emit(Instruction::ImportStar { - name: module.clone(), - }); - } else { - self.emit(Instruction::Import { - name: module.clone(), - symbols: symbols_strings, - }); - names.iter().for_each(|name| self.store_name(&name)); - } + // from module import symbol + // from module import symbol as alias + let (names, symbols_strings): (Vec, Vec) = symbols + .iter() + .map(|ast::ImportSymbol { symbol, alias }| { + ( + alias.clone().unwrap_or_else(|| symbol.to_string()), + symbol.to_string(), + ) + }) + .unzip(); + self.emit(Instruction::Import { + name: module.clone(), + symbols: symbols_strings, + }); + names.iter().rev().for_each(|name| self.store_name(&name)); } } } From e61fa6dd7482d4bdfebddad1462c596604ba9853 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 14 Jun 2019 08:49:20 +0300 Subject: [PATCH 755/884] Use Iterator to create from_list --- vm/src/frame.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 8aaed07081..8ea1148547 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -908,10 +908,10 @@ impl Frame { } fn import(&self, vm: &VirtualMachine, module: &str, symbols: &Vec) -> FrameResult { - let mut from_list = vec![]; - for symbol in symbols { - from_list.push(vm.ctx.new_str(symbol.to_string())); - } + let from_list = symbols + .iter() + .map(|symbol| vm.ctx.new_str(symbol.to_string())) + .collect(); let module = vm.import(module, &vm.ctx.new_tuple(from_list))?; if symbols.is_empty() { From 60e799727f9945510e9de5a58c01fc2d95d09682 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 14 Jun 2019 15:25:24 +0300 Subject: [PATCH 756/884] Support reversed on sequence --- tests/snippets/builtin_reversed.py | 3 +++ vm/src/builtins.rs | 22 ++++++++++++++++------ vm/src/obj/objiter.rs | 29 ++++++++++++++++++----------- vm/src/pyobject.rs | 1 + 4 files changed, 38 insertions(+), 17 deletions(-) diff --git a/tests/snippets/builtin_reversed.py b/tests/snippets/builtin_reversed.py index 2bbfcb98a2..261b5c3263 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 9779a50547..6982e0493b 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 9aebc85f67..0fcd078c7c 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 d0bccd856e..e1c2cd42cb 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(), From e592f3d888af7e3a37b8119869363b993f219b03 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 14 Jun 2019 10:42:45 -0500 Subject: [PATCH 757/884] Add py_compile_bytecode! macro --- Cargo.lock | 9 +++ compiler/src/bytecode.rs | 2 + derive/Cargo.toml | 2 + derive/src/compile_bytecode.rs | 138 +++++++++++++++++++++++++++++++++ derive/src/lib.rs | 6 ++ parser/src/lexer.rs | 4 +- vm/Cargo.toml | 1 + vm/src/frozen.rs | 37 ++++----- vm/src/import.rs | 33 ++++---- vm/src/lib.rs | 2 + vm/src/stdlib/imp.rs | 19 +++-- vm/src/vm.rs | 2 +- 12 files changed, 206 insertions(+), 49 deletions(-) create mode 100644 derive/src/compile_bytecode.rs diff --git a/Cargo.lock b/Cargo.lock index 7bb6469d7d..c7109c005e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -495,6 +495,11 @@ dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "maplit" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memchr" version = "2.2.0" @@ -916,6 +921,8 @@ version = "0.1.0" dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rustpython_compiler 0.1.0", + "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -948,6 +955,7 @@ dependencies = [ "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1547,6 +1555,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)" = "6281b86796ba5e4366000be6e9e18bf35580adf9e63fbe2294aadb587613a319" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" +"checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" diff --git a/compiler/src/bytecode.rs b/compiler/src/bytecode.rs index aede69c934..6fdfaf32cf 100644 --- a/compiler/src/bytecode.rs +++ b/compiler/src/bytecode.rs @@ -9,6 +9,8 @@ use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fmt; +pub use ast::{ConversionFlag, Location}; + /// Primary container of a single code object. Each python function has /// a codeobject. Also a module has a codeobject. #[derive(Clone, PartialEq, Serialize, Deserialize)] diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 91fdc150e9..b8c45fe8db 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -11,3 +11,5 @@ proc-macro = true syn = { version = "0.15.29", features = ["full"] } quote = "0.6.11" proc-macro2 = "0.4.27" +rustpython_compiler = { path = "../compiler" } +serde_json = "1.0" diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs new file mode 100644 index 0000000000..564027d6b5 --- /dev/null +++ b/derive/src/compile_bytecode.rs @@ -0,0 +1,138 @@ +use super::Diagnostic; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use rustpython_compiler::{bytecode::CodeObject, compile}; +use serde_json; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use syn::parse::{Parse, ParseStream, Result as ParseResult}; +use syn::{self, parse2, Ident, Lit, Meta, MetaList, NestedMeta, Token}; + +struct BytecodeConst { + ident: Ident, + meta: MetaList, +} + +impl BytecodeConst { + fn compile(&self, manifest_dir: &Path) -> Result { + let meta = &self.meta; + + let mut source_path = None; + let mut mode = None; + let mut source_lit = None; + + for meta in &meta.nested { + match meta { + NestedMeta::Literal(lit) => source_lit = Some(lit), + NestedMeta::Meta(Meta::NameValue(name_value)) => { + if name_value.ident == "mode" { + mode = Some(match &name_value.lit { + Lit::Str(s) => match s.value().as_str() { + "exec" => compile::Mode::Exec, + "eval" => compile::Mode::Eval, + "single" => compile::Mode::Single, + _ => bail_span!(s, "mode must be exec, eval, or single"), + }, + _ => bail_span!(name_value.lit, "mode must be a string"), + }) + } else if name_value.ident == "source_path" { + source_path = Some(match &name_value.lit { + Lit::Str(s) => s.value(), + _ => bail_span!(name_value.lit, "source_path must be string"), + }) + } + } + _ => {} + } + } + + let source = if meta.ident == "file" { + let path = match source_lit { + Some(Lit::Str(s)) => s.value(), + _ => bail_span!(source_lit, "Expected string literal for path to file()"), + }; + let path = manifest_dir.join(path); + fs::read_to_string(&path) + .map_err(|err| err_span!(source_lit, "Error reading file {:?}: {}", path, err))? + } else if meta.ident == "source" { + match source_lit { + Some(Lit::Str(s)) => s.value(), + _ => bail_span!(source_lit, "Expected string literal for source()"), + } + } else { + bail_span!(meta.ident, "Expected either 'file' or 'source'") + }; + + compile::compile( + &source, + &mode.unwrap_or(compile::Mode::Exec), + source_path.unwrap_or_else(|| "".to_string()), + ) + .map_err(|err| err_span!(source_lit, "Compile error: {}", err)) + } +} + +impl Parse for BytecodeConst { + /// Parse the form `static ref IDENT = metalist(...);` + fn parse(input: ParseStream) -> ParseResult { + input.parse::()?; + input.parse::()?; + let ident = input.parse()?; + input.parse::()?; + let meta = input.parse()?; + input.parse::()?; + Ok(BytecodeConst { ident, meta }) + } +} + +struct PyCompileInput(Vec); + +impl Parse for PyCompileInput { + fn parse(input: ParseStream) -> ParseResult { + std::iter::from_fn(|| { + if input.is_empty() { + None + } else { + Some(input.parse()) + } + }) + .collect::>() + .map(PyCompileInput) + } +} + +pub fn impl_py_compile_bytecode(input: TokenStream2) -> Result { + let PyCompileInput(consts) = parse2(input)?; + + let manifest_dir = PathBuf::from( + env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not present"), + ); + + // let name_prefix = &parse_quote!(::rustpython_vm::bytecode); + + let consts = consts + .into_iter() + .map(|bytecode_const| -> Result<_, Diagnostic> { + let code_obj = bytecode_const.compile(&manifest_dir)?; + let ident = bytecode_const.ident; + let json = serde_json::to_string(&code_obj).expect("Failed to serialize"); + // TODO: Figure out some way of outputting this as something more efficient to + // deserialize without crashing rustc with a SIGKILL (yes, that happened) + Ok(quote! { + static ref #ident: ::rustpython_vm::bytecode::CodeObject = { + use serde_json; + serde_json::from_str(#json).expect("Deserializing CodeObject failed") + }; + }) + }) + .collect::, _>>()?; + + let output = quote! { + lazy_static! { + #(#consts)* + } + }; + + Ok(output) +} diff --git a/derive/src/lib.rs b/derive/src/lib.rs index c88ca67692..bb73479cb2 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -4,6 +4,7 @@ extern crate proc_macro; #[macro_use] mod error; +mod compile_bytecode; mod from_args; mod pyclass; @@ -47,3 +48,8 @@ pub fn pystruct_sequence(attr: TokenStream, item: TokenStream) -> TokenStream { let item = parse_macro_input!(item as Item); result_to_tokens(pyclass::impl_pystruct_sequence(attr, item)) } + +#[proc_macro] +pub fn py_compile_bytecode(input: TokenStream) -> TokenStream { + result_to_tokens(compile_bytecode::impl_py_compile_bytecode(input.into())) +} diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 3efdf6fbcc..a54bbfea89 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -74,8 +74,8 @@ pub enum LexicalErrorType { #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct Location { - row: usize, - column: usize, + pub row: usize, + pub column: usize, } impl Location { diff --git a/vm/Cargo.toml b/vm/Cargo.toml index de53223ae9..da67d9ad71 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -32,6 +32,7 @@ indexmap = "1.0.2" crc = "^1.0.0" bincode = "1.1.4" unicode_categories = "0.1.1" +maplit = "1.0" # TODO: release and publish to crates.io diff --git a/vm/src/frozen.rs b/vm/src/frozen.rs index 42b42c65b2..34a45617ac 100644 --- a/vm/src/frozen.rs +++ b/vm/src/frozen.rs @@ -1,25 +1,20 @@ -use std::collections::hash_map::HashMap; +use crate::bytecode::CodeObject; +use std::collections::HashMap; -const HELLO: &str = "initialized = True +py_compile_bytecode! { + static ref HELLO = source( +"initialized = True print(\"Hello world!\") -"; - -const IMPORTLIB_BOOTSTRAP: &'static str = include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../Lib/importlib/_bootstrap.py" -)); -const IMPORTLIB_BOOTSTRAP_EXTERNAL: &'static str = include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../Lib/importlib/_bootstrap_external.py" -)); - -pub fn get_module_inits() -> HashMap { - let mut modules = HashMap::new(); - modules.insert("__hello__".to_string(), HELLO); - modules.insert("_frozen_importlib".to_string(), IMPORTLIB_BOOTSTRAP); - modules.insert( - "_frozen_importlib_external".to_string(), - IMPORTLIB_BOOTSTRAP_EXTERNAL, +", ); - modules + static ref IMPORTLIB_BOOTSTRAP = file("../Lib/importlib/_bootstrap.py"); + static ref IMPORTLIB_BOOTSTRAP_EXTERNAL = file("../Lib/importlib/_bootstrap_external.py"); +} + +pub fn get_module_inits() -> HashMap<&'static str, &'static CodeObject> { + hashmap! { + "__hello__" => &*HELLO, + "_frozen_importlib_external" => &*IMPORTLIB_BOOTSTRAP_EXTERNAL, + "_frozen_importlib" => &*IMPORTLIB_BOOTSTRAP, + } } diff --git a/vm/src/import.rs b/vm/src/import.rs index a24e936dce..cd36d84999 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -4,10 +4,11 @@ use std::path::PathBuf; +use crate::bytecode::CodeObject; use crate::compile; use crate::frame::Scope; -use crate::obj::{objsequence, objstr}; -use crate::pyobject::{ItemProtocol, PyResult}; +use crate::obj::{objcode, objsequence, objstr}; +use crate::pyobject::{ItemProtocol, PyResult, PyValue}; use crate::util; use crate::vm::VirtualMachine; @@ -24,13 +25,10 @@ pub fn init_importlib(vm: &VirtualMachine) -> PyResult { } pub fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { - if let Some(frozen) = vm.frozen.borrow().get(module_name) { - import_file( - vm, - module_name, - format!("frozen {}", module_name), - frozen.to_string(), - ) + if let Some(&frozen) = vm.frozen.borrow().get(module_name) { + let mut frozen = frozen.clone(); + frozen.source_path = format!("frozen {}", module_name); + import_codeobj(vm, module_name, frozen) } else { Err(vm.new_import_error(format!("Cannot import frozen module {}", module_name))) } @@ -83,25 +81,30 @@ pub fn import_file( file_path: String, content: String, ) -> PyResult { - let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); - let code_obj = vm - .compile(&content, &compile::Mode::Exec, file_path.clone()) + let code_obj = compile::compile(&content, &compile::Mode::Exec, file_path) .map_err(|err| vm.new_syntax_error(&err))?; - // trace!("Code object: {:?}", code_obj); + import_codeobj(vm, module_name, code_obj) +} +pub fn import_codeobj(vm: &VirtualMachine, module_name: &str, code_obj: CodeObject) -> PyResult { let attrs = vm.ctx.new_dict(); attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; + let file_path = &code_obj.source_path; if !file_path.starts_with("frozen") { // TODO: Should be removed after precompiling frozen modules. - attrs.set_item("__file__", vm.new_str(file_path), vm)?; + attrs.set_item("__file__", vm.new_str(file_path.to_owned()), vm)?; } let module = vm.ctx.new_module(module_name, attrs.clone()); // Store module in cache to prevent infinite loop with mutual importing libs: + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); sys_modules.set_item(module_name, module.clone(), vm)?; // Execute main code in module: - vm.run_code_obj(code_obj, Scope::with_builtins(None, attrs, vm))?; + vm.run_code_obj( + objcode::PyCode::new(code_obj).into_ref(vm), + Scope::with_builtins(None, attrs, vm), + )?; Ok(module) } diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 269a0a3b5b..71f8868f86 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -17,6 +17,8 @@ extern crate lazy_static; extern crate lexical; #[macro_use] extern crate log; +#[macro_use] +extern crate maplit; // extern crate env_logger; extern crate rustpython_parser; diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index fda6f11cda..46f994b375 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -1,6 +1,5 @@ -use crate::compile; use crate::import; -use crate::obj::objcode::PyCodeRef; +use crate::obj::objcode::PyCode; use crate::obj::objmodule::PyModuleRef; use crate::obj::objstr; use crate::obj::objstr::PyStringRef; @@ -55,14 +54,14 @@ fn imp_exec_builtin(_mod: PyModuleRef, _vm: &VirtualMachine) -> i32 { 0 } -fn imp_get_frozen_object(name: PyStringRef, vm: &VirtualMachine) -> PyResult { - let name_str = name.as_str(); - if let Some(frozen) = vm.frozen.borrow().get(name_str) { - vm.compile(frozen, &compile::Mode::Exec, format!("frozen {}", name_str)) - .map_err(|err| vm.new_syntax_error(&err)) - } else { - Err(vm.new_import_error(format!("No such frozen object named {}", name.as_str()))) - } +fn imp_get_frozen_object(name: PyStringRef, vm: &VirtualMachine) -> PyResult { + vm.frozen + .borrow() + .get(name.as_str()) + .map(|&frozen| PyCode::new(frozen.clone())) + .ok_or_else(|| { + vm.new_import_error(format!("No such frozen object named {}", name.as_str())) + }) } fn imp_init_frozen(name: PyStringRef, vm: &VirtualMachine) -> PyResult { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 3f23742a1f..fafdb4f326 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -53,7 +53,7 @@ pub struct VirtualMachine { pub frames: RefCell>, pub wasm_id: Option, pub exceptions: RefCell>, - pub frozen: RefCell>, + pub frozen: RefCell>, pub import_func: RefCell, } From 8143b69165ee98ad8549db9bf5341e05a88000ca Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 14 Jun 2019 10:47:28 -0500 Subject: [PATCH 758/884] Change imp_get_frozen_object --- compiler/src/bytecode.rs | 2 -- vm/src/stdlib/imp.rs | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/src/bytecode.rs b/compiler/src/bytecode.rs index 6fdfaf32cf..aede69c934 100644 --- a/compiler/src/bytecode.rs +++ b/compiler/src/bytecode.rs @@ -9,8 +9,6 @@ use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fmt; -pub use ast::{ConversionFlag, Location}; - /// Primary container of a single code object. Each python function has /// a codeobject. Also a module has a codeobject. #[derive(Clone, PartialEq, Serialize, Deserialize)] diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index 46f994b375..1f63388f0c 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -58,7 +58,11 @@ fn imp_get_frozen_object(name: PyStringRef, vm: &VirtualMachine) -> PyResult Date: Fri, 14 Jun 2019 12:26:35 -0500 Subject: [PATCH 759/884] Use bincode to deserialize into a CodeObject --- Cargo.lock | 2 +- derive/Cargo.toml | 2 +- derive/src/compile_bytecode.rs | 17 +++++++---------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7109c005e..ed984677fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -919,10 +919,10 @@ dependencies = [ name = "rustpython_derive" version = "0.1.0" dependencies = [ + "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_compiler 0.1.0", - "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/derive/Cargo.toml b/derive/Cargo.toml index b8c45fe8db..e5a3072720 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -12,4 +12,4 @@ syn = { version = "0.15.29", features = ["full"] } quote = "0.6.11" proc-macro2 = "0.4.27" rustpython_compiler = { path = "../compiler" } -serde_json = "1.0" +bincode = "1.1" diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index 564027d6b5..d4a1db1f3b 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -1,13 +1,13 @@ use super::Diagnostic; -use proc_macro2::TokenStream as TokenStream2; +use bincode; +use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use rustpython_compiler::{bytecode::CodeObject, compile}; -use serde_json; use std::env; use std::fs; use std::path::{Path, PathBuf}; use syn::parse::{Parse, ParseStream, Result as ParseResult}; -use syn::{self, parse2, Ident, Lit, Meta, MetaList, NestedMeta, Token}; +use syn::{self, parse2, Ident, Lit, LitByteStr, Meta, MetaList, NestedMeta, Token}; struct BytecodeConst { ident: Ident, @@ -109,20 +109,17 @@ pub fn impl_py_compile_bytecode(input: TokenStream2) -> Result Result<_, Diagnostic> { let code_obj = bytecode_const.compile(&manifest_dir)?; let ident = bytecode_const.ident; - let json = serde_json::to_string(&code_obj).expect("Failed to serialize"); - // TODO: Figure out some way of outputting this as something more efficient to - // deserialize without crashing rustc with a SIGKILL (yes, that happened) + let bytes = bincode::serialize(&code_obj).expect("Failed to serialize"); + let bytes = LitByteStr::new(&bytes, Span::call_site()); Ok(quote! { static ref #ident: ::rustpython_vm::bytecode::CodeObject = { - use serde_json; - serde_json::from_str(#json).expect("Deserializing CodeObject failed") + use bincode; + bincode::deserialize(#bytes).expect("Deserializing CodeObject failed") }; }) }) From 4d0eb06724ee22c8ec6063bd44f07dc8908effe5 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 14 Jun 2019 12:40:01 -0500 Subject: [PATCH 760/884] Default source_path --- derive/src/compile_bytecode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index d4a1db1f3b..9032cc62b8 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -67,7 +67,7 @@ impl BytecodeConst { compile::compile( &source, &mode.unwrap_or(compile::Mode::Exec), - source_path.unwrap_or_else(|| "".to_string()), + source_path.unwrap_or_else(|| "frozen".to_string()), ) .map_err(|err| err_span!(source_lit, "Compile error: {}", err)) } From 7e68f9924efed4df52f6abeb977bf36464e1c120 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 14 Jun 2019 14:52:30 -0500 Subject: [PATCH 761/884] Make Location's fields private again --- parser/src/lexer.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index a54bbfea89..3efdf6fbcc 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -74,8 +74,8 @@ pub enum LexicalErrorType { #[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] pub struct Location { - pub row: usize, - pub column: usize, + row: usize, + column: usize, } impl Location { From 48966a76e2f3ec4bdaec90ebf3afb83c3f865ef8 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 14 Jun 2019 18:20:02 -0500 Subject: [PATCH 762/884] Make py_compile_bytecode!() an expression --- Cargo.lock | 13 +++ derive/Cargo.toml | 1 + derive/src/compile_bytecode.rs | 186 ++++++++++++++++++--------------- derive/src/error.rs | 11 +- derive/src/lib.rs | 5 +- vm/Cargo.toml | 1 + vm/src/frozen.rs | 12 ++- vm/src/lib.rs | 4 + 8 files changed, 142 insertions(+), 91 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed984677fd..3b3525be21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -638,6 +638,16 @@ dependencies = [ "proc-macro-hack-impl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro-hack-impl" version = "0.3.3" @@ -920,6 +930,7 @@ name = "rustpython_derive" version = "0.1.0" dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_compiler 0.1.0", @@ -961,6 +972,7 @@ dependencies = [ "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-hack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "pwd 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1574,6 +1586,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum phf_shared 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)" = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" "checksum precomputed-hash 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" "checksum proc-macro-hack 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b773f824ff2a495833f85fcdddcf85e096949971decada2e93249fa2c6c3d32f" +"checksum proc-macro-hack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0c1dd4172a1e1f96f709341418f49b11ea6c2d95d53dca08c0f74cbd332d9cf3" "checksum proc-macro-hack-impl 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0f674ccc446da486175527473ec8aa064f980b0966bbf767ee743a5dff6244a7" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum pwd 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5dd32d8bece608e144ca20251e714ed107cdecdabb20c2d383cfc687825106a5" diff --git a/derive/Cargo.toml b/derive/Cargo.toml index e5a3072720..8ef8965620 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -13,3 +13,4 @@ quote = "0.6.11" proc-macro2 = "0.4.27" rustpython_compiler = { path = "../compiler" } bincode = "1.1" +proc-macro-hack = "0.5" diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index 9032cc62b8..26f943daa0 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -1,31 +1,76 @@ -use super::Diagnostic; +use crate::{extract_spans, Diagnostic}; use bincode; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; use rustpython_compiler::{bytecode::CodeObject, compile}; use std::env; use std::fs; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use syn::parse::{Parse, ParseStream, Result as ParseResult}; -use syn::{self, parse2, Ident, Lit, LitByteStr, Meta, MetaList, NestedMeta, Token}; +use syn::{self, parse2, Lit, LitByteStr, Meta, Token}; -struct BytecodeConst { - ident: Ident, - meta: MetaList, +enum CompilationSourceKind { + File(PathBuf), + SourceCode(String), } -impl BytecodeConst { - fn compile(&self, manifest_dir: &Path) -> Result { - let meta = &self.meta; +struct CompilationSource { + kind: CompilationSourceKind, + span: (Span, Span), +} + +impl CompilationSource { + fn compile(self, mode: &compile::Mode, source_path: String) -> Result { + let compile = |source| { + compile::compile(source, mode, source_path).map_err(|err| { + Diagnostic::spans_error(self.span, format!("Compile error: {}", err)) + }) + }; + + match &self.kind { + CompilationSourceKind::File(rel_path) => { + let mut path = PathBuf::from( + env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not present"), + ); + path.push(rel_path); + let source = fs::read_to_string(&path).map_err(|err| { + Diagnostic::spans_error( + self.span, + format!("Error reading file {:?}: {}", path, err), + ) + })?; + compile(&source) + } + CompilationSourceKind::SourceCode(code) => compile(code), + } + } +} + +struct PyCompileInput { + span: Span, + metas: Vec, +} +impl PyCompileInput { + fn compile(&self) -> Result { let mut source_path = None; let mut mode = None; - let mut source_lit = None; + let mut source: Option = None; + + fn assert_source_empty(source: &Option) -> Result<(), Diagnostic> { + if let Some(source) = source { + Err(Diagnostic::spans_error( + source.span.clone(), + "Cannot have more than one source", + )) + } else { + Ok(()) + } + } - for meta in &meta.nested { + for meta in &self.metas { match meta { - NestedMeta::Literal(lit) => source_lit = Some(lit), - NestedMeta::Meta(Meta::NameValue(name_value)) => { + Meta::NameValue(name_value) => { if name_value.ident == "mode" { mode = Some(match &name_value.lit { Lit::Str(s) => match s.value().as_str() { @@ -41,94 +86,69 @@ impl BytecodeConst { Lit::Str(s) => s.value(), _ => bail_span!(name_value.lit, "source_path must be string"), }) + } else if name_value.ident == "source" { + assert_source_empty(&source)?; + let code = match &name_value.lit { + Lit::Str(s) => s.value(), + _ => bail_span!(name_value.lit, "source must be a string"), + }; + source = Some(CompilationSource { + kind: CompilationSourceKind::SourceCode(code), + span: extract_spans(&name_value).unwrap(), + }); + } else if name_value.ident == "file" { + assert_source_empty(&source)?; + let path = match &name_value.lit { + Lit::Str(s) => PathBuf::from(s.value()), + _ => bail_span!(name_value.lit, "source must be a string"), + }; + source = Some(CompilationSource { + kind: CompilationSourceKind::File(path), + span: extract_spans(&name_value).unwrap(), + }); } } _ => {} } } - let source = if meta.ident == "file" { - let path = match source_lit { - Some(Lit::Str(s)) => s.value(), - _ => bail_span!(source_lit, "Expected string literal for path to file()"), - }; - let path = manifest_dir.join(path); - fs::read_to_string(&path) - .map_err(|err| err_span!(source_lit, "Error reading file {:?}: {}", path, err))? - } else if meta.ident == "source" { - match source_lit { - Some(Lit::Str(s)) => s.value(), - _ => bail_span!(source_lit, "Expected string literal for source()"), - } - } else { - bail_span!(meta.ident, "Expected either 'file' or 'source'") - }; - - compile::compile( - &source, - &mode.unwrap_or(compile::Mode::Exec), - source_path.unwrap_or_else(|| "frozen".to_string()), - ) - .map_err(|err| err_span!(source_lit, "Compile error: {}", err)) - } -} - -impl Parse for BytecodeConst { - /// Parse the form `static ref IDENT = metalist(...);` - fn parse(input: ParseStream) -> ParseResult { - input.parse::()?; - input.parse::()?; - let ident = input.parse()?; - input.parse::()?; - let meta = input.parse()?; - input.parse::()?; - Ok(BytecodeConst { ident, meta }) + source + .ok_or_else(|| { + Diagnostic::span_error( + self.span.clone(), + "Must have either file or source in py_compile_bytecode!()", + ) + })? + .compile( + &mode.unwrap_or(compile::Mode::Exec), + source_path.unwrap_or_else(|| "frozen".to_string()), + ) } } -struct PyCompileInput(Vec); - impl Parse for PyCompileInput { fn parse(input: ParseStream) -> ParseResult { - std::iter::from_fn(|| { - if input.is_empty() { - None - } else { - Some(input.parse()) - } - }) - .collect::>() - .map(PyCompileInput) + let span = input.cursor().span(); + let metas = input + .parse_terminated::(Meta::parse)? + .into_iter() + .collect(); + Ok(PyCompileInput { span, metas }) } } pub fn impl_py_compile_bytecode(input: TokenStream2) -> Result { - let PyCompileInput(consts) = parse2(input)?; - - let manifest_dir = PathBuf::from( - env::var_os("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR is not present"), - ); + let input: PyCompileInput = parse2(input)?; - let consts = consts - .into_iter() - .map(|bytecode_const| -> Result<_, Diagnostic> { - let code_obj = bytecode_const.compile(&manifest_dir)?; - let ident = bytecode_const.ident; - let bytes = bincode::serialize(&code_obj).expect("Failed to serialize"); - let bytes = LitByteStr::new(&bytes, Span::call_site()); - Ok(quote! { - static ref #ident: ::rustpython_vm::bytecode::CodeObject = { - use bincode; - bincode::deserialize(#bytes).expect("Deserializing CodeObject failed") - }; - }) - }) - .collect::, _>>()?; + let code_obj = input.compile()?; + let bytes = bincode::serialize(&code_obj).expect("Failed to serialize"); + let bytes = LitByteStr::new(&bytes, Span::call_site()); let output = quote! { - lazy_static! { - #(#consts)* - } + ({ + use bincode; + bincode::deserialize(#bytes).expect("Deserializing CodeObject failed") + }) }; Ok(output) diff --git a/derive/src/error.rs b/derive/src/error.rs index 8754b4a514..b9f25b63ac 100644 --- a/derive/src/error.rs +++ b/derive/src/error.rs @@ -85,6 +85,15 @@ impl Diagnostic { } } + pub fn spans_error>(spans: (Span, Span), text: T) -> Diagnostic { + Diagnostic { + inner: Repr::Single { + text: text.into(), + span: Some(spans), + }, + } + } + pub fn spanned_error>(node: &ToTokens, text: T) -> Diagnostic { Diagnostic { inner: Repr::Single { @@ -122,7 +131,7 @@ impl From for Diagnostic { } } -fn extract_spans(node: &ToTokens) -> Option<(Span, Span)> { +pub fn extract_spans(node: &dyn ToTokens) -> Option<(Span, Span)> { let mut t = TokenStream::new(); node.to_tokens(&mut t); let mut tokens = t.into_iter(); diff --git a/derive/src/lib.rs b/derive/src/lib.rs index bb73479cb2..dfcb7ea360 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -8,9 +8,10 @@ mod compile_bytecode; mod from_args; mod pyclass; -use error::Diagnostic; +use error::{extract_spans, Diagnostic}; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; +use proc_macro_hack::proc_macro_hack; use quote::ToTokens; use syn::{parse_macro_input, AttributeArgs, DeriveInput, Item}; @@ -49,7 +50,7 @@ pub fn pystruct_sequence(attr: TokenStream, item: TokenStream) -> TokenStream { result_to_tokens(pyclass::impl_pystruct_sequence(attr, item)) } -#[proc_macro] +#[proc_macro_hack] pub fn py_compile_bytecode(input: TokenStream) -> TokenStream { result_to_tokens(compile_bytecode::impl_py_compile_bytecode(input.into())) } diff --git a/vm/Cargo.toml b/vm/Cargo.toml index da67d9ad71..2687036db9 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -33,6 +33,7 @@ crc = "^1.0.0" bincode = "1.1.4" unicode_categories = "0.1.1" maplit = "1.0" +proc-macro-hack = "0.5" # TODO: release and publish to crates.io diff --git a/vm/src/frozen.rs b/vm/src/frozen.rs index 34a45617ac..72132340d5 100644 --- a/vm/src/frozen.rs +++ b/vm/src/frozen.rs @@ -1,14 +1,16 @@ use crate::bytecode::CodeObject; use std::collections::HashMap; -py_compile_bytecode! { - static ref HELLO = source( -"initialized = True +lazy_static! { + static ref HELLO: CodeObject = py_compile_bytecode!( + source = "initialized = True print(\"Hello world!\") ", ); - static ref IMPORTLIB_BOOTSTRAP = file("../Lib/importlib/_bootstrap.py"); - static ref IMPORTLIB_BOOTSTRAP_EXTERNAL = file("../Lib/importlib/_bootstrap_external.py"); + static ref IMPORTLIB_BOOTSTRAP: CodeObject = + py_compile_bytecode!(file = "../Lib/importlib/_bootstrap.py"); + static ref IMPORTLIB_BOOTSTRAP_EXTERNAL: CodeObject = + py_compile_bytecode!(file = "../Lib/importlib/_bootstrap_external.py"); } pub fn get_module_inits() -> HashMap<&'static str, &'static CodeObject> { diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 71f8868f86..a382f67226 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -29,6 +29,10 @@ extern crate self as rustpython_vm; pub use rustpython_derive::*; +use proc_macro_hack::proc_macro_hack; +#[proc_macro_hack] +pub use rustpython_derive::py_compile_bytecode; + //extern crate eval; use eval::eval::*; // use py_code_object::{Function, NativeType, PyCodeObject}; From b5ba62fe943a0f77c89903b525abd71a20e14d55 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 14 Jun 2019 19:49:37 -0500 Subject: [PATCH 763/884] Add lazy_static argument --- derive/src/compile_bytecode.rs | 46 ++++++++++++++++++++++++++++------ vm/src/frozen.rs | 27 +++++++++----------- 2 files changed, 51 insertions(+), 22 deletions(-) diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index 26f943daa0..44ee689685 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -51,11 +51,17 @@ struct PyCompileInput { metas: Vec, } +struct PyCompileResult { + code_obj: CodeObject, + lazy_static: bool, +} + impl PyCompileInput { - fn compile(&self) -> Result { + fn compile(&self) -> Result { let mut source_path = None; let mut mode = None; let mut source: Option = None; + let mut lazy_static = false; fn assert_source_empty(source: &Option) -> Result<(), Diagnostic> { if let Some(source) = source { @@ -108,11 +114,16 @@ impl PyCompileInput { }); } } + Meta::Word(ident) => { + if ident == "lazy_static" { + lazy_static = true; + } + } _ => {} } } - source + let code_obj = source .ok_or_else(|| { Diagnostic::span_error( self.span.clone(), @@ -122,7 +133,12 @@ impl PyCompileInput { .compile( &mode.unwrap_or(compile::Mode::Exec), source_path.unwrap_or_else(|| "frozen".to_string()), - ) + )?; + + Ok(PyCompileResult { + code_obj, + lazy_static, + }) } } @@ -140,16 +156,32 @@ impl Parse for PyCompileInput { pub fn impl_py_compile_bytecode(input: TokenStream2) -> Result { let input: PyCompileInput = parse2(input)?; - let code_obj = input.compile()?; + let PyCompileResult { + code_obj, + lazy_static, + } = input.compile()?; + let bytes = bincode::serialize(&code_obj).expect("Failed to serialize"); let bytes = LitByteStr::new(&bytes, Span::call_site()); let output = quote! { ({ - use bincode; - bincode::deserialize(#bytes).expect("Deserializing CodeObject failed") + use ::bincode; + bincode::deserialize::<::rustpython_vm::bytecode::CodeObject>(#bytes).expect("Deserializing CodeObject failed") }) }; - Ok(output) + if lazy_static { + Ok(quote! { + ({ + use ::lazy_static::lazy_static; + lazy_static! { + static ref STATIC: ::rustpython_vm::bytecode::CodeObject = #output; + } + &*STATIC + }) + }) + } else { + Ok(output) + } } diff --git a/vm/src/frozen.rs b/vm/src/frozen.rs index 72132340d5..3b6eaa8af6 100644 --- a/vm/src/frozen.rs +++ b/vm/src/frozen.rs @@ -1,22 +1,19 @@ use crate::bytecode::CodeObject; use std::collections::HashMap; -lazy_static! { - static ref HELLO: CodeObject = py_compile_bytecode!( - source = "initialized = True -print(\"Hello world!\") -", - ); - static ref IMPORTLIB_BOOTSTRAP: CodeObject = - py_compile_bytecode!(file = "../Lib/importlib/_bootstrap.py"); - static ref IMPORTLIB_BOOTSTRAP_EXTERNAL: CodeObject = - py_compile_bytecode!(file = "../Lib/importlib/_bootstrap_external.py"); -} - pub fn get_module_inits() -> HashMap<&'static str, &'static CodeObject> { hashmap! { - "__hello__" => &*HELLO, - "_frozen_importlib_external" => &*IMPORTLIB_BOOTSTRAP_EXTERNAL, - "_frozen_importlib" => &*IMPORTLIB_BOOTSTRAP, + "__hello__" => py_compile_bytecode!( + lazy_static, + source = "initialized = True; print(\"Hello world!\")\n", + ), + "_frozen_importlib" => py_compile_bytecode!( + lazy_static, + file = "../Lib/importlib/_bootstrap.py", + ), + "_frozen_importlib_external" => py_compile_bytecode!( + lazy_static, + file = "../Lib/importlib/_bootstrap_external.py", + ), } } From 241f5190cc09ec3474cacf842233e4b7946ddf8f Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 15 Jun 2019 00:30:14 -0500 Subject: [PATCH 764/884] Formatting --- derive/src/compile_bytecode.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index 44ee689685..a6979f4858 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -167,7 +167,8 @@ pub fn impl_py_compile_bytecode(input: TokenStream2) -> Result(#bytes).expect("Deserializing CodeObject failed") + bincode::deserialize::<::rustpython_vm::bytecode::CodeObject>(#bytes) + .expect("Deserializing CodeObject failed") }) }; From 82f83ef345f02c5afa45a8f54f0e898f6845eb1a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 14 Jun 2019 19:20:00 +0300 Subject: [PATCH 765/884] Support more open flags --- Cargo.lock | 1 + tests/snippets/stdlib_os.py | 16 +++++---- vm/Cargo.toml | 1 + vm/src/builtins.rs | 1 + vm/src/exceptions.rs | 3 ++ vm/src/stdlib/io.rs | 25 ++++++++------ vm/src/stdlib/os.rs | 65 +++++++++++++++++++++++++++++-------- 7 files changed, 82 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7bb6469d7d..ad9272003c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -939,6 +939,7 @@ name = "rustpython_vm" version = "0.1.0" dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index fe0b757211..6280fc3bb7 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -4,12 +4,13 @@ from testutils import assert_raises -fd = os.open('README.md', 0) +fd = os.open('README.md', os.O_RDONLY) assert fd > 0 os.close(fd) assert_raises(OSError, lambda: os.read(fd, 10)) -assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', 0)) +assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', os.O_RDONLY)) +assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', os.O_WRONLY)) assert os.O_RDONLY == 0 @@ -88,14 +89,17 @@ def __exit__(self, exc_type, exc_val, exc_tb): with TestWithTempDir() as tmpdir: fname = os.path.join(tmpdir, FILE_NAME) - with open(fname, "wb"): - pass - fd = os.open(fname, 1) + fd = os.open(fname, os.O_WRONLY | os.O_CREAT | os.O_EXCL) assert os.write(fd, CONTENT2) == len(CONTENT2) + os.close(fd) + + fd = os.open(fname, os.O_WRONLY | os.O_APPEND) assert os.write(fd, CONTENT3) == len(CONTENT3) os.close(fd) - fd = os.open(fname, 0) + assert_raises(FileExistsError, lambda: os.open(fname, os.O_WRONLY | os.O_CREAT | os.O_EXCL)) + + fd = os.open(fname, os.O_RDONLY) assert os.read(fd, len(CONTENT2)) == CONTENT2 assert os.read(fd, len(CONTENT3)) == CONTENT3 os.close(fd) diff --git a/vm/Cargo.toml b/vm/Cargo.toml index de53223ae9..c7159acd67 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -32,6 +32,7 @@ indexmap = "1.0.2" crc = "^1.0.0" bincode = "1.1.4" unicode_categories = "0.1.1" +bitflags = "1.1" # TODO: release and publish to crates.io diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 6982e0493b..570b1fbbf1 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -856,6 +856,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "IndexError" => ctx.exceptions.index_error.clone(), "ImportError" => ctx.exceptions.import_error.clone(), "FileNotFoundError" => ctx.exceptions.file_not_found_error.clone(), + "FileExistsError" => ctx.exceptions.file_exists_error.clone(), "StopIteration" => ctx.exceptions.stop_iteration.clone(), "ZeroDivisionError" => ctx.exceptions.zero_division_error.clone(), "KeyError" => ctx.exceptions.key_error.clone(), diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 4f1f947447..0c243d0a15 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -148,6 +148,7 @@ pub struct ExceptionZoo { pub base_exception_type: PyClassRef, pub exception_type: PyClassRef, pub file_not_found_error: PyClassRef, + pub file_exists_error: PyClassRef, pub import_error: PyClassRef, pub index_error: PyClassRef, pub key_error: PyClassRef, @@ -203,6 +204,7 @@ impl ExceptionZoo { let not_implemented_error = create_type("NotImplementedError", &type_type, &runtime_error); let file_not_found_error = create_type("FileNotFoundError", &type_type, &os_error); let permission_error = create_type("PermissionError", &type_type, &os_error); + let file_exists_error = create_type("FileExistsError", &type_type, &os_error); let warning = create_type("Warning", &type_type, &exception_type); let bytes_warning = create_type("BytesWarning", &type_type, &warning); @@ -224,6 +226,7 @@ impl ExceptionZoo { base_exception_type, exception_type, file_not_found_error, + file_exists_error, import_error, index_error, key_error, diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 2dafc71377..d7db77bc6a 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -21,16 +21,6 @@ use crate::obj::objtype::PyClassRef; use crate::pyobject::{BufferProtocol, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; -fn compute_c_flag(mode: &str) -> u16 { - match mode { - "w" => 512, - "x" => 512, - "a" => 8, - "+" => 2, - _ => 0, - } -} - #[derive(Debug)] struct PyStringIO { data: RefCell, @@ -132,6 +122,21 @@ fn buffered_reader_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.ctx.new_bytes(result)) } +fn compute_c_flag(mode: &str) -> u32 { + let flags = match mode { + "w" => os::FileCreationFlags::O_WRONLY | os::FileCreationFlags::O_CREAT, + "x" => { + os::FileCreationFlags::O_WRONLY + | os::FileCreationFlags::O_CREAT + | os::FileCreationFlags::O_EXCL + } + "a" => os::FileCreationFlags::O_APPEND, + "+" => os::FileCreationFlags::O_RDWR, + _ => os::FileCreationFlags::O_RDONLY, + }; + flags.bits() +} + fn file_io_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 14d720df6b..e6ab649143 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -5,6 +5,7 @@ use std::io::{self, ErrorKind, Read, Write}; use std::time::{Duration, SystemTime}; use std::{env, fs}; +use bitflags::bitflags; use num_traits::cast::ToPrimitive; use crate::function::{IntoPyNativeFunc, PyFuncArgs}; @@ -81,13 +82,26 @@ pub fn os_close(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) } +bitflags! { + pub struct FileCreationFlags: u32 { + // https://elixir.bootlin.com/linux/v4.8/source/include/uapi/asm-generic/fcntl.h + const O_RDONLY = 0o0000_0000; + const O_WRONLY = 0o0000_0001; + const O_RDWR = 0o0000_0002; + const O_CREAT = 0o0000_0100; + const O_EXCL = 0o0000_0200; + const O_APPEND = 0o0000_2000; + const O_NONBLOCK = 0o0000_4000; + } +} + pub fn os_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, required = [ (name, Some(vm.ctx.str_type())), - (mode, Some(vm.ctx.int_type())) + (flags, Some(vm.ctx.int_type())) ], optional = [(dir_fd, Some(vm.ctx.int_type()))] ); @@ -102,14 +116,32 @@ pub fn os_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { }; let fname = &make_path(vm, name, &dir_fd).value; - let handle = match objint::get_value(mode).to_u16().unwrap() { - 0 => OpenOptions::new().read(true).open(&fname), - 1 => OpenOptions::new().write(true).open(&fname), - 2 => OpenOptions::new().read(true).write(true).open(&fname), - 512 => OpenOptions::new().write(true).create(true).open(&fname), - _ => OpenOptions::new().read(true).open(&fname), + let flags = FileCreationFlags::from_bits(objint::get_value(flags).to_u32().unwrap()) + .ok_or(vm.new_value_error("Unsupported flag".to_string()))?; + + let mut options = &mut OpenOptions::new(); + + if flags.contains(FileCreationFlags::O_WRONLY) { + options = options.write(true); + } else if flags.contains(FileCreationFlags::O_RDWR) { + options = options.read(true).write(true); + } else { + options = options.read(true); } - .map_err(|err| match err.kind() { + + if flags.contains(FileCreationFlags::O_APPEND) { + options = options.append(true); + } + + if flags.contains(FileCreationFlags::O_CREAT) { + if flags.contains(FileCreationFlags::O_EXCL) { + options = options.create_new(true); + } else { + options = options.create(true); + } + } + + let handle = options.open(&fname).map_err(|err| match err.kind() { ErrorKind::NotFound => { let exc_type = vm.ctx.exceptions.file_not_found_error.clone(); vm.new_exception(exc_type, format!("No such file or directory: {}", &fname)) @@ -118,6 +150,10 @@ pub fn os_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let exc_type = vm.ctx.exceptions.permission_error.clone(); vm.new_exception(exc_type, format!("Permission denied: {}", &fname)) } + ErrorKind::AlreadyExists => { + let exc_type = vm.ctx.exceptions.file_exists_error.clone(); + vm.new_exception(exc_type, format!("File exists: {}", &fname)) + } _ => vm.new_value_error("Unhandled file IO error".to_string()), })?; @@ -743,12 +779,13 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "getcwd" => ctx.new_rustfunc(os_getcwd), "chdir" => ctx.new_rustfunc(os_chdir), "fspath" => ctx.new_rustfunc(os_fspath), - "O_RDONLY" => ctx.new_int(0), - "O_WRONLY" => ctx.new_int(1), - "O_RDWR" => ctx.new_int(2), - "O_NONBLOCK" => ctx.new_int(4), - "O_APPEND" => ctx.new_int(8), - "O_CREAT" => ctx.new_int(512) + "O_RDONLY" => ctx.new_int(FileCreationFlags::O_RDONLY.bits()), + "O_WRONLY" => ctx.new_int(FileCreationFlags::O_WRONLY.bits()), + "O_RDWR" => ctx.new_int(FileCreationFlags::O_RDWR.bits()), + "O_NONBLOCK" => ctx.new_int(FileCreationFlags::O_NONBLOCK.bits()), + "O_APPEND" => ctx.new_int(FileCreationFlags::O_APPEND.bits()), + "O_EXCL" => ctx.new_int(FileCreationFlags::O_EXCL.bits()), + "O_CREAT" => ctx.new_int(FileCreationFlags::O_CREAT.bits()) }); for support in support_funcs { From 92ad30ef6a76d4f2e37bbcdd6bbacfdefe858715 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 15 Jun 2019 17:02:46 +0300 Subject: [PATCH 766/884] Add mode argument to os.open --- vm/src/stdlib/os.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index e6ab649143..60fc6b11b9 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -103,7 +103,10 @@ pub fn os_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { (name, Some(vm.ctx.str_type())), (flags, Some(vm.ctx.int_type())) ], - optional = [(dir_fd, Some(vm.ctx.int_type()))] + optional = [ + (_mode, Some(vm.ctx.int_type())), + (dir_fd, Some(vm.ctx.int_type())) + ] ); let name = name.clone().downcast::().unwrap(); From 75c2e4ae9be1d78bed78335450d8500d5523e56f Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 15 Jun 2019 11:31:30 -0500 Subject: [PATCH 767/884] Remove lazy_static, just store as a `CodeObject` --- derive/src/compile_bytecode.rs | 41 +++++----------------------------- vm/src/frozen.rs | 11 ++++----- vm/src/import.rs | 2 +- vm/src/stdlib/imp.rs | 2 +- vm/src/vm.rs | 2 +- 5 files changed, 12 insertions(+), 46 deletions(-) diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index a6979f4858..310b5b8b1d 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -51,17 +51,11 @@ struct PyCompileInput { metas: Vec, } -struct PyCompileResult { - code_obj: CodeObject, - lazy_static: bool, -} - impl PyCompileInput { - fn compile(&self) -> Result { + fn compile(&self) -> Result { let mut source_path = None; let mut mode = None; let mut source: Option = None; - let mut lazy_static = false; fn assert_source_empty(source: &Option) -> Result<(), Diagnostic> { if let Some(source) = source { @@ -114,16 +108,11 @@ impl PyCompileInput { }); } } - Meta::Word(ident) => { - if ident == "lazy_static" { - lazy_static = true; - } - } _ => {} } } - let code_obj = source + source .ok_or_else(|| { Diagnostic::span_error( self.span.clone(), @@ -133,12 +122,7 @@ impl PyCompileInput { .compile( &mode.unwrap_or(compile::Mode::Exec), source_path.unwrap_or_else(|| "frozen".to_string()), - )?; - - Ok(PyCompileResult { - code_obj, - lazy_static, - }) + ) } } @@ -156,10 +140,7 @@ impl Parse for PyCompileInput { pub fn impl_py_compile_bytecode(input: TokenStream2) -> Result { let input: PyCompileInput = parse2(input)?; - let PyCompileResult { - code_obj, - lazy_static, - } = input.compile()?; + let code_obj = input.compile()?; let bytes = bincode::serialize(&code_obj).expect("Failed to serialize"); let bytes = LitByteStr::new(&bytes, Span::call_site()); @@ -172,17 +153,5 @@ pub fn impl_py_compile_bytecode(input: TokenStream2) -> Result HashMap<&'static str, &'static CodeObject> { +pub fn get_module_inits() -> HashMap { hashmap! { - "__hello__" => py_compile_bytecode!( - lazy_static, + "__hello__".into() => py_compile_bytecode!( source = "initialized = True; print(\"Hello world!\")\n", ), - "_frozen_importlib" => py_compile_bytecode!( - lazy_static, + "_frozen_importlib".into() => py_compile_bytecode!( file = "../Lib/importlib/_bootstrap.py", ), - "_frozen_importlib_external" => py_compile_bytecode!( - lazy_static, + "_frozen_importlib_external".into() => py_compile_bytecode!( file = "../Lib/importlib/_bootstrap_external.py", ), } diff --git a/vm/src/import.rs b/vm/src/import.rs index cd36d84999..0be1b4af2b 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -25,7 +25,7 @@ pub fn init_importlib(vm: &VirtualMachine) -> PyResult { } pub fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { - if let Some(&frozen) = vm.frozen.borrow().get(module_name) { + if let Some(frozen) = vm.frozen.borrow().get(module_name) { let mut frozen = frozen.clone(); frozen.source_path = format!("frozen {}", module_name); import_codeobj(vm, module_name, frozen) diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index 1f63388f0c..184fc5003c 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -58,7 +58,7 @@ fn imp_get_frozen_object(name: PyStringRef, vm: &VirtualMachine) -> PyResult>, pub wasm_id: Option, pub exceptions: RefCell>, - pub frozen: RefCell>, + pub frozen: RefCell>, pub import_func: RefCell, } From 6b9bbf3ea4b5e15806ad68959acdc53418af237e Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 15 Jun 2019 13:23:04 -0500 Subject: [PATCH 768/884] Change TODO --- vm/src/import.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index 0be1b4af2b..ad1fd8e6ab 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -91,7 +91,7 @@ pub fn import_codeobj(vm: &VirtualMachine, module_name: &str, code_obj: CodeObje attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; let file_path = &code_obj.source_path; if !file_path.starts_with("frozen") { - // TODO: Should be removed after precompiling frozen modules. + // TODO: Should be less hacky, not depend on source_path attrs.set_item("__file__", vm.new_str(file_path.to_owned()), vm)?; } let module = vm.ctx.new_module(module_name, attrs.clone()); From 1814977c458c044387f5ef9cb48e83b394ba028b Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 15 Jun 2019 20:19:59 -0500 Subject: [PATCH 769/884] Add doc comments --- derive/src/compile_bytecode.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index 310b5b8b1d..35db2eb044 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -1,3 +1,18 @@ +//! Parsing and processing for this form: +//! ```ignore +//! py_compile_input!( +//! // either: +//! source = "python_source_code", +//! // or +//! file = "file/path/relative/to/$CARGO_MANIFEST_DIR", +//! +//! // the mode to compile the code in +//! mode = "exec", // or "eval" or "single" +//! // the path put into the CodeObject, defaults to `None` +//! source_path = "frozen", +//! ) +//! ``` + use crate::{extract_spans, Diagnostic}; use bincode; use proc_macro2::{Span, TokenStream as TokenStream2}; @@ -46,6 +61,7 @@ impl CompilationSource { } } +/// This is essentially just a comma-separated list of Meta nodes, aka the inside of a MetaList. struct PyCompileInput { span: Span, metas: Vec, From b6b8f6ec9c7c81540bd8f7d8f83bdaa78742de86 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 14 Jun 2019 21:35:29 -0500 Subject: [PATCH 770/884] Have stdlib::get_module_inits() use maplit::hashmap! --- vm/src/stdlib/mod.rs | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 75e554cd63..9b413de32e 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -34,29 +34,27 @@ use crate::pyobject::PyObjectRef; pub type StdlibInitFunc = Box PyObjectRef>; pub fn get_module_inits() -> HashMap { - let mut modules = HashMap::new(); - modules.insert( - "ast".to_string(), - Box::new(ast::make_module) as StdlibInitFunc, - ); - modules.insert("binascii".to_string(), Box::new(binascii::make_module)); - modules.insert("dis".to_string(), Box::new(dis::make_module)); - modules.insert("itertools".to_string(), Box::new(itertools::make_module)); - modules.insert("json".to_string(), Box::new(json::make_module)); - modules.insert("keyword".to_string(), Box::new(keyword::make_module)); - modules.insert("marshal".to_string(), Box::new(marshal::make_module)); - modules.insert("math".to_string(), Box::new(math::make_module)); - modules.insert("platform".to_string(), Box::new(platform::make_module)); - modules.insert("re".to_string(), Box::new(re::make_module)); - modules.insert("random".to_string(), Box::new(random::make_module)); - modules.insert("string".to_string(), Box::new(string::make_module)); - modules.insert("struct".to_string(), Box::new(pystruct::make_module)); - modules.insert("_thread".to_string(), Box::new(thread::make_module)); - modules.insert("time".to_string(), Box::new(time_module::make_module)); - modules.insert("tokenize".to_string(), Box::new(tokenize::make_module)); - modules.insert("_weakref".to_string(), Box::new(weakref::make_module)); - modules.insert("_imp".to_string(), Box::new(imp::make_module)); - modules.insert("_warnings".to_string(), Box::new(warnings::make_module)); + let mut modules = hashmap! { + "ast".to_string() => Box::new(ast::make_module) as StdlibInitFunc, + "binascii".to_string() => Box::new(binascii::make_module), + "dis".to_string() => Box::new(dis::make_module), + "itertools".to_string() => Box::new(itertools::make_module), + "json".to_string() => Box::new(json::make_module), + "keyword".to_string() => Box::new(keyword::make_module), + "marshal".to_string() => Box::new(marshal::make_module), + "math".to_string() => Box::new(math::make_module), + "platform".to_string() => Box::new(platform::make_module), + "re".to_string() => Box::new(re::make_module), + "random".to_string() => Box::new(random::make_module), + "string".to_string() => Box::new(string::make_module), + "struct".to_string() => Box::new(pystruct::make_module), + "_thread".to_string() => Box::new(thread::make_module), + "time".to_string() => Box::new(time_module::make_module), + "tokenize".to_string() => Box::new(tokenize::make_module), + "_weakref".to_string() => Box::new(weakref::make_module), + "_imp".to_string() => Box::new(imp::make_module), + "_warnings".to_string() => Box::new(warnings::make_module), + }; // disable some modules on WASM #[cfg(not(target_arch = "wasm32"))] From 9b3521904a04ea1352719e4b78431398430be13a Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sun, 16 Jun 2019 15:47:27 -0500 Subject: [PATCH 771/884] Update bench.rs to use vm.compile() --- benchmarks/bench.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmarks/bench.rs b/benchmarks/bench.rs index 256fa47ad9..55f2763a5e 100644 --- a/benchmarks/bench.rs +++ b/benchmarks/bench.rs @@ -93,7 +93,7 @@ fn bench_rustpy_nbody(b: &mut test::Bencher) { let vm = VirtualMachine::new(); - let code = match compile::compile(&vm, source, &compile::Mode::Single, "".to_string()) { + let code = match vm.compile(source, &compile::Mode::Single, "".to_string()) { Ok(code) => code, Err(e) => panic!("{:?}", e), }; @@ -112,7 +112,7 @@ fn bench_rustpy_mandelbrot(b: &mut test::Bencher) { let vm = VirtualMachine::new(); - let code = match compile::compile(&vm, source, &compile::Mode::Single, "".to_string()) { + let code = match vm.compile(source, &compile::Mode::Single, "".to_string()) { Ok(code) => code, Err(e) => panic!("{:?}", e), }; From b74b65d37e2c0939c064fedd18405abd1ac7cc84 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Sat, 15 Jun 2019 13:20:16 -0500 Subject: [PATCH 772/884] Make CodeObject.source_path an Option --- compiler/src/bytecode.rs | 4 ++-- compiler/src/compile.rs | 27 +++++++++++++++++---------- derive/src/compile_bytecode.rs | 11 ++++++----- src/main.rs | 4 ++-- vm/src/builtins.rs | 6 +++--- vm/src/eval.rs | 13 ++++++++----- vm/src/frame.rs | 7 +++++-- vm/src/import.rs | 23 ++++++++++------------- vm/src/obj/objcode.rs | 2 +- vm/src/stdlib/imp.rs | 7 ++----- vm/src/vm.rs | 2 +- wasm/lib/src/browser_module.rs | 2 +- wasm/lib/src/vm_class.rs | 2 +- 13 files changed, 59 insertions(+), 51 deletions(-) diff --git a/compiler/src/bytecode.rs b/compiler/src/bytecode.rs index c87bc0ae0b..b5b3c11d31 100644 --- a/compiler/src/bytecode.rs +++ b/compiler/src/bytecode.rs @@ -21,7 +21,7 @@ pub struct CodeObject { pub varargs: Varargs, // *args or * pub kwonlyarg_names: Vec, pub varkeywords: Varargs, // **kwargs or ** - pub source_path: String, + pub source_path: Option, pub first_line_number: usize, pub obj_name: String, // Name of the object that created this code object pub is_generator: bool, @@ -269,7 +269,7 @@ impl CodeObject { varargs: Varargs, kwonlyarg_names: Vec, varkeywords: Varargs, - source_path: String, + source_path: Option, first_line_number: usize, obj_name: String, ) -> CodeObject { diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index 8b806b1d1a..0efa3adfb2 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -23,7 +23,11 @@ struct Compiler { } /// Compile a given sourcecode into a bytecode object. -pub fn compile(source: &str, mode: &Mode, source_path: String) -> Result { +pub fn compile( + source: &str, + mode: &Mode, + source_path: Option, +) -> Result { match mode { Mode::Exec => { let ast = parser::parse_program(source)?; @@ -42,11 +46,11 @@ pub fn compile(source: &str, mode: &Mode, source_path: String) -> Result, f: impl FnOnce(&mut Compiler) -> Result<(), CompileError>, ) -> Result { let mut compiler = Compiler::new(); - compiler.source_path = Some(source_path); + compiler.source_path = source_path; compiler.push_new_code_object("".to_string()); f(&mut compiler)?; let code = compiler.pop_code_object(); @@ -55,7 +59,10 @@ fn with_compiler( } /// Compile a standard Python program to bytecode -pub fn compile_program(ast: ast::Program, source_path: String) -> Result { +pub fn compile_program( + ast: ast::Program, + source_path: Option, +) -> Result { with_compiler(source_path, |compiler| { let symbol_table = make_symbol_table(&ast)?; compiler.compile_program(&ast, symbol_table) @@ -65,7 +72,7 @@ pub fn compile_program(ast: ast::Program, source_path: String) -> Result, - source_path: String, + source_path: Option, ) -> Result { with_compiler(source_path, |compiler| { let symbol_table = statements_to_symbol_table(&statement)?; @@ -76,7 +83,7 @@ pub fn compile_statement_eval( /// Compile a Python program to bytecode for the context of a REPL pub fn compile_program_single( ast: ast::Program, - source_path: String, + source_path: Option, ) -> Result { with_compiler(source_path, |compiler| { let symbol_table = make_symbol_table(&ast)?; @@ -119,7 +126,7 @@ impl Compiler { Varargs::None, Vec::new(), Varargs::None, - self.source_path.clone().unwrap(), + self.source_path.clone(), line_number, obj_name, )); @@ -596,7 +603,7 @@ impl Compiler { Varargs::from(&args.vararg), args.kwonlyargs.iter().map(|a| a.arg.clone()).collect(), Varargs::from(&args.kwarg), - self.source_path.clone().unwrap(), + self.source_path.clone(), line_number, name.to_string(), )); @@ -849,7 +856,7 @@ impl Compiler { Varargs::None, vec![], Varargs::None, - self.source_path.clone().unwrap(), + self.source_path.clone(), line_number, name.to_string(), )); @@ -1571,7 +1578,7 @@ impl Compiler { Varargs::None, vec![], Varargs::None, - self.source_path.clone().unwrap(), + self.source_path.clone(), line_number, name.clone(), )); diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index 35db2eb044..4c9946f899 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -35,7 +35,11 @@ struct CompilationSource { } impl CompilationSource { - fn compile(self, mode: &compile::Mode, source_path: String) -> Result { + fn compile( + self, + mode: &compile::Mode, + source_path: Option, + ) -> Result { let compile = |source| { compile::compile(source, mode, source_path).map_err(|err| { Diagnostic::spans_error(self.span, format!("Compile error: {}", err)) @@ -135,10 +139,7 @@ impl PyCompileInput { "Must have either file or source in py_compile_bytecode!()", ) })? - .compile( - &mode.unwrap_or(compile::Mode::Exec), - source_path.unwrap_or_else(|| "frozen".to_string()), - ) + .compile(&mode.unwrap_or(compile::Mode::Exec), source_path) } } diff --git a/src/main.rs b/src/main.rs index 5d4d2b9e72..895ea6290a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,7 +76,7 @@ fn main() { fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResult { let code_obj = vm - .compile(source, &compile::Mode::Exec, source_path.clone()) + .compile(source, &compile::Mode::Exec, Some(source_path.clone())) .map_err(|err| vm.new_syntax_error(&err))?; // trace!("Code object: {:?}", code_obj.borrow()); let attrs = vm.ctx.new_dict(); @@ -161,7 +161,7 @@ fn test_run_script() { } fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), CompileError> { - match vm.compile(source, &compile::Mode::Single, "".to_string()) { + match vm.compile(source, &compile::Mode::Single, Some("".to_string())) { Ok(code) => { match vm.run_code_obj(code, scope.clone()) { Ok(value) => { diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 570b1fbbf1..1d82ae1217 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -122,7 +122,7 @@ fn builtin_compile(args: CompileArgs, vm: &VirtualMachine) -> PyResult PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - vm.compile(&source, &mode, "".to_string()) + vm.compile(&source, &mode, Some("".to_string())) .map_err(|err| vm.new_syntax_error(&err))? } else { return Err(vm.new_type_error("code argument must be str or code object".to_string())); @@ -199,7 +199,7 @@ fn builtin_exec(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - vm.compile(&source, &mode, "".to_string()) + vm.compile(&source, &mode, Some("".to_string())) .map_err(|err| vm.new_syntax_error(&err))? } else if let Ok(code_obj) = PyCodeRef::try_from_object(vm, source.clone()) { code_obj diff --git a/vm/src/eval.rs b/vm/src/eval.rs index e326e300e5..706c7271f4 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -1,12 +1,15 @@ -extern crate rustpython_parser; - use crate::compile; use crate::frame::Scope; use crate::pyobject::PyResult; use crate::vm::VirtualMachine; -pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str) -> PyResult { - match vm.compile(source, &compile::Mode::Eval, source_path.to_string()) { +pub fn eval( + vm: &VirtualMachine, + source: &str, + scope: Scope, + source_path: Option, +) -> PyResult { + match vm.compile(source, &compile::Mode::Eval, source_path) { Ok(bytecode) => { debug!("Code object: {:?}", bytecode); vm.run_code_obj(bytecode, scope) @@ -25,7 +28,7 @@ mod tests { let source = String::from("print('Hello world')\n"); let mut vm = VirtualMachine::new(); let vars = vm.new_scope_with_builtins(); - let _result = eval(&mut vm, &source, vars, ""); + let _result = eval(&mut vm, &source, vars, Some("".to_string())); // TODO: check result? //assert_eq!( diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 8ea1148547..c671fc5bd7 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -271,7 +271,10 @@ impl Frame { } pub fn run(&self, vm: &VirtualMachine) -> Result { - let filename = &self.code.source_path.to_string(); + let filename = match self.code.source_path.clone() { + Some(s) => vm.ctx.new_str(s), + None => vm.get_none(), + }; // This is the name of the object being run: let run_obj_name = &self.code.obj_name.to_string(); @@ -299,7 +302,7 @@ impl Frame { .unwrap(); trace!("Adding to traceback: {:?} {:?}", traceback, lineno); let raise_location = vm.ctx.new_tuple(vec![ - vm.ctx.new_str(filename.clone()), + filename.clone(), vm.ctx.new_int(lineno.get_row()), vm.ctx.new_str(run_obj_name.clone()), ]); diff --git a/vm/src/import.rs b/vm/src/import.rs index ad1fd8e6ab..84f89d91cb 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -25,13 +25,12 @@ pub fn init_importlib(vm: &VirtualMachine) -> PyResult { } pub fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { - if let Some(frozen) = vm.frozen.borrow().get(module_name) { - let mut frozen = frozen.clone(); - frozen.source_path = format!("frozen {}", module_name); - import_codeobj(vm, module_name, frozen) - } else { - Err(vm.new_import_error(format!("Cannot import frozen module {}", module_name))) - } + vm.frozen + .borrow() + .get(module_name) + .cloned() + .ok_or_else(|| vm.new_import_error(format!("Cannot import frozen module {}", module_name))) + .and_then(|frozen| import_codeobj(vm, module_name, frozen)) } pub fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult { @@ -69,7 +68,7 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s import_file( vm, module_name, - file_path.to_str().unwrap().to_string(), + Some(file_path.to_str().unwrap().to_string()), source, ) } @@ -78,7 +77,7 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s pub fn import_file( vm: &VirtualMachine, module_name: &str, - file_path: String, + file_path: Option, content: String, ) -> PyResult { let code_obj = compile::compile(&content, &compile::Mode::Exec, file_path) @@ -89,10 +88,8 @@ pub fn import_file( pub fn import_codeobj(vm: &VirtualMachine, module_name: &str, code_obj: CodeObject) -> PyResult { let attrs = vm.ctx.new_dict(); attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; - let file_path = &code_obj.source_path; - if !file_path.starts_with("frozen") { - // TODO: Should be less hacky, not depend on source_path - attrs.set_item("__file__", vm.new_str(file_path.to_owned()), vm)?; + if let Some(source_path) = &code_obj.source_path { + attrs.set_item("__file__", vm.new_str(source_path.to_owned()), vm)?; } let module = vm.ctx.new_module(module_name, attrs.clone()); diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 51a73d6141..17d3e743d2 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -54,7 +54,7 @@ impl PyCodeRef { self.code.arg_names.len() } - fn co_filename(self, _vm: &VirtualMachine) -> String { + fn co_filename(self, _vm: &VirtualMachine) -> Option { self.code.source_path.clone() } diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index 184fc5003c..96437187e7 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -58,11 +58,8 @@ fn imp_get_frozen_object(name: PyStringRef, vm: &VirtualMachine) -> PyResult, ) -> Result { compile::compile(source, mode, source_path) .map(|codeobj| PyCode::new(codeobj).into_ref(self)) diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index b3d689d3f3..b2b59ecd2b 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -373,7 +373,7 @@ fn browser_load_module(module: PyStringRef, path: PyStringRef, vm: &VirtualMachi .expect("that the vm is valid when the promise resolves"); let vm = &stored_vm.vm; let resp_text = text.as_string().unwrap(); - let res = import_file(vm, module.as_str(), "WEB".to_string(), resp_text); + let res = import_file(vm, module.as_str(), Some("WEB".to_string()), resp_text); match res { Ok(_) => Ok(JsValue::null()), Err(err) => Err(convert::py_err_to_js_err(vm, &err)), diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 9526941ffd..efa7d503bd 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -263,7 +263,7 @@ impl WASMVirtualMachine { ref vm, ref scope, .. }| { source.push('\n'); - let code = vm.compile(&source, &mode, "".to_string()); + let code = vm.compile(&source, &mode, Some("".to_string())); let code = code.map_err(|err| { let js_err = SyntaxError::new(&format!("Error parsing Python code: {}", err)); if let rustpython_vm::error::CompileErrorType::Parse(ref parse_error) = From 8faab1f494203e8daceac7d01cd74602c3ea6a71 Mon Sep 17 00:00:00 2001 From: rmliddle Date: Mon, 17 Jun 2019 19:31:42 +1000 Subject: [PATCH 773/884] Implement String, BytesIO with Cursor> --- tests/snippets/bytes_io.py | 29 +++++++ tests/snippets/string_io.py | 36 +++++++++ vm/src/stdlib/io.rs | 146 ++++++++++++++++++++++++++++++------ 3 files changed, 189 insertions(+), 22 deletions(-) diff --git a/tests/snippets/bytes_io.py b/tests/snippets/bytes_io.py index edb81b6f57..f6f97e6841 100644 --- a/tests/snippets/bytes_io.py +++ b/tests/snippets/bytes_io.py @@ -16,6 +16,35 @@ def test_02(): assert f.read() == bytes_string assert f.read() == b'' +def test_03(): + """ + Tests that the read method (integer arg) + returns the expected value + """ + string = b'Test String 3' + f = BytesIO(string) + + assert f.read(1) == b'T' + assert f.read(1) == b'e' + assert f.read(1) == b's' + assert f.read(1) == b't' + +def test_04(): + """ + Tests that the read method increments the + cursor position and the seek method moves + the cursor to the appropriate position + """ + string = b'Test String 4' + f = BytesIO(string) + + assert f.read(4) == b'Test' + assert f.seek(0) == 0 + assert f.read(4) == b'Test' + if __name__ == "__main__": test_01() test_02() + test_03() + test_04() + diff --git a/tests/snippets/string_io.py b/tests/snippets/string_io.py index cb02f5a45b..0b4182527d 100644 --- a/tests/snippets/string_io.py +++ b/tests/snippets/string_io.py @@ -2,6 +2,10 @@ from io import StringIO def test_01(): + """ + Test that the constructor and getvalue + method return expected values + """ string = 'Test String 1' f = StringIO() f.write(string) @@ -9,12 +13,44 @@ def test_01(): assert f.getvalue() == string def test_02(): + """ + Test that the read method (no arg) + results the expected value + """ string = 'Test String 2' f = StringIO(string) assert f.read() == string assert f.read() == '' +def test_03(): + """ + Tests that the read method (integer arg) + returns the expected value + """ + string = 'Test String 3' + f = StringIO(string) + + assert f.read(1) == 'T' + assert f.read(1) == 'e' + assert f.read(1) == 's' + assert f.read(1) == 't' + +def test_04(): + """ + Tests that the read method increments the + cursor position and the seek method moves + the cursor to the appropriate position + """ + string = 'Test String 4' + f = StringIO(string) + + assert f.read(4) == 'Test' + assert f.seek(0) == 0 + assert f.read(4) == 'Test' + if __name__ == "__main__": test_01() test_02() + test_03() + test_04() diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index a2347ed024..f155e0cbda 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -5,6 +5,9 @@ use std::cell::RefCell; use std::fs::File; use std::io::prelude::*; use std::io::BufReader; +use std::io::Cursor; +use std::io::SeekFrom; + use std::path::PathBuf; use num_bigint::ToBigInt; @@ -33,7 +36,7 @@ fn compute_c_flag(mode: &str) -> u16 { #[derive(Debug)] struct PyStringIO { - data: RefCell, + data: RefCell>>, } type PyStringIORef = PyRef; @@ -45,19 +48,68 @@ impl PyValue for PyStringIO { } impl PyStringIORef { - fn write(self, data: objstr::PyStringRef, _vm: &VirtualMachine) { - let data = data.value.clone(); - self.data.borrow_mut().push_str(&data); + //write string to underlying vector + fn write(self, data: objstr::PyStringRef, vm: &VirtualMachine) -> PyResult { + let bytes = &data.value.clone().into_bytes(); + let length = bytes.len(); + + let mut cursor = self.data.borrow_mut(); + match cursor.write_all(bytes) { + Ok(_) => Ok(vm.ctx.new_int(length)), + Err(_) => Err(vm.new_type_error("Error Writing String".to_string())), + } } - fn getvalue(self, _vm: &VirtualMachine) -> String { - self.data.borrow().clone() + //return the entire contents of the underlying + fn getvalue(self, vm: &VirtualMachine) -> PyResult { + match String::from_utf8(self.data.borrow().clone().into_inner()) { + Ok(result) => Ok(vm.ctx.new_str(result)), + Err(_) => Err(vm.new_value_error("Error Retrieving Value".to_string())), + } } - fn read(self, _vm: &VirtualMachine) -> String { - let data = self.data.borrow().clone(); - self.data.borrow_mut().clear(); - data + //skip to the jth position + fn seek(self, offset: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let position = objint::get_value(&offset).to_u64().unwrap(); + if let Err(_) = self + .data + .borrow_mut() + .seek(SeekFrom::Start(position.clone())) + { + return Err(vm.new_value_error("Error Retrieving Value".to_string())); + } + + Ok(vm.ctx.new_int(position)) + } + + //Read k bytes from the object and return. + //If k is undefined || k == -1, then we read all bytes until the end of the file. + //This also increments the stream position by the value of k + fn read(self, bytes: OptionalArg>, vm: &VirtualMachine) -> PyResult { + let mut buffer = String::new(); + + match bytes { + OptionalArg::Present(Some(ref integer)) => { + let k = objint::get_value(integer).to_u64().unwrap(); + let mut handle = self.data.borrow().clone().take(k); + + //read bytes into string + if let Err(_) = handle.read_to_string(&mut buffer) { + return Err(vm.new_value_error("Error Retrieving Value".to_string())); + } + + //the take above consumes the struct value + //we add this back in with the takes into_inner method + self.data.replace(handle.into_inner()); + } + _ => { + if let Err(_) = self.data.borrow_mut().read_to_string(&mut buffer) { + return Err(vm.new_value_error("Error Retrieving Value".to_string())); + } + } + }; + + Ok(vm.ctx.new_str(buffer)) } } @@ -72,14 +124,14 @@ fn string_io_new( }; PyStringIO { - data: RefCell::new(raw_string), + data: RefCell::new(Cursor::new(raw_string.into_bytes())), } .into_ref_with_type(vm, cls) } -#[derive(Debug, Default, Clone)] +#[derive(Debug)] struct PyBytesIO { - data: RefCell>, + data: RefCell>>, } type PyBytesIORef = PyRef; @@ -91,19 +143,65 @@ impl PyValue for PyBytesIO { } impl PyBytesIORef { - fn write(self, data: objbytes::PyBytesRef, _vm: &VirtualMachine) { - let data = data.get_value(); - self.data.borrow_mut().extend(data); + //write string to underlying vector + fn write(self, data: objbytes::PyBytesRef, vm: &VirtualMachine) -> PyResult { + let bytes = data.get_value(); + let length = bytes.len(); + + let mut cursor = self.data.borrow_mut(); + match cursor.write_all(bytes) { + Ok(_) => Ok(vm.ctx.new_int(length)), + Err(_) => Err(vm.new_type_error("Error Writing String".to_string())), + } } + //return the entire contents of the underlying fn getvalue(self, vm: &VirtualMachine) -> PyResult { - Ok(vm.ctx.new_bytes(self.data.borrow().clone())) + Ok(vm.ctx.new_bytes(self.data.borrow().clone().into_inner())) + } + + //skip to the jth position + fn seek(self, offset: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let position = objint::get_value(&offset).to_u64().unwrap(); + if let Err(_) = self + .data + .borrow_mut() + .seek(SeekFrom::Start(position.clone())) + { + return Err(vm.new_value_error("Error Retrieving Value".to_string())); + } + + Ok(vm.ctx.new_int(position)) } - fn read(self, vm: &VirtualMachine) -> PyResult { - let data = self.data.borrow().clone(); - self.data.borrow_mut().clear(); - Ok(vm.ctx.new_bytes(data)) + //Read k bytes from the object and return. + //If k is undefined || k == -1, then we read all bytes until the end of the file. + //This also increments the stream position by the value of k + fn read(self, bytes: OptionalArg>, vm: &VirtualMachine) -> PyResult { + let mut buffer = Vec::new(); + + match bytes { + OptionalArg::Present(Some(ref integer)) => { + let k = objint::get_value(integer).to_u64().unwrap(); + let mut handle = self.data.borrow().clone().take(k); + + //read bytes into string + if let Err(_) = handle.read_to_end(&mut buffer) { + return Err(vm.new_value_error("Error Retrieving Value".to_string())); + } + + //the take above consumes the struct value + //we add this back in with the takes into_inner method + self.data.replace(handle.into_inner()); + } + _ => { + if let Err(_) = self.data.borrow_mut().read_to_end(&mut buffer) { + return Err(vm.new_value_error("Error Retrieving Value".to_string())); + } + } + }; + + Ok(vm.ctx.new_bytes(buffer)) } } @@ -118,7 +216,7 @@ fn bytes_io_new( }; PyBytesIO { - data: RefCell::new(raw_bytes), + data: RefCell::new(Cursor::new(raw_bytes)), } .into_ref_with_type(vm, cls) } @@ -514,6 +612,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { //StringIO: in-memory text let string_io = py_class!(ctx, "StringIO", text_io_base.clone(), { "__new__" => ctx.new_rustfunc(string_io_new), + "seek" => ctx.new_rustfunc(PyStringIORef::seek), "read" => ctx.new_rustfunc(PyStringIORef::read), "write" => ctx.new_rustfunc(PyStringIORef::write), "getvalue" => ctx.new_rustfunc(PyStringIORef::getvalue) @@ -523,6 +622,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let bytes_io = py_class!(ctx, "BytesIO", buffered_io_base.clone(), { "__new__" => ctx.new_rustfunc(bytes_io_new), "read" => ctx.new_rustfunc(PyBytesIORef::read), + "read1" => ctx.new_rustfunc(PyBytesIORef::read), + "seek" => ctx.new_rustfunc(PyBytesIORef::seek), "write" => ctx.new_rustfunc(PyBytesIORef::write), "getvalue" => ctx.new_rustfunc(PyBytesIORef::getvalue) }); @@ -627,4 +728,5 @@ mod tests { Err("invalid mode: 'a++'".to_string()) ); } + } From d74e1262461aca4b9bc6e2b8df53f83539720f78 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Fri, 14 Jun 2019 15:34:17 +0300 Subject: [PATCH 774/884] Add parser for C printf-style format strings This commit is a part of a series of future commits that will resolve #1007 . Code is based on vm/src/format.rs. --- vm/src/cformat.rs | 353 ++++++++++++++++++++++++++++++++++++++++++++++ vm/src/format.rs | 4 +- vm/src/lib.rs | 3 + 3 files changed, 358 insertions(+), 2 deletions(-) create mode 100644 vm/src/cformat.rs diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs new file mode 100644 index 0000000000..082cfb5e3e --- /dev/null +++ b/vm/src/cformat.rs @@ -0,0 +1,353 @@ +use crate::format::{parse_number, parse_precision}; +/// Implementation of Printf-Style string formatting +/// [https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting] +use num_bigint::{BigInt, Sign}; +use num_traits::Signed; +use std::cmp; +use std::str::FromStr; + +#[derive(Debug, PartialEq)] +pub enum CFormatErrorType { + UnmatchedKeyParentheses, + MissingModuloSign, + UnescapedModuloSignInLiteral, + UnsupportedFormatChar(char), + IncompleteFormat, + Unimplemented, +} + +// also contains how many chars the parsing function consumed +type ParsingError = (CFormatErrorType, usize); + +pub struct CFormatError { + pub typ: CFormatErrorType, + pub index: usize, +} + +#[derive(Debug, PartialEq)] +pub enum CFormatPreconversor { + Repr, + Str, + Ascii, +} + +#[derive(Debug, PartialEq)] +pub enum CFormatCase { + Lowercase, + Uppercase, +} + +#[derive(Debug, PartialEq)] +pub enum CNumberType { + Decimal, + Octal, + Hex(CFormatCase), +} + +#[derive(Debug, PartialEq)] +pub enum CFloatType { + Exponent(CFormatCase), + PointDecimal, + General(CFormatCase), +} + +#[derive(Debug, PartialEq)] +pub enum CFormatType { + Number(CNumberType), + Float(CFloatType), + Character, + String(CFormatPreconversor), +} + +bitflags! { + pub struct CConversionFlags: u32 { + const ALTERNATE_FORM = 0b0000_0001; + const ZERO_PAD = 0b0000_0010; + const LEFT_ADJUST = 0b0000_0100; + const BLANK_SIGN = 0b0000_1000; + const SIGN_CHAR = 0b0001_0000; + } +} + +#[derive(Debug, PartialEq)] +pub struct CFormatSpec { + pub mapping_key: Option, + pub flags: CConversionFlags, + pub min_field_width: Option, + pub precision: Option, + pub format_type: CFormatType, + pub format_char: char, +} + +#[derive(Debug, PartialEq)] +pub enum CFormatPart { + Literal(String), + Spec(CFormatSpec), +} + +#[derive(Debug, PartialEq)] +pub struct CFormatString { + pub format_parts: Vec<(usize, CFormatPart)>, +} + +impl FromStr for CFormatString { + type Err = CFormatError; + + fn from_str(text: &str) -> Result { + let mut cur_text: &str = text; + let mut index = 0; + let mut parts: Vec<(usize, CFormatPart)> = Vec::new(); + while !cur_text.is_empty() { + cur_text = parse_literal(cur_text) + .or_else(|_| parse_specifier(cur_text)) + .map(|(format_part, new_text, consumed)| { + parts.push((index, format_part)); + index = index + consumed; + new_text + }) + .map_err(|(e, consumed)| { + CFormatError { + typ: e, + index: index + consumed, + } + })?; + } + + Ok(CFormatString { + format_parts: parts, + }) + } +} + +fn parse_literal_single(text: &str) -> Result<(char, &str), CFormatErrorType> { + let mut chars = text.chars(); + // TODO get rid of the unwrap + let first_char = chars.next().unwrap(); + if first_char == '%' { + // if we see a %, it has to be escaped + match chars.next() { + Some(next_char) => { + if next_char != first_char { + Err(CFormatErrorType::UnescapedModuloSignInLiteral) + } else { + Ok((first_char, chars.as_str())) + } + } + None => Err(CFormatErrorType::IncompleteFormat), + } + } else { + Ok((first_char, chars.as_str())) + } +} + +fn parse_literal(text: &str) -> Result<(CFormatPart, &str, usize), ParsingError> { + let mut cur_text = text; + let mut result_string = String::new(); + let mut consumed = 0; + while !cur_text.is_empty() { + match parse_literal_single(cur_text) { + Ok((next_char, remaining)) => { + result_string.push(next_char); + consumed = consumed + 1; + cur_text = remaining; + } + Err(err) => { + if !result_string.is_empty() { + return Ok(( + CFormatPart::Literal(result_string.to_string()), + cur_text, + consumed, + )); + } else { + return Err((err, consumed)); + } + } + } + } + Ok(( + CFormatPart::Literal(result_string.to_string()), + "", + text.len(), + )) +} + +fn parse_spec_mapping_key(text: &str) -> Result<(Option, &str), CFormatErrorType> { + let mut chars = text.chars(); + + let next_char = chars.next(); + if next_char == Some('(') { + // Get remaining characters after opening parentheses. + let cur_text = chars.as_str(); + match cur_text.find(')') { + Some(position) => { + let (left, right) = cur_text.split_at(position); + + Ok((Some(left.to_string()), &right[1..])) + } + None => Err(CFormatErrorType::UnmatchedKeyParentheses), + } + } else { + Ok((None, text)) + } +} + +fn parse_flag_single(text: &str) -> (Option, &str) { + let mut chars = text.chars(); + match chars.next() { + Some('#') => (Some(CConversionFlags::ALTERNATE_FORM), chars.as_str()), + Some('0') => (Some(CConversionFlags::ZERO_PAD), chars.as_str()), + Some('-') => (Some(CConversionFlags::LEFT_ADJUST), chars.as_str()), + Some(' ') => (Some(CConversionFlags::BLANK_SIGN), chars.as_str()), + Some('+') => (Some(CConversionFlags::SIGN_CHAR), chars.as_str()), + _ => (None, text), + } +} + +fn parse_flags(text: &str) -> (CConversionFlags, &str) { + let mut flags = CConversionFlags::empty(); + let mut cur_text = text; + while !cur_text.is_empty() { + match parse_flag_single(cur_text) { + (Some(flag), text) => { + flags |= flag; + cur_text = text; + } + + (None, text) => { + return (flags, text); + } + } + } + + (flags, "") +} + +fn consume_length(text: &str) -> &str { + let mut chars = text.chars(); + match chars.next() { + Some('h') | Some('l') | Some('L') => chars.as_str(), + _ => text, + } +} + +fn parse_format_type(text: &str) -> Result<(CFormatType, &str, char), CFormatErrorType> { + use CFloatType::*; + use CFormatCase::{Lowercase, Uppercase}; + use CNumberType::*; + let mut chars = text.chars(); + let next_char = chars.next(); + match next_char { + Some('d') | Some('i') | Some('u') => Ok(( + CFormatType::Number(Decimal), + chars.as_str(), + next_char.unwrap(), + )), + Some('o') => Ok(( + CFormatType::Number(Octal), + chars.as_str(), + next_char.unwrap(), + )), + Some('x') => Ok(( + CFormatType::Number(Hex(Lowercase)), + chars.as_str(), + next_char.unwrap(), + )), + Some('X') => Ok(( + CFormatType::Number(Hex(Uppercase)), + chars.as_str(), + next_char.unwrap(), + )), + Some('e') => Ok(( + CFormatType::Float(Exponent(Lowercase)), + chars.as_str(), + next_char.unwrap(), + )), + Some('E') => Ok(( + CFormatType::Float(Exponent(Uppercase)), + chars.as_str(), + next_char.unwrap(), + )), + Some('f') => Ok(( + CFormatType::Float(PointDecimal), + chars.as_str(), + next_char.unwrap(), + )), + Some('F') => Ok(( + CFormatType::Float(PointDecimal), + chars.as_str(), + next_char.unwrap(), + )), + Some('g') => Ok(( + CFormatType::Float(General(Lowercase)), + text, + next_char.unwrap(), + )), + Some('G') => Ok(( + CFormatType::Float(General(Uppercase)), + text, + next_char.unwrap(), + )), + Some('c') => Ok((CFormatType::Character, chars.as_str(), next_char.unwrap())), + Some('r') => Ok(( + CFormatType::String(CFormatPreconversor::Repr), + chars.as_str(), + next_char.unwrap(), + )), + Some('s') => Ok(( + CFormatType::String(CFormatPreconversor::Str), + chars.as_str(), + next_char.unwrap(), + )), + Some('a') => Ok(( + CFormatType::String(CFormatPreconversor::Ascii), + chars.as_str(), + next_char.unwrap(), + )), + Some(c) => Err(CFormatErrorType::UnsupportedFormatChar(c)), + None => Err(CFormatErrorType::IncompleteFormat), // should not happen because it is handled earlier in the parsing + } +} + +fn parse_specifier(text: &str) -> Result<(CFormatPart, &str, usize), ParsingError> { + let consumed = 0; + let mut chars = text.chars(); + if chars.next() != Some('%') { + return Err((CFormatErrorType::MissingModuloSign, consumed)); + } + let consumed = consumed + 1; + + let (mapping_key, after_mapping_key) = + parse_spec_mapping_key(chars.as_str()).map_err(|err| (err, consumed))?; + let consumed = text.find(after_mapping_key).unwrap(); + let (flags, after_flags) = parse_flags(after_mapping_key); + let (width, after_width) = parse_number(after_flags); + let (precision, after_precision) = parse_precision(after_width); + // A length modifier (h, l, or L) may be present, + // but is ignored as it is not necessary for Python – so e.g. %ld is identical to %d. + let after_length = consume_length(after_precision); + let (format_type, remaining_text, format_char) = + parse_format_type(after_length).map_err(|err| (err, consumed))?; + let consumed = text.find(remaining_text).unwrap(); + + // apply default precision for float types + let precision = match precision { + Some(precision) => Some(precision), + None => match format_type { + CFormatType::Float(_) => Some(6), + _ => None, + }, + }; + + Ok(( + CFormatPart::Spec(CFormatSpec { + mapping_key: mapping_key, + flags: flags, + min_field_width: width, + precision: precision, + format_type: format_type, + format_char: format_char, + }), + remaining_text, + consumed, + )) +} diff --git a/vm/src/format.rs b/vm/src/format.rs index eb59bf84ac..35ea31b64f 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -151,7 +151,7 @@ fn parse_fill_and_align(text: &str) -> (Option, Option, &str) } } -fn parse_number(text: &str) -> (Option, &str) { +pub fn parse_number(text: &str) -> (Option, &str) { let num_digits: usize = get_num_digits(text); if num_digits == 0 { return (None, text); @@ -189,7 +189,7 @@ fn parse_zero(text: &str) -> &str { } } -fn parse_precision(text: &str) -> (Option, &str) { +pub fn parse_precision(text: &str) -> (Option, &str) { let mut chars = text.chars(); match chars.next() { Some('.') => { diff --git a/vm/src/lib.rs b/vm/src/lib.rs index a382f67226..8c64cc550f 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -12,6 +12,8 @@ clippy::implicit_hasher )] +#[macro_use] +extern crate bitflags; #[macro_use] extern crate lazy_static; extern crate lexical; @@ -41,6 +43,7 @@ pub use rustpython_derive::py_compile_bytecode; pub mod macros; mod builtins; +pub mod cformat; mod dictdatatype; pub mod eval; mod exceptions; From f27265cf8a01c2243cda30046bcab48e678f4247 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Tue, 11 Jun 2019 22:36:12 +0300 Subject: [PATCH 775/884] Add get_value for objtuple --- vm/src/obj/objtuple.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index 40a5749ee7..b4f1699dd6 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -45,6 +45,10 @@ impl PyTuple { pub type PyTupleRef = PyRef; +pub fn get_value(obj: &PyObjectRef) -> Vec { + obj.payload::().unwrap().elements.clone() +} + impl PyTupleRef { fn lt(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.tuple_type()) { From fe6c1488d96ad58b4d8278622753c70587dd62ae Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Fri, 14 Jun 2019 16:10:31 +0300 Subject: [PATCH 776/884] Start implementing str.__mod__ --- vm/src/obj/objstr.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index d0b76d186c..cc2e0e4467 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -11,6 +11,7 @@ use unicode_casing::CharExt; use unicode_segmentation::UnicodeSegmentation; use unicode_xid::UnicodeXID; +use crate::cformat::{CFormatString, CFormatErrorType}; use crate::format::{FormatParseError, FormatPart, FormatPreconversor, FormatString}; use crate::function::{single_or_tuple_any, OptionalArg, PyFuncArgs}; use crate::pyhash; @@ -429,6 +430,28 @@ impl PyString { } } + #[pymethod(name = "__mod__")] + fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let format_string_text = &self.value; + match CFormatString::from_str(format_string_text) { + Ok(format_string) => perform_clike_format(vm, format_string, values.clone()), + Err(err) => match err.typ { + CFormatErrorType::UnsupportedFormatChar(c) => Err(vm.new_value_error(format!( + "unsupported format character '{}' ({:#x}) at index {}", + c, c as u8, err.index + ))), + CFormatErrorType::UnmatchedKeyParentheses => { + // TODO in cpython, this error comes after verifying that `values` is a mapping type. + Err(vm.new_value_error("incomplete format key".to_string())) + } + CFormatErrorType::IncompleteFormat => { + Err(vm.new_value_error("incomplete format".to_string())) + } + _ => Err(vm.new_value_error("Unexpected error parsing format string".to_string())), + }, + } + } + #[pymethod] fn format(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if args.args.is_empty() { @@ -1072,6 +1095,15 @@ fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: & Ok(result) } +fn perform_clike_format( + vm: &VirtualMachine, + format_string: CFormatString, + values_obj: PyObjectRef, +) -> PyResult { + // TODO + Err(vm.new_type_error("Not implemented".to_string())) +} + fn perform_format( vm: &VirtualMachine, format_string: &FormatString, From 481b0448831f035fc7d51b947e85211a9fad013e Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sat, 15 Jun 2019 13:48:42 +0300 Subject: [PATCH 777/884] Support modulo formatting of numbers and string types --- vm/src/cformat.rs | 118 +++++++++++++++++++++++++++++++++++++++ vm/src/obj/objstr.rs | 130 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 243 insertions(+), 5 deletions(-) diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index 082cfb5e3e..1f2d175f65 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -79,12 +79,130 @@ pub struct CFormatSpec { pub format_char: char, } +impl CFormatSpec { + fn compute_fill_string(fill_char: char, fill_chars_needed: usize) -> String { + (0..fill_chars_needed) + .map(|_| fill_char) + .collect::() + } + + pub fn fill_string(&self, string: String, fill_char: char, num_prefix_chars: Option) -> String { + let mut num_chars = string.len(); + if let Some(num_prefix_chars) = num_prefix_chars { + num_chars = num_chars + num_prefix_chars; + } + let num_chars = num_chars; + + let width = match self.min_field_width { + Some(width) => cmp::max(width, num_chars), + None => num_chars, + }; + let fill_chars_needed = width - num_chars; + let fill_string = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed); + + if !fill_string.is_empty() { + if self.flags.contains(CConversionFlags::LEFT_ADJUST) { + format!("{}{}", string, fill_string) + } else { + format!("{}{}", fill_string, string) + } + } else { + string + } + } + + pub fn format_string(&self, string: String) -> String { + let mut string = string; + // truncate if needed + if let Some(precision) = self.precision { + if string.len() > precision { + string.truncate(precision); + } + } + self.fill_string(string, ' ', None) + } + + pub fn format_number(&self, num: &BigInt) -> String { + use CFormatCase::{Lowercase, Uppercase}; + use CNumberType::*; + let fill_char = if self.flags.contains(CConversionFlags::ZERO_PAD) { + '0' + } else { + ' ' + }; + let magnitude = num.abs(); + let prefix = if self.flags.contains(CConversionFlags::ALTERNATE_FORM) { + match self.format_type { + CFormatType::Number(Octal) => "0o", + CFormatType::Number(Hex(Lowercase)) => "0x", + CFormatType::Number(Hex(Uppercase)) => "0X", + _ => "", + } + } else { + "" + }; + + let magnitude_string: String = match self.format_type { + CFormatType::Number(Decimal) => magnitude.to_str_radix(10), + CFormatType::Number(Octal) => magnitude.to_str_radix(8), + CFormatType::Number(Hex(Lowercase)) => magnitude.to_str_radix(16), + CFormatType::Number(Hex(Uppercase)) => { + let mut result = magnitude.to_str_radix(16); + result.make_ascii_uppercase(); + result + } + _ => unreachable!(), // Should not happen because caller has to make sure that this is a number + }; + + + let sign_string = match num.sign() { + Sign::Minus => "-", + _ => { + if self.flags.contains(CConversionFlags::SIGN_CHAR) { + "+" + } else if self.flags.contains(CConversionFlags::BLANK_SIGN) { + " " + } else { + "" + } + } + }; + + + let prefix = format!("{}{}", sign_string, prefix); + let magnitude_filled_string = if !self.flags.contains(CConversionFlags::LEFT_ADJUST) { + // '-' flags overrides '0' flag + self.fill_string(magnitude_string, fill_char, Some(prefix.len())) + } else { + magnitude_string + }; + + format!("{}{}", prefix, magnitude_filled_string) + } +} + #[derive(Debug, PartialEq)] pub enum CFormatPart { Literal(String), Spec(CFormatSpec), } +impl CFormatPart { + pub fn is_specifier(&self) -> bool { + match self { + CFormatPart::Spec(_) => true, + _ => false, + } + } + + pub fn has_key(&self) -> bool { + match self { + CFormatPart::Spec(s) => s.mapping_key.is_some(), + _ => false, + } + } +} + #[derive(Debug, PartialEq)] pub struct CFormatString { pub format_parts: Vec<(usize, CFormatPart)>, diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index cc2e0e4467..13c5f776d5 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -11,7 +11,10 @@ use unicode_casing::CharExt; use unicode_segmentation::UnicodeSegmentation; use unicode_xid::UnicodeXID; -use crate::cformat::{CFormatString, CFormatErrorType}; +use crate::cformat::{ + CFormatErrorType, CFormatPart, CFormatPreconversor, CFormatSpec, CFormatString, CFormatType, + CNumberType, +}; use crate::format::{FormatParseError, FormatPart, FormatPreconversor, FormatString}; use crate::function::{single_or_tuple_any, OptionalArg, PyFuncArgs}; use crate::pyhash; @@ -27,6 +30,7 @@ use super::objint::{self, PyInt}; use super::objnone::PyNone; use super::objsequence::PySliceableSequence; use super::objslice::PySlice; +use super::objtuple; use super::objtype::{self, PyClassRef}; use unicode_categories::UnicodeCategories; @@ -434,7 +438,7 @@ impl PyString { fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult { let format_string_text = &self.value; match CFormatString::from_str(format_string_text) { - Ok(format_string) => perform_clike_format(vm, format_string, values.clone()), + Ok(format_string) => do_cformat(vm, format_string, values.clone()), Err(err) => match err.typ { CFormatErrorType::UnsupportedFormatChar(c) => Err(vm.new_value_error(format!( "unsupported format character '{}' ({:#x}) at index {}", @@ -1076,6 +1080,10 @@ fn count_char(s: &str, c: char) -> usize { s.chars().filter(|x| *x == c).count() } +fn call_getitem(vm: &VirtualMachine, container: &PyObjectRef, key: &PyObjectRef) -> PyResult { + vm.call_method(container, "__getitem__", vec![key.clone()]) +} + fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: &str) -> PyResult { let (preconversor, new_format_spec) = FormatPreconversor::parse_and_consume(format_spec); let argument = match preconversor { @@ -1095,13 +1103,125 @@ fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: & Ok(result) } -fn perform_clike_format( +fn do_cformat_specifier( + vm: &VirtualMachine, + format_spec: &CFormatSpec, + obj: PyObjectRef, +) -> Result { + use CNumberType::*; + // do the formatting by type + let format_type = &format_spec.format_type; + + match format_type { + CFormatType::String(preconversor) => { + let result = match preconversor { + CFormatPreconversor::Str => vm.call_method(&obj.clone(), "__str__", vec![])?, + CFormatPreconversor::Repr => vm.call_method(&obj.clone(), "__repr__", vec![])?, + CFormatPreconversor::Ascii => vm.call_method(&obj.clone(), "__repr__", vec![])?, + }; + Ok(format_spec.format_string(get_value(&result))) + } + CFormatType::Number(_) => { + if !objtype::isinstance(&obj, &vm.ctx.int_type()) { + let required_type_string = match format_type { + CFormatType::Number(Decimal) => "a number", + CFormatType::Number(_) => "an integer", + _ => unreachable!(), + }; + return Err(vm.new_type_error(format!( + "%{} format: {} is required, not {}", + format_spec.format_char, + required_type_string, + obj.class() + ))); + } + Ok(format_spec.format_number(objint::get_value(&obj))) + } + _ => Err(vm.new_not_implemented_error(format!( + "Not yet implemented for %{}", + format_spec.format_char + ))), + } +} + +fn do_cformat( vm: &VirtualMachine, format_string: CFormatString, values_obj: PyObjectRef, ) -> PyResult { - // TODO - Err(vm.new_type_error("Not implemented".to_string())) + let mut final_string = String::new(); + let num_specifiers = format_string + .format_parts + .iter() + .filter(|(_, part)| CFormatPart::is_specifier(part)) + .count(); + let mapping_required = format_string + .format_parts + .iter() + .any(|(_, part)| CFormatPart::has_key(part)) + && format_string + .format_parts + .iter() + .filter(|(_, part)| CFormatPart::is_specifier(part)) + .all(|(_, part)| CFormatPart::has_key(part)); + + let values_obj = if mapping_required { + if !objtype::isinstance(&values_obj, &vm.ctx.dict_type()) { + return Err(vm.new_type_error("format requires a mapping".to_string())); + } + values_obj + } else { + // check for only literal parts, in which case only empty tuple is allowed + if 0 == num_specifiers + && (!objtype::isinstance(&values_obj, &vm.ctx.tuple_type()) + || !objtuple::get_value(&values_obj).is_empty()) + { + return Err(vm.new_type_error( + "not all arguments converted during string formatting".to_string(), + )); + } + + // convert `values_obj` to a new tuple if it's not a tuple + if !objtype::isinstance(&values_obj, &vm.ctx.tuple_type()) { + vm.ctx.new_tuple(vec![values_obj.clone()]) + } else { + values_obj.clone() + } + + // if values.len() > num_specifiers { + // return Err(vm.new_type_error("not all arguments converted during string formatting".to_string())); + // } else if values.len() < num_specifiers { + // return Err(vm.new_type_error("not enough arguments for format string".to_string())); + // } + }; + + let mut auto_index: usize = 0; + for (_, part) in &format_string.format_parts { + let result_string: String = match part { + CFormatPart::Spec(format_spec) => { + // try to get the object + let obj: PyObjectRef = match &format_spec.mapping_key { + Some(key) => { + // TODO: change the KeyError message to match the one in cpython + call_getitem(vm, &values_obj, &vm.ctx.new_str(key.to_string()))? + } + None => { + // TODO: translate exception from IndexError to TypeError + let obj = call_getitem(vm, &values_obj, &vm.ctx.new_int(auto_index))?; + auto_index += 1; + + obj + } + }; + + do_cformat_specifier(vm, &format_spec, obj) + } + CFormatPart::Literal(literal) => Ok(literal.clone()), + }?; + final_string.push_str(&result_string); + } + + Ok(vm.ctx.new_str(final_string)) } fn perform_format( From dc77728dbca08a62b6a51a219b2c139791bf6dfd Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sat, 15 Jun 2019 16:31:26 +0300 Subject: [PATCH 778/884] cformat: Add some unittests. Also, this commit fixes some bugs that were found using said unittests. --- vm/src/cformat.rs | 347 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 293 insertions(+), 54 deletions(-) diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index 1f2d175f65..3e2af73462 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -19,6 +19,7 @@ pub enum CFormatErrorType { // also contains how many chars the parsing function consumed type ParsingError = (CFormatErrorType, usize); +#[derive(Debug, PartialEq)] pub struct CFormatError { pub typ: CFormatErrorType, pub index: usize, @@ -77,6 +78,7 @@ pub struct CFormatSpec { pub precision: Option, pub format_type: CFormatType, pub format_char: char, + chars_consumed: usize, } impl CFormatSpec { @@ -86,7 +88,12 @@ impl CFormatSpec { .collect::() } - pub fn fill_string(&self, string: String, fill_char: char, num_prefix_chars: Option) -> String { + pub fn fill_string( + &self, + string: String, + fill_char: char, + num_prefix_chars: Option, + ) -> String { let mut num_chars = string.len(); if let Some(num_prefix_chars) = num_prefix_chars { num_chars = num_chars + num_prefix_chars; @@ -125,11 +132,6 @@ impl CFormatSpec { pub fn format_number(&self, num: &BigInt) -> String { use CFormatCase::{Lowercase, Uppercase}; use CNumberType::*; - let fill_char = if self.flags.contains(CConversionFlags::ZERO_PAD) { - '0' - } else { - ' ' - }; let magnitude = num.abs(); let prefix = if self.flags.contains(CConversionFlags::ALTERNATE_FORM) { match self.format_type { @@ -154,7 +156,6 @@ impl CFormatSpec { _ => unreachable!(), // Should not happen because caller has to make sure that this is a number }; - let sign_string = match num.sign() { Sign::Minus => "-", _ => { @@ -168,16 +169,22 @@ impl CFormatSpec { } }; - let prefix = format!("{}{}", sign_string, prefix); - let magnitude_filled_string = if !self.flags.contains(CConversionFlags::LEFT_ADJUST) { - // '-' flags overrides '0' flag - self.fill_string(magnitude_string, fill_char, Some(prefix.len())) - } else { - magnitude_string - }; - format!("{}{}", prefix, magnitude_filled_string) + if self.flags.contains(CConversionFlags::ZERO_PAD) { + let fill_char = if !self.flags.contains(CConversionFlags::LEFT_ADJUST) { + '0' + } else { + ' ' // '-' overrides the '0' conversion if both are given + }; + format!( + "{}{}", + prefix, + self.fill_string(magnitude_string, fill_char, Some(prefix.len())) + ) + } else { + self.fill_string(format!("{}{}", prefix, magnitude_string), ' ', None) + } } } @@ -223,11 +230,9 @@ impl FromStr for CFormatString { index = index + consumed; new_text }) - .map_err(|(e, consumed)| { - CFormatError { - typ: e, - index: index + consumed, - } + .map_err(|(e, consumed)| CFormatError { + typ: e, + index: index + consumed, })?; } @@ -426,46 +431,280 @@ fn parse_format_type(text: &str) -> Result<(CFormatType, &str, char), CFormatErr } } -fn parse_specifier(text: &str) -> Result<(CFormatPart, &str, usize), ParsingError> { - let consumed = 0; - let mut chars = text.chars(); - if chars.next() != Some('%') { - return Err((CFormatErrorType::MissingModuloSign, consumed)); - } - let consumed = consumed + 1; - - let (mapping_key, after_mapping_key) = - parse_spec_mapping_key(chars.as_str()).map_err(|err| (err, consumed))?; - let consumed = text.find(after_mapping_key).unwrap(); - let (flags, after_flags) = parse_flags(after_mapping_key); - let (width, after_width) = parse_number(after_flags); - let (precision, after_precision) = parse_precision(after_width); - // A length modifier (h, l, or L) may be present, - // but is ignored as it is not necessary for Python – so e.g. %ld is identical to %d. - let after_length = consume_length(after_precision); - let (format_type, remaining_text, format_char) = - parse_format_type(after_length).map_err(|err| (err, consumed))?; - let consumed = text.find(remaining_text).unwrap(); - - // apply default precision for float types - let precision = match precision { - Some(precision) => Some(precision), - None => match format_type { - CFormatType::Float(_) => Some(6), - _ => None, - }, - }; +impl FromStr for CFormatSpec { + type Err = ParsingError; - Ok(( - CFormatPart::Spec(CFormatSpec { + fn from_str(text: &str) -> Result { + let consumed = 0; + let mut chars = text.chars(); + if chars.next() != Some('%') { + return Err((CFormatErrorType::MissingModuloSign, consumed)); + } + let consumed = consumed + 1; + + let (mapping_key, after_mapping_key) = + parse_spec_mapping_key(chars.as_str()).map_err(|err| (err, consumed))?; + let (flags, after_flags) = parse_flags(after_mapping_key); + let (width, after_width) = parse_number(after_flags); + let (precision, after_precision) = parse_precision(after_width); + // A length modifier (h, l, or L) may be present, + // but is ignored as it is not necessary for Python – so e.g. %ld is identical to %d. + let after_length = consume_length(after_precision); + let consumed = text.find(after_length).unwrap(); + let (format_type, _remaining_text, format_char) = + parse_format_type(after_length).map_err(|err| (err, consumed))?; + let consumed = consumed + 1; // not using text.find because if the str is exausted consumed will be 0 + + // apply default precision for float types + let precision = match precision { + Some(precision) => Some(precision), + None => match format_type { + CFormatType::Float(_) => Some(6), + _ => None, + }, + }; + + Ok(CFormatSpec { mapping_key: mapping_key, flags: flags, min_field_width: width, precision: precision, format_type: format_type, format_char: format_char, - }), - remaining_text, - consumed, + chars_consumed: consumed, + }) + } +} + +fn parse_specifier(text: &str) -> Result<(CFormatPart, &str, usize), ParsingError> { + let spec = text.parse::()?; + let chars_consumed = spec.chars_consumed; + Ok(( + CFormatPart::Spec(spec), + &text[chars_consumed..], + chars_consumed, )) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_fill_and_align() { + assert_eq!( + "%10s" + .parse::() + .unwrap() + .format_string("test".to_string()), + " test".to_string() + ); + assert_eq!( + "%-10s" + .parse::() + .unwrap() + .format_string("test".to_string()), + "test ".to_string() + ); + assert_eq!( + "%#10x" + .parse::() + .unwrap() + .format_number(&BigInt::from(0x1337)), + " 0x1337".to_string() + ); + assert_eq!( + "%-#10x" + .parse::() + .unwrap() + .format_number(&BigInt::from(0x1337)), + "0x1337 ".to_string() + ); + } + + #[test] + fn test_parse_key() { + let expected = Ok(CFormatSpec { + mapping_key: Some("amount".to_string()), + format_type: CFormatType::Number(CNumberType::Decimal), + format_char: 'd', + chars_consumed: 10, + min_field_width: None, + precision: None, + flags: CConversionFlags::empty(), + }); + assert_eq!("%(amount)d".parse::(), expected); + } + + #[test] + fn test_format_parse_key_fail() { + assert_eq!( + "%(aged".parse::(), + Err(CFormatError { + typ: CFormatErrorType::UnmatchedKeyParentheses, + index: 1 + }) + ); + } + + #[test] + fn test_format_parse_type_fail() { + assert_eq!( + "Hello %n".parse::(), + Err(CFormatError { + typ: CFormatErrorType::UnsupportedFormatChar('n'), + index: 7 + }) + ); + } + + #[test] + fn test_incomplete_format_fail() { + assert_eq!( + "Hello %".parse::(), + Err(CFormatError { + typ: CFormatErrorType::IncompleteFormat, + index: 6 + }) + ); + } + + #[test] + fn test_parse_flags() { + let expected = Ok(CFormatSpec { + format_type: CFormatType::Number(CNumberType::Decimal), + format_char: 'd', + chars_consumed: 17, + min_field_width: Some(10), + precision: None, + mapping_key: None, + flags: CConversionFlags::all(), + }); + let parsed = "% 0 -+++###10d".parse::(); + assert_eq!(parsed, expected); + assert_eq!( + parsed.unwrap().format_number(&BigInt::from(12)), + "+12 ".to_string() + ); + } + + #[test] + fn test_parse_and_format_string() { + assert_eq!( + "%5.4s" + .parse::() + .unwrap() + .format_string("Hello, World!".to_string()), + " Hell".to_string() + ); + assert_eq!( + "%-5.4s" + .parse::() + .unwrap() + .format_string("Hello, World!".to_string()), + "Hell ".to_string() + ); + } + + #[test] + fn test_parse_and_format_number() { + assert_eq!( + "%05d" + .parse::() + .unwrap() + .format_number(&BigInt::from(27)), + "00027".to_string() + ); + assert_eq!( + "%+05d" + .parse::() + .unwrap() + .format_number(&BigInt::from(27)), + "+0027".to_string() + ); + assert_eq!( + "%-d" + .parse::() + .unwrap() + .format_number(&BigInt::from(-27)), + "-27".to_string() + ); + assert_eq!( + "% d" + .parse::() + .unwrap() + .format_number(&BigInt::from(27)), + " 27".to_string() + ); + assert_eq!( + "% d" + .parse::() + .unwrap() + .format_number(&BigInt::from(-27)), + "-27".to_string() + ); + assert_eq!( + "%08x" + .parse::() + .unwrap() + .format_number(&BigInt::from(0x1337)), + "00001337".to_string() + ); + assert_eq!( + "%#010x" + .parse::() + .unwrap() + .format_number(&BigInt::from(0x1337)), + "0x00001337".to_string() + ); + assert_eq!( + "%-#010x" + .parse::() + .unwrap() + .format_number(&BigInt::from(0x1337)), + "0x1337 ".to_string() + ); + } + + #[test] + fn test_format_parse() { + let fmt = "Hello, my name is %s and I'm %d years old"; + let expected = Ok(CFormatString { + format_parts: vec![ + (0, CFormatPart::Literal("Hello, my name is ".to_string())), + ( + 18, + CFormatPart::Spec(CFormatSpec { + format_type: CFormatType::String(CFormatPreconversor::Str), + format_char: 's', + chars_consumed: 2, + mapping_key: None, + min_field_width: None, + precision: None, + flags: CConversionFlags::empty(), + }), + ), + (20, CFormatPart::Literal(" and I'm ".to_string())), + ( + 29, + CFormatPart::Spec(CFormatSpec { + format_type: CFormatType::Number(CNumberType::Decimal), + format_char: 'd', + chars_consumed: 2, + mapping_key: None, + min_field_width: None, + precision: None, + flags: CConversionFlags::empty(), + }), + ), + (31, CFormatPart::Literal(" years old".to_string())), + ], + }); + let result = fmt.parse::(); + assert_eq!( + result, expected, + "left = {:#?} \n\n\n right = {:#?}", + result, expected + ); + } +} From d5ffecea327f9e4046f9afbaeeaf67f8224e78f0 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sat, 15 Jun 2019 16:51:50 +0300 Subject: [PATCH 779/884] objstr: Improve errors for modulo format --- vm/src/obj/objstr.rs | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 13c5f776d5..8d1ace5a44 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1187,12 +1187,6 @@ fn do_cformat( } else { values_obj.clone() } - - // if values.len() > num_specifiers { - // return Err(vm.new_type_error("not all arguments converted during string formatting".to_string())); - // } else if values.len() < num_specifiers { - // return Err(vm.new_type_error("not enough arguments for format string".to_string())); - // } }; let mut auto_index: usize = 0; @@ -1206,8 +1200,13 @@ fn do_cformat( call_getitem(vm, &values_obj, &vm.ctx.new_str(key.to_string()))? } None => { - // TODO: translate exception from IndexError to TypeError - let obj = call_getitem(vm, &values_obj, &vm.ctx.new_int(auto_index))?; + let elements = objtuple::get_value(&values_obj); + let obj = match elements.into_iter().nth(auto_index) { + Some(obj) => Ok(obj), + None => Err(vm.new_type_error( + "not enough arguments for format string".to_string(), + )), + }?; auto_index += 1; obj @@ -1221,6 +1220,20 @@ fn do_cformat( final_string.push_str(&result_string); } + // check that all arguments were converted + if !mapping_required { + if !objtuple::get_value(&values_obj) + .into_iter() + .skip(auto_index) + .collect::>() + .is_empty() + { + return Err(vm.new_type_error( + "not all arguments converted during string formatting".to_string(), + )); + } + } + Ok(vm.ctx.new_str(final_string)) } From e374aff0bc6446ef733075a1af5bf7800d5e603b Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sat, 15 Jun 2019 22:48:11 +0300 Subject: [PATCH 780/884] Support '*' for width and precsision for printf-style formatting --- vm/src/cformat.rs | 50 ++++++++++++++++++++++++++++++++++++-------- vm/src/format.rs | 6 +++--- vm/src/obj/objstr.rs | 48 +++++++++++++++++++++++++++++++++++------- 3 files changed, 85 insertions(+), 19 deletions(-) diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index 3e2af73462..7b837aaf5a 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -1,4 +1,4 @@ -use crate::format::{parse_number, parse_precision}; +use crate::format::get_num_digits; /// Implementation of Printf-Style string formatting /// [https://docs.python.org/3/library/stdtypes.html#printf-style-string-formatting] use num_bigint::{BigInt, Sign}; @@ -70,12 +70,18 @@ bitflags! { } } +#[derive(Debug, PartialEq)] +pub enum CFormatQuantity { + Amount(usize), + FromValuesTuple, +} + #[derive(Debug, PartialEq)] pub struct CFormatSpec { pub mapping_key: Option, pub flags: CConversionFlags, - pub min_field_width: Option, - pub precision: Option, + pub min_field_width: Option, + pub precision: Option, pub format_type: CFormatType, pub format_char: char, chars_consumed: usize, @@ -101,8 +107,8 @@ impl CFormatSpec { let num_chars = num_chars; let width = match self.min_field_width { - Some(width) => cmp::max(width, num_chars), - None => num_chars, + Some(CFormatQuantity::Amount(width)) => cmp::max(width, num_chars), + _ => num_chars, }; let fill_chars_needed = width - num_chars; let fill_string = CFormatSpec::compute_fill_string(fill_char, fill_chars_needed); @@ -121,7 +127,7 @@ impl CFormatSpec { pub fn format_string(&self, string: String) -> String { let mut string = string; // truncate if needed - if let Some(precision) = self.precision { + if let Some(CFormatQuantity::Amount(precision)) = self.precision { if string.len() > precision { string.truncate(precision); } @@ -242,6 +248,32 @@ impl FromStr for CFormatString { } } +fn parse_quantity(text: &str) -> (Option, &str) { + let num_digits: usize = get_num_digits(text); + if num_digits == 0 { + let mut chars = text.chars(); + return match chars.next() { + Some('*') => (Some(CFormatQuantity::FromValuesTuple), chars.as_str()), + _ => (None, text), + }; + } + // This should never fail + ( + Some(CFormatQuantity::Amount( + text[..num_digits].parse::().unwrap(), + )), + &text[num_digits..], + ) +} + +fn parse_precision(text: &str) -> (Option, &str) { + let mut chars = text.chars(); + match chars.next() { + Some('.') => parse_quantity(&chars.as_str()), + _ => (None, text), + } +} + fn parse_literal_single(text: &str) -> Result<(char, &str), CFormatErrorType> { let mut chars = text.chars(); // TODO get rid of the unwrap @@ -445,7 +477,7 @@ impl FromStr for CFormatSpec { let (mapping_key, after_mapping_key) = parse_spec_mapping_key(chars.as_str()).map_err(|err| (err, consumed))?; let (flags, after_flags) = parse_flags(after_mapping_key); - let (width, after_width) = parse_number(after_flags); + let (width, after_width) = parse_quantity(after_flags); let (precision, after_precision) = parse_precision(after_width); // A length modifier (h, l, or L) may be present, // but is ignored as it is not necessary for Python – so e.g. %ld is identical to %d. @@ -459,7 +491,7 @@ impl FromStr for CFormatSpec { let precision = match precision { Some(precision) => Some(precision), None => match format_type { - CFormatType::Float(_) => Some(6), + CFormatType::Float(_) => Some(CFormatQuantity::Amount(6)), _ => None, }, }; @@ -575,7 +607,7 @@ mod tests { format_type: CFormatType::Number(CNumberType::Decimal), format_char: 'd', chars_consumed: 17, - min_field_width: Some(10), + min_field_width: Some(CFormatQuantity::Amount(10)), precision: None, mapping_key: None, flags: CConversionFlags::all(), diff --git a/vm/src/format.rs b/vm/src/format.rs index 35ea31b64f..727cbb05b7 100644 --- a/vm/src/format.rs +++ b/vm/src/format.rs @@ -110,7 +110,7 @@ pub struct FormatSpec { format_type: Option, } -fn get_num_digits(text: &str) -> usize { +pub fn get_num_digits(text: &str) -> usize { for (index, character) in text.char_indices() { if !character.is_digit(10) { return index; @@ -151,7 +151,7 @@ fn parse_fill_and_align(text: &str) -> (Option, Option, &str) } } -pub fn parse_number(text: &str) -> (Option, &str) { +fn parse_number(text: &str) -> (Option, &str) { let num_digits: usize = get_num_digits(text); if num_digits == 0 { return (None, text); @@ -189,7 +189,7 @@ fn parse_zero(text: &str) -> &str { } } -pub fn parse_precision(text: &str) -> (Option, &str) { +fn parse_precision(text: &str) -> (Option, &str) { let mut chars = text.chars(); match chars.next() { Some('.') => { diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 8d1ace5a44..93ced37ffe 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -12,8 +12,8 @@ use unicode_segmentation::UnicodeSegmentation; use unicode_xid::UnicodeXID; use crate::cformat::{ - CFormatErrorType, CFormatPart, CFormatPreconversor, CFormatSpec, CFormatString, CFormatType, - CNumberType, + CFormatErrorType, CFormatPart, CFormatPreconversor, CFormatQuantity, CFormatSpec, + CFormatString, CFormatType, CNumberType, }; use crate::format::{FormatParseError, FormatPart, FormatPreconversor, FormatString}; use crate::function::{single_or_tuple_any, OptionalArg, PyFuncArgs}; @@ -1146,7 +1146,7 @@ fn do_cformat_specifier( fn do_cformat( vm: &VirtualMachine, - format_string: CFormatString, + mut format_string: CFormatString, values_obj: PyObjectRef, ) -> PyResult { let mut final_string = String::new(); @@ -1169,7 +1169,7 @@ fn do_cformat( if !objtype::isinstance(&values_obj, &vm.ctx.dict_type()) { return Err(vm.new_type_error("format requires a mapping".to_string())); } - values_obj + values_obj.clone() } else { // check for only literal parts, in which case only empty tuple is allowed if 0 == num_specifiers @@ -1190,7 +1190,7 @@ fn do_cformat( }; let mut auto_index: usize = 0; - for (_, part) in &format_string.format_parts { + for (_, part) in &mut format_string.format_parts { let result_string: String = match part { CFormatPart::Spec(format_spec) => { // try to get the object @@ -1200,8 +1200,42 @@ fn do_cformat( call_getitem(vm, &values_obj, &vm.ctx.new_str(key.to_string()))? } None => { - let elements = objtuple::get_value(&values_obj); - let obj = match elements.into_iter().nth(auto_index) { + let mut elements = objtuple::get_value(&values_obj) + .into_iter() + .skip(auto_index); + + let mut try_quantity_from_tuple = |q: &mut Option| { + match q { + Some(CFormatQuantity::FromValuesTuple) => { + match elements.next() { + Some(width_obj) => { + auto_index += 1; + if !objtype::isinstance(&width_obj, &vm.ctx.int_type()) + { + Err(vm.new_type_error("* wants int".to_string())) + } else { + // TODO: handle errors when truncating BigInt to usize + *q = Some(CFormatQuantity::Amount( + objint::get_value(&width_obj) + .to_usize() + .unwrap(), + )); + Ok(()) + } + } + None => Err(vm.new_type_error( + "not enough arguments for format string".to_string(), + )), + } + } + _ => Ok(()), + } + }; + + try_quantity_from_tuple(&mut format_spec.min_field_width)?; + try_quantity_from_tuple(&mut format_spec.precision)?; + + let obj = match elements.next() { Some(obj) => Ok(obj), None => Err(vm.new_type_error( "not enough arguments for format string".to_string(), From 8e880b9bdd108c50447da76d13c0f3d496ba073a Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sun, 16 Jun 2019 10:54:35 +0300 Subject: [PATCH 781/884] cformat: Fix a bug when formatting unicode strings String::len() returns number of bytes and not number of chars, so it doens't work as expected for unicode strings. Also, added a test that would have triggered the bug. --- vm/src/cformat.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index 7b837aaf5a..d3447742a0 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -100,7 +100,7 @@ impl CFormatSpec { fill_char: char, num_prefix_chars: Option, ) -> String { - let mut num_chars = string.len(); + let mut num_chars = string.chars().count(); if let Some(num_prefix_chars) = num_prefix_chars { num_chars = num_chars + num_prefix_chars; } @@ -128,8 +128,8 @@ impl CFormatSpec { let mut string = string; // truncate if needed if let Some(CFormatQuantity::Amount(precision)) = self.precision { - if string.len() > precision { - string.truncate(precision); + if string.chars().count() > precision { + string = string.chars().take(precision).collect::(); } } self.fill_string(string, ' ', None) @@ -186,7 +186,7 @@ impl CFormatSpec { format!( "{}{}", prefix, - self.fill_string(magnitude_string, fill_char, Some(prefix.len())) + self.fill_string(magnitude_string, fill_char, Some(prefix.chars().count())) ) } else { self.fill_string(format!("{}{}", prefix, magnitude_string), ' ', None) @@ -322,7 +322,7 @@ fn parse_literal(text: &str) -> Result<(CFormatPart, &str, usize), ParsingError> Ok(( CFormatPart::Literal(result_string.to_string()), "", - text.len(), + text.chars().count(), )) } @@ -638,6 +638,17 @@ mod tests { ); } + #[test] + fn test_parse_and_format_unicode_string() { + assert_eq!( + "%.2s" + .parse::() + .unwrap() + .format_string("❤❤❤❤❤❤❤❤".to_string()), + "❤❤".to_string() + ); + } + #[test] fn test_parse_and_format_number() { assert_eq!( From 8d612f3a36c5770058f92866f77c9d2bd351a279 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sun, 16 Jun 2019 10:57:46 +0300 Subject: [PATCH 782/884] Add %c formatting --- vm/src/obj/objstr.rs | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 93ced37ffe..b580dd86d6 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1,6 +1,7 @@ extern crate unicode_categories; extern crate unicode_xid; +use std::char; use std::fmt; use std::ops::Range; use std::str::FromStr; @@ -442,7 +443,7 @@ impl PyString { Err(err) => match err.typ { CFormatErrorType::UnsupportedFormatChar(c) => Err(vm.new_value_error(format!( "unsupported format character '{}' ({:#x}) at index {}", - c, c as u8, err.index + c, c as u32, err.index ))), CFormatErrorType::UnmatchedKeyParentheses => { // TODO in cpython, this error comes after verifying that `values` is a mapping type. @@ -1105,7 +1106,7 @@ fn call_object_format(vm: &VirtualMachine, argument: PyObjectRef, format_spec: & fn do_cformat_specifier( vm: &VirtualMachine, - format_spec: &CFormatSpec, + format_spec: &mut CFormatSpec, obj: PyObjectRef, ) -> Result { use CNumberType::*; @@ -1137,6 +1138,33 @@ fn do_cformat_specifier( } Ok(format_spec.format_number(objint::get_value(&obj))) } + CFormatType::Character => { + let char_string = { + if objtype::isinstance(&obj, &vm.ctx.int_type()) { + // BigInt truncation is fine in this case because only the unicode range is relevant + match objint::get_value(&obj).to_u32().and_then(char::from_u32) { + Some(value) => Ok(value.to_string()), + None => { + Err(vm.new_overflow_error("%c arg not in range(0x110000)".to_string())) + } + } + } else if objtype::isinstance(&obj, &vm.ctx.str_type()) { + let s: String = get_value(&obj); + let num_chars = s.chars().count(); + if num_chars != 1 { + Err(vm.new_type_error("%c requires int or char".to_string())) + } else { + println!("Hurray!"); + Ok(s.chars().next().unwrap().to_string()) + } + } else { + // TODO re-arrange this block so this error is only created once + Err(vm.new_type_error("%c requires int or char".to_string())) + } + }?; + format_spec.precision = Some(CFormatQuantity::Amount(1)); + Ok(format_spec.format_string(char_string)) + } _ => Err(vm.new_not_implemented_error(format!( "Not yet implemented for %{}", format_spec.format_char @@ -1246,8 +1274,7 @@ fn do_cformat( obj } }; - - do_cformat_specifier(vm, &format_spec, obj) + do_cformat_specifier(vm, format_spec, obj) } CFormatPart::Literal(literal) => Ok(literal.clone()), }?; From 5d38b9b6245815ca36677e6908bb8e58171db55e Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Sun, 16 Jun 2019 19:57:01 +0300 Subject: [PATCH 783/884] Simplify error handling for CFormatString::from_str --- vm/src/cformat.rs | 17 +++++++++++++++++ vm/src/obj/objstr.rs | 24 +++++------------------- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index d3447742a0..f165b943a5 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -4,6 +4,7 @@ use crate::format::get_num_digits; use num_bigint::{BigInt, Sign}; use num_traits::Signed; use std::cmp; +use std::fmt; use std::str::FromStr; #[derive(Debug, PartialEq)] @@ -25,6 +26,22 @@ pub struct CFormatError { pub index: usize, } +impl fmt::Display for CFormatError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use CFormatErrorType::*; + match self.typ { + UnmatchedKeyParentheses => write!(f, "incomplete format key"), + CFormatErrorType::IncompleteFormat => write!(f, "incomplete format"), + UnsupportedFormatChar(c) => write!( + f, + "unsupported format character '{}' ({:#x}) at index {}", + c, c as u32, self.index + ), + _ => write!(f, "unexpected error parsing format string"), + } + } +} + #[derive(Debug, PartialEq)] pub enum CFormatPreconversor { Repr, diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index b580dd86d6..1d0174093a 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -13,8 +13,8 @@ use unicode_segmentation::UnicodeSegmentation; use unicode_xid::UnicodeXID; use crate::cformat::{ - CFormatErrorType, CFormatPart, CFormatPreconversor, CFormatQuantity, CFormatSpec, - CFormatString, CFormatType, CNumberType, + CFormatPart, CFormatPreconversor, CFormatQuantity, CFormatSpec, CFormatString, CFormatType, + CNumberType, }; use crate::format::{FormatParseError, FormatPart, FormatPreconversor, FormatString}; use crate::function::{single_or_tuple_any, OptionalArg, PyFuncArgs}; @@ -438,23 +438,9 @@ impl PyString { #[pymethod(name = "__mod__")] fn modulo(&self, values: PyObjectRef, vm: &VirtualMachine) -> PyResult { let format_string_text = &self.value; - match CFormatString::from_str(format_string_text) { - Ok(format_string) => do_cformat(vm, format_string, values.clone()), - Err(err) => match err.typ { - CFormatErrorType::UnsupportedFormatChar(c) => Err(vm.new_value_error(format!( - "unsupported format character '{}' ({:#x}) at index {}", - c, c as u32, err.index - ))), - CFormatErrorType::UnmatchedKeyParentheses => { - // TODO in cpython, this error comes after verifying that `values` is a mapping type. - Err(vm.new_value_error("incomplete format key".to_string())) - } - CFormatErrorType::IncompleteFormat => { - Err(vm.new_value_error("incomplete format".to_string())) - } - _ => Err(vm.new_value_error("Unexpected error parsing format string".to_string())), - }, - } + let format_string = CFormatString::from_str(format_string_text) + .map_err(|err| vm.new_value_error(format!("{}", err)))?; + do_cformat(vm, format_string, values.clone()) } #[pymethod] From fca41783530b0a5a184b45a425f10f468e3142ec Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Mon, 17 Jun 2019 12:06:43 +0300 Subject: [PATCH 784/884] Avoid unnecessary allocation in modulo formatting --- vm/src/obj/objstr.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 1d0174093a..188799d61a 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1269,11 +1269,11 @@ fn do_cformat( // check that all arguments were converted if !mapping_required { - if !objtuple::get_value(&values_obj) + if objtuple::get_value(&values_obj) .into_iter() .skip(auto_index) - .collect::>() - .is_empty() + .next() + .is_some() { return Err(vm.new_type_error( "not all arguments converted during string formatting".to_string(), From 6fe2c43bf78d175fedc6393ba15e161d7a9b163c Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Mon, 17 Jun 2019 12:27:53 +0300 Subject: [PATCH 785/884] Move lambda to its own function in do_cformat --- vm/src/obj/objstr.rs | 82 +++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 188799d61a..c8078e1887 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1158,6 +1158,36 @@ fn do_cformat_specifier( } } +fn try_update_quantity_from_tuple( + vm: &VirtualMachine, + elements: &mut Iterator, + q: &mut Option, + mut tuple_index: usize, +) -> PyResult { + match q { + Some(CFormatQuantity::FromValuesTuple) => { + match elements.next() { + Some(width_obj) => { + tuple_index += 1; + if !objtype::isinstance(&width_obj, &vm.ctx.int_type()) { + Err(vm.new_type_error("* wants int".to_string())) + } else { + // TODO: handle errors when truncating BigInt to usize + *q = Some(CFormatQuantity::Amount( + objint::get_value(&width_obj).to_usize().unwrap(), + )); + Ok(tuple_index) + } + } + None => { + Err(vm.new_type_error("not enough arguments for format string".to_string())) + } + } + } + _ => Ok(tuple_index), + } +} + fn do_cformat( vm: &VirtualMachine, mut format_string: CFormatString, @@ -1203,7 +1233,7 @@ fn do_cformat( } }; - let mut auto_index: usize = 0; + let mut tuple_index: usize = 0; for (_, part) in &mut format_string.format_parts { let result_string: String = match part { CFormatPart::Spec(format_spec) => { @@ -1216,38 +1246,20 @@ fn do_cformat( None => { let mut elements = objtuple::get_value(&values_obj) .into_iter() - .skip(auto_index); - - let mut try_quantity_from_tuple = |q: &mut Option| { - match q { - Some(CFormatQuantity::FromValuesTuple) => { - match elements.next() { - Some(width_obj) => { - auto_index += 1; - if !objtype::isinstance(&width_obj, &vm.ctx.int_type()) - { - Err(vm.new_type_error("* wants int".to_string())) - } else { - // TODO: handle errors when truncating BigInt to usize - *q = Some(CFormatQuantity::Amount( - objint::get_value(&width_obj) - .to_usize() - .unwrap(), - )); - Ok(()) - } - } - None => Err(vm.new_type_error( - "not enough arguments for format string".to_string(), - )), - } - } - _ => Ok(()), - } - }; - - try_quantity_from_tuple(&mut format_spec.min_field_width)?; - try_quantity_from_tuple(&mut format_spec.precision)?; + .skip(tuple_index); + + tuple_index = try_update_quantity_from_tuple( + vm, + &mut elements, + &mut format_spec.min_field_width, + tuple_index, + )?; + tuple_index = try_update_quantity_from_tuple( + vm, + &mut elements, + &mut format_spec.precision, + tuple_index, + )?; let obj = match elements.next() { Some(obj) => Ok(obj), @@ -1255,7 +1267,7 @@ fn do_cformat( "not enough arguments for format string".to_string(), )), }?; - auto_index += 1; + tuple_index += 1; obj } @@ -1271,7 +1283,7 @@ fn do_cformat( if !mapping_required { if objtuple::get_value(&values_obj) .into_iter() - .skip(auto_index) + .skip(tuple_index) .next() .is_some() { From adccfa63cf3096088a6892bf22a027ad6ada0800 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Mon, 17 Jun 2019 17:38:34 +0300 Subject: [PATCH 786/884] Fix bugs when parsing format specifiers with key mappings. * Algorithm used for parsing the key from "%(key)d" format did not handle multiple parenthesis correctly. * Second problem is that they way we calculated number of consumed chars was based on str::find, making the code vulnerable to false-positives. --- vm/src/cformat.rs | 75 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/vm/src/cformat.rs b/vm/src/cformat.rs index f165b943a5..f8f98210b3 100644 --- a/vm/src/cformat.rs +++ b/vm/src/cformat.rs @@ -343,19 +343,41 @@ fn parse_literal(text: &str) -> Result<(CFormatPart, &str, usize), ParsingError> )) } +fn parse_text_inside_parentheses(text: &str) -> Option<(String, &str)> { + let mut counter = 1; + let mut chars = text.chars(); + let mut contained_text = String::new(); + while counter > 0 { + let c = chars.next(); + + match c { + Some('(') => { + counter += 1; + } + Some(')') => { + counter -= 1; + } + None => { + return None; + } + _ => (), + } + + if counter > 0 { + contained_text.push(c.unwrap()); + } + } + + Some((contained_text, chars.as_str())) +} + fn parse_spec_mapping_key(text: &str) -> Result<(Option, &str), CFormatErrorType> { let mut chars = text.chars(); let next_char = chars.next(); if next_char == Some('(') { - // Get remaining characters after opening parentheses. - let cur_text = chars.as_str(); - match cur_text.find(')') { - Some(position) => { - let (left, right) = cur_text.split_at(position); - - Ok((Some(left.to_string()), &right[1..])) - } + match parse_text_inside_parentheses(chars.as_str()) { + Some((key, remaining_text)) => Ok((Some(key), remaining_text)), None => Err(CFormatErrorType::UnmatchedKeyParentheses), } } else { @@ -480,29 +502,30 @@ fn parse_format_type(text: &str) -> Result<(CFormatType, &str, char), CFormatErr } } +fn calc_consumed(a: &str, b: &str) -> usize { + a.chars().count() - b.chars().count() +} + impl FromStr for CFormatSpec { type Err = ParsingError; fn from_str(text: &str) -> Result { - let consumed = 0; let mut chars = text.chars(); if chars.next() != Some('%') { - return Err((CFormatErrorType::MissingModuloSign, consumed)); + return Err((CFormatErrorType::MissingModuloSign, 1)); } - let consumed = consumed + 1; - let (mapping_key, after_mapping_key) = - parse_spec_mapping_key(chars.as_str()).map_err(|err| (err, consumed))?; + let after_modulo_sign = chars.as_str(); + let (mapping_key, after_mapping_key) = parse_spec_mapping_key(after_modulo_sign) + .map_err(|err| (err, calc_consumed(text, after_modulo_sign)))?; let (flags, after_flags) = parse_flags(after_mapping_key); let (width, after_width) = parse_quantity(after_flags); let (precision, after_precision) = parse_precision(after_width); // A length modifier (h, l, or L) may be present, // but is ignored as it is not necessary for Python – so e.g. %ld is identical to %d. let after_length = consume_length(after_precision); - let consumed = text.find(after_length).unwrap(); - let (format_type, _remaining_text, format_char) = - parse_format_type(after_length).map_err(|err| (err, consumed))?; - let consumed = consumed + 1; // not using text.find because if the str is exausted consumed will be 0 + let (format_type, remaining_text, format_char) = parse_format_type(after_length) + .map_err(|err| (err, calc_consumed(text, after_length)))?; // apply default precision for float types let precision = match precision { @@ -520,7 +543,7 @@ impl FromStr for CFormatSpec { precision: precision, format_type: format_type, format_char: format_char, - chars_consumed: consumed, + chars_consumed: calc_consumed(text, remaining_text), }) } } @@ -583,6 +606,20 @@ mod tests { flags: CConversionFlags::empty(), }); assert_eq!("%(amount)d".parse::(), expected); + + let expected = Ok(CFormatSpec { + mapping_key: Some("m((u(((l((((ti))))p)))l))e".to_string()), + format_type: CFormatType::Number(CNumberType::Decimal), + format_char: 'd', + chars_consumed: 30, + min_field_width: None, + precision: None, + flags: CConversionFlags::empty(), + }); + assert_eq!( + "%(m((u(((l((((ti))))p)))l))e)d".parse::(), + expected + ); } #[test] @@ -613,7 +650,7 @@ mod tests { "Hello %".parse::(), Err(CFormatError { typ: CFormatErrorType::IncompleteFormat, - index: 6 + index: 7 }) ); } From 64af5c43df547441e643b079c0455e1e451dd013 Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Mon, 17 Jun 2019 19:31:33 +0300 Subject: [PATCH 787/884] Add some test snippets for modulo string formatting --- tests/snippets/strings.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index be95045da9..4067a4d2f0 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -204,6 +204,20 @@ def __repr__(self): assert "{foo} {foo!s} {foo!r} {foo!a}".format(foo=f) == 'str(Foo) str(Foo) repr(Foo) repr(Foo)' # assert '{} {!r} {:10} {!r:10} {foo!r:10} {foo!r} {foo}'.format('txt1', 'txt2', 'txt3', 'txt4', 'txt5', foo='bar') + +# Printf-style String formatting +assert "%d %d" % (1, 2) == "1 2" +assert "%*c " % (3, '❤') == " ❤ " +assert "%(first)s %(second)s" % {'second': 'World!', 'first': "Hello,"} == "Hello, World!" +assert "%(key())s" % {'key()': 'aaa'} +assert "%s %a %r" % (f, f, f) == "str(Foo) repr(Foo) repr(Foo)" +assert "repr() shows quotes: %r; str() doesn't: %s" % ("test1", "test2") == "repr() shows quotes: 'test1'; str() doesn't: test2" + +assert_raises(TypeError, lambda: "My name is %s and I'm %(age)d years old" % ("Foo", 25), msg="format requires a mapping") +assert_raises(TypeError, lambda: "My name is %(name)s" % "Foo", msg="format requires a mapping") +assert_raises(ValueError, lambda: "This %(food}s is great!" % {"food": "cookie"}, msg="incomplete format key") +assert_raises(ValueError, lambda: "My name is %" % "Foo", msg="incomplete format") + assert 'a' < 'b' assert 'a' <= 'b' assert 'a' <= 'a' From 700d7d4b62c2db7ba03d71017402112e31dc461d Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 18 Jun 2019 18:32:36 +0300 Subject: [PATCH 788/884] Add os.rename --- vm/src/stdlib/os.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 60fc6b11b9..8410733ed8 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -671,6 +671,10 @@ fn os_fspath(path: PyObjectRef, vm: &VirtualMachine) -> PyResult { } } +fn os_rename(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { + fs::rename(&src.value, &dst.value).map_err(|s| vm.new_os_error(s.to_string())) +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -751,8 +755,8 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { // pathconf Some None None // readlink Some Some None SupportFunc::new(vm, "remove", os_remove, Some(false), Some(false), None), - // rename Some Some None - // replace Some Some None + SupportFunc::new(vm, "rename", os_rename, Some(false), Some(false), None), + SupportFunc::new(vm, "replace", os_rename, Some(false), Some(false), None), // TODO: Fix replace SupportFunc::new(vm, "rmdir", os_rmdir, Some(false), Some(false), None), SupportFunc::new(vm, "scandir", os_scandir, Some(false), None, None), SupportFunc::new(vm, "stat", os_stat, Some(false), Some(false), Some(false)), From 2f408f221036aceeb777fb0703c56bd7d9082760 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 18 Jun 2019 18:55:37 +0300 Subject: [PATCH 789/884] Add conversion from io::Error to OSError --- vm/src/stdlib/os.rs | 71 ++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 33 deletions(-) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 8410733ed8..24a7a67414 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -144,23 +144,34 @@ pub fn os_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } } - let handle = options.open(&fname).map_err(|err| match err.kind() { + let handle = options + .open(&fname) + .map_err(|err| convert_io_error(vm, err))?; + + Ok(vm.ctx.new_int(raw_file_number(handle))) +} + +fn convert_io_error(vm: &VirtualMachine, err: io::Error) -> PyObjectRef { + let os_error = match err.kind() { ErrorKind::NotFound => { let exc_type = vm.ctx.exceptions.file_not_found_error.clone(); - vm.new_exception(exc_type, format!("No such file or directory: {}", &fname)) + vm.new_exception(exc_type, err.to_string()) } ErrorKind::PermissionDenied => { let exc_type = vm.ctx.exceptions.permission_error.clone(); - vm.new_exception(exc_type, format!("Permission denied: {}", &fname)) + vm.new_exception(exc_type, err.to_string()) } ErrorKind::AlreadyExists => { let exc_type = vm.ctx.exceptions.file_exists_error.clone(); - vm.new_exception(exc_type, format!("File exists: {}", &fname)) + vm.new_exception(exc_type, err.to_string()) } - _ => vm.new_value_error("Unhandled file IO error".to_string()), - })?; - - Ok(vm.ctx.new_int(raw_file_number(handle))) + _ => vm.new_os_error(err.to_string()), + }; + if let Some(errno) = err.raw_os_error() { + vm.set_attr(&os_error, "errno", vm.ctx.new_int(errno)) + .unwrap(); + } + os_error } fn os_error(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -182,8 +193,7 @@ fn os_error(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { fn os_fsync(fd: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { let file = rust_file(fd.as_bigint().to_i64().unwrap()); - file.sync_all() - .map_err(|s| vm.new_os_error(s.to_string()))?; + file.sync_all().map_err(|err| convert_io_error(vm, err))?; // Avoid closing the fd raw_file_number(file); Ok(()) @@ -192,10 +202,8 @@ fn os_fsync(fd: PyIntRef, vm: &VirtualMachine) -> PyResult<()> { fn os_read(fd: PyIntRef, n: PyIntRef, vm: &VirtualMachine) -> PyResult { let mut buffer = vec![0u8; n.as_bigint().to_usize().unwrap()]; let mut file = rust_file(fd.as_bigint().to_i64().unwrap()); - match file.read_exact(&mut buffer) { - Ok(_) => (), - Err(s) => return Err(vm.new_os_error(s.to_string())), - }; + file.read_exact(&mut buffer) + .map_err(|err| convert_io_error(vm, err))?; // Avoid closing the fd raw_file_number(file); @@ -204,10 +212,7 @@ fn os_read(fd: PyIntRef, n: PyIntRef, vm: &VirtualMachine) -> PyResult { fn os_write(fd: PyIntRef, data: PyBytesRef, vm: &VirtualMachine) -> PyResult { let mut file = rust_file(fd.as_bigint().to_i64().unwrap()); - let written = match file.write(&data) { - Ok(written) => written, - Err(s) => return Err(vm.new_os_error(s.to_string())), - }; + let written = file.write(&data).map_err(|err| convert_io_error(vm, err))?; // Avoid closing the fd raw_file_number(file); @@ -216,21 +221,21 @@ fn os_write(fd: PyIntRef, data: PyBytesRef, vm: &VirtualMachine) -> PyResult { fn os_remove(path: PyStringRef, dir_fd: DirFd, vm: &VirtualMachine) -> PyResult<()> { let path = make_path(vm, path, &dir_fd); - fs::remove_file(&path.value).map_err(|s| vm.new_os_error(s.to_string())) + fs::remove_file(&path.value).map_err(|err| convert_io_error(vm, err)) } fn os_mkdir(path: PyStringRef, dir_fd: DirFd, vm: &VirtualMachine) -> PyResult<()> { let path = make_path(vm, path, &dir_fd); - fs::create_dir(&path.value).map_err(|s| vm.new_os_error(s.to_string())) + fs::create_dir(&path.value).map_err(|err| convert_io_error(vm, err)) } fn os_mkdirs(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { - fs::create_dir_all(&path.value).map_err(|s| vm.new_os_error(s.to_string())) + fs::create_dir_all(&path.value).map_err(|err| convert_io_error(vm, err)) } fn os_rmdir(path: PyStringRef, dir_fd: DirFd, vm: &VirtualMachine) -> PyResult<()> { let path = make_path(vm, path, &dir_fd); - fs::remove_dir(&path.value).map_err(|s| vm.new_os_error(s.to_string())) + fs::remove_dir(&path.value).map_err(|err| convert_io_error(vm, err)) } fn os_listdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { @@ -239,7 +244,7 @@ fn os_listdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { let res: PyResult> = iter .map(|entry| match entry { Ok(path) => Ok(vm.ctx.new_str(path.file_name().into_string().unwrap())), - Err(s) => Err(vm.new_os_error(s.to_string())), + Err(s) => Err(convert_io_error(vm, s)), }) .collect(); Ok(vm.ctx.new_list(res?)) @@ -309,7 +314,7 @@ impl DirEntryRef { true => fs::metadata(self.entry.path()), false => fs::symlink_metadata(self.entry.path()), }; - let meta = metadata.map_err(|s| vm.new_os_error(s.to_string()))?; + let meta = metadata.map_err(|err| convert_io_error(vm, err))?; Ok(action(meta)) } @@ -333,7 +338,7 @@ impl DirEntryRef { Ok(self .entry .file_type() - .map_err(|s| vm.new_os_error(s.to_string()))? + .map_err(|err| convert_io_error(vm, err))? .is_symlink()) } @@ -366,7 +371,7 @@ impl ScandirIterator { match self.entries.borrow_mut().next() { Some(entry) => match entry { Ok(entry) => Ok(DirEntry { entry }.into_ref(vm).into_object()), - Err(s) => Err(vm.new_os_error(s.to_string())), + Err(s) => Err(convert_io_error(vm, s)), }, None => Err(objiter::new_stop_iteration(vm)), } @@ -385,7 +390,7 @@ fn os_scandir(path: PyStringRef, vm: &VirtualMachine) -> PyResult { } .into_ref(vm) .into_object()), - Err(s) => Err(vm.new_os_error(s.to_string())), + Err(s) => Err(convert_io_error(vm, s)), } } @@ -496,7 +501,7 @@ macro_rules! os_unix_stat_inner { } get_stats(&$path.value, $follow_symlinks.follow_symlinks) - .map_err(|s| $vm.new_os_error(s.to_string())) + .map_err(|err| convert_io_error($vm, err)) }}; } @@ -609,7 +614,7 @@ fn os_symlink( ) -> PyResult<()> { use std::os::unix::fs as unix_fs; let dst = make_path(vm, dst, &dir_fd); - unix_fs::symlink(&src.value, &dst.value).map_err(|s| vm.new_os_error(s.to_string())) + unix_fs::symlink(&src.value, &dst.value).map_err(|err| convert_io_error(vm, err)) } #[cfg(windows)] @@ -632,7 +637,7 @@ fn os_symlink( } Err(_) => win_fs::symlink_file(&src.value, &dst.value), }; - ret.map_err(|s| vm.new_os_error(s.to_string())) + ret.map_err(|err| convert_io_error(vm, err)) } #[cfg(all(not(unix), not(windows)))] @@ -647,7 +652,7 @@ fn os_symlink( fn os_getcwd(vm: &VirtualMachine) -> PyResult { Ok(env::current_dir() - .map_err(|s| vm.new_os_error(s.to_string()))? + .map_err(|err| convert_io_error(vm, err))? .as_path() .to_str() .unwrap() @@ -655,7 +660,7 @@ fn os_getcwd(vm: &VirtualMachine) -> PyResult { } fn os_chdir(path: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { - env::set_current_dir(&path.value).map_err(|s| vm.new_os_error(s.to_string())) + env::set_current_dir(&path.value).map_err(|err| convert_io_error(vm, err)) } fn os_fspath(path: PyObjectRef, vm: &VirtualMachine) -> PyResult { @@ -672,7 +677,7 @@ fn os_fspath(path: PyObjectRef, vm: &VirtualMachine) -> PyResult { } fn os_rename(src: PyStringRef, dst: PyStringRef, vm: &VirtualMachine) -> PyResult<()> { - fs::rename(&src.value, &dst.value).map_err(|s| vm.new_os_error(s.to_string())) + fs::rename(&src.value, &dst.value).map_err(|err| convert_io_error(vm, err)) } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { From 9ee1ad93e0bc255d8fe636230ec91abde1eef0c0 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 18 Jun 2019 18:58:23 +0300 Subject: [PATCH 790/884] Test os.rename --- tests/snippets/stdlib_os.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 6280fc3bb7..7a851f8d65 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -11,6 +11,7 @@ assert_raises(OSError, lambda: os.read(fd, 10)) assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', os.O_RDONLY)) assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', os.O_WRONLY)) +assert_raises(FileNotFoundError, lambda: os.rename('DOES_NOT_EXIST', 'DOES_NOT_EXIST 2')) assert os.O_RDONLY == 0 @@ -80,6 +81,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): FILE_NAME = "test1" FILE_NAME2 = "test2" +FILE_NAME3 = "test3" SYMLINK_FILE = "symlink" SYMLINK_FOLDER = "symlink1" FOLDER = "dir1" @@ -104,6 +106,19 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert os.read(fd, len(CONTENT3)) == CONTENT3 os.close(fd) + fname3 = os.path.join(tmpdir, FILE_NAME3) + os.rename(fname, fname3) + assert os.path.exists(fname) == False + assert os.path.exists(fname3) == True + + fd = os.open(fname3, 0) + assert os.read(fd, len(CONTENT2) + len(CONTENT3)) == CONTENT2 + CONTENT3 + os.close(fd) + + os.rename(fname3, fname) + assert os.path.exists(fname3) == False + assert os.path.exists(fname) == True + # wait a little bit to ensure that the file times aren't the same time.sleep(0.1) From 17cd22b8e8dafabf6001a75e76fe3bb4c28875bb Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 18 Jun 2019 19:00:05 +0300 Subject: [PATCH 791/884] Test OSError errno --- tests/snippets/stdlib_os.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index 7a851f8d65..ef9b9111d1 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -13,6 +13,12 @@ assert_raises(FileNotFoundError, lambda: os.open('DOES_NOT_EXIST', os.O_WRONLY)) assert_raises(FileNotFoundError, lambda: os.rename('DOES_NOT_EXIST', 'DOES_NOT_EXIST 2')) +try: + os.open('DOES_NOT_EXIST', 0) +except OSError as err: + assert err.errno == 2 + + assert os.O_RDONLY == 0 assert os.O_WRONLY == 1 From a8d80193c72f34cb56ae09a21ddb5f5a12f3c362 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 18 Jun 2019 13:31:58 -0500 Subject: [PATCH 792/884] Move PyObject serialization to its own file --- vm/src/lib.rs | 1 + vm/src/ser_de.rs | 199 +++++++++++++++++++++++++++++++++++ vm/src/stdlib/json.rs | 224 +++------------------------------------- vm/src/vm.rs | 6 +- wasm/lib/src/convert.rs | 2 +- 5 files changed, 218 insertions(+), 214 deletions(-) create mode 100644 vm/src/ser_de.rs diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 8c64cc550f..6b8a42415b 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -55,6 +55,7 @@ pub mod import; pub mod obj; mod pyhash; pub mod pyobject; +pub mod ser_de; pub mod stdlib; mod sysmodule; mod traceback; diff --git a/vm/src/ser_de.rs b/vm/src/ser_de.rs new file mode 100644 index 0000000000..6c45cc6f1e --- /dev/null +++ b/vm/src/ser_de.rs @@ -0,0 +1,199 @@ +use std::fmt; + +use serde; +use serde::de::Visitor; +use serde::ser::{SerializeMap, SerializeSeq}; + +use crate::obj::{ + objbool, + objdict::PyDictRef, + objfloat, objint, objsequence, + objstr::{self, PyString}, + objtype, +}; +use crate::pyobject::{IdProtocol, ItemProtocol, PyObjectRef, TypeProtocol}; +use crate::VirtualMachine; +use num_traits::cast::ToPrimitive; + +// We need to have a VM available to serialise a PyObject based on its subclass, so we implement +// PyObject serialisation via a proxy object which holds a reference to a VM +pub struct PyObjectSerializer<'s> { + pyobject: &'s PyObjectRef, + vm: &'s VirtualMachine, +} + +impl<'s> PyObjectSerializer<'s> { + pub fn new(vm: &'s VirtualMachine, pyobject: &'s PyObjectRef) -> Self { + PyObjectSerializer { pyobject, vm } + } + + fn clone_with_object(&self, pyobject: &'s PyObjectRef) -> PyObjectSerializer { + PyObjectSerializer { + pyobject, + vm: self.vm, + } + } +} + +impl<'s> serde::Serialize for PyObjectSerializer<'s> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let serialize_seq_elements = + |serializer: S, elements: &Vec| -> Result { + let mut seq = serializer.serialize_seq(Some(elements.len()))?; + for e in elements.iter() { + seq.serialize_element(&self.clone_with_object(e))?; + } + seq.end() + }; + if objtype::isinstance(self.pyobject, &self.vm.ctx.str_type()) { + serializer.serialize_str(&objstr::get_value(&self.pyobject)) + } else if objtype::isinstance(self.pyobject, &self.vm.ctx.float_type()) { + serializer.serialize_f64(objfloat::get_value(self.pyobject)) + } else if objtype::isinstance(self.pyobject, &self.vm.ctx.bool_type()) { + serializer.serialize_bool(objbool::get_value(self.pyobject)) + } else if objtype::isinstance(self.pyobject, &self.vm.ctx.int_type()) { + let v = objint::get_value(self.pyobject); + serializer.serialize_i64(v.to_i64().unwrap()) + // Although this may seem nice, it does not give the right result: + // v.serialize(serializer) + } else if objtype::isinstance(self.pyobject, &self.vm.ctx.list_type()) { + let elements = objsequence::get_elements_list(self.pyobject); + serialize_seq_elements(serializer, &elements) + } else if objtype::isinstance(self.pyobject, &self.vm.ctx.tuple_type()) { + let elements = objsequence::get_elements_tuple(self.pyobject); + serialize_seq_elements(serializer, &elements) + } else if objtype::isinstance(self.pyobject, &self.vm.ctx.dict_type()) { + let dict: PyDictRef = self.pyobject.clone().downcast().unwrap(); + let pairs: Vec<_> = dict.into_iter().collect(); + let mut map = serializer.serialize_map(Some(pairs.len()))?; + for (key, e) in pairs.iter() { + map.serialize_entry(&self.clone_with_object(key), &self.clone_with_object(&e))?; + } + map.end() + } else if self.pyobject.is(&self.vm.get_none()) { + serializer.serialize_none() + } else { + Err(serde::ser::Error::custom(format!( + "Object of type '{:?}' is not serializable", + self.pyobject.class() + ))) + } + } +} + +// This object is used as the seed for deserialization so we have access to the PyContext for type +// creation +#[derive(Clone)] +pub struct PyObjectDeserializer<'c> { + vm: &'c VirtualMachine, +} + +impl<'c> PyObjectDeserializer<'c> { + pub fn new(vm: &'c VirtualMachine) -> Self { + PyObjectDeserializer { vm } + } +} + +impl<'de> serde::de::DeserializeSeed<'de> for PyObjectDeserializer<'de> { + type Value = PyObjectRef; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(self.clone()) + } +} + +impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { + type Value = PyObjectRef; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a type that can deserialise in Python") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + Ok(self.vm.ctx.new_str(value.to_string())) + } + + fn visit_string(self, value: String) -> Result + where + E: serde::de::Error, + { + Ok(self.vm.ctx.new_str(value)) + } + + fn visit_i64(self, value: i64) -> Result + where + E: serde::de::Error, + { + // The JSON deserializer always uses the i64/u64 deserializers, so we only need to + // implement those for now + Ok(self.vm.ctx.new_int(value)) + } + + fn visit_u64(self, value: u64) -> Result + where + E: serde::de::Error, + { + // The JSON deserializer always uses the i64/u64 deserializers, so we only need to + // implement those for now + Ok(self.vm.ctx.new_int(value)) + } + + fn visit_f64(self, value: f64) -> Result + where + E: serde::de::Error, + { + Ok(self.vm.ctx.new_float(value)) + } + + fn visit_bool(self, value: bool) -> Result + where + E: serde::de::Error, + { + Ok(self.vm.ctx.new_bool(value)) + } + + fn visit_seq(self, mut access: A) -> Result + where + A: serde::de::SeqAccess<'de>, + { + let mut seq = Vec::with_capacity(access.size_hint().unwrap_or(0)); + while let Some(value) = access.next_element_seed(self.clone())? { + seq.push(value); + } + Ok(self.vm.ctx.new_list(seq)) + } + + fn visit_map(self, mut access: M) -> Result + where + M: serde::de::MapAccess<'de>, + { + let dict = self.vm.ctx.new_dict(); + // TODO: Given keys must be strings, we can probably do something more efficient + // than wrapping the given object up and then unwrapping it to determine whether or + // not it is a string + while let Some((key_obj, value)) = access.next_entry_seed(self.clone(), self.clone())? { + let key: String = match key_obj.payload::() { + Some(PyString { ref value }) => value.clone(), + _ => unimplemented!("map keys must be strings"), + }; + dict.set_item(&key, value, self.vm).unwrap(); + } + Ok(dict.into_object()) + } + + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(self.vm.get_none()) + } +} diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 4b0ac1ca97..4a52681504 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -1,204 +1,24 @@ -use std::fmt; - -use serde; -use serde::de::{DeserializeSeed, Visitor}; -use serde::ser::{SerializeMap, SerializeSeq}; -use serde_json; - -use crate::function::PyFuncArgs; -use crate::obj::{ - objbool, - objdict::PyDictRef, - objfloat, objint, objsequence, - objstr::{self, PyString}, - objtype, -}; -use crate::pyobject::{create_type, IdProtocol, ItemProtocol, PyObjectRef, PyResult, TypeProtocol}; +use crate::obj::objstr::PyStringRef; +use crate::pyobject::{create_type, ItemProtocol, PyObjectRef, PyResult}; +use crate::ser_de::{PyObjectDeserializer, PyObjectSerializer}; use crate::VirtualMachine; -use num_traits::cast::ToPrimitive; - -// We need to have a VM available to serialise a PyObject based on its subclass, so we implement -// PyObject serialisation via a proxy object which holds a reference to a VM -struct PyObjectSerializer<'s> { - pyobject: &'s PyObjectRef, - vm: &'s VirtualMachine, -} - -impl<'s> PyObjectSerializer<'s> { - fn clone_with_object(&self, pyobject: &'s PyObjectRef) -> PyObjectSerializer { - PyObjectSerializer { - pyobject, - vm: self.vm, - } - } -} - -impl<'s> serde::Serialize for PyObjectSerializer<'s> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let serialize_seq_elements = - |serializer: S, elements: &Vec| -> Result { - let mut seq = serializer.serialize_seq(Some(elements.len()))?; - for e in elements.iter() { - seq.serialize_element(&self.clone_with_object(e))?; - } - seq.end() - }; - if objtype::isinstance(self.pyobject, &self.vm.ctx.str_type()) { - serializer.serialize_str(&objstr::get_value(&self.pyobject)) - } else if objtype::isinstance(self.pyobject, &self.vm.ctx.float_type()) { - serializer.serialize_f64(objfloat::get_value(self.pyobject)) - } else if objtype::isinstance(self.pyobject, &self.vm.ctx.bool_type()) { - serializer.serialize_bool(objbool::get_value(self.pyobject)) - } else if objtype::isinstance(self.pyobject, &self.vm.ctx.int_type()) { - let v = objint::get_value(self.pyobject); - serializer.serialize_i64(v.to_i64().unwrap()) - // Although this may seem nice, it does not give the right result: - // v.serialize(serializer) - } else if objtype::isinstance(self.pyobject, &self.vm.ctx.list_type()) { - let elements = objsequence::get_elements_list(self.pyobject); - serialize_seq_elements(serializer, &elements) - } else if objtype::isinstance(self.pyobject, &self.vm.ctx.tuple_type()) { - let elements = objsequence::get_elements_tuple(self.pyobject); - serialize_seq_elements(serializer, &elements) - } else if objtype::isinstance(self.pyobject, &self.vm.ctx.dict_type()) { - let dict: PyDictRef = self.pyobject.clone().downcast().unwrap(); - let pairs: Vec<_> = dict.into_iter().collect(); - let mut map = serializer.serialize_map(Some(pairs.len()))?; - for (key, e) in pairs.iter() { - map.serialize_entry(&self.clone_with_object(key), &self.clone_with_object(&e))?; - } - map.end() - } else if self.pyobject.is(&self.vm.get_none()) { - serializer.serialize_none() - } else { - Err(serde::ser::Error::custom(format!( - "Object of type '{:?}' is not serializable", - self.pyobject.class() - ))) - } - } -} - -// This object is used as the seed for deserialization so we have access to the PyContext for type -// creation -#[derive(Clone)] -struct PyObjectDeserializer<'c> { - vm: &'c VirtualMachine, -} - -impl<'de> serde::de::DeserializeSeed<'de> for PyObjectDeserializer<'de> { - type Value = PyObjectRef; +use serde::de::DeserializeSeed; +use serde_json; - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_any(self.clone()) - } +/// Implement json.dumps +pub fn json_dumps(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let serializer = PyObjectSerializer::new(vm, &obj); + serde_json::to_string(&serializer).map_err(|err| vm.new_type_error(err.to_string())) } -impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { - type Value = PyObjectRef; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a type that can deserialise in Python") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - Ok(self.vm.ctx.new_str(value.to_string())) - } - - fn visit_string(self, value: String) -> Result - where - E: serde::de::Error, - { - Ok(self.vm.ctx.new_str(value)) - } - - fn visit_i64(self, value: i64) -> Result - where - E: serde::de::Error, - { - // The JSON deserializer always uses the i64/u64 deserializers, so we only need to - // implement those for now - Ok(self.vm.ctx.new_int(value)) - } - - fn visit_u64(self, value: u64) -> Result - where - E: serde::de::Error, - { - // The JSON deserializer always uses the i64/u64 deserializers, so we only need to - // implement those for now - Ok(self.vm.ctx.new_int(value)) - } - - fn visit_f64(self, value: f64) -> Result - where - E: serde::de::Error, - { - Ok(self.vm.ctx.new_float(value)) - } - - fn visit_bool(self, value: bool) -> Result - where - E: serde::de::Error, - { - Ok(self.vm.ctx.new_bool(value)) - } - - fn visit_seq(self, mut access: A) -> Result - where - A: serde::de::SeqAccess<'de>, - { - let mut seq = Vec::with_capacity(access.size_hint().unwrap_or(0)); - while let Some(value) = access.next_element_seed(self.clone())? { - seq.push(value); - } - Ok(self.vm.ctx.new_list(seq)) - } - - fn visit_map(self, mut access: M) -> Result - where - M: serde::de::MapAccess<'de>, - { - let dict = self.vm.ctx.new_dict(); - // TODO: Given keys must be strings, we can probably do something more efficient - // than wrapping the given object up and then unwrapping it to determine whether or - // not it is a string - while let Some((key_obj, value)) = access.next_entry_seed(self.clone(), self.clone())? { - let key: String = match key_obj.payload::() { - Some(PyString { ref value }) => value.clone(), - _ => unimplemented!("map keys must be strings"), - }; - dict.set_item(&key, value, self.vm).unwrap(); - } - Ok(dict.into_object()) - } +/// Implement json.loads +pub fn json_loads(string: PyStringRef, vm: &VirtualMachine) -> PyResult { + // TODO: Implement non-trivial deserialization case - fn visit_unit(self) -> Result - where - E: serde::de::Error, - { - Ok(self.vm.get_none()) - } -} + let de = PyObjectDeserializer::new(vm); -pub fn ser_pyobject(vm: &VirtualMachine, obj: &PyObjectRef) -> PyResult { - let serializer = PyObjectSerializer { pyobject: obj, vm }; - serde_json::to_string(&serializer).map_err(|err| vm.new_type_error(err.to_string())) -} - -pub fn de_pyobject(vm: &VirtualMachine, s: &str) -> PyResult { - let de = PyObjectDeserializer { vm }; // TODO: Support deserializing string sub-classes - de.deserialize(&mut serde_json::Deserializer::from_str(s)) + de.deserialize(&mut serde_json::Deserializer::from_str(string.as_str())) .map_err(|err| { let module = vm .get_attribute(vm.sys_module.clone(), "modules") @@ -216,22 +36,6 @@ pub fn de_pyobject(vm: &VirtualMachine, s: &str) -> PyResult { }) } -/// Implement json.dumps -fn json_dumps(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - // TODO: Implement non-trivial serialisation case - arg_check!(vm, args, required = [(obj, None)]); - let string = ser_pyobject(vm, obj)?; - Ok(vm.context().new_str(string)) -} - -/// Implement json.loads -fn json_loads(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - // TODO: Implement non-trivial deserialization case - arg_check!(vm, args, required = [(string, Some(vm.ctx.str_type()))]); - - de_pyobject(vm, &objstr::get_value(&string)) -} - pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 1c09163aae..803fdffc22 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -718,12 +718,12 @@ impl VirtualMachine { }) } - pub fn serialize(&self, obj: &PyObjectRef) -> PyResult { - crate::stdlib::json::ser_pyobject(self, obj) + pub fn serialize(&self, obj: PyObjectRef) -> PyResult { + crate::stdlib::json::json_dumps(obj, self) } pub fn deserialize(&self, s: &str) -> PyResult { - crate::stdlib::json::de_pyobject(self, s) + crate::stdlib::json::json_loads(PyString::from(s).into_ref(self), self) } pub fn is_callable(&self, obj: &PyObjectRef) -> bool { diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index a65141c571..34ec1145b9 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -130,7 +130,7 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { view.slice(0, bytes.len() as u32).into() } } else { - match vm.serialize(&py_obj) { + match vm.serialize(py_obj) { Ok(json) => js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED), Err(_) => JsValue::UNDEFINED, } From 9ac854aea57c0552b8417ad11d00b20f94cdd631 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 18 Jun 2019 13:51:04 -0500 Subject: [PATCH 793/884] Use serde_wasm_bindgen for wasm ser/de --- Cargo.lock | 20 ++++++++++++++++++++ vm/src/stdlib/mod.rs | 2 +- vm/src/vm.rs | 8 -------- wasm/lib/Cargo.toml | 2 ++ wasm/lib/src/convert.rs | 17 ++++++++--------- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb150aa91b..682fd3ce91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,6 +326,11 @@ name = "fixedbitset" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -1000,6 +1005,8 @@ dependencies = [ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "serde-wasm-bindgen 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen-futures 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "web-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1052,6 +1059,17 @@ dependencies = [ "serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "js-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", + "wasm-bindgen 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "serde_derive" version = "1.0.92" @@ -1547,6 +1565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" "checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" @@ -1623,6 +1642,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "32746bf0f26eab52f06af0d0aa1984f641341d06d8d673c693871da2d188c9be" +"checksum serde-wasm-bindgen 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ee6f12f7ed0e7ad2e55200da37dbabc2cadeb942355c5b629aa3771f5ac5636" "checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 9b413de32e..041c6711d6 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -3,7 +3,7 @@ mod binascii; mod dis; mod imp; mod itertools; -pub(crate) mod json; +mod json; mod keyword; mod marshal; mod math; diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 803fdffc22..4b84bf9fe0 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -718,14 +718,6 @@ impl VirtualMachine { }) } - pub fn serialize(&self, obj: PyObjectRef) -> PyResult { - crate::stdlib::json::json_dumps(obj, self) - } - - pub fn deserialize(&self, s: &str) -> PyResult { - crate::stdlib::json::json_loads(PyString::from(s).into_ref(self), self) - } - pub fn is_callable(&self, obj: &PyObjectRef) -> bool { match_class!(obj, PyFunction => true, diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index cebc753a83..3e045ae876 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -16,6 +16,8 @@ rustpython_vm = { path = "../../vm" } cfg-if = "0.1.2" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.3" +serde-wasm-bindgen = "0.1" +serde = "1.0" js-sys = "0.3" futures = "0.1" num-traits = "0.2" diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 34ec1145b9..20ca0dbd82 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -1,5 +1,7 @@ use js_sys::{Array, ArrayBuffer, Object, Promise, Reflect, Uint8Array}; use num_traits::cast::ToPrimitive; +use serde::{de::DeserializeSeed, ser::Serialize}; +use serde_wasm_bindgen; use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; use rustpython_vm::function::PyFuncArgs; @@ -130,10 +132,9 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { view.slice(0, bytes.len() as u32).into() } } else { - match vm.serialize(py_obj) { - Ok(json) => js_sys::JSON::parse(&json).unwrap_or(JsValue::UNDEFINED), - Err(_) => JsValue::UNDEFINED, - } + rustpython_vm::ser_de::PyObjectSerializer::new(vm, &py_obj) + .serialize(&serde_wasm_bindgen::Serializer::new()) + .unwrap_or(JsValue::UNDEFINED) } } @@ -224,10 +225,8 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { // Because `JSON.stringify(undefined)` returns undefined vm.get_none() } else { - let json = match js_sys::JSON::stringify(&js_val) { - Ok(json) => String::from(json), - Err(_) => return vm.get_none(), - }; - vm.deserialize(&json).unwrap_or_else(|_| vm.get_none()) + rustpython_vm::ser_de::PyObjectDeserializer::new(vm) + .deserialize(serde_wasm_bindgen::Deserializer::from(js_val)) + .unwrap_or_else(|_| vm.get_none()) } } From e68b4943caeb29bdb29af2a9b98a1f537f47f8c5 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 18 Jun 2019 20:37:08 -0500 Subject: [PATCH 794/884] Add #[pyclassmethod] to #[pyimpl] --- derive/src/pyclass.rs | 71 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index d216b9357e..65c3c63134 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -11,6 +11,10 @@ enum ClassItem { item_ident: Ident, py_name: String, }, + ClassMethod { + item_ident: Ident, + py_name: String, + }, Property { item_ident: Ident, py_name: String, @@ -49,8 +53,8 @@ impl ClassItem { let nesteds = meta_to_vec(meta).map_err(|meta| { err_span!( meta, - "#[pyproperty = \"...\"] cannot be a name/value, you probably meant \ - #[pyproperty(name = \"...\")]", + "#[pymethod = \"...\"] cannot be a name/value, you probably meant \ + #[pymethod(name = \"...\")]", ) })?; let mut py_name = None; @@ -80,6 +84,47 @@ impl ClassItem { py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), }); attr_idx = Some(i); + } else if name == "pyclassmethod" { + if item.is_some() { + bail_span!( + sig.ident, + "You can only have one #[py*] attribute on an impl item" + ) + } + let nesteds = meta_to_vec(meta).map_err(|meta| { + err_span!( + meta, + "#[pyclassmethod = \"...\"] cannot be a name/value, you probably meant \ + #[pyclassmethod(name = \"...\")]", + ) + })?; + let mut py_name = None; + for meta in nesteds { + let meta = match meta { + NestedMeta::Meta(meta) => meta, + NestedMeta::Literal(_) => continue, + }; + match meta { + Meta::NameValue(name_value) => { + if name_value.ident == "name" { + if let Lit::Str(s) = &name_value.lit { + py_name = Some(s.value()); + } else { + bail_span!( + &sig.ident, + "#[pyclassmethod(name = ...)] must be a string" + ); + } + } + } + _ => {} + } + } + item = Some(ClassItem::ClassMethod { + item_ident: sig.ident.clone(), + py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), + }); + attr_idx = Some(i); } else if name == "pyproperty" { if item.is_some() { bail_span!( @@ -210,18 +255,20 @@ pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> Result {} } } - let methods = items.iter().filter_map(|item| { - if let ClassItem::Method { + let methods = items.iter().filter_map(|item| match item { + ClassItem::Method { item_ident, py_name, - } = item - { - Some(quote! { - class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_ident)); - }) - } else { - None - } + } => Some(quote! { + class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_ident)); + }), + ClassItem::ClassMethod { + item_ident, + py_name, + } => Some(quote! { + class.set_str_attr(#py_name, ctx.new_classmethod(Self::#item_ident)); + }), + _ => None, }); let properties = properties .iter() From 974dc683e4f681ffe51853378fea853236e3631a Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Sat, 15 Jun 2019 15:48:51 +0800 Subject: [PATCH 795/884] support unicode literal - support unicode literal \x with 2 digits - support unicode literal \u with 4 digits - support unicode literal \U with 8 digits - avoid to parse \x as unicode literal in bytes --- parser/Cargo.toml | 1 + parser/src/lexer.rs | 26 ++++++++++++++++++++++++++ tests/snippets/strings.py | 9 +++++++++ 3 files changed, 36 insertions(+) diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 6cc7805c66..5d10344204 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -17,3 +17,4 @@ num-traits = "0.2" unicode-xid = "0.1.0" unic-emoji-char = "0.9.0" serde = { version = "1.0.66", features = ["derive"] } +wtf8 = "0.0.3" diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 3efdf6fbcc..4b47ec2d78 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -13,6 +13,7 @@ use std::collections::HashMap; use std::str::FromStr; use unic_emoji_char::is_emoji_presentation; use unicode_xid::UnicodeXID; +use wtf8; #[derive(Clone, Copy, PartialEq, Debug)] struct IndentationLevel { @@ -67,6 +68,7 @@ pub struct LexicalError { #[derive(Debug)] pub enum LexicalErrorType { StringError, + UnicodeError, NestingError, UnrecognizedToken { tok: char }, OtherError(String), @@ -456,6 +458,27 @@ where } } + fn unicode_literal(&mut self, literal_number: usize) -> Result { + let mut p: u32 = 0u32; + let unicode_error = Err(LexicalError { + error: LexicalErrorType::UnicodeError, + location: self.get_pos(), + }); + for i in 1..=literal_number { + match self.next_char() { + Some(c) => match c.to_digit(16) { + Some(d) => p += d << (literal_number - i) * 4, + None => return unicode_error, + }, + None => return unicode_error, + } + } + match wtf8::CodePoint::from_u32(p) { + Some(cp) => return Ok(cp.to_char_lossy()), + None => return unicode_error, + } + } + fn lex_string( &mut self, is_bytes: bool, @@ -513,6 +536,9 @@ where Some('t') => { string_content.push('\t'); } + Some('u') => string_content.push(self.unicode_literal(4)?), + Some('U') => string_content.push(self.unicode_literal(8)?), + Some('x') if !is_bytes => string_content.push(self.unicode_literal(2)?), Some('v') => string_content.push('\x0b'), Some(c) => { string_content.push('\\'); diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index be95045da9..ae03833a80 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -240,3 +240,12 @@ def try_mutate_str(): assert "abcdefg".isprintable() assert not "abcdefg\n".isprintable() assert "ʹ".isprintable() + +# test unicode iterals +assert "\xac" == "¬" +assert "\u0037" == "7" +assert "\u0040" == "@" +assert "\u0041" == "A" +assert "\u00BE" == "¾" +assert "\u9487" == "钇" +assert "\U0001F609" == "😉" From 0ac42ac9aaad059476d87f15182c990acea72446 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 19 Jun 2019 17:30:09 -0500 Subject: [PATCH 796/884] Add convenience functions --- vm/src/lib.rs | 2 +- vm/src/{ser_de.rs => py_serde.rs} | 31 ++++++++++++++++++++--- vm/src/stdlib/json.rs | 41 ++++++++++++++----------------- wasm/lib/src/convert.rs | 8 +++--- 4 files changed, 50 insertions(+), 32 deletions(-) rename vm/src/{ser_de.rs => py_serde.rs} (90%) diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 6b8a42415b..f2d0cb57d2 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -53,9 +53,9 @@ mod frozen; pub mod function; pub mod import; pub mod obj; +pub mod py_serde; mod pyhash; pub mod pyobject; -pub mod ser_de; pub mod stdlib; mod sysmodule; mod traceback; diff --git a/vm/src/ser_de.rs b/vm/src/py_serde.rs similarity index 90% rename from vm/src/ser_de.rs rename to vm/src/py_serde.rs index 6c45cc6f1e..10901bf7a4 100644 --- a/vm/src/ser_de.rs +++ b/vm/src/py_serde.rs @@ -1,8 +1,8 @@ use std::fmt; use serde; -use serde::de::Visitor; -use serde::ser::{SerializeMap, SerializeSeq}; +use serde::de::{DeserializeSeed, Visitor}; +use serde::ser::{Serialize, SerializeMap, SerializeSeq}; use crate::obj::{ objbool, @@ -15,6 +15,29 @@ use crate::pyobject::{IdProtocol, ItemProtocol, PyObjectRef, TypeProtocol}; use crate::VirtualMachine; use num_traits::cast::ToPrimitive; +#[inline] +pub fn serialize( + vm: &VirtualMachine, + pyobject: &PyObjectRef, + serializer: S, +) -> Result +where + S: serde::Serializer, +{ + PyObjectSerializer { vm, pyobject }.serialize(serializer) +} + +#[inline] +pub fn deserialize<'de, D>( + vm: &'de VirtualMachine, + deserializer: D, +) -> Result<::Value, D::Error> +where + D: serde::Deserializer<'de>, +{ + PyObjectDeserializer { vm }.deserialize(deserializer) +} + // We need to have a VM available to serialise a PyObject based on its subclass, so we implement // PyObject serialisation via a proxy object which holds a reference to a VM pub struct PyObjectSerializer<'s> { @@ -24,7 +47,7 @@ pub struct PyObjectSerializer<'s> { impl<'s> PyObjectSerializer<'s> { pub fn new(vm: &'s VirtualMachine, pyobject: &'s PyObjectRef) -> Self { - PyObjectSerializer { pyobject, vm } + PyObjectSerializer { vm, pyobject } } fn clone_with_object(&self, pyobject: &'s PyObjectRef) -> PyObjectSerializer { @@ -97,7 +120,7 @@ impl<'c> PyObjectDeserializer<'c> { } } -impl<'de> serde::de::DeserializeSeed<'de> for PyObjectDeserializer<'de> { +impl<'de> DeserializeSeed<'de> for PyObjectDeserializer<'de> { type Value = PyObjectRef; fn deserialize(self, deserializer: D) -> Result diff --git a/vm/src/stdlib/json.rs b/vm/src/stdlib/json.rs index 4a52681504..8b5d21b758 100644 --- a/vm/src/stdlib/json.rs +++ b/vm/src/stdlib/json.rs @@ -1,39 +1,36 @@ use crate::obj::objstr::PyStringRef; +use crate::py_serde; use crate::pyobject::{create_type, ItemProtocol, PyObjectRef, PyResult}; -use crate::ser_de::{PyObjectDeserializer, PyObjectSerializer}; use crate::VirtualMachine; -use serde::de::DeserializeSeed; use serde_json; /// Implement json.dumps pub fn json_dumps(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let serializer = PyObjectSerializer::new(vm, &obj); + let serializer = py_serde::PyObjectSerializer::new(vm, &obj); serde_json::to_string(&serializer).map_err(|err| vm.new_type_error(err.to_string())) } /// Implement json.loads pub fn json_loads(string: PyStringRef, vm: &VirtualMachine) -> PyResult { // TODO: Implement non-trivial deserialization case + let de_result = + py_serde::deserialize(vm, &mut serde_json::Deserializer::from_str(string.as_str())); - let de = PyObjectDeserializer::new(vm); - - // TODO: Support deserializing string sub-classes - de.deserialize(&mut serde_json::Deserializer::from_str(string.as_str())) - .map_err(|err| { - let module = vm - .get_attribute(vm.sys_module.clone(), "modules") - .unwrap() - .get_item("json", vm) - .unwrap(); - let json_decode_error = vm.get_attribute(module, "JSONDecodeError").unwrap(); - let json_decode_error = json_decode_error.downcast().unwrap(); - let exc = vm.new_exception(json_decode_error, format!("{}", err)); - vm.set_attr(&exc, "lineno", vm.ctx.new_int(err.line())) - .unwrap(); - vm.set_attr(&exc, "colno", vm.ctx.new_int(err.column())) - .unwrap(); - exc - }) + de_result.map_err(|err| { + let module = vm + .get_attribute(vm.sys_module.clone(), "modules") + .unwrap() + .get_item("json", vm) + .unwrap(); + let json_decode_error = vm.get_attribute(module, "JSONDecodeError").unwrap(); + let json_decode_error = json_decode_error.downcast().unwrap(); + let exc = vm.new_exception(json_decode_error, format!("{}", err)); + vm.set_attr(&exc, "lineno", vm.ctx.new_int(err.line())) + .unwrap(); + vm.set_attr(&exc, "colno", vm.ctx.new_int(err.column())) + .unwrap(); + exc + }) } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index 20ca0dbd82..daf48dded0 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -1,11 +1,11 @@ use js_sys::{Array, ArrayBuffer, Object, Promise, Reflect, Uint8Array}; use num_traits::cast::ToPrimitive; -use serde::{de::DeserializeSeed, ser::Serialize}; use serde_wasm_bindgen; use wasm_bindgen::{closure::Closure, prelude::*, JsCast}; use rustpython_vm::function::PyFuncArgs; use rustpython_vm::obj::{objbytes, objint, objsequence, objtype}; +use rustpython_vm::py_serde; use rustpython_vm::pyobject::{ItemProtocol, PyObjectRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; @@ -132,8 +132,7 @@ pub fn py_to_js(vm: &VirtualMachine, py_obj: PyObjectRef) -> JsValue { view.slice(0, bytes.len() as u32).into() } } else { - rustpython_vm::ser_de::PyObjectSerializer::new(vm, &py_obj) - .serialize(&serde_wasm_bindgen::Serializer::new()) + py_serde::serialize(vm, &py_obj, &serde_wasm_bindgen::Serializer::new()) .unwrap_or(JsValue::UNDEFINED) } } @@ -225,8 +224,7 @@ pub fn js_to_py(vm: &VirtualMachine, js_val: JsValue) -> PyObjectRef { // Because `JSON.stringify(undefined)` returns undefined vm.get_none() } else { - rustpython_vm::ser_de::PyObjectDeserializer::new(vm) - .deserialize(serde_wasm_bindgen::Deserializer::from(js_val)) + py_serde::deserialize(vm, serde_wasm_bindgen::Deserializer::from(js_val)) .unwrap_or_else(|_| vm.get_none()) } } From 86b8c07581e3335533ad7d1ca4aa846f4c26137b Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Wed, 19 Jun 2019 20:03:31 -0500 Subject: [PATCH 797/884] Update Cargo.lock --- Cargo.lock | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index bb150aa91b..f4b8806ece 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -950,6 +950,7 @@ dependencies = [ "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "unic-emoji-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wtf8 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1500,6 +1501,11 @@ dependencies = [ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "wtf8" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "xdg" version = "2.2.0" @@ -1682,4 +1688,5 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" +"checksum wtf8 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d6b9309a86639c488a8eb2b5331cb5127cc9feb0a94a0db4b5d1ab5b84977956" "checksum xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d089681aa106a86fade1b0128fb5daf07d5867a509ab036d99988dec80429a57" From cb832e9aa196395770810818dece7d1039bbd5ad Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 20 Jun 2019 11:57:59 -0500 Subject: [PATCH 798/884] Improve errors messages for derive(FromArgs) --- derive/src/from_args.rs | 125 +++++++++++++++++++++++----------------- derive/src/lib.rs | 5 +- 2 files changed, 75 insertions(+), 55 deletions(-) diff --git a/derive/src/from_args.rs b/derive/src/from_args.rs index d017a32e5a..fe326fb555 100644 --- a/derive/src/from_args.rs +++ b/derive/src/from_args.rs @@ -1,6 +1,9 @@ +use crate::Diagnostic; use proc_macro2::TokenStream as TokenStream2; use quote::quote; -use syn::{Attribute, Data, DeriveInput, Expr, Field, Fields, Ident, Lit, Meta, NestedMeta}; +use syn::{ + parse_quote, Attribute, Data, DeriveInput, Expr, Field, Fields, Ident, Lit, Meta, NestedMeta, +}; /// The kind of the python parameter, this corresponds to the value of Parameter.kind /// (https://docs.python.org/3/library/inspect.html#inspect.Parameter.kind) @@ -11,15 +14,15 @@ enum ParameterKind { } impl ParameterKind { - fn from_ident(ident: &Ident) -> ParameterKind { + fn from_ident(ident: &Ident) -> Option { if ident == "positional_only" { - ParameterKind::PositionalOnly + Some(ParameterKind::PositionalOnly) } else if ident == "positional_or_keyword" { - ParameterKind::PositionalOrKeyword + Some(ParameterKind::PositionalOrKeyword) } else if ident == "keyword_only" { - ParameterKind::KeywordOnly + Some(ParameterKind::KeywordOnly) } else { - panic!("Unrecognised attribute") + None } } } @@ -31,19 +34,27 @@ struct ArgAttribute { } impl ArgAttribute { - fn from_attribute(attr: &Attribute) -> Option { + fn from_attribute(attr: &Attribute) -> Option> { if !attr.path.is_ident("pyarg") { return None; } - - match attr.parse_meta().unwrap() { + let inner = move || match attr.parse_meta()? { Meta::List(list) => { let mut iter = list.nested.iter(); - let first_arg = iter.next().expect("at least one argument in pyarg list"); + let first_arg = iter.next().ok_or_else(|| { + err_span!(list, "There must be at least one argument to #[pyarg()]") + })?; let kind = match first_arg { NestedMeta::Meta(Meta::Word(ident)) => ParameterKind::from_ident(ident), - _ => panic!("Bad syntax for first pyarg attribute argument"), + _ => None, }; + let kind = kind.ok_or_else(|| { + err_span!( + first_arg, + "The first argument to #[pyarg()] must be the parameter type, either \ + 'positional_only', 'positional_or_keyword', or 'keyword_only'." + ) + })?; let mut attribute = ArgAttribute { kind, @@ -52,68 +63,77 @@ impl ArgAttribute { }; while let Some(arg) = iter.next() { - attribute.parse_argument(arg); + attribute.parse_argument(arg)?; } - assert!( - attribute.default.is_none() || !attribute.optional, - "Can't set both a default value and optional" - ); + if attribute.default.is_some() && attribute.optional { + bail_span!(attr, "Can't set both a default value and optional"); + } - Some(attribute) + Ok(attribute) } - _ => panic!("Bad syntax for pyarg attribute"), - } + _ => bail_span!(attr, "pyarg must be a list, like #[pyarg(...)]"), + }; + Some(inner()) } - fn parse_argument(&mut self, arg: &NestedMeta) { + fn parse_argument(&mut self, arg: &NestedMeta) -> Result<(), Diagnostic> { match arg { NestedMeta::Meta(Meta::Word(ident)) => { if ident == "default" { - assert!(self.default.is_none(), "Default already set"); - let expr = syn::parse_str::("Default::default()").unwrap(); + if self.default.is_some() { + bail_span!(ident, "Default already set"); + } + let expr = parse_quote!(Default::default()); self.default = Some(expr); } else if ident == "optional" { self.optional = true; } else { - panic!("Unrecognised pyarg attribute '{}'", ident); + bail_span!(ident, "Unrecognised pyarg attribute"); } } NestedMeta::Meta(Meta::NameValue(name_value)) => { if name_value.ident == "default" { - assert!(self.default.is_none(), "Default already set"); + if self.default.is_some() { + bail_span!(name_value, "Default already set"); + } match name_value.lit { Lit::Str(ref val) => { - let expr = val - .parse::() - .expect("a valid expression for default argument"); + let expr = val.parse::().map_err(|_| { + err_span!(val, "Expected a valid expression for default argument") + })?; self.default = Some(expr); } - _ => panic!("Expected string value for default argument"), + _ => bail_span!(name_value, "Expected string value for default argument"), } } else if name_value.ident == "optional" { match name_value.lit { Lit::Bool(ref val) => { self.optional = val.value; } - _ => panic!("Expected boolean value for optional argument"), + _ => bail_span!( + name_value.lit, + "Expected boolean value for optional argument" + ), } } else { - panic!("Unrecognised pyarg attribute '{}'", name_value.ident); + bail_span!(name_value, "Unrecognised pyarg attribute"); } } - _ => panic!("Bad syntax for first pyarg attribute argument"), - }; + _ => bail_span!(arg, "Unrecognised pyarg attribute"), + } + + Ok(()) } } -fn generate_field(field: &Field) -> TokenStream2 { +fn generate_field(field: &Field) -> Result { let mut pyarg_attrs = field .attrs .iter() .filter_map(ArgAttribute::from_attribute) - .collect::>(); + .collect::, _>>()?; let attr = if pyarg_attrs.is_empty() { ArgAttribute { kind: ParameterKind::PositionalOrKeyword, @@ -123,10 +143,7 @@ fn generate_field(field: &Field) -> TokenStream2 { } else if pyarg_attrs.len() == 1 { pyarg_attrs.remove(0) } else { - panic!( - "Multiple pyarg attributes on field '{}'", - field.ident.as_ref().unwrap() - ); + bail_span!(field, "Multiple pyarg attributes on field"); }; let name = &field.ident; @@ -156,7 +173,7 @@ fn generate_field(field: &Field) -> TokenStream2 { } }; - match attr.kind { + let file_output = match attr.kind { ParameterKind::PositionalOnly => { quote! { #name: args.take_positional()#middle#ending, @@ -172,29 +189,33 @@ fn generate_field(field: &Field) -> TokenStream2 { #name: args.take_keyword(stringify!(#name))#middle#ending, } } - } + }; + Ok(file_output) } -pub fn impl_from_args(input: DeriveInput) -> TokenStream2 { +pub fn impl_from_args(input: DeriveInput) -> Result { let fields = match input.data { - Data::Struct(ref data) => { - match data.fields { - Fields::Named(ref fields) => fields.named.iter().map(generate_field), - Fields::Unnamed(_) | Fields::Unit => unimplemented!(), // TODO: better error message - } - } - Data::Enum(_) | Data::Union(_) => unimplemented!(), // TODO: better error message + Data::Struct(syn::DataStruct { + fields: Fields::Named(fields), + .. + }) => fields + .named + .iter() + .map(generate_field) + .collect::>()?, + _ => bail_span!(input, "FromArgs input must be a struct with named fields"), }; - let name = &input.ident; - quote! { + let name = input.ident; + let output = quote! { impl ::rustpython_vm::function::FromArgs for #name { fn from_args( vm: &::rustpython_vm::VirtualMachine, args: &mut ::rustpython_vm::function::PyFuncArgs ) -> Result { - Ok(#name { #(#fields)* }) + Ok(#name { #fields }) } } - } + }; + Ok(output) } diff --git a/derive/src/lib.rs b/derive/src/lib.rs index dfcb7ea360..21ac982ccf 100644 --- a/derive/src/lib.rs +++ b/derive/src/lib.rs @@ -24,9 +24,8 @@ fn result_to_tokens(result: Result) -> TokenStream { #[proc_macro_derive(FromArgs, attributes(pyarg))] pub fn derive_from_args(input: TokenStream) -> TokenStream { - let ast: DeriveInput = syn::parse(input).unwrap(); - - from_args::impl_from_args(ast).into() + let input = parse_macro_input!(input as DeriveInput); + result_to_tokens(from_args::impl_from_args(input)) } #[proc_macro_attribute] From aefbae40e4de258c9b201f5a24a2d35bc6ff0e84 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 20 Jun 2019 12:01:20 -0500 Subject: [PATCH 799/884] Revert "Make CodeObject.source_path an Option" --- compiler/src/bytecode.rs | 4 ++-- compiler/src/compile.rs | 27 ++++++++++----------------- derive/src/compile_bytecode.rs | 11 +++++------ src/main.rs | 4 ++-- vm/src/builtins.rs | 6 +++--- vm/src/eval.rs | 13 +++++-------- vm/src/frame.rs | 7 ++----- vm/src/import.rs | 23 +++++++++++++---------- vm/src/obj/objcode.rs | 2 +- vm/src/stdlib/imp.rs | 7 +++++-- vm/src/vm.rs | 2 +- wasm/lib/src/browser_module.rs | 2 +- wasm/lib/src/vm_class.rs | 2 +- 13 files changed, 51 insertions(+), 59 deletions(-) diff --git a/compiler/src/bytecode.rs b/compiler/src/bytecode.rs index b5b3c11d31..c87bc0ae0b 100644 --- a/compiler/src/bytecode.rs +++ b/compiler/src/bytecode.rs @@ -21,7 +21,7 @@ pub struct CodeObject { pub varargs: Varargs, // *args or * pub kwonlyarg_names: Vec, pub varkeywords: Varargs, // **kwargs or ** - pub source_path: Option, + pub source_path: String, pub first_line_number: usize, pub obj_name: String, // Name of the object that created this code object pub is_generator: bool, @@ -269,7 +269,7 @@ impl CodeObject { varargs: Varargs, kwonlyarg_names: Vec, varkeywords: Varargs, - source_path: Option, + source_path: String, first_line_number: usize, obj_name: String, ) -> CodeObject { diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index 0efa3adfb2..8b806b1d1a 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -23,11 +23,7 @@ struct Compiler { } /// Compile a given sourcecode into a bytecode object. -pub fn compile( - source: &str, - mode: &Mode, - source_path: Option, -) -> Result { +pub fn compile(source: &str, mode: &Mode, source_path: String) -> Result { match mode { Mode::Exec => { let ast = parser::parse_program(source)?; @@ -46,11 +42,11 @@ pub fn compile( /// A helper function for the shared code of the different compile functions fn with_compiler( - source_path: Option, + source_path: String, f: impl FnOnce(&mut Compiler) -> Result<(), CompileError>, ) -> Result { let mut compiler = Compiler::new(); - compiler.source_path = source_path; + compiler.source_path = Some(source_path); compiler.push_new_code_object("".to_string()); f(&mut compiler)?; let code = compiler.pop_code_object(); @@ -59,10 +55,7 @@ fn with_compiler( } /// Compile a standard Python program to bytecode -pub fn compile_program( - ast: ast::Program, - source_path: Option, -) -> Result { +pub fn compile_program(ast: ast::Program, source_path: String) -> Result { with_compiler(source_path, |compiler| { let symbol_table = make_symbol_table(&ast)?; compiler.compile_program(&ast, symbol_table) @@ -72,7 +65,7 @@ pub fn compile_program( /// Compile a single Python expression to bytecode pub fn compile_statement_eval( statement: Vec, - source_path: Option, + source_path: String, ) -> Result { with_compiler(source_path, |compiler| { let symbol_table = statements_to_symbol_table(&statement)?; @@ -83,7 +76,7 @@ pub fn compile_statement_eval( /// Compile a Python program to bytecode for the context of a REPL pub fn compile_program_single( ast: ast::Program, - source_path: Option, + source_path: String, ) -> Result { with_compiler(source_path, |compiler| { let symbol_table = make_symbol_table(&ast)?; @@ -126,7 +119,7 @@ impl Compiler { Varargs::None, Vec::new(), Varargs::None, - self.source_path.clone(), + self.source_path.clone().unwrap(), line_number, obj_name, )); @@ -603,7 +596,7 @@ impl Compiler { Varargs::from(&args.vararg), args.kwonlyargs.iter().map(|a| a.arg.clone()).collect(), Varargs::from(&args.kwarg), - self.source_path.clone(), + self.source_path.clone().unwrap(), line_number, name.to_string(), )); @@ -856,7 +849,7 @@ impl Compiler { Varargs::None, vec![], Varargs::None, - self.source_path.clone(), + self.source_path.clone().unwrap(), line_number, name.to_string(), )); @@ -1578,7 +1571,7 @@ impl Compiler { Varargs::None, vec![], Varargs::None, - self.source_path.clone(), + self.source_path.clone().unwrap(), line_number, name.clone(), )); diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index 4c9946f899..35db2eb044 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -35,11 +35,7 @@ struct CompilationSource { } impl CompilationSource { - fn compile( - self, - mode: &compile::Mode, - source_path: Option, - ) -> Result { + fn compile(self, mode: &compile::Mode, source_path: String) -> Result { let compile = |source| { compile::compile(source, mode, source_path).map_err(|err| { Diagnostic::spans_error(self.span, format!("Compile error: {}", err)) @@ -139,7 +135,10 @@ impl PyCompileInput { "Must have either file or source in py_compile_bytecode!()", ) })? - .compile(&mode.unwrap_or(compile::Mode::Exec), source_path) + .compile( + &mode.unwrap_or(compile::Mode::Exec), + source_path.unwrap_or_else(|| "frozen".to_string()), + ) } } diff --git a/src/main.rs b/src/main.rs index 895ea6290a..5d4d2b9e72 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,7 +76,7 @@ fn main() { fn _run_string(vm: &VirtualMachine, source: &str, source_path: String) -> PyResult { let code_obj = vm - .compile(source, &compile::Mode::Exec, Some(source_path.clone())) + .compile(source, &compile::Mode::Exec, source_path.clone()) .map_err(|err| vm.new_syntax_error(&err))?; // trace!("Code object: {:?}", code_obj.borrow()); let attrs = vm.ctx.new_dict(); @@ -161,7 +161,7 @@ fn test_run_script() { } fn shell_exec(vm: &VirtualMachine, source: &str, scope: Scope) -> Result<(), CompileError> { - match vm.compile(source, &compile::Mode::Single, Some("".to_string())) { + match vm.compile(source, &compile::Mode::Single, "".to_string()) { Ok(code) => { match vm.run_code_obj(code, scope.clone()) { Ok(value) => { diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 1d82ae1217..570b1fbbf1 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -122,7 +122,7 @@ fn builtin_compile(args: CompileArgs, vm: &VirtualMachine) -> PyResult PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - vm.compile(&source, &mode, Some("".to_string())) + vm.compile(&source, &mode, "".to_string()) .map_err(|err| vm.new_syntax_error(&err))? } else { return Err(vm.new_type_error("code argument must be str or code object".to_string())); @@ -199,7 +199,7 @@ fn builtin_exec(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let source = objstr::get_value(source); // TODO: fix this newline bug: let source = format!("{}\n", source); - vm.compile(&source, &mode, Some("".to_string())) + vm.compile(&source, &mode, "".to_string()) .map_err(|err| vm.new_syntax_error(&err))? } else if let Ok(code_obj) = PyCodeRef::try_from_object(vm, source.clone()) { code_obj diff --git a/vm/src/eval.rs b/vm/src/eval.rs index 706c7271f4..e326e300e5 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -1,15 +1,12 @@ +extern crate rustpython_parser; + use crate::compile; use crate::frame::Scope; use crate::pyobject::PyResult; use crate::vm::VirtualMachine; -pub fn eval( - vm: &VirtualMachine, - source: &str, - scope: Scope, - source_path: Option, -) -> PyResult { - match vm.compile(source, &compile::Mode::Eval, source_path) { +pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str) -> PyResult { + match vm.compile(source, &compile::Mode::Eval, source_path.to_string()) { Ok(bytecode) => { debug!("Code object: {:?}", bytecode); vm.run_code_obj(bytecode, scope) @@ -28,7 +25,7 @@ mod tests { let source = String::from("print('Hello world')\n"); let mut vm = VirtualMachine::new(); let vars = vm.new_scope_with_builtins(); - let _result = eval(&mut vm, &source, vars, Some("".to_string())); + let _result = eval(&mut vm, &source, vars, ""); // TODO: check result? //assert_eq!( diff --git a/vm/src/frame.rs b/vm/src/frame.rs index c671fc5bd7..8ea1148547 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -271,10 +271,7 @@ impl Frame { } pub fn run(&self, vm: &VirtualMachine) -> Result { - let filename = match self.code.source_path.clone() { - Some(s) => vm.ctx.new_str(s), - None => vm.get_none(), - }; + let filename = &self.code.source_path.to_string(); // This is the name of the object being run: let run_obj_name = &self.code.obj_name.to_string(); @@ -302,7 +299,7 @@ impl Frame { .unwrap(); trace!("Adding to traceback: {:?} {:?}", traceback, lineno); let raise_location = vm.ctx.new_tuple(vec![ - filename.clone(), + vm.ctx.new_str(filename.clone()), vm.ctx.new_int(lineno.get_row()), vm.ctx.new_str(run_obj_name.clone()), ]); diff --git a/vm/src/import.rs b/vm/src/import.rs index 84f89d91cb..ad1fd8e6ab 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -25,12 +25,13 @@ pub fn init_importlib(vm: &VirtualMachine) -> PyResult { } pub fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { - vm.frozen - .borrow() - .get(module_name) - .cloned() - .ok_or_else(|| vm.new_import_error(format!("Cannot import frozen module {}", module_name))) - .and_then(|frozen| import_codeobj(vm, module_name, frozen)) + if let Some(frozen) = vm.frozen.borrow().get(module_name) { + let mut frozen = frozen.clone(); + frozen.source_path = format!("frozen {}", module_name); + import_codeobj(vm, module_name, frozen) + } else { + Err(vm.new_import_error(format!("Cannot import frozen module {}", module_name))) + } } pub fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult { @@ -68,7 +69,7 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s import_file( vm, module_name, - Some(file_path.to_str().unwrap().to_string()), + file_path.to_str().unwrap().to_string(), source, ) } @@ -77,7 +78,7 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s pub fn import_file( vm: &VirtualMachine, module_name: &str, - file_path: Option, + file_path: String, content: String, ) -> PyResult { let code_obj = compile::compile(&content, &compile::Mode::Exec, file_path) @@ -88,8 +89,10 @@ pub fn import_file( pub fn import_codeobj(vm: &VirtualMachine, module_name: &str, code_obj: CodeObject) -> PyResult { let attrs = vm.ctx.new_dict(); attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; - if let Some(source_path) = &code_obj.source_path { - attrs.set_item("__file__", vm.new_str(source_path.to_owned()), vm)?; + let file_path = &code_obj.source_path; + if !file_path.starts_with("frozen") { + // TODO: Should be less hacky, not depend on source_path + attrs.set_item("__file__", vm.new_str(file_path.to_owned()), vm)?; } let module = vm.ctx.new_module(module_name, attrs.clone()); diff --git a/vm/src/obj/objcode.rs b/vm/src/obj/objcode.rs index 17d3e743d2..51a73d6141 100644 --- a/vm/src/obj/objcode.rs +++ b/vm/src/obj/objcode.rs @@ -54,7 +54,7 @@ impl PyCodeRef { self.code.arg_names.len() } - fn co_filename(self, _vm: &VirtualMachine) -> Option { + fn co_filename(self, _vm: &VirtualMachine) -> String { self.code.source_path.clone() } diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index 96437187e7..184fc5003c 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -58,8 +58,11 @@ fn imp_get_frozen_object(name: PyStringRef, vm: &VirtualMachine) -> PyResult, + source_path: String, ) -> Result { compile::compile(source, mode, source_path) .map(|codeobj| PyCode::new(codeobj).into_ref(self)) diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index b2b59ecd2b..b3d689d3f3 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -373,7 +373,7 @@ fn browser_load_module(module: PyStringRef, path: PyStringRef, vm: &VirtualMachi .expect("that the vm is valid when the promise resolves"); let vm = &stored_vm.vm; let resp_text = text.as_string().unwrap(); - let res = import_file(vm, module.as_str(), Some("WEB".to_string()), resp_text); + let res = import_file(vm, module.as_str(), "WEB".to_string(), resp_text); match res { Ok(_) => Ok(JsValue::null()), Err(err) => Err(convert::py_err_to_js_err(vm, &err)), diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index efa7d503bd..9526941ffd 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -263,7 +263,7 @@ impl WASMVirtualMachine { ref vm, ref scope, .. }| { source.push('\n'); - let code = vm.compile(&source, &mode, Some("".to_string())); + let code = vm.compile(&source, &mode, "".to_string()); let code = code.map_err(|err| { let js_err = SyntaxError::new(&format!("Error parsing Python code: {}", err)); if let rustpython_vm::error::CompileErrorType::Parse(ref parse_error) = From c359c430c23331c713b6e32019da775e3b7803d8 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 20 Jun 2019 22:43:13 -0500 Subject: [PATCH 800/884] Add set_file_attr to import_codeobj --- derive/src/compile_bytecode.rs | 18 ++++++------ vm/src/frozen.rs | 3 ++ vm/src/import.rs | 51 ++++++++++++++++++---------------- 3 files changed, 39 insertions(+), 33 deletions(-) diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index 35db2eb044..449b2c4889 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -8,8 +8,8 @@ //! //! // the mode to compile the code in //! mode = "exec", // or "eval" or "single" -//! // the path put into the CodeObject, defaults to `None` -//! source_path = "frozen", +//! // the path put into the CodeObject, defaults to "frozen" +//! module_name = "frozen", //! ) //! ``` @@ -35,9 +35,9 @@ struct CompilationSource { } impl CompilationSource { - fn compile(self, mode: &compile::Mode, source_path: String) -> Result { + fn compile(self, mode: &compile::Mode, module_name: String) -> Result { let compile = |source| { - compile::compile(source, mode, source_path).map_err(|err| { + compile::compile(source, mode, module_name).map_err(|err| { Diagnostic::spans_error(self.span, format!("Compile error: {}", err)) }) }; @@ -69,7 +69,7 @@ struct PyCompileInput { impl PyCompileInput { fn compile(&self) -> Result { - let mut source_path = None; + let mut module_name = None; let mut mode = None; let mut source: Option = None; @@ -97,10 +97,10 @@ impl PyCompileInput { }, _ => bail_span!(name_value.lit, "mode must be a string"), }) - } else if name_value.ident == "source_path" { - source_path = Some(match &name_value.lit { + } else if name_value.ident == "module_name" { + module_name = Some(match &name_value.lit { Lit::Str(s) => s.value(), - _ => bail_span!(name_value.lit, "source_path must be string"), + _ => bail_span!(name_value.lit, "module_name must be string"), }) } else if name_value.ident == "source" { assert_source_empty(&source)?; @@ -137,7 +137,7 @@ impl PyCompileInput { })? .compile( &mode.unwrap_or(compile::Mode::Exec), - source_path.unwrap_or_else(|| "frozen".to_string()), + module_name.unwrap_or_else(|| "frozen".to_string()), ) } } diff --git a/vm/src/frozen.rs b/vm/src/frozen.rs index 32d9f4614b..4f29088abf 100644 --- a/vm/src/frozen.rs +++ b/vm/src/frozen.rs @@ -5,12 +5,15 @@ pub fn get_module_inits() -> HashMap { hashmap! { "__hello__".into() => py_compile_bytecode!( source = "initialized = True; print(\"Hello world!\")\n", + module_name = "__hello__", ), "_frozen_importlib".into() => py_compile_bytecode!( file = "../Lib/importlib/_bootstrap.py", + module_name = "_frozen_importlib", ), "_frozen_importlib_external".into() => py_compile_bytecode!( file = "../Lib/importlib/_bootstrap_external.py", + module_name = "_frozen_importlib_external", ), } } diff --git a/vm/src/import.rs b/vm/src/import.rs index ad1fd8e6ab..8b56b23b0e 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -25,24 +25,24 @@ pub fn init_importlib(vm: &VirtualMachine) -> PyResult { } pub fn import_frozen(vm: &VirtualMachine, module_name: &str) -> PyResult { - if let Some(frozen) = vm.frozen.borrow().get(module_name) { - let mut frozen = frozen.clone(); - frozen.source_path = format!("frozen {}", module_name); - import_codeobj(vm, module_name, frozen) - } else { - Err(vm.new_import_error(format!("Cannot import frozen module {}", module_name))) - } + vm.frozen + .borrow() + .get(module_name) + .ok_or_else(|| vm.new_import_error(format!("Cannot import frozen module {}", module_name))) + .and_then(|frozen| import_codeobj(vm, module_name, frozen.clone(), false)) } pub fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult { - let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); - if let Some(make_module_func) = vm.stdlib_inits.borrow().get(module_name) { - let module = make_module_func(vm); - sys_modules.set_item(module_name, module.clone(), vm)?; - Ok(module) - } else { - Err(vm.new_import_error(format!("Cannot import bultin module {}", module_name))) - } + vm.stdlib_inits + .borrow() + .get(module_name) + .ok_or_else(|| vm.new_import_error(format!("Cannot import bultin module {}", module_name))) + .and_then(|make_module_func| { + let module = make_module_func(vm); + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules")?; + sys_modules.set_item(module_name, module.clone(), vm)?; + Ok(module) + }) } pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &str) -> PyResult { @@ -57,8 +57,8 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s } else if vm.stdlib_inits.borrow().contains_key(module_name) { import_builtin(vm, module_name) } else { - let notfound_error = vm.context().exceptions.module_not_found_error.clone(); - let import_error = vm.context().exceptions.import_error.clone(); + let notfound_error = &vm.ctx.exceptions.module_not_found_error; + let import_error = &vm.ctx.exceptions.import_error; // Time to search for module in any place: let file_path = find_source(vm, current_path, module_name) @@ -83,21 +83,24 @@ pub fn import_file( ) -> PyResult { let code_obj = compile::compile(&content, &compile::Mode::Exec, file_path) .map_err(|err| vm.new_syntax_error(&err))?; - import_codeobj(vm, module_name, code_obj) + import_codeobj(vm, module_name, code_obj, true) } -pub fn import_codeobj(vm: &VirtualMachine, module_name: &str, code_obj: CodeObject) -> PyResult { +pub fn import_codeobj( + vm: &VirtualMachine, + module_name: &str, + code_obj: CodeObject, + set_file_attr: bool, +) -> PyResult { let attrs = vm.ctx.new_dict(); attrs.set_item("__name__", vm.new_str(module_name.to_string()), vm)?; - let file_path = &code_obj.source_path; - if !file_path.starts_with("frozen") { - // TODO: Should be less hacky, not depend on source_path - attrs.set_item("__file__", vm.new_str(file_path.to_owned()), vm)?; + if set_file_attr { + attrs.set_item("__file__", vm.new_str(code_obj.source_path.to_owned()), vm)?; } let module = vm.ctx.new_module(module_name, attrs.clone()); // Store module in cache to prevent infinite loop with mutual importing libs: - let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules")?; sys_modules.set_item(module_name, module.clone(), vm)?; // Execute main code in module: From e96a7014c45219164d449e9843d8d2a305c4455c Mon Sep 17 00:00:00 2001 From: rmliddle Date: Fri, 21 Jun 2019 13:51:51 +1000 Subject: [PATCH 801/884] Updates BytesIO to use "BufferedIO" --- vm/src/stdlib/io.rs | 154 ++++++++++++++++++++++++++++++-------------- 1 file changed, 105 insertions(+), 49 deletions(-) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index f155e0cbda..51e7c6d8ef 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -34,6 +34,70 @@ fn compute_c_flag(mode: &str) -> u16 { } } +fn byte_count(bytes: OptionalArg>) -> i64 { + match bytes { + OptionalArg::Present(Some(ref int)) => objint::get_value(int).to_i64().unwrap(), + _ => (-1 as i64), + } +} + +#[derive(Debug)] +struct BufferedIO { + cursor: Cursor>, +} + +impl BufferedIO { + fn new(cursor: Cursor>) -> BufferedIO { + BufferedIO { cursor: cursor } + } + + fn write(&mut self, data: Vec) -> Option { + let length = data.len(); + + match self.cursor.write_all(&data) { + Ok(_) => Some(length as u64), + Err(_) => None, + } + } + + //return the entire contents of the underlying + fn getvalue(&self) -> Vec { + self.cursor.clone().into_inner() + } + + //skip to the jth position + fn seek(&mut self, offset: u64) -> Option { + match self.cursor.seek(SeekFrom::Start(offset.clone())) { + Ok(_) => Some(offset), + Err(_) => None, + } + } + + //Read k bytes from the object and return. + fn read(&mut self, bytes: i64) -> Option> { + let mut buffer = Vec::new(); + + //for a defined number of bytes, i.e. bytes != -1 + if bytes > 0 { + let mut handle = self.cursor.clone().take(bytes as u64); + //read handle into buffer + if let Err(_) = handle.read_to_end(&mut buffer) { + return None; + } + //the take above consumes the struct value + //we add this back in with the takes into_inner method + self.cursor = handle.into_inner(); + } else { + //read handle into buffer + if let Err(_) = self.cursor.read_to_end(&mut buffer) { + return None; + } + }; + + Some(buffer) + } +} + #[derive(Debug)] struct PyStringIO { data: RefCell>>, @@ -131,7 +195,7 @@ fn string_io_new( #[derive(Debug)] struct PyBytesIO { - data: RefCell>>, + buffer: RefCell, } type PyBytesIORef = PyRef; @@ -143,65 +207,36 @@ impl PyValue for PyBytesIO { } impl PyBytesIORef { - //write string to underlying vector fn write(self, data: objbytes::PyBytesRef, vm: &VirtualMachine) -> PyResult { let bytes = data.get_value(); - let length = bytes.len(); - let mut cursor = self.data.borrow_mut(); - match cursor.write_all(bytes) { - Ok(_) => Ok(vm.ctx.new_int(length)), - Err(_) => Err(vm.new_type_error("Error Writing String".to_string())), + match self.buffer.borrow_mut().write(bytes.to_vec()) { + Some(value) => Ok(vm.ctx.new_int(value)), + None => Err(vm.new_type_error("Error Writing Bytes".to_string())), } } - - //return the entire contents of the underlying + //Retrieves the entire bytes object value from the underlying buffer fn getvalue(self, vm: &VirtualMachine) -> PyResult { - Ok(vm.ctx.new_bytes(self.data.borrow().clone().into_inner())) - } - - //skip to the jth position - fn seek(self, offset: PyObjectRef, vm: &VirtualMachine) -> PyResult { - let position = objint::get_value(&offset).to_u64().unwrap(); - if let Err(_) = self - .data - .borrow_mut() - .seek(SeekFrom::Start(position.clone())) - { - return Err(vm.new_value_error("Error Retrieving Value".to_string())); - } - - Ok(vm.ctx.new_int(position)) + Ok(vm.ctx.new_bytes(self.buffer.borrow().getvalue())) } - //Read k bytes from the object and return. + //Takes an integer k (bytes) and returns them from the underlying buffer //If k is undefined || k == -1, then we read all bytes until the end of the file. //This also increments the stream position by the value of k fn read(self, bytes: OptionalArg>, vm: &VirtualMachine) -> PyResult { - let mut buffer = Vec::new(); - - match bytes { - OptionalArg::Present(Some(ref integer)) => { - let k = objint::get_value(integer).to_u64().unwrap(); - let mut handle = self.data.borrow().clone().take(k); - - //read bytes into string - if let Err(_) = handle.read_to_end(&mut buffer) { - return Err(vm.new_value_error("Error Retrieving Value".to_string())); - } - - //the take above consumes the struct value - //we add this back in with the takes into_inner method - self.data.replace(handle.into_inner()); - } - _ => { - if let Err(_) = self.data.borrow_mut().read_to_end(&mut buffer) { - return Err(vm.new_value_error("Error Retrieving Value".to_string())); - } - } - }; + match self.buffer.borrow_mut().read(byte_count(bytes)) { + Some(value) => Ok(vm.ctx.new_bytes(value)), + None => Err(vm.new_value_error("Error Retrieving Value".to_string())), + } + } - Ok(vm.ctx.new_bytes(buffer)) + //skip to the jth position + fn seek(self, offset: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let position = objint::get_value(&offset).to_u64().unwrap(); + match self.buffer.borrow_mut().seek(position) { + Some(value) => Ok(vm.ctx.new_int(value)), + None => Err(vm.new_value_error("Error Performing Operation".to_string())), + } } } @@ -216,7 +251,7 @@ fn bytes_io_new( }; PyBytesIO { - data: RefCell::new(Cursor::new(raw_bytes)), + buffer: RefCell::new(BufferedIO::new(Cursor::new(raw_bytes))), } .into_ref_with_type(vm, cls) } @@ -729,4 +764,25 @@ mod tests { ); } + #[test] + fn test_buffered_read() { + let data = vec![1, 2, 3, 4]; + let bytes: i64 = -1; + let mut buffered = BufferedIO { + cursor: Cursor::new(data.clone()), + }; + + assert_eq!(buffered.read(bytes).unwrap(), data); + } + + #[test] + fn test_buffered_seek() { + let data = vec![1, 2, 3, 4]; + let offset: u64 = 2; + let mut buffered = BufferedIO { + cursor: Cursor::new(data.clone()), + }; + + assert_eq!(buffered.seek(offset.clone()).unwrap(), offset); + } } From c972e7c73e8d85433b8a7c0285efdb5655c37b4f Mon Sep 17 00:00:00 2001 From: Michel Heily Date: Fri, 21 Jun 2019 12:57:58 +0300 Subject: [PATCH 802/884] Remove a mistakenly commited debug print --- vm/src/obj/objstr.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index c8078e1887..1b6870e149 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1140,7 +1140,6 @@ fn do_cformat_specifier( if num_chars != 1 { Err(vm.new_type_error("%c requires int or char".to_string())) } else { - println!("Hurray!"); Ok(s.chars().next().unwrap().to_string()) } } else { From 61b26878de8cf47e849d0f51a6ddfa63ea611950 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 17 Jun 2019 17:26:15 -0500 Subject: [PATCH 803/884] Add rustpython_vm::__exports module and fix warning --- derive/src/compile_bytecode.rs | 2 +- derive/src/pyclass.rs | 71 ++++++++++++++++++++++++++++------ vm/src/lib.rs | 5 +++ vm/src/stdlib/mod.rs | 1 + 4 files changed, 66 insertions(+), 13 deletions(-) diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index 449b2c4889..e344746e54 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -163,7 +163,7 @@ pub fn impl_py_compile_bytecode(input: TokenStream2) -> Result(#bytes) .expect("Deserializing CodeObject failed") }) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index d216b9357e..65c3c63134 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -11,6 +11,10 @@ enum ClassItem { item_ident: Ident, py_name: String, }, + ClassMethod { + item_ident: Ident, + py_name: String, + }, Property { item_ident: Ident, py_name: String, @@ -49,8 +53,8 @@ impl ClassItem { let nesteds = meta_to_vec(meta).map_err(|meta| { err_span!( meta, - "#[pyproperty = \"...\"] cannot be a name/value, you probably meant \ - #[pyproperty(name = \"...\")]", + "#[pymethod = \"...\"] cannot be a name/value, you probably meant \ + #[pymethod(name = \"...\")]", ) })?; let mut py_name = None; @@ -80,6 +84,47 @@ impl ClassItem { py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), }); attr_idx = Some(i); + } else if name == "pyclassmethod" { + if item.is_some() { + bail_span!( + sig.ident, + "You can only have one #[py*] attribute on an impl item" + ) + } + let nesteds = meta_to_vec(meta).map_err(|meta| { + err_span!( + meta, + "#[pyclassmethod = \"...\"] cannot be a name/value, you probably meant \ + #[pyclassmethod(name = \"...\")]", + ) + })?; + let mut py_name = None; + for meta in nesteds { + let meta = match meta { + NestedMeta::Meta(meta) => meta, + NestedMeta::Literal(_) => continue, + }; + match meta { + Meta::NameValue(name_value) => { + if name_value.ident == "name" { + if let Lit::Str(s) = &name_value.lit { + py_name = Some(s.value()); + } else { + bail_span!( + &sig.ident, + "#[pyclassmethod(name = ...)] must be a string" + ); + } + } + } + _ => {} + } + } + item = Some(ClassItem::ClassMethod { + item_ident: sig.ident.clone(), + py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), + }); + attr_idx = Some(i); } else if name == "pyproperty" { if item.is_some() { bail_span!( @@ -210,18 +255,20 @@ pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> Result {} } } - let methods = items.iter().filter_map(|item| { - if let ClassItem::Method { + let methods = items.iter().filter_map(|item| match item { + ClassItem::Method { item_ident, py_name, - } = item - { - Some(quote! { - class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_ident)); - }) - } else { - None - } + } => Some(quote! { + class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_ident)); + }), + ClassItem::ClassMethod { + item_ident, + py_name, + } => Some(quote! { + class.set_str_attr(#py_name, ctx.new_classmethod(Self::#item_ident)); + }), + _ => None, }); let properties = properties .iter() diff --git a/vm/src/lib.rs b/vm/src/lib.rs index f2d0cb57d2..f60be1c7df 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -66,3 +66,8 @@ mod vm; pub use self::exceptions::print_exception; pub use self::vm::VirtualMachine; pub use rustpython_compiler::*; + +#[doc(hidden)] +pub mod __exports { + pub use bincode; +} diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 041c6711d6..8329b3092c 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -34,6 +34,7 @@ use crate::pyobject::PyObjectRef; pub type StdlibInitFunc = Box PyObjectRef>; pub fn get_module_inits() -> HashMap { + #[allow(unused_mut)] let mut modules = hashmap! { "ast".to_string() => Box::new(ast::make_module) as StdlibInitFunc, "binascii".to_string() => Box::new(binascii::make_module), From 56bcb02a5e2227649dc0417d3402dadba0548cac Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 17 Jun 2019 17:27:08 -0500 Subject: [PATCH 804/884] Add _js module and fix imports --- wasm/lib/src/js_module.rs | 198 ++++++++++++++++++++++++++++++++++ wasm/lib/src/lib.rs | 1 + wasm/lib/src/vm_class.rs | 18 +++- wasm/lib/src/wasm_builtins.rs | 36 ++++++- 4 files changed, 248 insertions(+), 5 deletions(-) create mode 100644 wasm/lib/src/js_module.rs diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs new file mode 100644 index 0000000000..0c5a8971a0 --- /dev/null +++ b/wasm/lib/src/js_module.rs @@ -0,0 +1,198 @@ +use crate::convert; +use js_sys::{Array, Object, Reflect}; +use rustpython_vm::function::Args; +use rustpython_vm::obj::{objfloat::PyFloatRef, objstr::PyStringRef, objtype::PyClassRef}; +use rustpython_vm::pyobject::{PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject}; +use rustpython_vm::VirtualMachine; +use wasm_bindgen::{prelude::*, JsCast}; + +// I don't know why there is no other option for this +#[wasm_bindgen(inline_js = "export function type_of(a) { return typeof a; }")] +extern "C" { + #[wasm_bindgen] + fn type_of(a: JsValue) -> String; +} + +#[pyclass(name = "JsValue")] +#[derive(Debug)] +pub struct PyJsValue { + value: JsValue, +} +type PyJsValueRef = PyRef; + +impl PyValue for PyJsValue { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("_js", "JsValue") + } +} + +enum JsProperty { + Str(PyStringRef), + Js(PyJsValueRef), +} + +impl TryFromObject for JsProperty { + fn try_from_object(vm: &VirtualMachine, obj: PyObjectRef) -> PyResult { + PyStringRef::try_from_object(vm, obj.clone()) + .map(JsProperty::Str) + .or_else(|_| PyJsValueRef::try_from_object(vm, obj).map(JsProperty::Js)) + } +} + +impl JsProperty { + fn to_jsvalue(self) -> JsValue { + match self { + JsProperty::Str(s) => s.as_str().into(), + JsProperty::Js(value) => value.value.clone(), + } + } +} + +#[pyimpl] +impl PyJsValue { + #[inline] + pub fn new(value: impl Into) -> PyJsValue { + PyJsValue { + value: value.into(), + } + } + + #[pyclassmethod] + fn null(cls: PyClassRef, vm: &VirtualMachine) -> PyResult { + PyJsValue::new(JsValue::NULL).into_ref_with_type(vm, cls) + } + + #[pyclassmethod] + fn undefined(cls: PyClassRef, vm: &VirtualMachine) -> PyResult { + PyJsValue::new(JsValue::UNDEFINED).into_ref_with_type(vm, cls) + } + + #[pyclassmethod] + fn fromstr(cls: PyClassRef, s: PyStringRef, vm: &VirtualMachine) -> PyResult { + PyJsValue::new(s.as_str()).into_ref_with_type(vm, cls) + } + + #[pyclassmethod] + fn fromfloat(cls: PyClassRef, n: PyFloatRef, vm: &VirtualMachine) -> PyResult { + PyJsValue::new(n.to_f64()).into_ref_with_type(vm, cls) + } + + #[pyclassmethod] + fn new_object( + cls: PyClassRef, + opts: NewObjectOptions, + vm: &VirtualMachine, + ) -> PyResult { + let value = if let Some(proto) = opts.prototype { + if let Some(proto) = proto.value.dyn_ref::() { + Object::create(proto) + } else if proto.value.is_null() { + Object::create(proto.value.unchecked_ref()) + } else { + return Err(vm.new_value_error(format!("prototype must be an Object or null"))); + } + } else { + Object::new() + }; + PyJsValue::new(value).into_ref_with_type(vm, cls) + } + + #[pymethod] + fn get_prop(&self, name: JsProperty, vm: &VirtualMachine) -> PyResult { + let name = &name.to_jsvalue(); + if Reflect::has(&self.value, name).map_err(|err| convert::js_to_py(vm, err))? { + Reflect::get(&self.value, name) + .map(PyJsValue::new) + .map_err(|err| convert::js_to_py(vm, err)) + } else { + Err(vm.new_attribute_error(format!("No attribute {:?} on JS value", name))) + } + } + + #[pymethod] + fn set_prop( + &self, + name: JsProperty, + value: PyJsValueRef, + vm: &VirtualMachine, + ) -> PyResult { + Reflect::set(&self.value, &name.to_jsvalue(), &value.value) + .map(PyJsValue::new) + .map_err(|err| convert::js_to_py(vm, err)) + } + + #[pymethod] + fn as_str(&self, _vm: &VirtualMachine) -> Option { + self.value.as_string() + } + + #[pymethod] + fn as_float(&self, _vm: &VirtualMachine) -> Option { + self.value.as_f64() + } + + #[pymethod] + /// Checks that `typeof self == "object" && self !== null`. Use instead + /// of `value.typeof() == "object"` + fn is_object(&self, _vm: &VirtualMachine) -> bool { + self.value.is_object() + } + + #[pymethod] + fn call( + &self, + args: Args, + opts: CallOptions, + vm: &VirtualMachine, + ) -> PyResult { + let func = self + .value + .dyn_ref::() + .ok_or_else(|| vm.new_type_error("JS value is not callable".to_string()))?; + let this = opts + .this + .map(|this| this.value.clone()) + .unwrap_or(JsValue::UNDEFINED); + let js_args = Array::new(); + for arg in args { + js_args.push(&arg.value); + } + Reflect::apply(func, &this, &js_args) + .map(PyJsValue::new) + .map_err(|err| convert::js_to_py(vm, err)) + } + + #[pymethod(name = "typeof")] + fn type_of(&self, _vm: &VirtualMachine) -> String { + type_of(self.value.clone()) + } + + #[pymethod(name = "__repr__")] + fn repr(&self, _vm: &VirtualMachine) -> String { + format!("{:?}", self.value) + } +} + +#[derive(FromArgs)] +struct CallOptions { + #[pyarg(keyword_only, default = "None")] + this: Option, +} + +#[derive(FromArgs)] +struct NewObjectOptions { + #[pyarg(keyword_only, default = "None")] + prototype: Option, +} + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + py_module!(vm, "_js", { + "JsValue" => PyJsValue::make_class(&vm.ctx), + }) +} + +pub fn setup_js_module(vm: &VirtualMachine) { + vm.stdlib_inits + .borrow_mut() + .insert("_js".to_string(), Box::new(make_module)); +} diff --git a/wasm/lib/src/lib.rs b/wasm/lib/src/lib.rs index 35d4595199..52049ee076 100644 --- a/wasm/lib/src/lib.rs +++ b/wasm/lib/src/lib.rs @@ -1,5 +1,6 @@ pub mod browser_module; pub mod convert; +pub mod js_module; pub mod vm_class; pub mod wasm_builtins; diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 9526941ffd..886a7013e2 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -8,11 +8,12 @@ use wasm_bindgen::prelude::*; use rustpython_vm::compile; use rustpython_vm::frame::{NameProtocol, Scope}; use rustpython_vm::function::PyFuncArgs; -use rustpython_vm::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult}; +use rustpython_vm::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; use crate::browser_module::setup_browser_module; use crate::convert; +use crate::js_module; use crate::wasm_builtins; pub(crate) struct StoredVirtualMachine { @@ -26,11 +27,24 @@ pub(crate) struct StoredVirtualMachine { impl StoredVirtualMachine { fn new(id: String, inject_browser_module: bool) -> StoredVirtualMachine { let mut vm = VirtualMachine::new(); + vm.wasm_id = Some(id); let scope = vm.new_scope_with_builtins(); + + js_module::setup_js_module(&vm); if inject_browser_module { + vm.stdlib_inits.borrow_mut().insert( + "_window".to_string(), + Box::new(|vm| { + py_module!(vm, "_window", { + "window" => js_module::PyJsValue::new(wasm_builtins::window()).into_ref(vm), + }) + }), + ); setup_browser_module(&vm); } - vm.wasm_id = Some(id); + + *vm.import_func.borrow_mut() = vm.ctx.new_rustfunc(wasm_builtins::builtin_import); + StoredVirtualMachine { vm, scope: RefCell::new(scope), diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index ada0c83f53..6a89f5e35f 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -7,9 +7,13 @@ use js_sys::{self, Array}; use web_sys::{self, console}; -use rustpython_vm::function::PyFuncArgs; -use rustpython_vm::obj::{objstr, objtype}; -use rustpython_vm::pyobject::{IdProtocol, PyObjectRef, PyResult, TypeProtocol}; +use rustpython_vm::function::{Args, KwArgs, PyFuncArgs}; +use rustpython_vm::import; +use rustpython_vm::obj::{ + objstr::{self, PyStringRef}, + objtype, +}; +use rustpython_vm::pyobject::{IdProtocol, ItemProtocol, PyObjectRef, PyResult, TypeProtocol}; use rustpython_vm::VirtualMachine; pub(crate) fn window() -> web_sys::Window { @@ -76,3 +80,29 @@ pub fn builtin_print_console(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult console::log(&arr); Ok(vm.get_none()) } + +pub fn builtin_import( + module_name: PyStringRef, + _args: Args, + _kwargs: KwArgs, + vm: &VirtualMachine, +) -> PyResult { + let module_name = module_name.as_str(); + + let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); + + // First, see if we already loaded the module: + if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) { + Ok(module) + } else if vm.frozen.borrow().contains_key(module_name) { + import::import_frozen(vm, module_name) + } else if vm.stdlib_inits.borrow().contains_key(module_name) { + import::import_builtin(vm, module_name) + } else { + let notfound_error = vm.context().exceptions.module_not_found_error.clone(); + Err(vm.new_exception( + notfound_error, + format!("Module {:?} not found", module_name), + )) + } +} From 93cdd94ab7e3b700b2b4b5c0218dcf12efb93e29 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 17 Jun 2019 17:27:55 -0500 Subject: [PATCH 805/884] Add browser.py module --- wasm/lib/src/browser.py | 12 ++++++++++++ wasm/lib/src/browser_module.rs | 6 +++++- 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 wasm/lib/src/browser.py diff --git a/wasm/lib/src/browser.py b/wasm/lib/src/browser.py new file mode 100644 index 0000000000..5696e99469 --- /dev/null +++ b/wasm/lib/src/browser.py @@ -0,0 +1,12 @@ +from _browser import * +from _js import JsValue +from _window import window + + +_alert = window.get_prop("alert") + + +def alert(msg): + if type(msg) != str: + raise TypeError("msg must be a string") + _alert.call(JsValue.fromstr(msg)) diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index b3d689d3f3..19a98613b8 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -418,5 +418,9 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { pub fn setup_browser_module(vm: &VirtualMachine) { vm.stdlib_inits .borrow_mut() - .insert("browser".to_string(), Box::new(make_module)); + .insert("_browser".to_string(), Box::new(make_module)); + vm.frozen.borrow_mut().insert( + "browser".to_string(), + py_compile_bytecode!(file = "src/browser.py"), + ); } From 5627ab234502b6700f76ad946c0d2d6bcf7caa0d Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 17 Jun 2019 17:53:13 -0500 Subject: [PATCH 806/884] Add PyJsValue::construct() --- wasm/lib/src/js_module.rs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index 0c5a8971a0..0301726c4b 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -162,6 +162,36 @@ impl PyJsValue { .map_err(|err| convert::js_to_py(vm, err)) } + #[pymethod] + fn construct( + &self, + args: Args, + opts: NewObjectOptions, + vm: &VirtualMachine, + ) -> PyResult { + let ctor = self + .value + .dyn_ref::() + .ok_or_else(|| vm.new_type_error("JS value is not callable".to_string()))?; + let proto = opts + .prototype + .as_ref() + .and_then(|proto| proto.value.dyn_ref::().clone()); + let js_args = Array::new(); + for arg in args { + js_args.push(&arg.value); + } + let constructed_result = if let Some(proto) = proto { + Reflect::construct_with_new_target(ctor, &js_args, &proto) + } else { + Reflect::construct(ctor, &js_args) + }; + constructed_result + .map(PyJsValue::new) + .map_err(|err| convert::js_to_py(vm, err)) + } + + #[pymethod(name = "typeof")] fn type_of(&self, _vm: &VirtualMachine) -> String { type_of(self.value.clone()) From a3735da48962cefb82ca9590723a7f8f85f5aefb Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 17 Jun 2019 19:45:04 -0500 Subject: [PATCH 807/884] Move confirm and prompt to browser.py --- wasm/lib/src/browser.py | 25 +++++++++++++++++++++++++ wasm/lib/src/browser_module.rs | 19 ------------------- wasm/lib/src/js_module.rs | 6 ++++++ 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/wasm/lib/src/browser.py b/wasm/lib/src/browser.py index 5696e99469..b80695757a 100644 --- a/wasm/lib/src/browser.py +++ b/wasm/lib/src/browser.py @@ -1,4 +1,5 @@ from _browser import * + from _js import JsValue from _window import window @@ -10,3 +11,27 @@ def alert(msg): if type(msg) != str: raise TypeError("msg must be a string") _alert.call(JsValue.fromstr(msg)) + + +_confirm = window.get_prop("confirm") + + +def confirm(msg): + if type(msg) != str: + raise TypeError("msg must be a string") + return _confirm.call(JsValue.fromstr(msg)).as_bool() + + +_prompt = window.get_prop("prompt") + + +def prompt(msg, default_val=None): + if type(msg) != str: + raise TypeError("msg must be a string") + if default_val is not None and type(default_val) != str: + raise TypeError("default_val must be a string") + + return _prompt.call( + JsValue.fromstr(arg) for arg in [msg, default_val] if arg + ).as_str() + diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index 19a98613b8..db76a1a7e1 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -312,22 +312,6 @@ impl Element { } } -fn browser_alert(message: PyStringRef, vm: &VirtualMachine) -> PyResult { - window() - .alert_with_message(message.as_str()) - .expect("alert() not to fail"); - - Ok(vm.get_none()) -} - -fn browser_confirm(message: PyStringRef, vm: &VirtualMachine) -> PyResult { - let result = window() - .confirm_with_message(message.as_str()) - .expect("confirm() not to fail"); - - Ok(vm.new_bool(result)) -} - fn browser_prompt( message: PyStringRef, default: OptionalArg, @@ -408,9 +392,6 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "Document" => document_class, "document" => document, "Element" => element, - "alert" => ctx.new_rustfunc(browser_alert), - "confirm" => ctx.new_rustfunc(browser_confirm), - "prompt" => ctx.new_rustfunc(browser_prompt), "load_module" => ctx.new_rustfunc(browser_load_module), }) } diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index 0301726c4b..438a5e042f 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -130,6 +130,11 @@ impl PyJsValue { fn as_float(&self, _vm: &VirtualMachine) -> Option { self.value.as_f64() } + + #[pymethod] + fn as_bool(&self, _vm: &VirtualMachine) -> Option { + self.value.as_bool() + } #[pymethod] /// Checks that `typeof self == "object" && self !== null`. Use instead @@ -186,6 +191,7 @@ impl PyJsValue { } else { Reflect::construct(ctor, &js_args) }; + constructed_result .map(PyJsValue::new) .map_err(|err| convert::js_to_py(vm, err)) From 886f77bdfff646a833a7dbabd573cf6c29d90772 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 17 Jun 2019 20:13:06 -0500 Subject: [PATCH 808/884] Add PyJsValue::instanceof --- wasm/lib/src/browser_module.rs | 19 ----------- wasm/lib/src/js_module.rs | 60 +++++++++++++++++++--------------- 2 files changed, 34 insertions(+), 45 deletions(-) diff --git a/wasm/lib/src/browser_module.rs b/wasm/lib/src/browser_module.rs index db76a1a7e1..364fa614bb 100644 --- a/wasm/lib/src/browser_module.rs +++ b/wasm/lib/src/browser_module.rs @@ -312,25 +312,6 @@ impl Element { } } -fn browser_prompt( - message: PyStringRef, - default: OptionalArg, - vm: &VirtualMachine, -) -> PyResult { - let result = if let OptionalArg::Present(default) = default { - window().prompt_with_message_and_default(message.as_str(), default.as_str()) - } else { - window().prompt_with_message(message.as_str()) - }; - - let result = match result.expect("prompt() not to fail") { - Some(result) => vm.new_str(result), - None => vm.get_none(), - }; - - Ok(result) -} - fn browser_load_module(module: PyStringRef, path: PyStringRef, vm: &VirtualMachine) -> PyResult { let weak_vm = weak_vm(vm); diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index 438a5e042f..4de6343a57 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -6,11 +6,15 @@ use rustpython_vm::pyobject::{PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue use rustpython_vm::VirtualMachine; use wasm_bindgen::{prelude::*, JsCast}; -// I don't know why there is no other option for this -#[wasm_bindgen(inline_js = "export function type_of(a) { return typeof a; }")] +#[wasm_bindgen(inline_js = " +export function type_of(a) { return typeof a; } +export function instance_of(lhs, rhs) { return lhs instanceof rhs; } +")] extern "C" { #[wasm_bindgen] - fn type_of(a: JsValue) -> String; + fn type_of(a: &JsValue) -> String; + #[wasm_bindgen(catch)] + fn instance_of(lhs: &JsValue, rhs: &JsValue) -> Result; } #[pyclass(name = "JsValue")] @@ -121,28 +125,6 @@ impl PyJsValue { .map_err(|err| convert::js_to_py(vm, err)) } - #[pymethod] - fn as_str(&self, _vm: &VirtualMachine) -> Option { - self.value.as_string() - } - - #[pymethod] - fn as_float(&self, _vm: &VirtualMachine) -> Option { - self.value.as_f64() - } - - #[pymethod] - fn as_bool(&self, _vm: &VirtualMachine) -> Option { - self.value.as_bool() - } - - #[pymethod] - /// Checks that `typeof self == "object" && self !== null`. Use instead - /// of `value.typeof() == "object"` - fn is_object(&self, _vm: &VirtualMachine) -> bool { - self.value.is_object() - } - #[pymethod] fn call( &self, @@ -197,10 +179,36 @@ impl PyJsValue { .map_err(|err| convert::js_to_py(vm, err)) } + #[pymethod] + fn as_str(&self, _vm: &VirtualMachine) -> Option { + self.value.as_string() + } + + #[pymethod] + fn as_float(&self, _vm: &VirtualMachine) -> Option { + self.value.as_f64() + } + + #[pymethod] + fn as_bool(&self, _vm: &VirtualMachine) -> Option { + self.value.as_bool() + } #[pymethod(name = "typeof")] fn type_of(&self, _vm: &VirtualMachine) -> String { - type_of(self.value.clone()) + type_of(&self.value) + } + + #[pymethod] + /// Checks that `typeof self == "object" && self !== null`. Use instead + /// of `value.typeof() == "object"` + fn is_object(&self, _vm: &VirtualMachine) -> bool { + self.value.is_object() + } + + #[pymethod] + fn instanceof(&self, rhs: PyJsValueRef, vm: &VirtualMachine) -> PyResult { + instance_of(&self.value, &rhs.value).map_err(|err| convert::js_to_py(vm, err)) } #[pymethod(name = "__repr__")] From 072c8725fc1c8cae246ce5769b1f6f9874b4f915 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 18 Jun 2019 19:34:13 -0500 Subject: [PATCH 809/884] Fix get_prop on primitives --- wasm/lib/src/js_module.rs | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index 4de6343a57..c756917055 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -7,10 +7,19 @@ use rustpython_vm::VirtualMachine; use wasm_bindgen::{prelude::*, JsCast}; #[wasm_bindgen(inline_js = " +export function has_prop(target, prop) { return prop in Object(target); } +export function get_prop(target, prop) { return target[prop]; } +export function set_prop(target, prop, value) { target[prop] = value; } export function type_of(a) { return typeof a; } export function instance_of(lhs, rhs) { return lhs instanceof rhs; } ")] extern "C" { + #[wasm_bindgen(catch)] + fn has_prop(target: &JsValue, prop: &JsValue) -> Result; + #[wasm_bindgen(catch)] + fn get_prop(target: &JsValue, prop: &JsValue) -> Result; + #[wasm_bindgen(catch)] + fn set_prop(target: &JsValue, prop: &JsValue, value: &JsValue) -> Result<(), JsValue>; #[wasm_bindgen] fn type_of(a: &JsValue) -> String; #[wasm_bindgen(catch)] @@ -101,11 +110,16 @@ impl PyJsValue { PyJsValue::new(value).into_ref_with_type(vm, cls) } + #[pymethod] + fn has_prop(&self, name: JsProperty, vm: &VirtualMachine) -> PyResult { + has_prop(&self.value, &name.to_jsvalue()).map_err(|err| convert::js_to_py(vm, err)) + } + #[pymethod] fn get_prop(&self, name: JsProperty, vm: &VirtualMachine) -> PyResult { let name = &name.to_jsvalue(); - if Reflect::has(&self.value, name).map_err(|err| convert::js_to_py(vm, err))? { - Reflect::get(&self.value, name) + if has_prop(&self.value, name).map_err(|err| convert::js_to_py(vm, err))? { + get_prop(&self.value, name) .map(PyJsValue::new) .map_err(|err| convert::js_to_py(vm, err)) } else { @@ -114,14 +128,8 @@ impl PyJsValue { } #[pymethod] - fn set_prop( - &self, - name: JsProperty, - value: PyJsValueRef, - vm: &VirtualMachine, - ) -> PyResult { - Reflect::set(&self.value, &name.to_jsvalue(), &value.value) - .map(PyJsValue::new) + fn set_prop(&self, name: JsProperty, value: PyJsValueRef, vm: &VirtualMachine) -> PyResult<()> { + set_prop(&self.value, &name.to_jsvalue(), &value.value) .map_err(|err| convert::js_to_py(vm, err)) } From 8cf38f5b25bf76a9ea90393fc4adc64aeb0a7370 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 18 Jun 2019 20:36:11 -0500 Subject: [PATCH 810/884] Move classmethods on JsValue to be normal methods --- derive/src/pyclass.rs | 71 +++++++-------------------------------- wasm/lib/src/browser.py | 12 +++---- wasm/lib/src/js_module.rs | 34 +++++++++---------- 3 files changed, 33 insertions(+), 84 deletions(-) diff --git a/derive/src/pyclass.rs b/derive/src/pyclass.rs index 65c3c63134..d216b9357e 100644 --- a/derive/src/pyclass.rs +++ b/derive/src/pyclass.rs @@ -11,10 +11,6 @@ enum ClassItem { item_ident: Ident, py_name: String, }, - ClassMethod { - item_ident: Ident, - py_name: String, - }, Property { item_ident: Ident, py_name: String, @@ -53,8 +49,8 @@ impl ClassItem { let nesteds = meta_to_vec(meta).map_err(|meta| { err_span!( meta, - "#[pymethod = \"...\"] cannot be a name/value, you probably meant \ - #[pymethod(name = \"...\")]", + "#[pyproperty = \"...\"] cannot be a name/value, you probably meant \ + #[pyproperty(name = \"...\")]", ) })?; let mut py_name = None; @@ -84,47 +80,6 @@ impl ClassItem { py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), }); attr_idx = Some(i); - } else if name == "pyclassmethod" { - if item.is_some() { - bail_span!( - sig.ident, - "You can only have one #[py*] attribute on an impl item" - ) - } - let nesteds = meta_to_vec(meta).map_err(|meta| { - err_span!( - meta, - "#[pyclassmethod = \"...\"] cannot be a name/value, you probably meant \ - #[pyclassmethod(name = \"...\")]", - ) - })?; - let mut py_name = None; - for meta in nesteds { - let meta = match meta { - NestedMeta::Meta(meta) => meta, - NestedMeta::Literal(_) => continue, - }; - match meta { - Meta::NameValue(name_value) => { - if name_value.ident == "name" { - if let Lit::Str(s) = &name_value.lit { - py_name = Some(s.value()); - } else { - bail_span!( - &sig.ident, - "#[pyclassmethod(name = ...)] must be a string" - ); - } - } - } - _ => {} - } - } - item = Some(ClassItem::ClassMethod { - item_ident: sig.ident.clone(), - py_name: py_name.unwrap_or_else(|| sig.ident.to_string()), - }); - attr_idx = Some(i); } else if name == "pyproperty" { if item.is_some() { bail_span!( @@ -255,20 +210,18 @@ pub fn impl_pyimpl(_attr: AttributeArgs, item: Item) -> Result {} } } - let methods = items.iter().filter_map(|item| match item { - ClassItem::Method { + let methods = items.iter().filter_map(|item| { + if let ClassItem::Method { item_ident, py_name, - } => Some(quote! { - class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_ident)); - }), - ClassItem::ClassMethod { - item_ident, - py_name, - } => Some(quote! { - class.set_str_attr(#py_name, ctx.new_classmethod(Self::#item_ident)); - }), - _ => None, + } = item + { + Some(quote! { + class.set_str_attr(#py_name, ctx.new_rustfunc(Self::#item_ident)); + }) + } else { + None + } }); let properties = properties .iter() diff --git a/wasm/lib/src/browser.py b/wasm/lib/src/browser.py index b80695757a..b146f696ac 100644 --- a/wasm/lib/src/browser.py +++ b/wasm/lib/src/browser.py @@ -4,13 +4,16 @@ from _window import window +jsstr = window.new_from_str + + _alert = window.get_prop("alert") def alert(msg): if type(msg) != str: raise TypeError("msg must be a string") - _alert.call(JsValue.fromstr(msg)) + _alert.call(jsstr(msg)) _confirm = window.get_prop("confirm") @@ -19,7 +22,7 @@ def alert(msg): def confirm(msg): if type(msg) != str: raise TypeError("msg must be a string") - return _confirm.call(JsValue.fromstr(msg)).as_bool() + return _confirm.call(jsstr(msg)).as_bool() _prompt = window.get_prop("prompt") @@ -31,7 +34,4 @@ def prompt(msg, default_val=None): if default_val is not None and type(default_val) != str: raise TypeError("default_val must be a string") - return _prompt.call( - JsValue.fromstr(arg) for arg in [msg, default_val] if arg - ).as_str() - + return _prompt.call(*(jsstr(arg) for arg in [msg, default_val] if arg)).as_str() diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index c756917055..0437c4a893 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -70,32 +70,28 @@ impl PyJsValue { } } - #[pyclassmethod] - fn null(cls: PyClassRef, vm: &VirtualMachine) -> PyResult { - PyJsValue::new(JsValue::NULL).into_ref_with_type(vm, cls) + #[pymethod] + fn null(&self, _vm: &VirtualMachine) -> PyJsValue { + PyJsValue::new(JsValue::NULL) } - #[pyclassmethod] - fn undefined(cls: PyClassRef, vm: &VirtualMachine) -> PyResult { - PyJsValue::new(JsValue::UNDEFINED).into_ref_with_type(vm, cls) + #[pymethod] + fn undefined(&self, _vm: &VirtualMachine) -> PyJsValue { + PyJsValue::new(JsValue::UNDEFINED) } - #[pyclassmethod] - fn fromstr(cls: PyClassRef, s: PyStringRef, vm: &VirtualMachine) -> PyResult { - PyJsValue::new(s.as_str()).into_ref_with_type(vm, cls) + #[pymethod] + fn new_from_str(&self, s: PyStringRef, _vm: &VirtualMachine) -> PyJsValue { + PyJsValue::new(s.as_str()) } - #[pyclassmethod] - fn fromfloat(cls: PyClassRef, n: PyFloatRef, vm: &VirtualMachine) -> PyResult { - PyJsValue::new(n.to_f64()).into_ref_with_type(vm, cls) + #[pymethod] + fn new_from_float(&self, n: PyFloatRef, _vm: &VirtualMachine) -> PyJsValue { + PyJsValue::new(n.to_f64()) } - #[pyclassmethod] - fn new_object( - cls: PyClassRef, - opts: NewObjectOptions, - vm: &VirtualMachine, - ) -> PyResult { + #[pymethod] + fn new_object(&self, opts: NewObjectOptions, vm: &VirtualMachine) -> PyResult { let value = if let Some(proto) = opts.prototype { if let Some(proto) = proto.value.dyn_ref::() { Object::create(proto) @@ -107,7 +103,7 @@ impl PyJsValue { } else { Object::new() }; - PyJsValue::new(value).into_ref_with_type(vm, cls) + Ok(PyJsValue::new(value)) } #[pymethod] From 298b05d56da4a5384444454306d01b6144f832cf Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 18 Jun 2019 21:44:42 -0500 Subject: [PATCH 811/884] Don't convert errors from js to python --- vm/src/pyobject.rs | 6 +++++- wasm/lib/src/js_module.rs | 31 ++++++++++++++++++++----------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/vm/src/pyobject.rs b/vm/src/pyobject.rs index e1c2cd42cb..82eb6e5287 100644 --- a/vm/src/pyobject.rs +++ b/vm/src/pyobject.rs @@ -1379,7 +1379,11 @@ pub trait PyClassImpl: PyClassDef { } fn make_class(ctx: &PyContext) -> PyClassRef { - let py_class = ctx.new_class(Self::NAME, ctx.object()); + Self::make_class_with_base(ctx, ctx.object()) + } + + fn make_class_with_base(ctx: &PyContext, base: PyClassRef) -> PyClassRef { + let py_class = ctx.new_class(Self::NAME, base); Self::extend_class(ctx, &py_class); py_class } diff --git a/wasm/lib/src/js_module.rs b/wasm/lib/src/js_module.rs index 0437c4a893..6db733d66e 100644 --- a/wasm/lib/src/js_module.rs +++ b/wasm/lib/src/js_module.rs @@ -1,8 +1,9 @@ -use crate::convert; use js_sys::{Array, Object, Reflect}; use rustpython_vm::function::Args; use rustpython_vm::obj::{objfloat::PyFloatRef, objstr::PyStringRef, objtype::PyClassRef}; -use rustpython_vm::pyobject::{PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject}; +use rustpython_vm::pyobject::{ + create_type, PyClassImpl, PyObjectRef, PyRef, PyResult, PyValue, TryFromObject, +}; use rustpython_vm::VirtualMachine; use wasm_bindgen::{prelude::*, JsCast}; @@ -108,16 +109,16 @@ impl PyJsValue { #[pymethod] fn has_prop(&self, name: JsProperty, vm: &VirtualMachine) -> PyResult { - has_prop(&self.value, &name.to_jsvalue()).map_err(|err| convert::js_to_py(vm, err)) + has_prop(&self.value, &name.to_jsvalue()).map_err(|err| new_js_error(vm, err)) } #[pymethod] fn get_prop(&self, name: JsProperty, vm: &VirtualMachine) -> PyResult { let name = &name.to_jsvalue(); - if has_prop(&self.value, name).map_err(|err| convert::js_to_py(vm, err))? { + if has_prop(&self.value, name).map_err(|err| new_js_error(vm, err))? { get_prop(&self.value, name) .map(PyJsValue::new) - .map_err(|err| convert::js_to_py(vm, err)) + .map_err(|err| new_js_error(vm, err)) } else { Err(vm.new_attribute_error(format!("No attribute {:?} on JS value", name))) } @@ -125,8 +126,7 @@ impl PyJsValue { #[pymethod] fn set_prop(&self, name: JsProperty, value: PyJsValueRef, vm: &VirtualMachine) -> PyResult<()> { - set_prop(&self.value, &name.to_jsvalue(), &value.value) - .map_err(|err| convert::js_to_py(vm, err)) + set_prop(&self.value, &name.to_jsvalue(), &value.value).map_err(|err| new_js_error(vm, err)) } #[pymethod] @@ -150,7 +150,7 @@ impl PyJsValue { } Reflect::apply(func, &this, &js_args) .map(PyJsValue::new) - .map_err(|err| convert::js_to_py(vm, err)) + .map_err(|err| new_js_error(vm, err)) } #[pymethod] @@ -180,7 +180,7 @@ impl PyJsValue { constructed_result .map(PyJsValue::new) - .map_err(|err| convert::js_to_py(vm, err)) + .map_err(|err| new_js_error(vm, err)) } #[pymethod] @@ -212,7 +212,7 @@ impl PyJsValue { #[pymethod] fn instanceof(&self, rhs: PyJsValueRef, vm: &VirtualMachine) -> PyResult { - instance_of(&self.value, &rhs.value).map_err(|err| convert::js_to_py(vm, err)) + instance_of(&self.value, &rhs.value).map_err(|err| new_js_error(vm, err)) } #[pymethod(name = "__repr__")] @@ -233,9 +233,18 @@ struct NewObjectOptions { prototype: Option, } +fn new_js_error(vm: &VirtualMachine, err: JsValue) -> PyObjectRef { + let exc = vm.new_exception(vm.class("_js", "JsError"), format!("{:?}", err)); + vm.set_attr(&exc, "js_value", PyJsValue::new(err).into_ref(vm)) + .unwrap(); + exc +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; py_module!(vm, "_js", { - "JsValue" => PyJsValue::make_class(&vm.ctx), + "JsError" => create_type("JsError", &ctx.type_type, &ctx.exceptions.exception_type), + "JsValue" => PyJsValue::make_class(ctx), }) } From 658b6ca9b924236b59a055309fa261367d477af6 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 18 Jun 2019 23:24:10 -0500 Subject: [PATCH 812/884] Fix errors --- wasm/lib/src/convert.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wasm/lib/src/convert.rs b/wasm/lib/src/convert.rs index daf48dded0..6de4a5987a 100644 --- a/wasm/lib/src/convert.rs +++ b/wasm/lib/src/convert.rs @@ -45,7 +45,7 @@ pub fn py_err_to_js_err(vm: &VirtualMachine, py_err: &PyObjectRef) -> JsValue { let elements = objsequence::get_elements_list(&tb).to_vec(); if let Some(top) = elements.get(0) { if objtype::isinstance(&top, &vm.ctx.tuple_type()) { - let element = objsequence::get_elements_list(&top); + let element = objsequence::get_elements_tuple(&top); if let Some(lineno) = objint::to_int(vm, &element[1], 10) .ok() From ed63ebd0a3c7a74c1a079e4edc8c646ab1243b03 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 22 Jun 2019 13:13:34 +0200 Subject: [PATCH 813/884] Added the python zen module. --- Lib/this.py | 28 ++++++++++++++++++++++++++++ vm/src/sysmodule.rs | 34 ++++++++++++++++++---------------- 2 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 Lib/this.py diff --git a/Lib/this.py b/Lib/this.py new file mode 100644 index 0000000000..e68dd3ff39 --- /dev/null +++ b/Lib/this.py @@ -0,0 +1,28 @@ +s = """Gur Mra bs Clguba, ol Gvz Crgref + +Ornhgvshy vf orggre guna htyl. +Rkcyvpvg vf orggre guna vzcyvpvg. +Fvzcyr vf orggre guna pbzcyrk. +Pbzcyrk vf orggre guna pbzcyvpngrq. +Syng vf orggre guna arfgrq. +Fcnefr vf orggre guna qrafr. +Ernqnovyvgl pbhagf. +Fcrpvny pnfrf nera'g fcrpvny rabhtu gb oernx gur ehyrf. +Nygubhtu cenpgvpnyvgl orngf chevgl. +Reebef fubhyq arire cnff fvyragyl. +Hayrff rkcyvpvgyl fvyraprq. +Va gur snpr bs nzovthvgl, ershfr gur grzcgngvba gb thrff. +Gurer fubhyq or bar-- naq cersrenoyl bayl bar --boivbhf jnl gb qb vg. +Nygubhtu gung jnl znl abg or boivbhf ng svefg hayrff lbh'er Qhgpu. +Abj vf orggre guna arire. +Nygubhtu arire vf bsgra orggre guna *evtug* abj. +Vs gur vzcyrzragngvba vf uneq gb rkcynva, vg'f n onq vqrn. +Vs gur vzcyrzragngvba vf rnfl gb rkcynva, vg znl or n tbbq vqrn. +Anzrfcnprf ner bar ubaxvat terng vqrn -- yrg'f qb zber bs gubfr!""" + +d = {} +for c in (65, 97): + for i in range(26): + d[chr(i+c)] = chr((i+13) % 26 + c) + +print("".join([d.get(c, c) for c in s])) diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index 8ce352d32d..b81b8db52a 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -116,22 +116,24 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectR let path_list = if cfg!(target_arch = "wasm32") { vec![] } else { - let get_paths = |paths| match paths { - Some(paths) => env::split_paths(paths), - None => env::split_paths(""), - }; - - let rustpy_path = env::var_os("RUSTPYTHONPATH"); - let py_path = env::var_os("PYTHONPATH"); - get_paths(rustpy_path.as_ref()) - .chain(get_paths(py_path.as_ref())) - .map(|path| { - ctx.new_str( - path.into_os_string() - .into_string() - .expect("PYTHONPATH isn't valid unicode"), - ) - }) + fn get_paths(env_variable_name: &str) -> Vec { + let paths = env::var_os(env_variable_name); + match paths { + Some(paths) => env::split_paths(&paths) + .map(|path| { + path.into_os_string() + .into_string() + .expect(&format!("{} isn't valid unicode", env_variable_name)) + }) + .collect(), + None => vec![], + } + } + + get_paths("RUSTPYTHONPATH") + .into_iter() + .chain(get_paths("PYTHONPATH").into_iter()) + .map(|path| ctx.new_str(path)) .collect() }; let path = ctx.new_list(path_list); From 0f0a9369b510da97804a6a85d84ad6c707f5a33d Mon Sep 17 00:00:00 2001 From: rmliddle Date: Sun, 23 Jun 2019 11:36:59 +1000 Subject: [PATCH 814/884] Updated StringIO to use "StringIO" --- vm/src/stdlib/io.rs | 69 +++++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 51e7c6d8ef..6c5580a7b9 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -100,7 +100,7 @@ impl BufferedIO { #[derive(Debug)] struct PyStringIO { - data: RefCell>>, + buffer: RefCell, } type PyStringIORef = PyRef; @@ -115,18 +115,16 @@ impl PyStringIORef { //write string to underlying vector fn write(self, data: objstr::PyStringRef, vm: &VirtualMachine) -> PyResult { let bytes = &data.value.clone().into_bytes(); - let length = bytes.len(); - let mut cursor = self.data.borrow_mut(); - match cursor.write_all(bytes) { - Ok(_) => Ok(vm.ctx.new_int(length)), - Err(_) => Err(vm.new_type_error("Error Writing String".to_string())), + match self.buffer.borrow_mut().write(bytes.to_vec()) { + Some(value) => Ok(vm.ctx.new_int(value)), + None => Err(vm.new_type_error("Error Writing String".to_string())), } } //return the entire contents of the underlying fn getvalue(self, vm: &VirtualMachine) -> PyResult { - match String::from_utf8(self.data.borrow().clone().into_inner()) { + match String::from_utf8(self.buffer.borrow().getvalue()) { Ok(result) => Ok(vm.ctx.new_str(result)), Err(_) => Err(vm.new_value_error("Error Retrieving Value".to_string())), } @@ -135,45 +133,25 @@ impl PyStringIORef { //skip to the jth position fn seek(self, offset: PyObjectRef, vm: &VirtualMachine) -> PyResult { let position = objint::get_value(&offset).to_u64().unwrap(); - if let Err(_) = self - .data - .borrow_mut() - .seek(SeekFrom::Start(position.clone())) - { - return Err(vm.new_value_error("Error Retrieving Value".to_string())); + match self.buffer.borrow_mut().seek(position) { + Some(value) => Ok(vm.ctx.new_int(value)), + None => Err(vm.new_value_error("Error Performing Operation".to_string())), } - - Ok(vm.ctx.new_int(position)) } //Read k bytes from the object and return. //If k is undefined || k == -1, then we read all bytes until the end of the file. //This also increments the stream position by the value of k fn read(self, bytes: OptionalArg>, vm: &VirtualMachine) -> PyResult { - let mut buffer = String::new(); - - match bytes { - OptionalArg::Present(Some(ref integer)) => { - let k = objint::get_value(integer).to_u64().unwrap(); - let mut handle = self.data.borrow().clone().take(k); - - //read bytes into string - if let Err(_) = handle.read_to_string(&mut buffer) { - return Err(vm.new_value_error("Error Retrieving Value".to_string())); - } - - //the take above consumes the struct value - //we add this back in with the takes into_inner method - self.data.replace(handle.into_inner()); - } - _ => { - if let Err(_) = self.data.borrow_mut().read_to_string(&mut buffer) { - return Err(vm.new_value_error("Error Retrieving Value".to_string())); - } - } + let data = match self.buffer.borrow_mut().read(byte_count(bytes)) { + Some(value) => value, + None => Vec::new(), }; - Ok(vm.ctx.new_str(buffer)) + match String::from_utf8(data) { + Ok(value) => Ok(vm.ctx.new_str(value)), + Err(_) => Err(vm.new_value_error("Error Retrieving Value".to_string())), + } } } @@ -188,7 +166,7 @@ fn string_io_new( }; PyStringIO { - data: RefCell::new(Cursor::new(raw_string.into_bytes())), + buffer: RefCell::new(BufferedIO::new(Cursor::new(raw_string.into_bytes()))), } .into_ref_with_type(vm, cls) } @@ -778,11 +756,22 @@ mod tests { #[test] fn test_buffered_seek() { let data = vec![1, 2, 3, 4]; - let offset: u64 = 2; + let count: u64 = 2; let mut buffered = BufferedIO { cursor: Cursor::new(data.clone()), }; - assert_eq!(buffered.seek(offset.clone()).unwrap(), offset); + assert_eq!(buffered.seek(count.clone()).unwrap(), count); + assert_eq!(buffered.read(count.clone() as i64).unwrap(), vec![3, 4]); + } + + #[test] + fn test_buffered_value() { + let data = vec![1, 2, 3, 4]; + let buffered = BufferedIO { + cursor: Cursor::new(data.clone()), + }; + + assert_eq!(buffered.getvalue(), data); } } From 9bc392ca70126a9e1f7120e2e1a9440765e69e49 Mon Sep 17 00:00:00 2001 From: rmliddle Date: Sun, 23 Jun 2019 11:59:21 +1000 Subject: [PATCH 815/884] removed duplicate c_flag function --- vm/src/stdlib/io.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 7743528cd6..783e9b96f0 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -24,16 +24,6 @@ use crate::obj::objtype::PyClassRef; use crate::pyobject::{BufferProtocol, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; -fn compute_c_flag(mode: &str) -> u16 { - match mode { - "w" => 512, - "x" => 512, - "a" => 8, - "+" => 2, - _ => 0, - } -} - fn byte_count(bytes: OptionalArg>) -> i64 { match bytes { OptionalArg::Present(Some(ref int)) => objint::get_value(int).to_i64().unwrap(), From f2922e3f2519f149bea128e222148042db7290f0 Mon Sep 17 00:00:00 2001 From: romab Date: Sun, 23 Jun 2019 11:34:12 +0500 Subject: [PATCH 816/884] add bytearray and bytes input types for ord() --- vm/src/builtins.rs | 48 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 570b1fbbf1..9eaafbf73d 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -28,6 +28,7 @@ use crate::pyobject::{ }; use crate::vm::VirtualMachine; +use crate::obj::objbyteinner::PyByteInner; #[cfg(not(target_arch = "wasm32"))] use crate::stdlib::io::io_open; @@ -530,20 +531,39 @@ fn builtin_oct(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn builtin_ord(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!(vm, args, required = [(string, Some(vm.ctx.str_type()))]); - let string = objstr::borrow_value(string); - let string_len = string.chars().count(); - if string_len != 1 { - return Err(vm.new_type_error(format!( - "ord() expected a character, but string of length {} found", - string_len - ))); - } - match string.chars().next() { - Some(character) => Ok(vm.context().new_int(character as i32)), - None => Err(vm.new_type_error( - "ord() could not guess the integer representing this character".to_string(), - )), + arg_check!(vm, args, required = [(string, None)]); + if objtype::isinstance(string, &vm.ctx.str_type()) { + let string = objstr::borrow_value(string); + let string_len = string.chars().count(); + if string_len != 1 { + return Err(vm.new_type_error(format!( + "ord() expected a character, but string of length {} found", + string_len + ))); + } + match string.chars().next() { + Some(character) => Ok(vm.context().new_int(character as i32)), + None => Err(vm.new_type_error( + "ord() could not guess the integer representing this character".to_string(), + )), + } + } else if objtype::isinstance(string, &vm.ctx.bytearray_type()) + || objtype::isinstance(string, &vm.ctx.bytes_type()) + { + let inner = PyByteInner::try_from_object(vm, string.clone()).unwrap(); + let bytes_len = inner.elements.len(); + if bytes_len != 1 { + return Err(vm.new_type_error(format!( + "ord() expected a character, but string of length {} found", + bytes_len + ))); + } + Ok(vm.context().new_int(inner.elements[0])) + } else { + Err(vm.new_type_error(format!( + "ord() expected a string, bytes or bytearray, but found {}", + string.class().name + ))) } } From a5276df980ac02473e428ecfe3f5d0755aff5f28 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 21 Jun 2019 11:34:44 +0300 Subject: [PATCH 817/884] Support relative import --- vm/src/frame.rs | 7 +++++-- vm/src/vm.rs | 9 +++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 8ea1148547..7844902dc8 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -912,7 +912,9 @@ impl Frame { .iter() .map(|symbol| vm.ctx.new_str(symbol.to_string())) .collect(); - let module = vm.import(module, &vm.ctx.new_tuple(from_list))?; + let level = module.chars().take_while(|char| *char == '.').count(); + let module_name = &module[level..]; + let module = vm.import(module_name, &vm.ctx.new_tuple(from_list), level)?; if symbols.is_empty() { self.push_value(module); @@ -928,7 +930,8 @@ impl Frame { } fn import_star(&self, vm: &VirtualMachine, module: &str) -> FrameResult { - let module = vm.import(module, &vm.ctx.new_tuple(vec![]))?; + let level = module.chars().take_while(|char| *char == '.').count(); + let module = vm.import(module, &vm.ctx.new_tuple(vec![]), level)?; // Grab all the names from the module and put them in the context if let Some(dict) = &module.dict { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 1857307588..3691c2c87c 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -136,7 +136,7 @@ impl VirtualMachine { pub fn try_class(&self, module: &str, class: &str) -> PyResult { let class = self - .get_attribute(self.import(module, &self.ctx.new_tuple(vec![]))?, class)? + .get_attribute(self.import(module, &self.ctx.new_tuple(vec![]), 0)?, class)? .downcast() .expect("not a class"); Ok(class) @@ -144,7 +144,7 @@ impl VirtualMachine { pub fn class(&self, module: &str, class: &str) -> PyClassRef { let module = self - .import(module, &self.ctx.new_tuple(vec![])) + .import(module, &self.ctx.new_tuple(vec![]), 0) .unwrap_or_else(|_| panic!("unable to import {}", module)); let class = self .get_attribute(module.clone(), class) @@ -302,7 +302,7 @@ impl VirtualMachine { TryFromObject::try_from_object(self, repr) } - pub fn import(&self, module: &str, from_list: &PyObjectRef) -> PyResult { + pub fn import(&self, module: &str, from_list: &PyObjectRef, level: usize) -> PyResult { let sys_modules = self .get_attribute(self.sys_module.clone(), "modules") .unwrap(); @@ -314,9 +314,10 @@ impl VirtualMachine { func, vec![ self.ctx.new_str(module.to_string()), - self.get_none(), + self.get_locals().clone().as_object().clone(), self.get_none(), from_list.clone(), + self.ctx.new_int(level), ], ), Err(_) => Err(self.new_exception( From ae5259670c665b40b805ca53e8886af5423c2da8 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 21 Jun 2019 11:34:55 +0300 Subject: [PATCH 818/884] Test relative import --- tests/snippets/dir_module/__init__.py | 1 + tests/snippets/dir_module/relative.py | 1 + tests/snippets/import_module.py | 2 ++ 3 files changed, 4 insertions(+) create mode 100644 tests/snippets/dir_module/__init__.py create mode 100644 tests/snippets/dir_module/relative.py create mode 100644 tests/snippets/import_module.py diff --git a/tests/snippets/dir_module/__init__.py b/tests/snippets/dir_module/__init__.py new file mode 100644 index 0000000000..8783d6d154 --- /dev/null +++ b/tests/snippets/dir_module/__init__.py @@ -0,0 +1 @@ +from .relative import value \ No newline at end of file diff --git a/tests/snippets/dir_module/relative.py b/tests/snippets/dir_module/relative.py new file mode 100644 index 0000000000..78ed77f40c --- /dev/null +++ b/tests/snippets/dir_module/relative.py @@ -0,0 +1 @@ +value = 5 diff --git a/tests/snippets/import_module.py b/tests/snippets/import_module.py new file mode 100644 index 0000000000..5570fe0d1a --- /dev/null +++ b/tests/snippets/import_module.py @@ -0,0 +1,2 @@ +import dir_module +assert dir_module.value == 5 From e8c0644a0489432e3aa2f978238522aa29c16b5b Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 21 Jun 2019 11:40:32 +0300 Subject: [PATCH 819/884] Test relative import from upper level --- tests/snippets/dir_module/__init__.py | 3 ++- tests/snippets/dir_module/dir_module_inner/__init__.py | 3 +++ tests/snippets/import_module.py | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/snippets/dir_module/dir_module_inner/__init__.py diff --git a/tests/snippets/dir_module/__init__.py b/tests/snippets/dir_module/__init__.py index 8783d6d154..5d9faff33d 100644 --- a/tests/snippets/dir_module/__init__.py +++ b/tests/snippets/dir_module/__init__.py @@ -1 +1,2 @@ -from .relative import value \ No newline at end of file +from .relative import value +from .dir_module_inner import value2 diff --git a/tests/snippets/dir_module/dir_module_inner/__init__.py b/tests/snippets/dir_module/dir_module_inner/__init__.py new file mode 100644 index 0000000000..20e95590f1 --- /dev/null +++ b/tests/snippets/dir_module/dir_module_inner/__init__.py @@ -0,0 +1,3 @@ +from ..relative import value + +value2 = value + 2 diff --git a/tests/snippets/import_module.py b/tests/snippets/import_module.py index 5570fe0d1a..b82aad5091 100644 --- a/tests/snippets/import_module.py +++ b/tests/snippets/import_module.py @@ -1,2 +1,3 @@ import dir_module assert dir_module.value == 5 +assert dir_module.value2 == 7 From 7effca35332dd2d2bf87e3084ce44e0459ad321a Mon Sep 17 00:00:00 2001 From: romab Date: Sun, 23 Jun 2019 23:24:01 +0500 Subject: [PATCH 820/884] added tests for ord() in tests/snippets --- tests/snippets/builtin_ord.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/snippets/builtin_ord.py b/tests/snippets/builtin_ord.py index 2549f28e7d..c9bc40f5f3 100644 --- a/tests/snippets/builtin_ord.py +++ b/tests/snippets/builtin_ord.py @@ -3,7 +3,11 @@ assert ord("a") == 97 assert ord("é") == 233 assert ord("🤡") == 129313 +assert ord(b'a') == 97 +assert ord(bytearray(b'a')) == 97 assert_raises(TypeError, lambda: ord(), "ord() is called with no argument") assert_raises(TypeError, lambda: ord(""), "ord() is called with an empty string") assert_raises(TypeError, lambda: ord("ab"), "ord() is called with more than one character") +assert_raises(TypeError, lambda: ord(b"ab"), "ord() expected a character, but string of length 2 found") +assert_raises(TypeError, lambda: ord(1), "ord() expected a string, bytes or bytearray, but found int") From cd1d7b1a43950d7449e1d4a622930d39908ebe60 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Mon, 24 Jun 2019 17:29:03 +0300 Subject: [PATCH 821/884] add args attribute to exceptions, make __str__ and __repr__ compatible with CPython --- tests/snippets/exceptions.py | 25 +++++++++++ vm/src/exceptions.rs | 87 +++++++++++++++++++++++++++--------- vm/src/vm.rs | 2 +- 3 files changed, 91 insertions(+), 23 deletions(-) create mode 100644 tests/snippets/exceptions.py diff --git a/tests/snippets/exceptions.py b/tests/snippets/exceptions.py new file mode 100644 index 0000000000..4717804981 --- /dev/null +++ b/tests/snippets/exceptions.py @@ -0,0 +1,25 @@ +empty_exc = KeyError() +assert str(empty_exc) == '' +assert repr(empty_exc) == 'KeyError()' +assert len(empty_exc.args) == 0 +assert type(empty_exc.args) == tuple + +exc = KeyError('message') +assert str(exc) == "'message'" +assert repr(exc) == "KeyError('message')" + +exc = KeyError('message', 'another message') +assert str(exc) == "('message', 'another message')" +assert repr(exc) == "KeyError('message', 'another message')" +assert exc.args[0] == 'message' +assert exc.args[1] == 'another message' + +class A: + def __repr__(self): + return 'repr' + def __str__(self): + return 'str' + +exc = KeyError(A()) +assert str(exc) == 'repr' +assert repr(exc) == 'KeyError(repr)' diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 0c243d0a15..945261a885 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -1,5 +1,6 @@ use crate::function::PyFuncArgs; use crate::obj::objsequence; +use crate::obj::objtuple::{PyTuple, PyTupleRef}; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{create_type, IdProtocol, PyContext, PyObjectRef, PyResult, TypeProtocol}; @@ -8,16 +9,12 @@ use std::fs::File; use std::io::{BufRead, BufReader}; fn exception_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - let zelf = args.args[0].clone(); - let msg = if args.args.len() > 1 { - args.args[1].clone() - } else { - let empty_string = String::default(); - vm.new_str(empty_string) - }; + let exc_self = args.args[0].clone(); + let exc_args = vm.ctx.new_tuple(args.args[1..].to_vec()); + vm.set_attr(&exc_self, "args", exc_args)?; + let traceback = vm.ctx.new_list(Vec::new()); - vm.set_attr(&zelf, "msg", msg)?; - vm.set_attr(&zelf, "__traceback__", traceback)?; + vm.set_attr(&exc_self, "__traceback__", traceback)?; Ok(vm.get_none()) } @@ -119,25 +116,70 @@ pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) { } } +fn exception_args_as_string(vm: &VirtualMachine, varargs: PyTupleRef) -> Vec { + match varargs.elements.len() { + 0 => vec![], + 1 => { + let args0_repr = match vm.to_repr(&varargs.elements[0]) { + Ok(args0_repr) => args0_repr.value.clone(), + Err(_) => "".to_string(), + }; + vec![args0_repr] + } + _ => { + let mut args_vec = Vec::with_capacity(varargs.elements.len()); + for vararg in &varargs.elements { + let arg_repr = match vm.to_repr(vararg) { + Ok(arg_repr) => arg_repr.value.clone(), + Err(_) => "".to_string(), + }; + args_vec.push(arg_repr); + } + args_vec + } + } +} + fn exception_str(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))] ); - let msg = if let Ok(m) = vm.get_attribute(exc.clone(), "msg") { - match vm.to_pystr(&m) { - Ok(msg) => msg, - _ => "".to_string(), - } - } else { - panic!("Error message must be set"); + let args = vm + .get_attribute(exc.clone(), "args") + .unwrap() + .downcast::() + .expect("'args' must be a tuple"); + let args_str = exception_args_as_string(vm, args); + let joined_str = match args_str.len() { + 0 => "".to_string(), + 1 => args_str[0].to_string(), + _ => format!("({})", args_str.join(", ")), }; - let mut exc_repr = exc.class().name.clone(); - if !msg.is_empty() { - &exc_repr.push_str(&format!(": {}", msg)); - } - Ok(vm.new_str(exc_repr)) + Ok(vm.new_str(joined_str)) +} + +fn exception_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + arg_check!( + vm, + args, + required = [(exc, Some(vm.ctx.exceptions.exception_type.clone()))] + ); + let args = vm + .get_attribute(exc.clone(), "args") + .unwrap() + .downcast::() + .expect("'args' must be a tuple"); + let args_repr = exception_args_as_string(vm, args); + + let exc_repr = exc.class().name.clone(); + let joined_str = match args_repr.len() { + 0 => format!("{}()", exc_repr), + 1 => format!("{}({})", exc_repr, args_repr[0].to_string()), + _ => format!("{}({})", exc_repr, args_repr.join(", ")), + }; + Ok(vm.new_str(joined_str)) } #[derive(Debug)] @@ -266,6 +308,7 @@ pub fn init(context: &PyContext) { let exception_type = &context.exceptions.exception_type; extend_class!(context, exception_type, { - "__str__" => context.new_rustfunc(exception_str) + "__str__" => context.new_rustfunc(exception_str), + "__repr__" => context.new_rustfunc(exception_repr), }); } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 1857307588..3aaa23dc10 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -173,7 +173,7 @@ impl VirtualMachine { self.invoke(exc_type.into_object(), args) } - /// Create Python instance of `exc_type` with message + /// Create Python instance of `exc_type` with message as first element of `args` tuple pub fn new_exception(&self, exc_type: PyClassRef, msg: String) -> PyObjectRef { // TODO: exc_type may be user-defined exception, so we should return PyResult // TODO: maybe there is a clearer way to create an instance: From ff1049a1ebd3140ae09262ada337e7d44ae69e81 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Mon, 24 Jun 2019 18:09:27 +0300 Subject: [PATCH 822/884] fix regression in the exception representation in the traceback --- vm/src/exceptions.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 945261a885..cebc9638b6 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -110,9 +110,18 @@ pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) { println!("No traceback set on exception"); } - match vm.to_str(exc) { - Ok(txt) => println!("{}", txt.value), - Err(err) => println!("Error during error {:?}", err), + let varargs = vm + .get_attribute(exc.clone(), "args") + .unwrap() + .downcast::() + .expect("'args' must be a tuple"); + let args_repr = exception_args_as_string(vm, varargs); + + let exc_repr = exc.class().name.clone(); + match args_repr.len() { + 0 => println!("{}", exc_repr), + 1 => println!("{}: {}", exc_repr, args_repr[0]), + _ => println!("{}: ({})", exc_repr, args_repr.join(", ")), } } From 82101d9f1ab85bfc803baad556f9beb6bbc4bdc0 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Mon, 24 Jun 2019 18:10:27 +0300 Subject: [PATCH 823/884] rename exc_repr -> exc_name to better describe what it refers to --- vm/src/exceptions.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index cebc9638b6..067dbccfa6 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -117,11 +117,11 @@ pub fn print_exception_inner(vm: &VirtualMachine, exc: &PyObjectRef) { .expect("'args' must be a tuple"); let args_repr = exception_args_as_string(vm, varargs); - let exc_repr = exc.class().name.clone(); + let exc_name = exc.class().name.clone(); match args_repr.len() { - 0 => println!("{}", exc_repr), - 1 => println!("{}: {}", exc_repr, args_repr[0]), - _ => println!("{}: ({})", exc_repr, args_repr.join(", ")), + 0 => println!("{}", exc_name), + 1 => println!("{}: {}", exc_name, args_repr[0]), + _ => println!("{}: ({})", exc_name, args_repr.join(", ")), } } @@ -182,11 +182,11 @@ fn exception_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { .expect("'args' must be a tuple"); let args_repr = exception_args_as_string(vm, args); - let exc_repr = exc.class().name.clone(); + let exc_name = exc.class().name.clone(); let joined_str = match args_repr.len() { - 0 => format!("{}()", exc_repr), - 1 => format!("{}({})", exc_repr, args_repr[0].to_string()), - _ => format!("{}({})", exc_repr, args_repr.join(", ")), + 0 => format!("{}()", exc_name), + 1 => format!("{}({})", exc_name, args_repr[0].to_string()), + _ => format!("{}({})", exc_name, args_repr.join(", ")), }; Ok(vm.new_str(joined_str)) } From 060cd75783d49d78b2526b70fa4ed914f2c353d8 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Mon, 24 Jun 2019 18:13:18 +0300 Subject: [PATCH 824/884] simplify exception_repr a bit --- vm/src/exceptions.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 067dbccfa6..e5c6950f72 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -185,7 +185,6 @@ fn exception_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let exc_name = exc.class().name.clone(); let joined_str = match args_repr.len() { 0 => format!("{}()", exc_name), - 1 => format!("{}({})", exc_name, args_repr[0].to_string()), _ => format!("{}({})", exc_name, args_repr.join(", ")), }; Ok(vm.new_str(joined_str)) From 1e53e6c168288b4459f8a6fe0f80c2f70389a880 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Mon, 24 Jun 2019 22:19:01 +0300 Subject: [PATCH 825/884] specify python 3.6 specifically as a Pipfile venv target --- tests/Pipfile | 2 +- tests/Pipfile.lock | 99 +++++++++++++++++++++++++++++----------------- 2 files changed, 63 insertions(+), 38 deletions(-) diff --git a/tests/Pipfile b/tests/Pipfile index 8ccbddc4da..4bbae44ef9 100644 --- a/tests/Pipfile +++ b/tests/Pipfile @@ -10,4 +10,4 @@ pytest = "*" [dev-packages] [requires] -python_version = "3" +python_version = "3.6" diff --git a/tests/Pipfile.lock b/tests/Pipfile.lock index 78d20f6053..02b33bbdba 100644 --- a/tests/Pipfile.lock +++ b/tests/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "b2d2d68e7d4330ff8d889816c56b9cee4bf54962c86b2c11382108176a201ec8" + "sha256": "ce98de5914393363a8cb86a4753b3964caa53a4659a403a3ef357e2086363ef7" }, "pipfile-spec": 6, "requires": { - "python_version": "3" + "python_version": "3.6" }, "sources": [ { @@ -16,74 +16,99 @@ ] }, "default": { - "aenum": { - "hashes": [ - "sha256:3df9b84cce5dc9ed77c337079f97b66c44c0053eb87d6f4d46b888dc45801e38", - "sha256:7a77c205c4bc9d7fe9bd73b3193002d724aebf5909fa0d297534208953891ec8", - "sha256:a3208e4b28db3a7b232ff69b934aef2ea1bf27286d9978e1e597d46f490e4687" - ], - "version": "==2.1.2" - }, "atomicwrites": { "hashes": [ - "sha256:240831ea22da9ab882b551b31d4225591e5e447a68c5e188db5b89ca1d487585", - "sha256:a24da68318b08ac9c9c45029f4a10371ab5b20e4226738e150e6e7c571630ae6" + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" ], - "version": "==1.1.5" + "version": "==1.3.0" }, "attrs": { "hashes": [ - "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265", - "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b" + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" ], - "version": "==18.1.0" + "version": "==19.1.0" }, "bytecode": { "hashes": [ - "sha256:cc6931151c7f0a542f8cf7619fe1639af3b9529c4678860fa3239397cb0f7de0", - "sha256:e464004d4a9eeeca987cb4950dba11b827964b6c90cd331c1f20abd2dab3c962" + "sha256:68b1d591c7af0e5c5273e028d3cc0299fbe374dff0cf9149ec7e569be0c573e7", + "sha256:c43d5052cbff076bfdf5b0b93ff6c76e461aab628ce47d30637bb200b6b7bb2c" ], "index": "pypi", - "version": "==0.7.0" + "version": "==0.8.0" + }, + "importlib-metadata": { + "hashes": [ + "sha256:6dfd58dfe281e8d240937776065dd3624ad5469c835248219bd16cf2e12dbeb7", + "sha256:cb6ee23b46173539939964df59d3d72c3e0c1b5d54b84f1d8a7e912fe43612db" + ], + "version": "==0.18" }, "more-itertools": { "hashes": [ - "sha256:c187a73da93e7a8acc0001572aebc7e3c69daf7bf6881a2cea10650bd4420092", - "sha256:c476b5d3a34e12d40130bc2f935028b5f636df8f372dc2c1c01dc19681b2039e", - "sha256:fcbfeaea0be121980e15bc97b3817b5202ca73d0eae185b4550cbfce2a3ebb3d" + "sha256:2112d2ca570bb7c3e53ea1a35cd5df42bb0fd10c45f0fb97178679c3c03d64c7", + "sha256:c3e4748ba1aad8dba30a4886b0b1a2004f9a863837b8654e7059eebf727afa5a" + ], + "markers": "python_version > '2.7'", + "version": "==7.0.0" + }, + "packaging": { + "hashes": [ + "sha256:0c98a5d0be38ed775798ece1b9727178c4469d9c3b4ada66e8e6b7849f8732af", + "sha256:9e1cbf8c12b1f1ce0bb5344b8d7ecf66a6f8a6e91bcb0c84593ed6d3ab5c4ab3" ], - "version": "==4.3.0" + "version": "==19.0" }, "pluggy": { "hashes": [ - "sha256:6e3836e39f4d36ae72840833db137f7b7d35105079aee6ec4a62d9f80d594dd1", - "sha256:95eb8364a4708392bae89035f45341871286a333f749c3141c20573d2b3876e1" + "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", + "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" ], - "markers": "python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*'", - "version": "==0.7.1" + "version": "==0.12.0" }, "py": { "hashes": [ - "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7", - "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e" + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" ], - "markers": "python_version != '3.3.*' and python_version != '3.0.*' and python_version >= '2.7' and python_version != '3.1.*' and python_version != '3.2.*'", - "version": "==1.5.4" + "version": "==1.8.0" + }, + "pyparsing": { + "hashes": [ + "sha256:1873c03321fc118f4e9746baf201ff990ceb915f433f23b395f5580d1840cb2a", + "sha256:9b6323ef4ab914af344ba97510e966d64ba91055d6b9afa6b30799340e89cc03" + ], + "version": "==2.4.0" }, "pytest": { "hashes": [ - "sha256:86a8dbf407e437351cef4dba46736e9c5a6e3c3ac71b2e942209748e76ff2086", - "sha256:e74466e97ac14582a8188ff4c53e6cc3810315f342f6096899332ae864c1d432" + "sha256:4a784f1d4f2ef198fe9b7aef793e9fa1a3b2f84e822d9b3a64a181293a572d45", + "sha256:926855726d8ae8371803f7b2e6ec0a69953d9c6311fa7c3b6c1b929ff92d27da" ], "index": "pypi", - "version": "==3.7.1" + "version": "==4.6.3" }, "six": { "hashes": [ - "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9", - "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb" + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" + }, + "zipp": { + "hashes": [ + "sha256:8c1019c6aad13642199fbe458275ad6a84907634cc9f0989877ccc4a2840139d", + "sha256:ca943a7e809cc12257001ccfb99e3563da9af99d52f261725e96dfe0f9275bc3" ], - "version": "==1.11.0" + "version": "==0.5.1" } }, "develop": {} From 8852abc6c3ddb59daacad753020ddb06d122d355 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Mon, 24 Jun 2019 22:19:59 +0300 Subject: [PATCH 826/884] make exception __repr__ compatible with python3.6 --- tests/snippets/exceptions.py | 4 ++-- vm/src/exceptions.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/snippets/exceptions.py b/tests/snippets/exceptions.py index 4717804981..1349f46296 100644 --- a/tests/snippets/exceptions.py +++ b/tests/snippets/exceptions.py @@ -6,7 +6,7 @@ exc = KeyError('message') assert str(exc) == "'message'" -assert repr(exc) == "KeyError('message')" +assert repr(exc) == "KeyError('message',)" exc = KeyError('message', 'another message') assert str(exc) == "('message', 'another message')" @@ -22,4 +22,4 @@ def __str__(self): exc = KeyError(A()) assert str(exc) == 'repr' -assert repr(exc) == 'KeyError(repr)' +assert repr(exc) == 'KeyError(repr,)' diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index e5c6950f72..0a07926413 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -185,6 +185,7 @@ fn exception_repr(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let exc_name = exc.class().name.clone(); let joined_str = match args_repr.len() { 0 => format!("{}()", exc_name), + 1 => format!("{}({},)", exc_name, args_repr[0]), _ => format!("{}({})", exc_name, args_repr.join(", ")), }; Ok(vm.new_str(joined_str)) From da4c0bccfad8484f871c34bfa863ee86ada4ba04 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 25 Jun 2019 18:14:29 +0300 Subject: [PATCH 827/884] pass local variables to __import__ only if there is a frame --- vm/src/vm.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 3691c2c87c..9bcb77128c 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -314,7 +314,11 @@ impl VirtualMachine { func, vec![ self.ctx.new_str(module.to_string()), - self.get_locals().clone().as_object().clone(), + if self.current_frame().is_some() { + self.get_locals().into_object() + } else { + self.get_none() + }, self.get_none(), from_list.clone(), self.ctx.new_int(level), From 725cfcc58d964effd88292e242cbb16600cab0d0 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 25 Jun 2019 18:48:30 +0300 Subject: [PATCH 828/884] compute_c_flag ignore chars after first --- vm/src/stdlib/io.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 783e9b96f0..c9d1c1a803 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -284,16 +284,19 @@ fn buffered_reader_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn compute_c_flag(mode: &str) -> u32 { - let flags = match mode { - "w" => os::FileCreationFlags::O_WRONLY | os::FileCreationFlags::O_CREAT, - "x" => { - os::FileCreationFlags::O_WRONLY - | os::FileCreationFlags::O_CREAT - | os::FileCreationFlags::O_EXCL - } - "a" => os::FileCreationFlags::O_APPEND, - "+" => os::FileCreationFlags::O_RDWR, - _ => os::FileCreationFlags::O_RDONLY, + let flags = match mode.chars().next() { + Some(mode) => match mode { + 'w' => os::FileCreationFlags::O_WRONLY | os::FileCreationFlags::O_CREAT, + 'x' => { + os::FileCreationFlags::O_WRONLY + | os::FileCreationFlags::O_CREAT + | os::FileCreationFlags::O_EXCL + } + 'a' => os::FileCreationFlags::O_APPEND, + '+' => os::FileCreationFlags::O_RDWR, + _ => os::FileCreationFlags::O_RDONLY, + }, + None => os::FileCreationFlags::O_RDONLY, }; flags.bits() } From dc7eb24aab65255e3976280d735524b90ef6f84a Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 25 Jun 2019 19:13:27 +0300 Subject: [PATCH 829/884] Support fileno as name in FileIO --- tests/snippets/stdlib_io.py | 7 +++++ vm/src/stdlib/io.rs | 59 +++++++++++++++++-------------------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/tests/snippets/stdlib_io.py b/tests/snippets/stdlib_io.py index e6da760d24..08786c2198 100644 --- a/tests/snippets/stdlib_io.py +++ b/tests/snippets/stdlib_io.py @@ -1,4 +1,5 @@ from io import BufferedReader, FileIO +import os fi = FileIO('README.md') bb = BufferedReader(fi) @@ -14,3 +15,9 @@ assert len(result) <= 8*1024 assert len(result) >= 0 assert isinstance(result, bytes) + +fd = os.open('README.md', os.O_RDONLY) + +with FileIO(fd) as fio: + res2 = fio.read() + assert res == res2 diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index c9d1c1a803..88593cd04c 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -2,9 +2,7 @@ * I/O core tools. */ use std::cell::RefCell; -use std::fs::File; use std::io::prelude::*; -use std::io::BufReader; use std::io::Cursor; use std::io::SeekFrom; @@ -20,6 +18,7 @@ use crate::obj::objbytearray::PyByteArray; use crate::obj::objbytes; use crate::obj::objint; use crate::obj::objstr; +use crate::obj::objtype; use crate::obj::objtype::PyClassRef; use crate::pyobject::{BufferProtocol, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; @@ -305,47 +304,43 @@ fn file_io_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, args, - required = [(file_io, None), (name, Some(vm.ctx.str_type()))], + required = [(file_io, None), (name, None)], optional = [(mode, Some(vm.ctx.str_type()))] ); - let rust_mode = mode.map_or("r".to_string(), objstr::get_value); - - match compute_c_flag(&rust_mode).to_bigint() { - Some(os_mode) => { - let args = vec![name.clone(), vm.ctx.new_int(os_mode)]; - let file_no = os::os_open(vm, PyFuncArgs::new(args, vec![]))?; - - vm.set_attr(file_io, "name", name.clone())?; - vm.set_attr(file_io, "fileno", file_no)?; - vm.set_attr(file_io, "closefd", vm.new_bool(false))?; - vm.set_attr(file_io, "closed", vm.new_bool(false))?; + let file_no = if objtype::isinstance(&name, &vm.ctx.str_type()) { + let rust_mode = mode.map_or("r".to_string(), objstr::get_value); + let args = vec![ + name.clone(), + vm.ctx + .new_int(compute_c_flag(&rust_mode).to_bigint().unwrap()), + ]; + os::os_open(vm, PyFuncArgs::new(args, vec![]))? + } else if objtype::isinstance(&name, &vm.ctx.int_type()) { + name.clone() + } else { + return Err(vm.new_type_error("name parameter must be string or int".to_string())); + }; - Ok(vm.get_none()) - } - None => Err(vm.new_type_error(format!("invalid mode {}", rust_mode))), - } + vm.set_attr(file_io, "name", name.clone())?; + vm.set_attr(file_io, "fileno", file_no)?; + vm.set_attr(file_io, "closefd", vm.new_bool(false))?; + vm.set_attr(file_io, "closed", vm.new_bool(false))?; + Ok(vm.get_none()) } fn file_io_read(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!(vm, args, required = [(file_io, None)]); - let py_name = vm.get_attribute(file_io.clone(), "name")?; - let f = match File::open(objstr::get_value(&py_name)) { - Ok(v) => Ok(v), - Err(_) => Err(vm.new_type_error("Error opening file".to_string())), - }; - let buffer = match f { - Ok(v) => Ok(BufReader::new(v)), - Err(_) => Err(vm.new_type_error("Error reading from file".to_string())), - }; + let file_no = vm.get_attribute(file_io.clone(), "fileno")?; + let raw_fd = objint::get_value(&file_no).to_i64().unwrap(); + + let mut handle = os::rust_file(raw_fd); let mut bytes = vec![]; - if let Ok(mut buff) = buffer { - match buff.read_to_end(&mut bytes) { - Ok(_) => {} - Err(_) => return Err(vm.new_value_error("Error reading from Buffer".to_string())), - } + match handle.read_to_end(&mut bytes) { + Ok(_) => {} + Err(_) => return Err(vm.new_value_error("Error reading from Buffer".to_string())), } Ok(vm.ctx.new_bytes(bytes)) From 4db83aca7f1e1fbc8b6f69cb230d4a8b1c0dfd7b Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 26 Jun 2019 13:41:58 +0200 Subject: [PATCH 830/884] Add initial unicodedata module. --- Cargo.lock | 269 +++++++++++++++++++++++++++++++++++ tests/snippets/unicode_fu.py | 8 ++ vm/Cargo.toml | 2 + vm/src/stdlib/mod.rs | 2 + vm/src/stdlib/unicodedata.rs | 94 ++++++++++++ 5 files changed, 375 insertions(+) create mode 100644 vm/src/stdlib/unicodedata.rs diff --git a/Cargo.lock b/Cargo.lock index 35435cabd2..5c07af7648 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -505,6 +505,11 @@ name = "maplit" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memchr" version = "2.2.0" @@ -990,10 +995,12 @@ dependencies = [ "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb)", "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode_names2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1272,6 +1279,45 @@ name = "ucd-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unic" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-bidi 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-common 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-emoji 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-idna 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-normal 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-segment 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-bidi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-bidi 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-char" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-basics 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-char-basics" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unic-char-property" version = "0.9.0" @@ -1290,6 +1336,14 @@ name = "unic-common" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unic-emoji" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-emoji-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unic-emoji-char" version = "0.9.0" @@ -1300,6 +1354,191 @@ dependencies = [ "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unic-idna" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-idna-mapping 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-idna-punycode 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-normal 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-bidi 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-normal 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-idna-mapping" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-idna-punycode" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unic-normal" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-ucd-normal 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-ucd-segment 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-ucd-age 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-bidi 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-block 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-case 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-category 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-common 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-hangul 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-ident 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-name 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-name_aliases 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-normal 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-segment 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-age" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-bidi" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-block" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-case" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-category" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-hangul" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-name" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-hangul 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-name_aliases" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-normal" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-category 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-hangul 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unic-ucd-version" version = "0.9.0" @@ -1346,6 +1585,11 @@ name = "unicode_categories" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "unicode_names2" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unreachable" version = "1.0.0" @@ -1594,6 +1838,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" @@ -1674,10 +1919,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" "checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" +"checksum unic 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e31748f3e294dc6a9243a44686e8155a162af9a11cd56e07c0ebbc530b2a8a87" +"checksum unic-bidi 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1356b759fb6a82050666f11dce4b6fe3571781f1449f3ef78074e408d468ec09" +"checksum unic-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "af25df79bd134107f088ba725d9c470600f16263205d0be36c75e75b020bac0a" +"checksum unic-char-basics 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "20e5d239bc6394309225a0c1b13e1d059565ff2cfef1a437aff4a5871fa06c4b" "checksum unic-char-property 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" "checksum unic-char-range 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" "checksum unic-common 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" +"checksum unic-emoji 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "74193f32f7966ad20b819e70e29c6f1ac8c386692a9d5e90078eef80ea008bfb" "checksum unic-emoji-char 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0b07221e68897210270a38bde4babb655869637af0f69407f96053a34f76494d" +"checksum unic-idna 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "621e9cf526f2094d2c2ced579766458a92f8f422d6bb934c503ba1a95823a62d" +"checksum unic-idna-mapping 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4de70fd4e5331537347a50a0dbc938efb1f127c9f6e5efec980fc90585aa1343" +"checksum unic-idna-punycode 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "06feaedcbf9f1fc259144d833c0d630b8b15207b0486ab817d29258bc89f2f8a" +"checksum unic-normal 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f09d64d33589a94628bc2aeb037f35c2e25f3f049c7348b5aa5580b48e6bba62" +"checksum unic-segment 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +"checksum unic-ucd 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "625b18f7601e1127504a20ae731dc3c7826d0e86d5f7fe3434f8137669240efd" +"checksum unic-ucd-age 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6c8cfdfe71af46b871dc6af2c24fcd360e2f3392ee4c5111877f2947f311671c" +"checksum unic-ucd-bidi 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d1d568b51222484e1f8209ce48caa6b430bf352962b877d592c29ab31fb53d8c" +"checksum unic-ucd-block 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6b2a16f2d7ecd25325a1053ca5a66e7fa1b68911a65c5e97f8d2e1b236b6f1d7" +"checksum unic-ucd-case 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d98d6246a79bac6cf66beee01422bda7c882e11d837fa4969bfaaba5fdea6d3" +"checksum unic-ucd-category 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b8d4591f5fcfe1bd4453baaf803c40e1b1e69ff8455c47620440b46efef91c0" +"checksum unic-ucd-common 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e9b78b910beafa1aae5c59bf00877c6cece1c5db28a1241ad801e86cecdff4ad" +"checksum unic-ucd-hangul 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eb1dc690e19010e1523edb9713224cba5ef55b54894fe33424439ec9a40c0054" +"checksum unic-ucd-ident 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +"checksum unic-ucd-name 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9c8fc55a45b2531089dc1773bf60c1f104b38e434b774ffc37b9c29a9b0f492e" +"checksum unic-ucd-name_aliases 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6b7674212643087699ba247a63dd05f1204c7e4880ec9342e545a7cffcc6a46f" +"checksum unic-ucd-normal 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "86aed873b8202d22b13859dda5fe7c001d271412c31d411fd9b827e030569410" +"checksum unic-ucd-segment 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" "checksum unic-ucd-version 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" "checksum unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb)" = "" "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" @@ -1686,6 +1954,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +"checksum unicode_names2 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0dc6c5da0c8d7200f9488cc346bd30ba62bcd9f79ef937ea6573132e3d507df9" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde" "checksum utf8parse 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8772a4ccbb4e89959023bc5b7cb8623a795caa7092d99f3aa9501b9484d4557d" diff --git a/tests/snippets/unicode_fu.py b/tests/snippets/unicode_fu.py index 96d5bf9770..bae97a3720 100644 --- a/tests/snippets/unicode_fu.py +++ b/tests/snippets/unicode_fu.py @@ -11,3 +11,11 @@ c = ᚴ*3 assert c == '👋👋👋' + +import unicodedata +assert unicodedata.category('a') == 'Ll' +assert unicodedata.category('A') == 'Lu' +assert unicodedata.name('a') == 'LATIN SMALL LETTER A' +assert unicodedata.lookup('LATIN SMALL LETTER A') == 'a' +assert unicodedata.bidirectional('a') == 'L' +assert unicodedata.normalize('NFC', 'bla') == 'bla' diff --git a/vm/Cargo.toml b/vm/Cargo.toml index d2dc30e42c..8a0b72939b 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -32,6 +32,8 @@ indexmap = "1.0.2" crc = "^1.0.0" bincode = "1.1.4" unicode_categories = "0.1.1" +unicode_names2 = "0.2.2" +unic = "0.9.0" maplit = "1.0" proc-macro-hack = "0.5" bitflags = "1.1" diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 8329b3092c..90fdd11c64 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -16,6 +16,7 @@ mod string; mod thread; mod time_module; mod tokenize; +mod unicodedata; mod warnings; mod weakref; use std::collections::HashMap; @@ -54,6 +55,7 @@ pub fn get_module_inits() -> HashMap { "tokenize".to_string() => Box::new(tokenize::make_module), "_weakref".to_string() => Box::new(weakref::make_module), "_imp".to_string() => Box::new(imp::make_module), + "unicodedata".to_string() => Box::new(unicodedata::make_module), "_warnings".to_string() => Box::new(warnings::make_module), }; diff --git a/vm/src/stdlib/unicodedata.rs b/vm/src/stdlib/unicodedata.rs new file mode 100644 index 0000000000..0eeea35a54 --- /dev/null +++ b/vm/src/stdlib/unicodedata.rs @@ -0,0 +1,94 @@ +/* Access to the unicode database. + See also: https://docs.python.org/3/library/unicodedata.html +*/ + +use crate::function::OptionalArg; +use crate::obj::objstr::PyStringRef; +use crate::pyobject::{PyObjectRef, PyResult}; +use crate::vm::VirtualMachine; + +use unic::char::property::EnumeratedCharProperty; +use unic::ucd::category::GeneralCategory; +use unic::ucd::Name; +use unicode_names2; + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + let unidata_version = unic::UNICODE_VERSION.to_string(); + + py_module!(vm, "unicodedata", { + "bidirectional" => ctx.new_rustfunc(bidirectional), + "category" => ctx.new_rustfunc(category), + "name" => ctx.new_rustfunc(name), + "lookup" => ctx.new_rustfunc(lookup), + "normalize" => ctx.new_rustfunc(normalize), + "unidata_version" => ctx.new_str(unidata_version), + }) +} + +fn category(character: PyStringRef, vm: &VirtualMachine) -> PyResult { + let my_char = extract_char(character, vm)?; + let category = GeneralCategory::of(my_char); + Ok(vm.new_str(category.abbr_name().to_string())) +} + +fn lookup(name: PyStringRef, vm: &VirtualMachine) -> PyResult { + // TODO: we might want to use unic_ucd instead of unicode_names2 for this too, if possible: + if let Some(character) = unicode_names2::character(&name.value) { + Ok(vm.new_str(character.to_string())) + } else { + Err(vm.new_key_error(format!("undefined character name '{}'", name))) + } +} + +fn name( + character: PyStringRef, + default: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let my_char = extract_char(character, vm)?; + + if let Some(name) = Name::of(my_char) { + Ok(vm.new_str(name.to_string())) + } else { + match default { + OptionalArg::Present(obj) => Ok(obj), + OptionalArg::Missing => { + Err(vm.new_value_error("character name not found!".to_string())) + } + } + } +} + +fn bidirectional(character: PyStringRef, vm: &VirtualMachine) -> PyResult { + use unic::bidi::BidiClass; + let my_char = extract_char(character, vm)?; + let cls = BidiClass::of(my_char); + Ok(vm.new_str(cls.abbr_name().to_string())) +} + +fn normalize(form: PyStringRef, unistr: PyStringRef, vm: &VirtualMachine) -> PyResult { + use unic::normal::StrNormalForm; + let text = &unistr.value; + let normalized_text = match form.value.as_ref() { + "NFC" => text.nfc().collect::(), + "NFKC" => text.nfkc().collect::(), + "NFD" => text.nfd().collect::(), + "NFKD" => text.nfkd().collect::(), + _ => { + return Err(vm.new_value_error("unistr must be one of NFC, NFD".to_string())); + } + }; + + Ok(vm.new_str(normalized_text)) +} + +fn extract_char(character: PyStringRef, vm: &VirtualMachine) -> PyResult { + if character.value.len() != 1 { + return Err(vm.new_type_error("argument must be an unicode character, not str".to_string())); + } + + let my_char: char = character.value.chars().next().unwrap(); + Ok(my_char) +} From 9e194b390484b1d28c938493197077567be979d6 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Wed, 26 Jun 2019 16:38:11 +0200 Subject: [PATCH 831/884] Add endianness support to struct module. --- tests/snippets/test_struct.py | 14 ++ vm/src/stdlib/pystruct.rs | 325 ++++++++++++++++++++++++---------- 2 files changed, 250 insertions(+), 89 deletions(-) diff --git a/tests/snippets/test_struct.py b/tests/snippets/test_struct.py index c13e713a16..8f535f63cc 100644 --- a/tests/snippets/test_struct.py +++ b/tests/snippets/test_struct.py @@ -8,3 +8,17 @@ assert v1 == 14 assert v2 == 12 +data = struct.pack('IH', 14, 12) +assert data == bytes([0, 0, 0, 14, 0, 12]) + +v1, v2 = struct.unpack('>IH', data) +assert v1 == 14 +assert v2 == 12 + diff --git a/vm/src/stdlib/pystruct.rs b/vm/src/stdlib/pystruct.rs index 4a454b0c98..09f007e607 100644 --- a/vm/src/stdlib/pystruct.rs +++ b/vm/src/stdlib/pystruct.rs @@ -1,6 +1,8 @@ /* * Python struct module. * + * Docs: https://docs.python.org/3/library/struct.html + * * renamed to pystruct since struct is a rust keyword. * * Use this rust module to do byte packing: @@ -8,8 +10,9 @@ */ use std::io::{Cursor, Read, Write}; +use std::iter::Peekable; -use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{ReadBytesExt, WriteBytesExt}; use num_bigint::BigInt; use num_traits::ToPrimitive; @@ -18,29 +21,87 @@ use crate::obj::{objbool, objbytes, objfloat, objint, objstr, objtype}; use crate::pyobject::{PyObjectRef, PyResult}; use crate::VirtualMachine; +#[derive(Debug)] +struct FormatSpec { + endianness: Endianness, + codes: Vec, +} + +#[derive(Debug)] +enum Endianness { + Native, + Little, + Big, + Network, +} + #[derive(Debug)] struct FormatCode { code: char, - repeat: i32, } -fn parse_format_string(fmt: String) -> Vec { +fn parse_format_string(fmt: String) -> Result { + let mut chars = fmt.chars().peekable(); + // First determine "<", ">","!" or "=" - // TODO + let endianness = parse_endiannes(&mut chars); // Now, analyze struct string furter: + let codes = parse_format_codes(&mut chars)?; + + Ok(FormatSpec { endianness, codes }) +} + +/// Parse endianness +/// See also: https://docs.python.org/3/library/struct.html?highlight=struct#byte-order-size-and-alignment +fn parse_endiannes(chars: &mut Peekable) -> Endianness +where + I: Sized, + I: Iterator, +{ + match chars.peek() { + Some('@') => { + chars.next().unwrap(); + Endianness::Native + } + Some('=') => { + chars.next().unwrap(); + Endianness::Native + } + Some('<') => { + chars.next().unwrap(); + Endianness::Little + } + Some('>') => { + chars.next().unwrap(); + Endianness::Big + } + Some('!') => { + chars.next().unwrap(); + Endianness::Network + } + _ => Endianness::Native, + } +} + +fn parse_format_codes(chars: &mut Peekable) -> Result, String> +where + I: Sized, + I: Iterator, +{ let mut codes = vec![]; - for c in fmt.chars() { + for c in chars { match c { - 'b' | 'B' | 'h' | 'H' | 'i' | 'I' | 'q' | 'Q' | 'f' | 'd' => { - codes.push(FormatCode { code: c, repeat: 1 }) + 'b' | 'B' | 'h' | 'H' | 'i' | 'I' | 'l' | 'L' | 'q' | 'Q' | 'f' | 'd' => { + codes.push(FormatCode { code: c }) } c => { - panic!("Illegal format code {:?}", c); + return Err(format!("Illegal format code {:?}", c)); } } } - codes + + Ok(codes) } fn get_int(vm: &VirtualMachine, arg: &PyObjectRef) -> PyResult { @@ -69,60 +130,112 @@ fn pack_bool(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResu } } -fn pack_i16(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { +fn pack_i16(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> +where + Endianness: byteorder::ByteOrder, +{ let v = get_int(vm, arg)?.to_i16().unwrap(); - data.write_i16::(v).unwrap(); + data.write_i16::(v).unwrap(); Ok(()) } -fn pack_u16(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { +fn pack_u16(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> +where + Endianness: byteorder::ByteOrder, +{ let v = get_int(vm, arg)?.to_u16().unwrap(); - data.write_u16::(v).unwrap(); + data.write_u16::(v).unwrap(); Ok(()) } -fn pack_i32(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { +fn pack_i32(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> +where + Endianness: byteorder::ByteOrder, +{ let v = get_int(vm, arg)?.to_i32().unwrap(); - data.write_i32::(v).unwrap(); + data.write_i32::(v).unwrap(); Ok(()) } -fn pack_u32(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { +fn pack_u32(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> +where + Endianness: byteorder::ByteOrder, +{ let v = get_int(vm, arg)?.to_u32().unwrap(); - data.write_u32::(v).unwrap(); + data.write_u32::(v).unwrap(); Ok(()) } -fn pack_i64(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { +fn pack_i64(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> +where + Endianness: byteorder::ByteOrder, +{ let v = get_int(vm, arg)?.to_i64().unwrap(); - data.write_i64::(v).unwrap(); + data.write_i64::(v).unwrap(); Ok(()) } -fn pack_u64(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { +fn pack_u64(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> +where + Endianness: byteorder::ByteOrder, +{ let v = get_int(vm, arg)?.to_u64().unwrap(); - data.write_u64::(v).unwrap(); + data.write_u64::(v).unwrap(); + Ok(()) +} + +fn pack_f32(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> +where + Endianness: byteorder::ByteOrder, +{ + let v = get_float(vm, arg)? as f32; + data.write_f32::(v).unwrap(); + Ok(()) +} + +fn pack_f64(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> +where + Endianness: byteorder::ByteOrder, +{ + let v = get_float(vm, arg)?; + data.write_f64::(v).unwrap(); Ok(()) } -fn pack_f32(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { +fn get_float(vm: &VirtualMachine, arg: &PyObjectRef) -> PyResult { if objtype::isinstance(&arg, &vm.ctx.float_type()) { - let v = objfloat::get_value(arg) as f32; - data.write_f32::(v).unwrap(); - Ok(()) + Ok(objfloat::get_value(arg)) } else { Err(vm.new_type_error("Expected float".to_string())) } } -fn pack_f64(vm: &VirtualMachine, arg: &PyObjectRef, data: &mut Write) -> PyResult<()> { - if objtype::isinstance(&arg, &vm.ctx.float_type()) { - let v = objfloat::get_value(arg) as f64; - data.write_f64::(v).unwrap(); - Ok(()) - } else { - Err(vm.new_type_error("Expected float".to_string())) +fn pack_item( + vm: &VirtualMachine, + code: &FormatCode, + arg: &PyObjectRef, + data: &mut Write, +) -> PyResult<()> +where + Endianness: byteorder::ByteOrder, +{ + match code.code { + 'b' => pack_i8(vm, arg, data)?, + 'B' => pack_u8(vm, arg, data)?, + '?' => pack_bool(vm, arg, data)?, + 'h' => pack_i16::(vm, arg, data)?, + 'H' => pack_u16::(vm, arg, data)?, + 'i' | 'l' => pack_i32::(vm, arg, data)?, + 'I' | 'L' => pack_u32::(vm, arg, data)?, + 'q' => pack_i64::(vm, arg, data)?, + 'Q' => pack_u64::(vm, arg, data)?, + 'f' => pack_f32::(vm, arg, data)?, + 'd' => pack_f64::(vm, arg, data)?, + c => { + panic!("Unsupported format code {:?}", c); + } } + Ok(()) } fn struct_pack(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { @@ -136,30 +249,26 @@ fn struct_pack(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { if objtype::isinstance(&fmt_arg, &vm.ctx.str_type()) { let fmt_str = objstr::get_value(&fmt_arg); - let codes = parse_format_string(fmt_str); + let format_spec = parse_format_string(fmt_str).map_err(|e| vm.new_value_error(e))?;; - if codes.len() + 1 == args.args.len() { + if format_spec.codes.len() + 1 == args.args.len() { // Create data vector: let mut data = Vec::::new(); // Loop over all opcodes: - for (code, arg) in codes.iter().zip(args.args.iter().skip(1)) { + for (code, arg) in format_spec.codes.iter().zip(args.args.iter().skip(1)) { debug!("code: {:?}", code); - match code.code { - 'b' => pack_i8(vm, arg, &mut data)?, - 'B' => pack_u8(vm, arg, &mut data)?, - '?' => pack_bool(vm, arg, &mut data)?, - 'h' => pack_i16(vm, arg, &mut data)?, - 'H' => pack_u16(vm, arg, &mut data)?, - 'i' => pack_i32(vm, arg, &mut data)?, - 'I' => pack_u32(vm, arg, &mut data)?, - 'l' => pack_i32(vm, arg, &mut data)?, - 'L' => pack_u32(vm, arg, &mut data)?, - 'q' => pack_i64(vm, arg, &mut data)?, - 'Q' => pack_u64(vm, arg, &mut data)?, - 'f' => pack_f32(vm, arg, &mut data)?, - 'd' => pack_f64(vm, arg, &mut data)?, - c => { - panic!("Unsupported format code {:?}", c); + match format_spec.endianness { + Endianness::Little => { + pack_item::(vm, code, arg, &mut data)? + } + Endianness::Big => { + pack_item::(vm, code, arg, &mut data)? + } + Endianness::Network => { + pack_item::(vm, code, arg, &mut data)? + } + Endianness::Native => { + pack_item::(vm, code, arg, &mut data)? } } } @@ -168,7 +277,7 @@ fn struct_pack(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } else { Err(vm.new_type_error(format!( "Expected {} arguments (got: {})", - codes.len() + 1, + format_spec.codes.len() + 1, args.args.len() ))) } @@ -199,57 +308,81 @@ fn unpack_bool(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { } } -fn unpack_i16(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { - match rdr.read_i16::() { +fn unpack_i16(vm: &VirtualMachine, rdr: &mut Read) -> PyResult +where + Endianness: byteorder::ByteOrder, +{ + match rdr.read_i16::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_u16(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { - match rdr.read_u16::() { +fn unpack_u16(vm: &VirtualMachine, rdr: &mut Read) -> PyResult +where + Endianness: byteorder::ByteOrder, +{ + match rdr.read_u16::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_i32(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { - match rdr.read_i32::() { +fn unpack_i32(vm: &VirtualMachine, rdr: &mut Read) -> PyResult +where + Endianness: byteorder::ByteOrder, +{ + match rdr.read_i32::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_u32(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { - match rdr.read_u32::() { +fn unpack_u32(vm: &VirtualMachine, rdr: &mut Read) -> PyResult +where + Endianness: byteorder::ByteOrder, +{ + match rdr.read_u32::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_i64(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { - match rdr.read_i64::() { +fn unpack_i64(vm: &VirtualMachine, rdr: &mut Read) -> PyResult +where + Endianness: byteorder::ByteOrder, +{ + match rdr.read_i64::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_u64(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { - match rdr.read_u64::() { +fn unpack_u64(vm: &VirtualMachine, rdr: &mut Read) -> PyResult +where + Endianness: byteorder::ByteOrder, +{ + match rdr.read_u64::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_int(v)), } } -fn unpack_f32(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { - match rdr.read_f32::() { +fn unpack_f32(vm: &VirtualMachine, rdr: &mut Read) -> PyResult +where + Endianness: byteorder::ByteOrder, +{ + match rdr.read_f32::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_float(f64::from(v))), } } -fn unpack_f64(vm: &VirtualMachine, rdr: &mut Read) -> PyResult { - match rdr.read_f64::() { +fn unpack_f64(vm: &VirtualMachine, rdr: &mut Read) -> PyResult +where + Endianness: byteorder::ByteOrder, +{ + match rdr.read_f64::() { Err(err) => panic!("Error in reading {:?}", err), Ok(v) => Ok(vm.ctx.new_float(v)), } @@ -267,41 +400,55 @@ fn struct_unpack(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { let fmt_str = objstr::get_value(&fmt); - let codes = parse_format_string(fmt_str); + let format_spec = parse_format_string(fmt_str).map_err(|e| vm.new_value_error(e))?; let data = objbytes::get_value(buffer).to_vec(); let mut rdr = Cursor::new(data); let mut items = vec![]; - for code in codes { + for code in format_spec.codes { debug!("unpack code: {:?}", code); - match code.code { - 'b' => items.push(unpack_i8(vm, &mut rdr)?), - 'B' => items.push(unpack_u8(vm, &mut rdr)?), - '?' => items.push(unpack_bool(vm, &mut rdr)?), - 'h' => items.push(unpack_i16(vm, &mut rdr)?), - 'H' => items.push(unpack_u16(vm, &mut rdr)?), - 'i' => items.push(unpack_i32(vm, &mut rdr)?), - 'I' => items.push(unpack_u32(vm, &mut rdr)?), - 'l' => items.push(unpack_i32(vm, &mut rdr)?), - 'L' => items.push(unpack_u32(vm, &mut rdr)?), - 'q' => items.push(unpack_i64(vm, &mut rdr)?), - 'Q' => items.push(unpack_u64(vm, &mut rdr)?), - 'f' => items.push(unpack_f32(vm, &mut rdr)?), - 'd' => items.push(unpack_f64(vm, &mut rdr)?), - c => { - panic!("Unsupported format code {:?}", c); - } - } + let item = match format_spec.endianness { + Endianness::Little => unpack_code::(vm, &code, &mut rdr)?, + Endianness::Big => unpack_code::(vm, &code, &mut rdr)?, + Endianness::Network => unpack_code::(vm, &code, &mut rdr)?, + Endianness::Native => unpack_code::(vm, &code, &mut rdr)?, + }; + items.push(item); } Ok(vm.ctx.new_tuple(items)) } +fn unpack_code(vm: &VirtualMachine, code: &FormatCode, rdr: &mut Read) -> PyResult +where + Endianness: byteorder::ByteOrder, +{ + match code.code { + 'b' => unpack_i8(vm, rdr), + 'B' => unpack_u8(vm, rdr), + '?' => unpack_bool(vm, rdr), + 'h' => unpack_i16::(vm, rdr), + 'H' => unpack_u16::(vm, rdr), + 'i' | 'l' => unpack_i32::(vm, rdr), + 'I' | 'L' => unpack_u32::(vm, rdr), + 'q' => unpack_i64::(vm, rdr), + 'Q' => unpack_u64::(vm, rdr), + 'f' => unpack_f32::(vm, rdr), + 'd' => unpack_f64::(vm, rdr), + c => { + panic!("Unsupported format code {:?}", c); + } + } +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; + let struct_error = ctx.new_class("struct.error", ctx.object()); + py_module!(vm, "struct", { "pack" => ctx.new_rustfunc(struct_pack), - "unpack" => ctx.new_rustfunc(struct_unpack) + "unpack" => ctx.new_rustfunc(struct_unpack), + "error" => struct_error, }) } From 8cdf19c5f906fce012263663ffb8a9a2da494b84 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Tue, 25 Jun 2019 23:51:59 +0300 Subject: [PATCH 832/884] FileIO.write support ByteArray --- vm/src/stdlib/io.rs | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 88593cd04c..2d0b2286c1 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -16,10 +16,12 @@ use crate::function::{OptionalArg, PyFuncArgs}; use crate::import; use crate::obj::objbytearray::PyByteArray; use crate::obj::objbytes; +use crate::obj::objbytes::PyBytes; use crate::obj::objint; use crate::obj::objstr; use crate::obj::objtype; use crate::obj::objtype::PyClassRef; +use crate::pyobject::TypeProtocol; use crate::pyobject::{BufferProtocol, PyObjectRef, PyRef, PyResult, PyValue}; use crate::vm::VirtualMachine; @@ -383,11 +385,7 @@ fn file_io_readinto(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } fn file_io_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { - arg_check!( - vm, - args, - required = [(file_io, None), (obj, Some(vm.ctx.bytes_type()))] - ); + arg_check!(vm, args, required = [(file_io, None), (obj, None)]); let file_no = vm.get_attribute(file_io.clone(), "fileno")?; let raw_fd = objint::get_value(&file_no).to_i64().unwrap(); @@ -397,22 +395,25 @@ fn file_io_write(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { //to support windows - i.e. raw file_handles let mut handle = os::rust_file(raw_fd); - match obj.payload::() { - Some(bytes) => { - let value_mut = &mut bytes.inner.borrow_mut().elements; - match handle.write(&value_mut[..]) { - Ok(len) => { - //reset raw fd on the FileIO object - let updated = os::raw_file_number(handle); - vm.set_attr(file_io, "fileno", vm.ctx.new_int(updated))?; - - //return number of bytes written - Ok(vm.ctx.new_int(len)) - } - Err(_) => Err(vm.new_value_error("Error Writing Bytes to Handle".to_string())), - } + let bytes = match_class!(obj.clone(), + i @ PyBytes => Ok(i.get_value().to_vec()), + j @ PyByteArray => Ok(j.inner.borrow().elements.to_vec()), + obj => Err(vm.new_type_error(format!( + "a bytes-like object is required, not {}", + obj.class() + ))) + ); + + match handle.write(&bytes?) { + Ok(len) => { + //reset raw fd on the FileIO object + let updated = os::raw_file_number(handle); + vm.set_attr(file_io, "fileno", vm.ctx.new_int(updated))?; + + //return number of bytes written + Ok(vm.ctx.new_int(len)) } - None => Err(vm.new_value_error("Expected Bytes Object".to_string())), + Err(_) => Err(vm.new_value_error("Error Writing Bytes to Handle".to_string())), } } From f2dff1cd206d38ab7620c0a2086ad0f0b4c677df Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Wed, 26 Jun 2019 18:25:47 +0300 Subject: [PATCH 833/884] Add EOFError --- vm/src/builtins.rs | 1 + vm/src/exceptions.rs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 9eaafbf73d..d497b47f98 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -882,6 +882,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "KeyError" => ctx.exceptions.key_error.clone(), "OSError" => ctx.exceptions.os_error.clone(), "ModuleNotFoundError" => ctx.exceptions.module_not_found_error.clone(), + "EOFError" => ctx.exceptions.eof_error.clone(), // Warnings "Warning" => ctx.exceptions.warning.clone(), diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index 0c243d0a15..853067408c 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -165,6 +165,7 @@ pub struct ExceptionZoo { pub type_error: PyClassRef, pub value_error: PyClassRef, pub zero_division_error: PyClassRef, + pub eof_error: PyClassRef, pub warning: PyClassRef, pub bytes_warning: PyClassRef, @@ -205,6 +206,7 @@ impl ExceptionZoo { let file_not_found_error = create_type("FileNotFoundError", &type_type, &os_error); let permission_error = create_type("PermissionError", &type_type, &os_error); let file_exists_error = create_type("FileExistsError", &type_type, &os_error); + let eof_error = create_type("EOFError", &type_type, &exception_type); let warning = create_type("Warning", &type_type, &exception_type); let bytes_warning = create_type("BytesWarning", &type_type, &warning); @@ -242,6 +244,7 @@ impl ExceptionZoo { type_error, value_error, zero_division_error, + eof_error, warning, bytes_warning, unicode_warning, From c78c95f03a1e0876f61e484ae4c3abe3aa5b4fe0 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Tue, 25 Jun 2019 23:12:45 -0500 Subject: [PATCH 834/884] Add CPython library modules needed for unittest --- Lib/collections/__init__.py | 1279 +++++++++++++++++++++ Lib/collections/abc.py | 2 + Lib/difflib.py | 2097 +++++++++++++++++++++++++++++++++++ Lib/functools.py | 828 ++++++++++++++ Lib/linecache.py | 177 +++ Lib/warnings.py | 554 +++++++++ Lib/weakref.py | 632 +++++++++++ vm/src/stdlib/weakref.rs | 22 +- 8 files changed, 5590 insertions(+), 1 deletion(-) create mode 100644 Lib/collections/__init__.py create mode 100644 Lib/collections/abc.py create mode 100644 Lib/difflib.py create mode 100644 Lib/functools.py create mode 100644 Lib/linecache.py create mode 100644 Lib/warnings.py create mode 100644 Lib/weakref.py diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py new file mode 100644 index 0000000000..9a753db71c --- /dev/null +++ b/Lib/collections/__init__.py @@ -0,0 +1,1279 @@ +'''This module implements specialized container datatypes providing +alternatives to Python's general purpose built-in containers, dict, +list, set, and tuple. + +* namedtuple factory function for creating tuple subclasses with named fields +* deque list-like container with fast appends and pops on either end +* ChainMap dict-like class for creating a single view of multiple mappings +* Counter dict subclass for counting hashable objects +* OrderedDict dict subclass that remembers the order entries were added +* defaultdict dict subclass that calls a factory function to supply missing values +* UserDict wrapper around dictionary objects for easier dict subclassing +* UserList wrapper around list objects for easier list subclassing +* UserString wrapper around string objects for easier string subclassing + +''' + +__all__ = ['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList', + 'UserString', 'Counter', 'OrderedDict', 'ChainMap'] + +import _collections_abc +from operator import itemgetter as _itemgetter, eq as _eq +from keyword import iskeyword as _iskeyword +import sys as _sys +import heapq as _heapq +from _weakref import proxy as _proxy +from itertools import repeat as _repeat, chain as _chain, starmap as _starmap +from reprlib import recursive_repr as _recursive_repr + +try: + from _collections import deque +except ImportError: + pass +else: + _collections_abc.MutableSequence.register(deque) + +try: + from _collections import defaultdict +except ImportError: + pass + + +def __getattr__(name): + # For backwards compatibility, continue to make the collections ABCs + # through Python 3.6 available through the collections module. + # Note, no new collections ABCs were added in Python 3.7 + if name in _collections_abc.__all__: + obj = getattr(_collections_abc, name) + import warnings + warnings.warn("Using or importing the ABCs from 'collections' instead " + "of from 'collections.abc' is deprecated, " + "and in 3.8 it will stop working", + DeprecationWarning, stacklevel=2) + globals()[name] = obj + return obj + raise AttributeError(f'module {__name__!r} has no attribute {name!r}') + +################################################################################ +### OrderedDict +################################################################################ + +class _OrderedDictKeysView(_collections_abc.KeysView): + + def __reversed__(self): + yield from reversed(self._mapping) + +class _OrderedDictItemsView(_collections_abc.ItemsView): + + def __reversed__(self): + for key in reversed(self._mapping): + yield (key, self._mapping[key]) + +class _OrderedDictValuesView(_collections_abc.ValuesView): + + def __reversed__(self): + for key in reversed(self._mapping): + yield self._mapping[key] + +class _Link(object): + __slots__ = 'prev', 'next', 'key', '__weakref__' + +class OrderedDict(dict): + 'Dictionary that remembers insertion order' + # An inherited dict maps keys to values. + # The inherited dict provides __getitem__, __len__, __contains__, and get. + # The remaining methods are order-aware. + # Big-O running times for all methods are the same as regular dictionaries. + + # The internal self.__map dict maps keys to links in a doubly linked list. + # The circular doubly linked list starts and ends with a sentinel element. + # The sentinel element never gets deleted (this simplifies the algorithm). + # The sentinel is in self.__hardroot with a weakref proxy in self.__root. + # The prev links are weakref proxies (to prevent circular references). + # Individual links are kept alive by the hard reference in self.__map. + # Those hard references disappear when a key is deleted from an OrderedDict. + + def __init__(*args, **kwds): + '''Initialize an ordered dictionary. The signature is the same as + regular dictionaries. Keyword argument order is preserved. + ''' + if not args: + raise TypeError("descriptor '__init__' of 'OrderedDict' object " + "needs an argument") + self, *args = args + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + try: + self.__root + except AttributeError: + self.__hardroot = _Link() + self.__root = root = _proxy(self.__hardroot) + root.prev = root.next = root + self.__map = {} + self.__update(*args, **kwds) + + def __setitem__(self, key, value, + dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link): + 'od.__setitem__(i, y) <==> od[i]=y' + # Setting a new item creates a new link at the end of the linked list, + # and the inherited dictionary is updated with the new key/value pair. + if key not in self: + self.__map[key] = link = Link() + root = self.__root + last = root.prev + link.prev, link.next, link.key = last, root, key + last.next = link + root.prev = proxy(link) + dict_setitem(self, key, value) + + def __delitem__(self, key, dict_delitem=dict.__delitem__): + 'od.__delitem__(y) <==> del od[y]' + # Deleting an existing item uses self.__map to find the link which gets + # removed by updating the links in the predecessor and successor nodes. + dict_delitem(self, key) + link = self.__map.pop(key) + link_prev = link.prev + link_next = link.next + link_prev.next = link_next + link_next.prev = link_prev + link.prev = None + link.next = None + + def __iter__(self): + 'od.__iter__() <==> iter(od)' + # Traverse the linked list in order. + root = self.__root + curr = root.next + while curr is not root: + yield curr.key + curr = curr.next + + def __reversed__(self): + 'od.__reversed__() <==> reversed(od)' + # Traverse the linked list in reverse order. + root = self.__root + curr = root.prev + while curr is not root: + yield curr.key + curr = curr.prev + + def clear(self): + 'od.clear() -> None. Remove all items from od.' + root = self.__root + root.prev = root.next = root + self.__map.clear() + dict.clear(self) + + def popitem(self, last=True): + '''Remove and return a (key, value) pair from the dictionary. + + Pairs are returned in LIFO order if last is true or FIFO order if false. + ''' + if not self: + raise KeyError('dictionary is empty') + root = self.__root + if last: + link = root.prev + link_prev = link.prev + link_prev.next = root + root.prev = link_prev + else: + link = root.next + link_next = link.next + root.next = link_next + link_next.prev = root + key = link.key + del self.__map[key] + value = dict.pop(self, key) + return key, value + + def move_to_end(self, key, last=True): + '''Move an existing element to the end (or beginning if last is false). + + Raise KeyError if the element does not exist. + ''' + link = self.__map[key] + link_prev = link.prev + link_next = link.next + soft_link = link_next.prev + link_prev.next = link_next + link_next.prev = link_prev + root = self.__root + if last: + last = root.prev + link.prev = last + link.next = root + root.prev = soft_link + last.next = link + else: + first = root.next + link.prev = root + link.next = first + first.prev = soft_link + root.next = link + + def __sizeof__(self): + sizeof = _sys.getsizeof + n = len(self) + 1 # number of links including root + size = sizeof(self.__dict__) # instance dictionary + size += sizeof(self.__map) * 2 # internal dict and inherited dict + size += sizeof(self.__hardroot) * n # link objects + size += sizeof(self.__root) * n # proxy objects + return size + + update = __update = _collections_abc.MutableMapping.update + + def keys(self): + "D.keys() -> a set-like object providing a view on D's keys" + return _OrderedDictKeysView(self) + + def items(self): + "D.items() -> a set-like object providing a view on D's items" + return _OrderedDictItemsView(self) + + def values(self): + "D.values() -> an object providing a view on D's values" + return _OrderedDictValuesView(self) + + __ne__ = _collections_abc.MutableMapping.__ne__ + + __marker = object() + + def pop(self, key, default=__marker): + '''od.pop(k[,d]) -> v, remove specified key and return the corresponding + value. If key is not found, d is returned if given, otherwise KeyError + is raised. + + ''' + if key in self: + result = self[key] + del self[key] + return result + if default is self.__marker: + raise KeyError(key) + return default + + def setdefault(self, key, default=None): + '''Insert key with a value of default if key is not in the dictionary. + + Return the value for key if key is in the dictionary, else default. + ''' + if key in self: + return self[key] + self[key] = default + return default + + @_recursive_repr() + def __repr__(self): + 'od.__repr__() <==> repr(od)' + if not self: + return '%s()' % (self.__class__.__name__,) + return '%s(%r)' % (self.__class__.__name__, list(self.items())) + + def __reduce__(self): + 'Return state information for pickling' + inst_dict = vars(self).copy() + for k in vars(OrderedDict()): + inst_dict.pop(k, None) + return self.__class__, (), inst_dict or None, None, iter(self.items()) + + def copy(self): + 'od.copy() -> a shallow copy of od' + return self.__class__(self) + + @classmethod + def fromkeys(cls, iterable, value=None): + '''Create a new ordered dictionary with keys from iterable and values set to value. + ''' + self = cls() + for key in iterable: + self[key] = value + return self + + def __eq__(self, other): + '''od.__eq__(y) <==> od==y. Comparison to another OD is order-sensitive + while comparison to a regular mapping is order-insensitive. + + ''' + if isinstance(other, OrderedDict): + return dict.__eq__(self, other) and all(map(_eq, self, other)) + return dict.__eq__(self, other) + + +try: + from _collections import OrderedDict +except ImportError: + # Leave the pure Python version in place. + pass + + +################################################################################ +### namedtuple +################################################################################ + +_nt_itemgetters = {} + +def namedtuple(typename, field_names, *, rename=False, defaults=None, module=None): + """Returns a new subclass of tuple with named fields. + + >>> Point = namedtuple('Point', ['x', 'y']) + >>> Point.__doc__ # docstring for the new class + 'Point(x, y)' + >>> p = Point(11, y=22) # instantiate with positional args or keywords + >>> p[0] + p[1] # indexable like a plain tuple + 33 + >>> x, y = p # unpack like a regular tuple + >>> x, y + (11, 22) + >>> p.x + p.y # fields also accessible by name + 33 + >>> d = p._asdict() # convert to a dictionary + >>> d['x'] + 11 + >>> Point(**d) # convert from a dictionary + Point(x=11, y=22) + >>> p._replace(x=100) # _replace() is like str.replace() but targets named fields + Point(x=100, y=22) + + """ + + # Validate the field names. At the user's option, either generate an error + # message or automatically replace the field name with a valid name. + if isinstance(field_names, str): + field_names = field_names.replace(',', ' ').split() + field_names = list(map(str, field_names)) + typename = _sys.intern(str(typename)) + + if rename: + seen = set() + for index, name in enumerate(field_names): + if (not name.isidentifier() + or _iskeyword(name) + or name.startswith('_') + or name in seen): + field_names[index] = f'_{index}' + seen.add(name) + + for name in [typename] + field_names: + if type(name) is not str: + raise TypeError('Type names and field names must be strings') + if not name.isidentifier(): + raise ValueError('Type names and field names must be valid ' + f'identifiers: {name!r}') + if _iskeyword(name): + raise ValueError('Type names and field names cannot be a ' + f'keyword: {name!r}') + + seen = set() + for name in field_names: + if name.startswith('_') and not rename: + raise ValueError('Field names cannot start with an underscore: ' + f'{name!r}') + if name in seen: + raise ValueError(f'Encountered duplicate field name: {name!r}') + seen.add(name) + + field_defaults = {} + if defaults is not None: + defaults = tuple(defaults) + if len(defaults) > len(field_names): + raise TypeError('Got more default values than field names') + field_defaults = dict(reversed(list(zip(reversed(field_names), + reversed(defaults))))) + + # Variables used in the methods and docstrings + field_names = tuple(map(_sys.intern, field_names)) + num_fields = len(field_names) + arg_list = repr(field_names).replace("'", "")[1:-1] + repr_fmt = '(' + ', '.join(f'{name}=%r' for name in field_names) + ')' + tuple_new = tuple.__new__ + _len = len + + # Create all the named tuple methods to be added to the class namespace + + s = f'def __new__(_cls, {arg_list}): return _tuple_new(_cls, ({arg_list}))' + namespace = {'_tuple_new': tuple_new, '__name__': f'namedtuple_{typename}'} + # Note: exec() has the side-effect of interning the field names + exec(s, namespace) + __new__ = namespace['__new__'] + __new__.__doc__ = f'Create new instance of {typename}({arg_list})' + if defaults is not None: + __new__.__defaults__ = defaults + + @classmethod + def _make(cls, iterable): + result = tuple_new(cls, iterable) + if _len(result) != num_fields: + raise TypeError(f'Expected {num_fields} arguments, got {len(result)}') + return result + + _make.__func__.__doc__ = (f'Make a new {typename} object from a sequence ' + 'or iterable') + + def _replace(_self, **kwds): + result = _self._make(map(kwds.pop, field_names, _self)) + if kwds: + raise ValueError(f'Got unexpected field names: {list(kwds)!r}') + return result + + _replace.__doc__ = (f'Return a new {typename} object replacing specified ' + 'fields with new values') + + def __repr__(self): + 'Return a nicely formatted representation string' + return self.__class__.__name__ + repr_fmt % self + + def _asdict(self): + 'Return a new OrderedDict which maps field names to their values.' + return OrderedDict(zip(self._fields, self)) + + def __getnewargs__(self): + 'Return self as a plain tuple. Used by copy and pickle.' + return tuple(self) + + # Modify function metadata to help with introspection and debugging + + for method in (__new__, _make.__func__, _replace, + __repr__, _asdict, __getnewargs__): + method.__qualname__ = f'{typename}.{method.__name__}' + + # Build-up the class namespace dictionary + # and use type() to build the result class + class_namespace = { + '__doc__': f'{typename}({arg_list})', + '__slots__': (), + '_fields': field_names, + '_fields_defaults': field_defaults, + '__new__': __new__, + '_make': _make, + '_replace': _replace, + '__repr__': __repr__, + '_asdict': _asdict, + '__getnewargs__': __getnewargs__, + } + cache = _nt_itemgetters + for index, name in enumerate(field_names): + try: + itemgetter_object, doc = cache[index] + except KeyError: + itemgetter_object = _itemgetter(index) + doc = f'Alias for field number {index}' + cache[index] = itemgetter_object, doc + class_namespace[name] = property(itemgetter_object, doc=doc) + + result = type(typename, (tuple,), class_namespace) + + # For pickling to work, the __module__ variable needs to be set to the frame + # where the named tuple is created. Bypass this step in environments where + # sys._getframe is not defined (Jython for example) or sys._getframe is not + # defined for arguments greater than 0 (IronPython), or where the user has + # specified a particular module. + if module is None: + try: + module = _sys._getframe(1).f_globals.get('__name__', '__main__') + except (AttributeError, ValueError): + pass + if module is not None: + result.__module__ = module + + return result + + +######################################################################## +### Counter +######################################################################## + +def _count_elements(mapping, iterable): + 'Tally elements from the iterable.' + mapping_get = mapping.get + for elem in iterable: + mapping[elem] = mapping_get(elem, 0) + 1 + +try: # Load C helper function if available + from _collections import _count_elements +except ImportError: + pass + +class Counter(dict): + '''Dict subclass for counting hashable items. Sometimes called a bag + or multiset. Elements are stored as dictionary keys and their counts + are stored as dictionary values. + + >>> c = Counter('abcdeabcdabcaba') # count elements from a string + + >>> c.most_common(3) # three most common elements + [('a', 5), ('b', 4), ('c', 3)] + >>> sorted(c) # list all unique elements + ['a', 'b', 'c', 'd', 'e'] + >>> ''.join(sorted(c.elements())) # list elements with repetitions + 'aaaaabbbbcccdde' + >>> sum(c.values()) # total of all counts + 15 + + >>> c['a'] # count of letter 'a' + 5 + >>> for elem in 'shazam': # update counts from an iterable + ... c[elem] += 1 # by adding 1 to each element's count + >>> c['a'] # now there are seven 'a' + 7 + >>> del c['b'] # remove all 'b' + >>> c['b'] # now there are zero 'b' + 0 + + >>> d = Counter('simsalabim') # make another counter + >>> c.update(d) # add in the second counter + >>> c['a'] # now there are nine 'a' + 9 + + >>> c.clear() # empty the counter + >>> c + Counter() + + Note: If a count is set to zero or reduced to zero, it will remain + in the counter until the entry is deleted or the counter is cleared: + + >>> c = Counter('aaabbc') + >>> c['b'] -= 2 # reduce the count of 'b' by two + >>> c.most_common() # 'b' is still in, but its count is zero + [('a', 3), ('c', 1), ('b', 0)] + + ''' + # References: + # http://en.wikipedia.org/wiki/Multiset + # http://www.gnu.org/software/smalltalk/manual-base/html_node/Bag.html + # http://www.demo2s.com/Tutorial/Cpp/0380__set-multiset/Catalog0380__set-multiset.htm + # http://code.activestate.com/recipes/259174/ + # Knuth, TAOCP Vol. II section 4.6.3 + + def __init__(*args, **kwds): + '''Create a new, empty Counter object. And if given, count elements + from an input iterable. Or, initialize the count from another mapping + of elements to their counts. + + >>> c = Counter() # a new, empty counter + >>> c = Counter('gallahad') # a new counter from an iterable + >>> c = Counter({'a': 4, 'b': 2}) # a new counter from a mapping + >>> c = Counter(a=4, b=2) # a new counter from keyword args + + ''' + if not args: + raise TypeError("descriptor '__init__' of 'Counter' object " + "needs an argument") + self, *args = args + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + super(Counter, self).__init__() + self.update(*args, **kwds) + + def __missing__(self, key): + 'The count of elements not in the Counter is zero.' + # Needed so that self[missing_item] does not raise KeyError + return 0 + + def most_common(self, n=None): + '''List the n most common elements and their counts from the most + common to the least. If n is None, then list all element counts. + + >>> Counter('abcdeabcdabcaba').most_common(3) + [('a', 5), ('b', 4), ('c', 3)] + + ''' + # Emulate Bag.sortedByCount from Smalltalk + if n is None: + return sorted(self.items(), key=_itemgetter(1), reverse=True) + return _heapq.nlargest(n, self.items(), key=_itemgetter(1)) + + def elements(self): + '''Iterator over elements repeating each as many times as its count. + + >>> c = Counter('ABCABC') + >>> sorted(c.elements()) + ['A', 'A', 'B', 'B', 'C', 'C'] + + # Knuth's example for prime factors of 1836: 2**2 * 3**3 * 17**1 + >>> prime_factors = Counter({2: 2, 3: 3, 17: 1}) + >>> product = 1 + >>> for factor in prime_factors.elements(): # loop over factors + ... product *= factor # and multiply them + >>> product + 1836 + + Note, if an element's count has been set to zero or is a negative + number, elements() will ignore it. + + ''' + # Emulate Bag.do from Smalltalk and Multiset.begin from C++. + return _chain.from_iterable(_starmap(_repeat, self.items())) + + # Override dict methods where necessary + + @classmethod + def fromkeys(cls, iterable, v=None): + # There is no equivalent method for counters because setting v=1 + # means that no element can have a count greater than one. + raise NotImplementedError( + 'Counter.fromkeys() is undefined. Use Counter(iterable) instead.') + + def update(*args, **kwds): + '''Like dict.update() but add counts instead of replacing them. + + Source can be an iterable, a dictionary, or another Counter instance. + + >>> c = Counter('which') + >>> c.update('witch') # add elements from another iterable + >>> d = Counter('watch') + >>> c.update(d) # add elements from another counter + >>> c['h'] # four 'h' in which, witch, and watch + 4 + + ''' + # The regular dict.update() operation makes no sense here because the + # replace behavior results in the some of original untouched counts + # being mixed-in with all of the other counts for a mismash that + # doesn't have a straight-forward interpretation in most counting + # contexts. Instead, we implement straight-addition. Both the inputs + # and outputs are allowed to contain zero and negative counts. + + if not args: + raise TypeError("descriptor 'update' of 'Counter' object " + "needs an argument") + self, *args = args + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + iterable = args[0] if args else None + if iterable is not None: + if isinstance(iterable, _collections_abc.Mapping): + if self: + self_get = self.get + for elem, count in iterable.items(): + self[elem] = count + self_get(elem, 0) + else: + super(Counter, self).update(iterable) # fast path when counter is empty + else: + _count_elements(self, iterable) + if kwds: + self.update(kwds) + + def subtract(*args, **kwds): + '''Like dict.update() but subtracts counts instead of replacing them. + Counts can be reduced below zero. Both the inputs and outputs are + allowed to contain zero and negative counts. + + Source can be an iterable, a dictionary, or another Counter instance. + + >>> c = Counter('which') + >>> c.subtract('witch') # subtract elements from another iterable + >>> c.subtract(Counter('watch')) # subtract elements from another counter + >>> c['h'] # 2 in which, minus 1 in witch, minus 1 in watch + 0 + >>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch + -1 + + ''' + if not args: + raise TypeError("descriptor 'subtract' of 'Counter' object " + "needs an argument") + self, *args = args + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + iterable = args[0] if args else None + if iterable is not None: + self_get = self.get + if isinstance(iterable, _collections_abc.Mapping): + for elem, count in iterable.items(): + self[elem] = self_get(elem, 0) - count + else: + for elem in iterable: + self[elem] = self_get(elem, 0) - 1 + if kwds: + self.subtract(kwds) + + def copy(self): + 'Return a shallow copy.' + return self.__class__(self) + + def __reduce__(self): + return self.__class__, (dict(self),) + + def __delitem__(self, elem): + 'Like dict.__delitem__() but does not raise KeyError for missing values.' + if elem in self: + super().__delitem__(elem) + + def __repr__(self): + if not self: + return '%s()' % self.__class__.__name__ + try: + items = ', '.join(map('%r: %r'.__mod__, self.most_common())) + return '%s({%s})' % (self.__class__.__name__, items) + except TypeError: + # handle case where values are not orderable + return '{0}({1!r})'.format(self.__class__.__name__, dict(self)) + + # Multiset-style mathematical operations discussed in: + # Knuth TAOCP Volume II section 4.6.3 exercise 19 + # and at http://en.wikipedia.org/wiki/Multiset + # + # Outputs guaranteed to only include positive counts. + # + # To strip negative and zero counts, add-in an empty counter: + # c += Counter() + + def __add__(self, other): + '''Add counts from two counters. + + >>> Counter('abbb') + Counter('bcc') + Counter({'b': 4, 'c': 2, 'a': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + result = Counter() + for elem, count in self.items(): + newcount = count + other[elem] + if newcount > 0: + result[elem] = newcount + for elem, count in other.items(): + if elem not in self and count > 0: + result[elem] = count + return result + + def __sub__(self, other): + ''' Subtract count, but keep only results with positive counts. + + >>> Counter('abbbc') - Counter('bccd') + Counter({'b': 2, 'a': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + result = Counter() + for elem, count in self.items(): + newcount = count - other[elem] + if newcount > 0: + result[elem] = newcount + for elem, count in other.items(): + if elem not in self and count < 0: + result[elem] = 0 - count + return result + + def __or__(self, other): + '''Union is the maximum of value in either of the input counters. + + >>> Counter('abbb') | Counter('bcc') + Counter({'b': 3, 'c': 2, 'a': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + result = Counter() + for elem, count in self.items(): + other_count = other[elem] + newcount = other_count if count < other_count else count + if newcount > 0: + result[elem] = newcount + for elem, count in other.items(): + if elem not in self and count > 0: + result[elem] = count + return result + + def __and__(self, other): + ''' Intersection is the minimum of corresponding counts. + + >>> Counter('abbb') & Counter('bcc') + Counter({'b': 1}) + + ''' + if not isinstance(other, Counter): + return NotImplemented + result = Counter() + for elem, count in self.items(): + other_count = other[elem] + newcount = count if count < other_count else other_count + if newcount > 0: + result[elem] = newcount + return result + + def __pos__(self): + 'Adds an empty counter, effectively stripping negative and zero counts' + result = Counter() + for elem, count in self.items(): + if count > 0: + result[elem] = count + return result + + def __neg__(self): + '''Subtracts from an empty counter. Strips positive and zero counts, + and flips the sign on negative counts. + + ''' + result = Counter() + for elem, count in self.items(): + if count < 0: + result[elem] = 0 - count + return result + + def _keep_positive(self): + '''Internal method to strip elements with a negative or zero count''' + nonpositive = [elem for elem, count in self.items() if not count > 0] + for elem in nonpositive: + del self[elem] + return self + + def __iadd__(self, other): + '''Inplace add from another counter, keeping only positive counts. + + >>> c = Counter('abbb') + >>> c += Counter('bcc') + >>> c + Counter({'b': 4, 'c': 2, 'a': 1}) + + ''' + for elem, count in other.items(): + self[elem] += count + return self._keep_positive() + + def __isub__(self, other): + '''Inplace subtract counter, but keep only results with positive counts. + + >>> c = Counter('abbbc') + >>> c -= Counter('bccd') + >>> c + Counter({'b': 2, 'a': 1}) + + ''' + for elem, count in other.items(): + self[elem] -= count + return self._keep_positive() + + def __ior__(self, other): + '''Inplace union is the maximum of value from either counter. + + >>> c = Counter('abbb') + >>> c |= Counter('bcc') + >>> c + Counter({'b': 3, 'c': 2, 'a': 1}) + + ''' + for elem, other_count in other.items(): + count = self[elem] + if other_count > count: + self[elem] = other_count + return self._keep_positive() + + def __iand__(self, other): + '''Inplace intersection is the minimum of corresponding counts. + + >>> c = Counter('abbb') + >>> c &= Counter('bcc') + >>> c + Counter({'b': 1}) + + ''' + for elem, count in self.items(): + other_count = other[elem] + if other_count < count: + self[elem] = other_count + return self._keep_positive() + + +######################################################################## +### ChainMap +######################################################################## + +class ChainMap(_collections_abc.MutableMapping): + ''' A ChainMap groups multiple dicts (or other mappings) together + to create a single, updateable view. + + The underlying mappings are stored in a list. That list is public and can + be accessed or updated using the *maps* attribute. There is no other + state. + + Lookups search the underlying mappings successively until a key is found. + In contrast, writes, updates, and deletions only operate on the first + mapping. + + ''' + + def __init__(self, *maps): + '''Initialize a ChainMap by setting *maps* to the given mappings. + If no mappings are provided, a single empty dictionary is used. + + ''' + self.maps = list(maps) or [{}] # always at least one map + + def __missing__(self, key): + raise KeyError(key) + + def __getitem__(self, key): + for mapping in self.maps: + try: + return mapping[key] # can't use 'key in mapping' with defaultdict + except KeyError: + pass + return self.__missing__(key) # support subclasses that define __missing__ + + def get(self, key, default=None): + return self[key] if key in self else default + + def __len__(self): + return len(set().union(*self.maps)) # reuses stored hash values if possible + + def __iter__(self): + d = {} + for mapping in reversed(self.maps): + d.update(mapping) # reuses stored hash values if possible + return iter(d) + + def __contains__(self, key): + return any(key in m for m in self.maps) + + def __bool__(self): + return any(self.maps) + + @_recursive_repr() + def __repr__(self): + return '{0.__class__.__name__}({1})'.format( + self, ', '.join(map(repr, self.maps))) + + @classmethod + def fromkeys(cls, iterable, *args): + 'Create a ChainMap with a single dict created from the iterable.' + return cls(dict.fromkeys(iterable, *args)) + + def copy(self): + 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]' + return self.__class__(self.maps[0].copy(), *self.maps[1:]) + + __copy__ = copy + + def new_child(self, m=None): # like Django's Context.push() + '''New ChainMap with a new map followed by all previous maps. + If no map is provided, an empty dict is used. + ''' + if m is None: + m = {} + return self.__class__(m, *self.maps) + + @property + def parents(self): # like Django's Context.pop() + 'New ChainMap from maps[1:].' + return self.__class__(*self.maps[1:]) + + def __setitem__(self, key, value): + self.maps[0][key] = value + + def __delitem__(self, key): + try: + del self.maps[0][key] + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def popitem(self): + 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.' + try: + return self.maps[0].popitem() + except KeyError: + raise KeyError('No keys found in the first mapping.') + + def pop(self, key, *args): + 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].' + try: + return self.maps[0].pop(key, *args) + except KeyError: + raise KeyError('Key not found in the first mapping: {!r}'.format(key)) + + def clear(self): + 'Clear maps[0], leaving maps[1:] intact.' + self.maps[0].clear() + + +################################################################################ +### UserDict +################################################################################ + +class UserDict(_collections_abc.MutableMapping): + + # Start by filling-out the abstract methods + def __init__(*args, **kwargs): + if not args: + raise TypeError("descriptor '__init__' of 'UserDict' object " + "needs an argument") + self, *args = args + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + if args: + dict = args[0] + elif 'dict' in kwargs: + dict = kwargs.pop('dict') + import warnings + warnings.warn("Passing 'dict' as keyword argument is deprecated", + DeprecationWarning, stacklevel=2) + else: + dict = None + self.data = {} + if dict is not None: + self.update(dict) + if len(kwargs): + self.update(kwargs) + def __len__(self): return len(self.data) + def __getitem__(self, key): + if key in self.data: + return self.data[key] + if hasattr(self.__class__, "__missing__"): + return self.__class__.__missing__(self, key) + raise KeyError(key) + def __setitem__(self, key, item): self.data[key] = item + def __delitem__(self, key): del self.data[key] + def __iter__(self): + return iter(self.data) + + # Modify __contains__ to work correctly when __missing__ is present + def __contains__(self, key): + return key in self.data + + # Now, add the methods in dicts but not in MutableMapping + def __repr__(self): return repr(self.data) + def copy(self): + if self.__class__ is UserDict: + return UserDict(self.data.copy()) + import copy + data = self.data + try: + self.data = {} + c = copy.copy(self) + finally: + self.data = data + c.update(self) + return c + @classmethod + def fromkeys(cls, iterable, value=None): + d = cls() + for key in iterable: + d[key] = value + return d + + + +################################################################################ +### UserList +################################################################################ + +class UserList(_collections_abc.MutableSequence): + """A more or less complete user-defined wrapper around list objects.""" + def __init__(self, initlist=None): + self.data = [] + if initlist is not None: + # XXX should this accept an arbitrary sequence? + if type(initlist) == type(self.data): + self.data[:] = initlist + elif isinstance(initlist, UserList): + self.data[:] = initlist.data[:] + else: + self.data = list(initlist) + def __repr__(self): return repr(self.data) + def __lt__(self, other): return self.data < self.__cast(other) + def __le__(self, other): return self.data <= self.__cast(other) + def __eq__(self, other): return self.data == self.__cast(other) + def __gt__(self, other): return self.data > self.__cast(other) + def __ge__(self, other): return self.data >= self.__cast(other) + def __cast(self, other): + return other.data if isinstance(other, UserList) else other + def __contains__(self, item): return item in self.data + def __len__(self): return len(self.data) + def __getitem__(self, i): return self.data[i] + def __setitem__(self, i, item): self.data[i] = item + def __delitem__(self, i): del self.data[i] + def __add__(self, other): + if isinstance(other, UserList): + return self.__class__(self.data + other.data) + elif isinstance(other, type(self.data)): + return self.__class__(self.data + other) + return self.__class__(self.data + list(other)) + def __radd__(self, other): + if isinstance(other, UserList): + return self.__class__(other.data + self.data) + elif isinstance(other, type(self.data)): + return self.__class__(other + self.data) + return self.__class__(list(other) + self.data) + def __iadd__(self, other): + if isinstance(other, UserList): + self.data += other.data + elif isinstance(other, type(self.data)): + self.data += other + else: + self.data += list(other) + return self + def __mul__(self, n): + return self.__class__(self.data*n) + __rmul__ = __mul__ + def __imul__(self, n): + self.data *= n + return self + def append(self, item): self.data.append(item) + def insert(self, i, item): self.data.insert(i, item) + def pop(self, i=-1): return self.data.pop(i) + def remove(self, item): self.data.remove(item) + def clear(self): self.data.clear() + def copy(self): return self.__class__(self) + def count(self, item): return self.data.count(item) + def index(self, item, *args): return self.data.index(item, *args) + def reverse(self): self.data.reverse() + def sort(self, *args, **kwds): self.data.sort(*args, **kwds) + def extend(self, other): + if isinstance(other, UserList): + self.data.extend(other.data) + else: + self.data.extend(other) + + + +################################################################################ +### UserString +################################################################################ + +class UserString(_collections_abc.Sequence): + def __init__(self, seq): + if isinstance(seq, str): + self.data = seq + elif isinstance(seq, UserString): + self.data = seq.data[:] + else: + self.data = str(seq) + def __str__(self): return str(self.data) + def __repr__(self): return repr(self.data) + def __int__(self): return int(self.data) + def __float__(self): return float(self.data) + def __complex__(self): return complex(self.data) + def __hash__(self): return hash(self.data) + def __getnewargs__(self): + return (self.data[:],) + + def __eq__(self, string): + if isinstance(string, UserString): + return self.data == string.data + return self.data == string + def __lt__(self, string): + if isinstance(string, UserString): + return self.data < string.data + return self.data < string + def __le__(self, string): + if isinstance(string, UserString): + return self.data <= string.data + return self.data <= string + def __gt__(self, string): + if isinstance(string, UserString): + return self.data > string.data + return self.data > string + def __ge__(self, string): + if isinstance(string, UserString): + return self.data >= string.data + return self.data >= string + + def __contains__(self, char): + if isinstance(char, UserString): + char = char.data + return char in self.data + + def __len__(self): return len(self.data) + def __getitem__(self, index): return self.__class__(self.data[index]) + def __add__(self, other): + if isinstance(other, UserString): + return self.__class__(self.data + other.data) + elif isinstance(other, str): + return self.__class__(self.data + other) + return self.__class__(self.data + str(other)) + def __radd__(self, other): + if isinstance(other, str): + return self.__class__(other + self.data) + return self.__class__(str(other) + self.data) + def __mul__(self, n): + return self.__class__(self.data*n) + __rmul__ = __mul__ + def __mod__(self, args): + return self.__class__(self.data % args) + def __rmod__(self, format): + return self.__class__(format % args) + + # the following methods are defined in alphabetical order: + def capitalize(self): return self.__class__(self.data.capitalize()) + def casefold(self): + return self.__class__(self.data.casefold()) + def center(self, width, *args): + return self.__class__(self.data.center(width, *args)) + def count(self, sub, start=0, end=_sys.maxsize): + if isinstance(sub, UserString): + sub = sub.data + return self.data.count(sub, start, end) + def encode(self, encoding=None, errors=None): # XXX improve this? + if encoding: + if errors: + return self.__class__(self.data.encode(encoding, errors)) + return self.__class__(self.data.encode(encoding)) + return self.__class__(self.data.encode()) + def endswith(self, suffix, start=0, end=_sys.maxsize): + return self.data.endswith(suffix, start, end) + def expandtabs(self, tabsize=8): + return self.__class__(self.data.expandtabs(tabsize)) + def find(self, sub, start=0, end=_sys.maxsize): + if isinstance(sub, UserString): + sub = sub.data + return self.data.find(sub, start, end) + def format(self, *args, **kwds): + return self.data.format(*args, **kwds) + def format_map(self, mapping): + return self.data.format_map(mapping) + def index(self, sub, start=0, end=_sys.maxsize): + return self.data.index(sub, start, end) + def isalpha(self): return self.data.isalpha() + def isalnum(self): return self.data.isalnum() + def isascii(self): return self.data.isascii() + def isdecimal(self): return self.data.isdecimal() + def isdigit(self): return self.data.isdigit() + def isidentifier(self): return self.data.isidentifier() + def islower(self): return self.data.islower() + def isnumeric(self): return self.data.isnumeric() + def isprintable(self): return self.data.isprintable() + def isspace(self): return self.data.isspace() + def istitle(self): return self.data.istitle() + def isupper(self): return self.data.isupper() + def join(self, seq): return self.data.join(seq) + def ljust(self, width, *args): + return self.__class__(self.data.ljust(width, *args)) + def lower(self): return self.__class__(self.data.lower()) + def lstrip(self, chars=None): return self.__class__(self.data.lstrip(chars)) + maketrans = str.maketrans + def partition(self, sep): + return self.data.partition(sep) + def replace(self, old, new, maxsplit=-1): + if isinstance(old, UserString): + old = old.data + if isinstance(new, UserString): + new = new.data + return self.__class__(self.data.replace(old, new, maxsplit)) + def rfind(self, sub, start=0, end=_sys.maxsize): + if isinstance(sub, UserString): + sub = sub.data + return self.data.rfind(sub, start, end) + def rindex(self, sub, start=0, end=_sys.maxsize): + return self.data.rindex(sub, start, end) + def rjust(self, width, *args): + return self.__class__(self.data.rjust(width, *args)) + def rpartition(self, sep): + return self.data.rpartition(sep) + def rstrip(self, chars=None): + return self.__class__(self.data.rstrip(chars)) + def split(self, sep=None, maxsplit=-1): + return self.data.split(sep, maxsplit) + def rsplit(self, sep=None, maxsplit=-1): + return self.data.rsplit(sep, maxsplit) + def splitlines(self, keepends=False): return self.data.splitlines(keepends) + def startswith(self, prefix, start=0, end=_sys.maxsize): + return self.data.startswith(prefix, start, end) + def strip(self, chars=None): return self.__class__(self.data.strip(chars)) + def swapcase(self): return self.__class__(self.data.swapcase()) + def title(self): return self.__class__(self.data.title()) + def translate(self, *args): + return self.__class__(self.data.translate(*args)) + def upper(self): return self.__class__(self.data.upper()) + def zfill(self, width): return self.__class__(self.data.zfill(width)) diff --git a/Lib/collections/abc.py b/Lib/collections/abc.py new file mode 100644 index 0000000000..891600d16b --- /dev/null +++ b/Lib/collections/abc.py @@ -0,0 +1,2 @@ +from _collections_abc import * +from _collections_abc import __all__ diff --git a/Lib/difflib.py b/Lib/difflib.py new file mode 100644 index 0000000000..887c3c26ca --- /dev/null +++ b/Lib/difflib.py @@ -0,0 +1,2097 @@ +""" +Module difflib -- helpers for computing deltas between objects. + +Function get_close_matches(word, possibilities, n=3, cutoff=0.6): + Use SequenceMatcher to return list of the best "good enough" matches. + +Function context_diff(a, b): + For two lists of strings, return a delta in context diff format. + +Function ndiff(a, b): + Return a delta: the difference between `a` and `b` (lists of strings). + +Function restore(delta, which): + Return one of the two sequences that generated an ndiff delta. + +Function unified_diff(a, b): + For two lists of strings, return a delta in unified diff format. + +Class SequenceMatcher: + A flexible class for comparing pairs of sequences of any type. + +Class Differ: + For producing human-readable deltas from sequences of lines of text. + +Class HtmlDiff: + For producing HTML side by side comparison with change highlights. +""" + +__all__ = ['get_close_matches', 'ndiff', 'restore', 'SequenceMatcher', + 'Differ','IS_CHARACTER_JUNK', 'IS_LINE_JUNK', 'context_diff', + 'unified_diff', 'diff_bytes', 'HtmlDiff', 'Match'] + +from heapq import nlargest as _nlargest +from collections import namedtuple as _namedtuple + +Match = _namedtuple('Match', 'a b size') + +def _calculate_ratio(matches, length): + if length: + return 2.0 * matches / length + return 1.0 + +class SequenceMatcher: + + """ + SequenceMatcher is a flexible class for comparing pairs of sequences of + any type, so long as the sequence elements are hashable. The basic + algorithm predates, and is a little fancier than, an algorithm + published in the late 1980's by Ratcliff and Obershelp under the + hyperbolic name "gestalt pattern matching". The basic idea is to find + the longest contiguous matching subsequence that contains no "junk" + elements (R-O doesn't address junk). The same idea is then applied + recursively to the pieces of the sequences to the left and to the right + of the matching subsequence. This does not yield minimal edit + sequences, but does tend to yield matches that "look right" to people. + + SequenceMatcher tries to compute a "human-friendly diff" between two + sequences. Unlike e.g. UNIX(tm) diff, the fundamental notion is the + longest *contiguous* & junk-free matching subsequence. That's what + catches peoples' eyes. The Windows(tm) windiff has another interesting + notion, pairing up elements that appear uniquely in each sequence. + That, and the method here, appear to yield more intuitive difference + reports than does diff. This method appears to be the least vulnerable + to synching up on blocks of "junk lines", though (like blank lines in + ordinary text files, or maybe "

" lines in HTML files). That may be + because this is the only method of the 3 that has a *concept* of + "junk" . + + Example, comparing two strings, and considering blanks to be "junk": + + >>> s = SequenceMatcher(lambda x: x == " ", + ... "private Thread currentThread;", + ... "private volatile Thread currentThread;") + >>> + + .ratio() returns a float in [0, 1], measuring the "similarity" of the + sequences. As a rule of thumb, a .ratio() value over 0.6 means the + sequences are close matches: + + >>> print(round(s.ratio(), 3)) + 0.866 + >>> + + If you're only interested in where the sequences match, + .get_matching_blocks() is handy: + + >>> for block in s.get_matching_blocks(): + ... print("a[%d] and b[%d] match for %d elements" % block) + a[0] and b[0] match for 8 elements + a[8] and b[17] match for 21 elements + a[29] and b[38] match for 0 elements + + Note that the last tuple returned by .get_matching_blocks() is always a + dummy, (len(a), len(b), 0), and this is the only case in which the last + tuple element (number of elements matched) is 0. + + If you want to know how to change the first sequence into the second, + use .get_opcodes(): + + >>> for opcode in s.get_opcodes(): + ... print("%6s a[%d:%d] b[%d:%d]" % opcode) + equal a[0:8] b[0:8] + insert a[8:8] b[8:17] + equal a[8:29] b[17:38] + + See the Differ class for a fancy human-friendly file differencer, which + uses SequenceMatcher both to compare sequences of lines, and to compare + sequences of characters within similar (near-matching) lines. + + See also function get_close_matches() in this module, which shows how + simple code building on SequenceMatcher can be used to do useful work. + + Timing: Basic R-O is cubic time worst case and quadratic time expected + case. SequenceMatcher is quadratic time for the worst case and has + expected-case behavior dependent in a complicated way on how many + elements the sequences have in common; best case time is linear. + + Methods: + + __init__(isjunk=None, a='', b='') + Construct a SequenceMatcher. + + set_seqs(a, b) + Set the two sequences to be compared. + + set_seq1(a) + Set the first sequence to be compared. + + set_seq2(b) + Set the second sequence to be compared. + + find_longest_match(alo, ahi, blo, bhi) + Find longest matching block in a[alo:ahi] and b[blo:bhi]. + + get_matching_blocks() + Return list of triples describing matching subsequences. + + get_opcodes() + Return list of 5-tuples describing how to turn a into b. + + ratio() + Return a measure of the sequences' similarity (float in [0,1]). + + quick_ratio() + Return an upper bound on .ratio() relatively quickly. + + real_quick_ratio() + Return an upper bound on ratio() very quickly. + """ + + def __init__(self, isjunk=None, a='', b='', autojunk=True): + """Construct a SequenceMatcher. + + Optional arg isjunk is None (the default), or a one-argument + function that takes a sequence element and returns true iff the + element is junk. None is equivalent to passing "lambda x: 0", i.e. + no elements are considered to be junk. For example, pass + lambda x: x in " \\t" + if you're comparing lines as sequences of characters, and don't + want to synch up on blanks or hard tabs. + + Optional arg a is the first of two sequences to be compared. By + default, an empty string. The elements of a must be hashable. See + also .set_seqs() and .set_seq1(). + + Optional arg b is the second of two sequences to be compared. By + default, an empty string. The elements of b must be hashable. See + also .set_seqs() and .set_seq2(). + + Optional arg autojunk should be set to False to disable the + "automatic junk heuristic" that treats popular elements as junk + (see module documentation for more information). + """ + + # Members: + # a + # first sequence + # b + # second sequence; differences are computed as "what do + # we need to do to 'a' to change it into 'b'?" + # b2j + # for x in b, b2j[x] is a list of the indices (into b) + # at which x appears; junk and popular elements do not appear + # fullbcount + # for x in b, fullbcount[x] == the number of times x + # appears in b; only materialized if really needed (used + # only for computing quick_ratio()) + # matching_blocks + # a list of (i, j, k) triples, where a[i:i+k] == b[j:j+k]; + # ascending & non-overlapping in i and in j; terminated by + # a dummy (len(a), len(b), 0) sentinel + # opcodes + # a list of (tag, i1, i2, j1, j2) tuples, where tag is + # one of + # 'replace' a[i1:i2] should be replaced by b[j1:j2] + # 'delete' a[i1:i2] should be deleted + # 'insert' b[j1:j2] should be inserted + # 'equal' a[i1:i2] == b[j1:j2] + # isjunk + # a user-supplied function taking a sequence element and + # returning true iff the element is "junk" -- this has + # subtle but helpful effects on the algorithm, which I'll + # get around to writing up someday <0.9 wink>. + # DON'T USE! Only __chain_b uses this. Use "in self.bjunk". + # bjunk + # the items in b for which isjunk is True. + # bpopular + # nonjunk items in b treated as junk by the heuristic (if used). + + self.isjunk = isjunk + self.a = self.b = None + self.autojunk = autojunk + self.set_seqs(a, b) + + def set_seqs(self, a, b): + """Set the two sequences to be compared. + + >>> s = SequenceMatcher() + >>> s.set_seqs("abcd", "bcde") + >>> s.ratio() + 0.75 + """ + + self.set_seq1(a) + self.set_seq2(b) + + def set_seq1(self, a): + """Set the first sequence to be compared. + + The second sequence to be compared is not changed. + + >>> s = SequenceMatcher(None, "abcd", "bcde") + >>> s.ratio() + 0.75 + >>> s.set_seq1("bcde") + >>> s.ratio() + 1.0 + >>> + + SequenceMatcher computes and caches detailed information about the + second sequence, so if you want to compare one sequence S against + many sequences, use .set_seq2(S) once and call .set_seq1(x) + repeatedly for each of the other sequences. + + See also set_seqs() and set_seq2(). + """ + + if a is self.a: + return + self.a = a + self.matching_blocks = self.opcodes = None + + def set_seq2(self, b): + """Set the second sequence to be compared. + + The first sequence to be compared is not changed. + + >>> s = SequenceMatcher(None, "abcd", "bcde") + >>> s.ratio() + 0.75 + >>> s.set_seq2("abcd") + >>> s.ratio() + 1.0 + >>> + + SequenceMatcher computes and caches detailed information about the + second sequence, so if you want to compare one sequence S against + many sequences, use .set_seq2(S) once and call .set_seq1(x) + repeatedly for each of the other sequences. + + See also set_seqs() and set_seq1(). + """ + + if b is self.b: + return + self.b = b + self.matching_blocks = self.opcodes = None + self.fullbcount = None + self.__chain_b() + + # For each element x in b, set b2j[x] to a list of the indices in + # b where x appears; the indices are in increasing order; note that + # the number of times x appears in b is len(b2j[x]) ... + # when self.isjunk is defined, junk elements don't show up in this + # map at all, which stops the central find_longest_match method + # from starting any matching block at a junk element ... + # b2j also does not contain entries for "popular" elements, meaning + # elements that account for more than 1 + 1% of the total elements, and + # when the sequence is reasonably large (>= 200 elements); this can + # be viewed as an adaptive notion of semi-junk, and yields an enormous + # speedup when, e.g., comparing program files with hundreds of + # instances of "return NULL;" ... + # note that this is only called when b changes; so for cross-product + # kinds of matches, it's best to call set_seq2 once, then set_seq1 + # repeatedly + + def __chain_b(self): + # Because isjunk is a user-defined (not C) function, and we test + # for junk a LOT, it's important to minimize the number of calls. + # Before the tricks described here, __chain_b was by far the most + # time-consuming routine in the whole module! If anyone sees + # Jim Roskind, thank him again for profile.py -- I never would + # have guessed that. + # The first trick is to build b2j ignoring the possibility + # of junk. I.e., we don't call isjunk at all yet. Throwing + # out the junk later is much cheaper than building b2j "right" + # from the start. + b = self.b + self.b2j = b2j = {} + + for i, elt in enumerate(b): + indices = b2j.setdefault(elt, []) + indices.append(i) + + # Purge junk elements + self.bjunk = junk = set() + isjunk = self.isjunk + if isjunk: + for elt in b2j.keys(): + if isjunk(elt): + junk.add(elt) + for elt in junk: # separate loop avoids separate list of keys + del b2j[elt] + + # Purge popular elements that are not junk + self.bpopular = popular = set() + n = len(b) + if self.autojunk and n >= 200: + ntest = n // 100 + 1 + for elt, idxs in b2j.items(): + if len(idxs) > ntest: + popular.add(elt) + for elt in popular: # ditto; as fast for 1% deletion + del b2j[elt] + + def find_longest_match(self, alo, ahi, blo, bhi): + """Find longest matching block in a[alo:ahi] and b[blo:bhi]. + + If isjunk is not defined: + + Return (i,j,k) such that a[i:i+k] is equal to b[j:j+k], where + alo <= i <= i+k <= ahi + blo <= j <= j+k <= bhi + and for all (i',j',k') meeting those conditions, + k >= k' + i <= i' + and if i == i', j <= j' + + In other words, of all maximal matching blocks, return one that + starts earliest in a, and of all those maximal matching blocks that + start earliest in a, return the one that starts earliest in b. + + >>> s = SequenceMatcher(None, " abcd", "abcd abcd") + >>> s.find_longest_match(0, 5, 0, 9) + Match(a=0, b=4, size=5) + + If isjunk is defined, first the longest matching block is + determined as above, but with the additional restriction that no + junk element appears in the block. Then that block is extended as + far as possible by matching (only) junk elements on both sides. So + the resulting block never matches on junk except as identical junk + happens to be adjacent to an "interesting" match. + + Here's the same example as before, but considering blanks to be + junk. That prevents " abcd" from matching the " abcd" at the tail + end of the second sequence directly. Instead only the "abcd" can + match, and matches the leftmost "abcd" in the second sequence: + + >>> s = SequenceMatcher(lambda x: x==" ", " abcd", "abcd abcd") + >>> s.find_longest_match(0, 5, 0, 9) + Match(a=1, b=0, size=4) + + If no blocks match, return (alo, blo, 0). + + >>> s = SequenceMatcher(None, "ab", "c") + >>> s.find_longest_match(0, 2, 0, 1) + Match(a=0, b=0, size=0) + """ + + # CAUTION: stripping common prefix or suffix would be incorrect. + # E.g., + # ab + # acab + # Longest matching block is "ab", but if common prefix is + # stripped, it's "a" (tied with "b"). UNIX(tm) diff does so + # strip, so ends up claiming that ab is changed to acab by + # inserting "ca" in the middle. That's minimal but unintuitive: + # "it's obvious" that someone inserted "ac" at the front. + # Windiff ends up at the same place as diff, but by pairing up + # the unique 'b's and then matching the first two 'a's. + + a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.bjunk.__contains__ + besti, bestj, bestsize = alo, blo, 0 + # find longest junk-free match + # during an iteration of the loop, j2len[j] = length of longest + # junk-free match ending with a[i-1] and b[j] + j2len = {} + nothing = [] + for i in range(alo, ahi): + # look at all instances of a[i] in b; note that because + # b2j has no junk keys, the loop is skipped if a[i] is junk + j2lenget = j2len.get + newj2len = {} + for j in b2j.get(a[i], nothing): + # a[i] matches b[j] + if j < blo: + continue + if j >= bhi: + break + k = newj2len[j] = j2lenget(j-1, 0) + 1 + if k > bestsize: + besti, bestj, bestsize = i-k+1, j-k+1, k + j2len = newj2len + + # Extend the best by non-junk elements on each end. In particular, + # "popular" non-junk elements aren't in b2j, which greatly speeds + # the inner loop above, but also means "the best" match so far + # doesn't contain any junk *or* popular non-junk elements. + while besti > alo and bestj > blo and \ + not isbjunk(b[bestj-1]) and \ + a[besti-1] == b[bestj-1]: + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + while besti+bestsize < ahi and bestj+bestsize < bhi and \ + not isbjunk(b[bestj+bestsize]) and \ + a[besti+bestsize] == b[bestj+bestsize]: + bestsize += 1 + + # Now that we have a wholly interesting match (albeit possibly + # empty!), we may as well suck up the matching junk on each + # side of it too. Can't think of a good reason not to, and it + # saves post-processing the (possibly considerable) expense of + # figuring out what to do with it. In the case of an empty + # interesting match, this is clearly the right thing to do, + # because no other kind of match is possible in the regions. + while besti > alo and bestj > blo and \ + isbjunk(b[bestj-1]) and \ + a[besti-1] == b[bestj-1]: + besti, bestj, bestsize = besti-1, bestj-1, bestsize+1 + while besti+bestsize < ahi and bestj+bestsize < bhi and \ + isbjunk(b[bestj+bestsize]) and \ + a[besti+bestsize] == b[bestj+bestsize]: + bestsize = bestsize + 1 + + return Match(besti, bestj, bestsize) + + def get_matching_blocks(self): + """Return list of triples describing matching subsequences. + + Each triple is of the form (i, j, n), and means that + a[i:i+n] == b[j:j+n]. The triples are monotonically increasing in + i and in j. New in Python 2.5, it's also guaranteed that if + (i, j, n) and (i', j', n') are adjacent triples in the list, and + the second is not the last triple in the list, then i+n != i' or + j+n != j'. IOW, adjacent triples never describe adjacent equal + blocks. + + The last triple is a dummy, (len(a), len(b), 0), and is the only + triple with n==0. + + >>> s = SequenceMatcher(None, "abxcd", "abcd") + >>> list(s.get_matching_blocks()) + [Match(a=0, b=0, size=2), Match(a=3, b=2, size=2), Match(a=5, b=4, size=0)] + """ + + if self.matching_blocks is not None: + return self.matching_blocks + la, lb = len(self.a), len(self.b) + + # This is most naturally expressed as a recursive algorithm, but + # at least one user bumped into extreme use cases that exceeded + # the recursion limit on their box. So, now we maintain a list + # ('queue`) of blocks we still need to look at, and append partial + # results to `matching_blocks` in a loop; the matches are sorted + # at the end. + queue = [(0, la, 0, lb)] + matching_blocks = [] + while queue: + alo, ahi, blo, bhi = queue.pop() + i, j, k = x = self.find_longest_match(alo, ahi, blo, bhi) + # a[alo:i] vs b[blo:j] unknown + # a[i:i+k] same as b[j:j+k] + # a[i+k:ahi] vs b[j+k:bhi] unknown + if k: # if k is 0, there was no matching block + matching_blocks.append(x) + if alo < i and blo < j: + queue.append((alo, i, blo, j)) + if i+k < ahi and j+k < bhi: + queue.append((i+k, ahi, j+k, bhi)) + matching_blocks.sort() + + # It's possible that we have adjacent equal blocks in the + # matching_blocks list now. Starting with 2.5, this code was added + # to collapse them. + i1 = j1 = k1 = 0 + non_adjacent = [] + for i2, j2, k2 in matching_blocks: + # Is this block adjacent to i1, j1, k1? + if i1 + k1 == i2 and j1 + k1 == j2: + # Yes, so collapse them -- this just increases the length of + # the first block by the length of the second, and the first + # block so lengthened remains the block to compare against. + k1 += k2 + else: + # Not adjacent. Remember the first block (k1==0 means it's + # the dummy we started with), and make the second block the + # new block to compare against. + if k1: + non_adjacent.append((i1, j1, k1)) + i1, j1, k1 = i2, j2, k2 + if k1: + non_adjacent.append((i1, j1, k1)) + + non_adjacent.append( (la, lb, 0) ) + self.matching_blocks = list(map(Match._make, non_adjacent)) + return self.matching_blocks + + def get_opcodes(self): + """Return list of 5-tuples describing how to turn a into b. + + Each tuple is of the form (tag, i1, i2, j1, j2). The first tuple + has i1 == j1 == 0, and remaining tuples have i1 == the i2 from the + tuple preceding it, and likewise for j1 == the previous j2. + + The tags are strings, with these meanings: + + 'replace': a[i1:i2] should be replaced by b[j1:j2] + 'delete': a[i1:i2] should be deleted. + Note that j1==j2 in this case. + 'insert': b[j1:j2] should be inserted at a[i1:i1]. + Note that i1==i2 in this case. + 'equal': a[i1:i2] == b[j1:j2] + + >>> a = "qabxcd" + >>> b = "abycdf" + >>> s = SequenceMatcher(None, a, b) + >>> for tag, i1, i2, j1, j2 in s.get_opcodes(): + ... print(("%7s a[%d:%d] (%s) b[%d:%d] (%s)" % + ... (tag, i1, i2, a[i1:i2], j1, j2, b[j1:j2]))) + delete a[0:1] (q) b[0:0] () + equal a[1:3] (ab) b[0:2] (ab) + replace a[3:4] (x) b[2:3] (y) + equal a[4:6] (cd) b[3:5] (cd) + insert a[6:6] () b[5:6] (f) + """ + + if self.opcodes is not None: + return self.opcodes + i = j = 0 + self.opcodes = answer = [] + for ai, bj, size in self.get_matching_blocks(): + # invariant: we've pumped out correct diffs to change + # a[:i] into b[:j], and the next matching block is + # a[ai:ai+size] == b[bj:bj+size]. So we need to pump + # out a diff to change a[i:ai] into b[j:bj], pump out + # the matching block, and move (i,j) beyond the match + tag = '' + if i < ai and j < bj: + tag = 'replace' + elif i < ai: + tag = 'delete' + elif j < bj: + tag = 'insert' + if tag: + answer.append( (tag, i, ai, j, bj) ) + i, j = ai+size, bj+size + # the list of matching blocks is terminated by a + # sentinel with size 0 + if size: + answer.append( ('equal', ai, i, bj, j) ) + return answer + + def get_grouped_opcodes(self, n=3): + """ Isolate change clusters by eliminating ranges with no changes. + + Return a generator of groups with up to n lines of context. + Each group is in the same format as returned by get_opcodes(). + + >>> from pprint import pprint + >>> a = list(map(str, range(1,40))) + >>> b = a[:] + >>> b[8:8] = ['i'] # Make an insertion + >>> b[20] += 'x' # Make a replacement + >>> b[23:28] = [] # Make a deletion + >>> b[30] += 'y' # Make another replacement + >>> pprint(list(SequenceMatcher(None,a,b).get_grouped_opcodes())) + [[('equal', 5, 8, 5, 8), ('insert', 8, 8, 8, 9), ('equal', 8, 11, 9, 12)], + [('equal', 16, 19, 17, 20), + ('replace', 19, 20, 20, 21), + ('equal', 20, 22, 21, 23), + ('delete', 22, 27, 23, 23), + ('equal', 27, 30, 23, 26)], + [('equal', 31, 34, 27, 30), + ('replace', 34, 35, 30, 31), + ('equal', 35, 38, 31, 34)]] + """ + + codes = self.get_opcodes() + if not codes: + codes = [("equal", 0, 1, 0, 1)] + # Fixup leading and trailing groups if they show no changes. + if codes[0][0] == 'equal': + tag, i1, i2, j1, j2 = codes[0] + codes[0] = tag, max(i1, i2-n), i2, max(j1, j2-n), j2 + if codes[-1][0] == 'equal': + tag, i1, i2, j1, j2 = codes[-1] + codes[-1] = tag, i1, min(i2, i1+n), j1, min(j2, j1+n) + + nn = n + n + group = [] + for tag, i1, i2, j1, j2 in codes: + # End the current group and start a new one whenever + # there is a large range with no changes. + if tag == 'equal' and i2-i1 > nn: + group.append((tag, i1, min(i2, i1+n), j1, min(j2, j1+n))) + yield group + group = [] + i1, j1 = max(i1, i2-n), max(j1, j2-n) + group.append((tag, i1, i2, j1 ,j2)) + if group and not (len(group)==1 and group[0][0] == 'equal'): + yield group + + def ratio(self): + """Return a measure of the sequences' similarity (float in [0,1]). + + Where T is the total number of elements in both sequences, and + M is the number of matches, this is 2.0*M / T. + Note that this is 1 if the sequences are identical, and 0 if + they have nothing in common. + + .ratio() is expensive to compute if you haven't already computed + .get_matching_blocks() or .get_opcodes(), in which case you may + want to try .quick_ratio() or .real_quick_ratio() first to get an + upper bound. + + >>> s = SequenceMatcher(None, "abcd", "bcde") + >>> s.ratio() + 0.75 + >>> s.quick_ratio() + 0.75 + >>> s.real_quick_ratio() + 1.0 + """ + + matches = sum(triple[-1] for triple in self.get_matching_blocks()) + return _calculate_ratio(matches, len(self.a) + len(self.b)) + + def quick_ratio(self): + """Return an upper bound on ratio() relatively quickly. + + This isn't defined beyond that it is an upper bound on .ratio(), and + is faster to compute. + """ + + # viewing a and b as multisets, set matches to the cardinality + # of their intersection; this counts the number of matches + # without regard to order, so is clearly an upper bound + if self.fullbcount is None: + self.fullbcount = fullbcount = {} + for elt in self.b: + fullbcount[elt] = fullbcount.get(elt, 0) + 1 + fullbcount = self.fullbcount + # avail[x] is the number of times x appears in 'b' less the + # number of times we've seen it in 'a' so far ... kinda + avail = {} + availhas, matches = avail.__contains__, 0 + for elt in self.a: + if availhas(elt): + numb = avail[elt] + else: + numb = fullbcount.get(elt, 0) + avail[elt] = numb - 1 + if numb > 0: + matches = matches + 1 + return _calculate_ratio(matches, len(self.a) + len(self.b)) + + def real_quick_ratio(self): + """Return an upper bound on ratio() very quickly. + + This isn't defined beyond that it is an upper bound on .ratio(), and + is faster to compute than either .ratio() or .quick_ratio(). + """ + + la, lb = len(self.a), len(self.b) + # can't have more matches than the number of elements in the + # shorter sequence + return _calculate_ratio(min(la, lb), la + lb) + +def get_close_matches(word, possibilities, n=3, cutoff=0.6): + """Use SequenceMatcher to return list of the best "good enough" matches. + + word is a sequence for which close matches are desired (typically a + string). + + possibilities is a list of sequences against which to match word + (typically a list of strings). + + Optional arg n (default 3) is the maximum number of close matches to + return. n must be > 0. + + Optional arg cutoff (default 0.6) is a float in [0, 1]. Possibilities + that don't score at least that similar to word are ignored. + + The best (no more than n) matches among the possibilities are returned + in a list, sorted by similarity score, most similar first. + + >>> get_close_matches("appel", ["ape", "apple", "peach", "puppy"]) + ['apple', 'ape'] + >>> import keyword as _keyword + >>> get_close_matches("wheel", _keyword.kwlist) + ['while'] + >>> get_close_matches("Apple", _keyword.kwlist) + [] + >>> get_close_matches("accept", _keyword.kwlist) + ['except'] + """ + + if not n > 0: + raise ValueError("n must be > 0: %r" % (n,)) + if not 0.0 <= cutoff <= 1.0: + raise ValueError("cutoff must be in [0.0, 1.0]: %r" % (cutoff,)) + result = [] + s = SequenceMatcher() + s.set_seq2(word) + for x in possibilities: + s.set_seq1(x) + if s.real_quick_ratio() >= cutoff and \ + s.quick_ratio() >= cutoff and \ + s.ratio() >= cutoff: + result.append((s.ratio(), x)) + + # Move the best scorers to head of list + result = _nlargest(n, result) + # Strip scores for the best n matches + return [x for score, x in result] + +def _count_leading(line, ch): + """ + Return number of `ch` characters at the start of `line`. + + Example: + + >>> _count_leading(' abc', ' ') + 3 + """ + + i, n = 0, len(line) + while i < n and line[i] == ch: + i += 1 + return i + +class Differ: + r""" + Differ is a class for comparing sequences of lines of text, and + producing human-readable differences or deltas. Differ uses + SequenceMatcher both to compare sequences of lines, and to compare + sequences of characters within similar (near-matching) lines. + + Each line of a Differ delta begins with a two-letter code: + + '- ' line unique to sequence 1 + '+ ' line unique to sequence 2 + ' ' line common to both sequences + '? ' line not present in either input sequence + + Lines beginning with '? ' attempt to guide the eye to intraline + differences, and were not present in either input sequence. These lines + can be confusing if the sequences contain tab characters. + + Note that Differ makes no claim to produce a *minimal* diff. To the + contrary, minimal diffs are often counter-intuitive, because they synch + up anywhere possible, sometimes accidental matches 100 pages apart. + Restricting synch points to contiguous matches preserves some notion of + locality, at the occasional cost of producing a longer diff. + + Example: Comparing two texts. + + First we set up the texts, sequences of individual single-line strings + ending with newlines (such sequences can also be obtained from the + `readlines()` method of file-like objects): + + >>> text1 = ''' 1. Beautiful is better than ugly. + ... 2. Explicit is better than implicit. + ... 3. Simple is better than complex. + ... 4. Complex is better than complicated. + ... '''.splitlines(keepends=True) + >>> len(text1) + 4 + >>> text1[0][-1] + '\n' + >>> text2 = ''' 1. Beautiful is better than ugly. + ... 3. Simple is better than complex. + ... 4. Complicated is better than complex. + ... 5. Flat is better than nested. + ... '''.splitlines(keepends=True) + + Next we instantiate a Differ object: + + >>> d = Differ() + + Note that when instantiating a Differ object we may pass functions to + filter out line and character 'junk'. See Differ.__init__ for details. + + Finally, we compare the two: + + >>> result = list(d.compare(text1, text2)) + + 'result' is a list of strings, so let's pretty-print it: + + >>> from pprint import pprint as _pprint + >>> _pprint(result) + [' 1. Beautiful is better than ugly.\n', + '- 2. Explicit is better than implicit.\n', + '- 3. Simple is better than complex.\n', + '+ 3. Simple is better than complex.\n', + '? ++\n', + '- 4. Complex is better than complicated.\n', + '? ^ ---- ^\n', + '+ 4. Complicated is better than complex.\n', + '? ++++ ^ ^\n', + '+ 5. Flat is better than nested.\n'] + + As a single multi-line string it looks like this: + + >>> print(''.join(result), end="") + 1. Beautiful is better than ugly. + - 2. Explicit is better than implicit. + - 3. Simple is better than complex. + + 3. Simple is better than complex. + ? ++ + - 4. Complex is better than complicated. + ? ^ ---- ^ + + 4. Complicated is better than complex. + ? ++++ ^ ^ + + 5. Flat is better than nested. + + Methods: + + __init__(linejunk=None, charjunk=None) + Construct a text differencer, with optional filters. + + compare(a, b) + Compare two sequences of lines; generate the resulting delta. + """ + + def __init__(self, linejunk=None, charjunk=None): + """ + Construct a text differencer, with optional filters. + + The two optional keyword parameters are for filter functions: + + - `linejunk`: A function that should accept a single string argument, + and return true iff the string is junk. The module-level function + `IS_LINE_JUNK` may be used to filter out lines without visible + characters, except for at most one splat ('#'). It is recommended + to leave linejunk None; the underlying SequenceMatcher class has + an adaptive notion of "noise" lines that's better than any static + definition the author has ever been able to craft. + + - `charjunk`: A function that should accept a string of length 1. The + module-level function `IS_CHARACTER_JUNK` may be used to filter out + whitespace characters (a blank or tab; **note**: bad idea to include + newline in this!). Use of IS_CHARACTER_JUNK is recommended. + """ + + self.linejunk = linejunk + self.charjunk = charjunk + + def compare(self, a, b): + r""" + Compare two sequences of lines; generate the resulting delta. + + Each sequence must contain individual single-line strings ending with + newlines. Such sequences can be obtained from the `readlines()` method + of file-like objects. The delta generated also consists of newline- + terminated strings, ready to be printed as-is via the writeline() + method of a file-like object. + + Example: + + >>> print(''.join(Differ().compare('one\ntwo\nthree\n'.splitlines(True), + ... 'ore\ntree\nemu\n'.splitlines(True))), + ... end="") + - one + ? ^ + + ore + ? ^ + - two + - three + ? - + + tree + + emu + """ + + cruncher = SequenceMatcher(self.linejunk, a, b) + for tag, alo, ahi, blo, bhi in cruncher.get_opcodes(): + if tag == 'replace': + g = self._fancy_replace(a, alo, ahi, b, blo, bhi) + elif tag == 'delete': + g = self._dump('-', a, alo, ahi) + elif tag == 'insert': + g = self._dump('+', b, blo, bhi) + elif tag == 'equal': + g = self._dump(' ', a, alo, ahi) + else: + raise ValueError('unknown tag %r' % (tag,)) + + yield from g + + def _dump(self, tag, x, lo, hi): + """Generate comparison results for a same-tagged range.""" + for i in range(lo, hi): + yield '%s %s' % (tag, x[i]) + + def _plain_replace(self, a, alo, ahi, b, blo, bhi): + assert alo < ahi and blo < bhi + # dump the shorter block first -- reduces the burden on short-term + # memory if the blocks are of very different sizes + if bhi - blo < ahi - alo: + first = self._dump('+', b, blo, bhi) + second = self._dump('-', a, alo, ahi) + else: + first = self._dump('-', a, alo, ahi) + second = self._dump('+', b, blo, bhi) + + for g in first, second: + yield from g + + def _fancy_replace(self, a, alo, ahi, b, blo, bhi): + r""" + When replacing one block of lines with another, search the blocks + for *similar* lines; the best-matching pair (if any) is used as a + synch point, and intraline difference marking is done on the + similar pair. Lots of work, but often worth it. + + Example: + + >>> d = Differ() + >>> results = d._fancy_replace(['abcDefghiJkl\n'], 0, 1, + ... ['abcdefGhijkl\n'], 0, 1) + >>> print(''.join(results), end="") + - abcDefghiJkl + ? ^ ^ ^ + + abcdefGhijkl + ? ^ ^ ^ + """ + + # don't synch up unless the lines have a similarity score of at + # least cutoff; best_ratio tracks the best score seen so far + best_ratio, cutoff = 0.74, 0.75 + cruncher = SequenceMatcher(self.charjunk) + eqi, eqj = None, None # 1st indices of equal lines (if any) + + # search for the pair that matches best without being identical + # (identical lines must be junk lines, & we don't want to synch up + # on junk -- unless we have to) + for j in range(blo, bhi): + bj = b[j] + cruncher.set_seq2(bj) + for i in range(alo, ahi): + ai = a[i] + if ai == bj: + if eqi is None: + eqi, eqj = i, j + continue + cruncher.set_seq1(ai) + # computing similarity is expensive, so use the quick + # upper bounds first -- have seen this speed up messy + # compares by a factor of 3. + # note that ratio() is only expensive to compute the first + # time it's called on a sequence pair; the expensive part + # of the computation is cached by cruncher + if cruncher.real_quick_ratio() > best_ratio and \ + cruncher.quick_ratio() > best_ratio and \ + cruncher.ratio() > best_ratio: + best_ratio, best_i, best_j = cruncher.ratio(), i, j + if best_ratio < cutoff: + # no non-identical "pretty close" pair + if eqi is None: + # no identical pair either -- treat it as a straight replace + yield from self._plain_replace(a, alo, ahi, b, blo, bhi) + return + # no close pair, but an identical pair -- synch up on that + best_i, best_j, best_ratio = eqi, eqj, 1.0 + else: + # there's a close pair, so forget the identical pair (if any) + eqi = None + + # a[best_i] very similar to b[best_j]; eqi is None iff they're not + # identical + + # pump out diffs from before the synch point + yield from self._fancy_helper(a, alo, best_i, b, blo, best_j) + + # do intraline marking on the synch pair + aelt, belt = a[best_i], b[best_j] + if eqi is None: + # pump out a '-', '?', '+', '?' quad for the synched lines + atags = btags = "" + cruncher.set_seqs(aelt, belt) + for tag, ai1, ai2, bj1, bj2 in cruncher.get_opcodes(): + la, lb = ai2 - ai1, bj2 - bj1 + if tag == 'replace': + atags += '^' * la + btags += '^' * lb + elif tag == 'delete': + atags += '-' * la + elif tag == 'insert': + btags += '+' * lb + elif tag == 'equal': + atags += ' ' * la + btags += ' ' * lb + else: + raise ValueError('unknown tag %r' % (tag,)) + yield from self._qformat(aelt, belt, atags, btags) + else: + # the synch pair is identical + yield ' ' + aelt + + # pump out diffs from after the synch point + yield from self._fancy_helper(a, best_i+1, ahi, b, best_j+1, bhi) + + def _fancy_helper(self, a, alo, ahi, b, blo, bhi): + g = [] + if alo < ahi: + if blo < bhi: + g = self._fancy_replace(a, alo, ahi, b, blo, bhi) + else: + g = self._dump('-', a, alo, ahi) + elif blo < bhi: + g = self._dump('+', b, blo, bhi) + + yield from g + + def _qformat(self, aline, bline, atags, btags): + r""" + Format "?" output and deal with leading tabs. + + Example: + + >>> d = Differ() + >>> results = d._qformat('\tabcDefghiJkl\n', '\tabcdefGhijkl\n', + ... ' ^ ^ ^ ', ' ^ ^ ^ ') + >>> for line in results: print(repr(line)) + ... + '- \tabcDefghiJkl\n' + '? \t ^ ^ ^\n' + '+ \tabcdefGhijkl\n' + '? \t ^ ^ ^\n' + """ + + # Can hurt, but will probably help most of the time. + common = min(_count_leading(aline, "\t"), + _count_leading(bline, "\t")) + common = min(common, _count_leading(atags[:common], " ")) + common = min(common, _count_leading(btags[:common], " ")) + atags = atags[common:].rstrip() + btags = btags[common:].rstrip() + + yield "- " + aline + if atags: + yield "? %s%s\n" % ("\t" * common, atags) + + yield "+ " + bline + if btags: + yield "? %s%s\n" % ("\t" * common, btags) + +# With respect to junk, an earlier version of ndiff simply refused to +# *start* a match with a junk element. The result was cases like this: +# before: private Thread currentThread; +# after: private volatile Thread currentThread; +# If you consider whitespace to be junk, the longest contiguous match +# not starting with junk is "e Thread currentThread". So ndiff reported +# that "e volatil" was inserted between the 't' and the 'e' in "private". +# While an accurate view, to people that's absurd. The current version +# looks for matching blocks that are entirely junk-free, then extends the +# longest one of those as far as possible but only with matching junk. +# So now "currentThread" is matched, then extended to suck up the +# preceding blank; then "private" is matched, and extended to suck up the +# following blank; then "Thread" is matched; and finally ndiff reports +# that "volatile " was inserted before "Thread". The only quibble +# remaining is that perhaps it was really the case that " volatile" +# was inserted after "private". I can live with that . + +import re + +def IS_LINE_JUNK(line, pat=re.compile(r"\s*(?:#\s*)?$").match): + r""" + Return 1 for ignorable line: iff `line` is blank or contains a single '#'. + + Examples: + + >>> IS_LINE_JUNK('\n') + True + >>> IS_LINE_JUNK(' # \n') + True + >>> IS_LINE_JUNK('hello\n') + False + """ + + return pat(line) is not None + +def IS_CHARACTER_JUNK(ch, ws=" \t"): + r""" + Return 1 for ignorable character: iff `ch` is a space or tab. + + Examples: + + >>> IS_CHARACTER_JUNK(' ') + True + >>> IS_CHARACTER_JUNK('\t') + True + >>> IS_CHARACTER_JUNK('\n') + False + >>> IS_CHARACTER_JUNK('x') + False + """ + + return ch in ws + + +######################################################################## +### Unified Diff +######################################################################## + +def _format_range_unified(start, stop): + 'Convert range to the "ed" format' + # Per the diff spec at http://www.unix.org/single_unix_specification/ + beginning = start + 1 # lines start numbering with one + length = stop - start + if length == 1: + return '{}'.format(beginning) + if not length: + beginning -= 1 # empty ranges begin at line just before the range + return '{},{}'.format(beginning, length) + +def unified_diff(a, b, fromfile='', tofile='', fromfiledate='', + tofiledate='', n=3, lineterm='\n'): + r""" + Compare two sequences of lines; generate the delta as a unified diff. + + Unified diffs are a compact way of showing line changes and a few + lines of context. The number of context lines is set by 'n' which + defaults to three. + + By default, the diff control lines (those with ---, +++, or @@) are + created with a trailing newline. This is helpful so that inputs + created from file.readlines() result in diffs that are suitable for + file.writelines() since both the inputs and outputs have trailing + newlines. + + For inputs that do not have trailing newlines, set the lineterm + argument to "" so that the output will be uniformly newline free. + + The unidiff format normally has a header for filenames and modification + times. Any or all of these may be specified using strings for + 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. + The modification times are normally expressed in the ISO 8601 format. + + Example: + + >>> for line in unified_diff('one two three four'.split(), + ... 'zero one tree four'.split(), 'Original', 'Current', + ... '2005-01-26 23:30:50', '2010-04-02 10:20:52', + ... lineterm=''): + ... print(line) # doctest: +NORMALIZE_WHITESPACE + --- Original 2005-01-26 23:30:50 + +++ Current 2010-04-02 10:20:52 + @@ -1,4 +1,4 @@ + +zero + one + -two + -three + +tree + four + """ + + _check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm) + started = False + for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n): + if not started: + started = True + fromdate = '\t{}'.format(fromfiledate) if fromfiledate else '' + todate = '\t{}'.format(tofiledate) if tofiledate else '' + yield '--- {}{}{}'.format(fromfile, fromdate, lineterm) + yield '+++ {}{}{}'.format(tofile, todate, lineterm) + + first, last = group[0], group[-1] + file1_range = _format_range_unified(first[1], last[2]) + file2_range = _format_range_unified(first[3], last[4]) + yield '@@ -{} +{} @@{}'.format(file1_range, file2_range, lineterm) + + for tag, i1, i2, j1, j2 in group: + if tag == 'equal': + for line in a[i1:i2]: + yield ' ' + line + continue + if tag in {'replace', 'delete'}: + for line in a[i1:i2]: + yield '-' + line + if tag in {'replace', 'insert'}: + for line in b[j1:j2]: + yield '+' + line + + +######################################################################## +### Context Diff +######################################################################## + +def _format_range_context(start, stop): + 'Convert range to the "ed" format' + # Per the diff spec at http://www.unix.org/single_unix_specification/ + beginning = start + 1 # lines start numbering with one + length = stop - start + if not length: + beginning -= 1 # empty ranges begin at line just before the range + if length <= 1: + return '{}'.format(beginning) + return '{},{}'.format(beginning, beginning + length - 1) + +# See http://www.unix.org/single_unix_specification/ +def context_diff(a, b, fromfile='', tofile='', + fromfiledate='', tofiledate='', n=3, lineterm='\n'): + r""" + Compare two sequences of lines; generate the delta as a context diff. + + Context diffs are a compact way of showing line changes and a few + lines of context. The number of context lines is set by 'n' which + defaults to three. + + By default, the diff control lines (those with *** or ---) are + created with a trailing newline. This is helpful so that inputs + created from file.readlines() result in diffs that are suitable for + file.writelines() since both the inputs and outputs have trailing + newlines. + + For inputs that do not have trailing newlines, set the lineterm + argument to "" so that the output will be uniformly newline free. + + The context diff format normally has a header for filenames and + modification times. Any or all of these may be specified using + strings for 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. + The modification times are normally expressed in the ISO 8601 format. + If not specified, the strings default to blanks. + + Example: + + >>> print(''.join(context_diff('one\ntwo\nthree\nfour\n'.splitlines(True), + ... 'zero\none\ntree\nfour\n'.splitlines(True), 'Original', 'Current')), + ... end="") + *** Original + --- Current + *************** + *** 1,4 **** + one + ! two + ! three + four + --- 1,4 ---- + + zero + one + ! tree + four + """ + + _check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm) + prefix = dict(insert='+ ', delete='- ', replace='! ', equal=' ') + started = False + for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n): + if not started: + started = True + fromdate = '\t{}'.format(fromfiledate) if fromfiledate else '' + todate = '\t{}'.format(tofiledate) if tofiledate else '' + yield '*** {}{}{}'.format(fromfile, fromdate, lineterm) + yield '--- {}{}{}'.format(tofile, todate, lineterm) + + first, last = group[0], group[-1] + yield '***************' + lineterm + + file1_range = _format_range_context(first[1], last[2]) + yield '*** {} ****{}'.format(file1_range, lineterm) + + if any(tag in {'replace', 'delete'} for tag, _, _, _, _ in group): + for tag, i1, i2, _, _ in group: + if tag != 'insert': + for line in a[i1:i2]: + yield prefix[tag] + line + + file2_range = _format_range_context(first[3], last[4]) + yield '--- {} ----{}'.format(file2_range, lineterm) + + if any(tag in {'replace', 'insert'} for tag, _, _, _, _ in group): + for tag, _, _, j1, j2 in group: + if tag != 'delete': + for line in b[j1:j2]: + yield prefix[tag] + line + +def _check_types(a, b, *args): + # Checking types is weird, but the alternative is garbled output when + # someone passes mixed bytes and str to {unified,context}_diff(). E.g. + # without this check, passing filenames as bytes results in output like + # --- b'oldfile.txt' + # +++ b'newfile.txt' + # because of how str.format() incorporates bytes objects. + if a and not isinstance(a[0], str): + raise TypeError('lines to compare must be str, not %s (%r)' % + (type(a[0]).__name__, a[0])) + if b and not isinstance(b[0], str): + raise TypeError('lines to compare must be str, not %s (%r)' % + (type(b[0]).__name__, b[0])) + for arg in args: + if not isinstance(arg, str): + raise TypeError('all arguments must be str, not: %r' % (arg,)) + +def diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', + fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n'): + r""" + Compare `a` and `b`, two sequences of lines represented as bytes rather + than str. This is a wrapper for `dfunc`, which is typically either + unified_diff() or context_diff(). Inputs are losslessly converted to + strings so that `dfunc` only has to worry about strings, and encoded + back to bytes on return. This is necessary to compare files with + unknown or inconsistent encoding. All other inputs (except `n`) must be + bytes rather than str. + """ + def decode(s): + try: + return s.decode('ascii', 'surrogateescape') + except AttributeError as err: + msg = ('all arguments must be bytes, not %s (%r)' % + (type(s).__name__, s)) + raise TypeError(msg) from err + a = list(map(decode, a)) + b = list(map(decode, b)) + fromfile = decode(fromfile) + tofile = decode(tofile) + fromfiledate = decode(fromfiledate) + tofiledate = decode(tofiledate) + lineterm = decode(lineterm) + + lines = dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm) + for line in lines: + yield line.encode('ascii', 'surrogateescape') + +def ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK): + r""" + Compare `a` and `b` (lists of strings); return a `Differ`-style delta. + + Optional keyword parameters `linejunk` and `charjunk` are for filter + functions, or can be None: + + - linejunk: A function that should accept a single string argument and + return true iff the string is junk. The default is None, and is + recommended; the underlying SequenceMatcher class has an adaptive + notion of "noise" lines. + + - charjunk: A function that accepts a character (string of length + 1), and returns true iff the character is junk. The default is + the module-level function IS_CHARACTER_JUNK, which filters out + whitespace characters (a blank or tab; note: it's a bad idea to + include newline in this!). + + Tools/scripts/ndiff.py is a command-line front-end to this function. + + Example: + + >>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True), + ... 'ore\ntree\nemu\n'.splitlines(keepends=True)) + >>> print(''.join(diff), end="") + - one + ? ^ + + ore + ? ^ + - two + - three + ? - + + tree + + emu + """ + return Differ(linejunk, charjunk).compare(a, b) + +def _mdiff(fromlines, tolines, context=None, linejunk=None, + charjunk=IS_CHARACTER_JUNK): + r"""Returns generator yielding marked up from/to side by side differences. + + Arguments: + fromlines -- list of text lines to compared to tolines + tolines -- list of text lines to be compared to fromlines + context -- number of context lines to display on each side of difference, + if None, all from/to text lines will be generated. + linejunk -- passed on to ndiff (see ndiff documentation) + charjunk -- passed on to ndiff (see ndiff documentation) + + This function returns an iterator which returns a tuple: + (from line tuple, to line tuple, boolean flag) + + from/to line tuple -- (line num, line text) + line num -- integer or None (to indicate a context separation) + line text -- original line text with following markers inserted: + '\0+' -- marks start of added text + '\0-' -- marks start of deleted text + '\0^' -- marks start of changed text + '\1' -- marks end of added/deleted/changed text + + boolean flag -- None indicates context separation, True indicates + either "from" or "to" line contains a change, otherwise False. + + This function/iterator was originally developed to generate side by side + file difference for making HTML pages (see HtmlDiff class for example + usage). + + Note, this function utilizes the ndiff function to generate the side by + side difference markup. Optional ndiff arguments may be passed to this + function and they in turn will be passed to ndiff. + """ + import re + + # regular expression for finding intraline change indices + change_re = re.compile(r'(\++|\-+|\^+)') + + # create the difference iterator to generate the differences + diff_lines_iterator = ndiff(fromlines,tolines,linejunk,charjunk) + + def _make_line(lines, format_key, side, num_lines=[0,0]): + """Returns line of text with user's change markup and line formatting. + + lines -- list of lines from the ndiff generator to produce a line of + text from. When producing the line of text to return, the + lines used are removed from this list. + format_key -- '+' return first line in list with "add" markup around + the entire line. + '-' return first line in list with "delete" markup around + the entire line. + '?' return first line in list with add/delete/change + intraline markup (indices obtained from second line) + None return first line in list with no markup + side -- indice into the num_lines list (0=from,1=to) + num_lines -- from/to current line number. This is NOT intended to be a + passed parameter. It is present as a keyword argument to + maintain memory of the current line numbers between calls + of this function. + + Note, this function is purposefully not defined at the module scope so + that data it needs from its parent function (within whose context it + is defined) does not need to be of module scope. + """ + num_lines[side] += 1 + # Handle case where no user markup is to be added, just return line of + # text with user's line format to allow for usage of the line number. + if format_key is None: + return (num_lines[side],lines.pop(0)[2:]) + # Handle case of intraline changes + if format_key == '?': + text, markers = lines.pop(0), lines.pop(0) + # find intraline changes (store change type and indices in tuples) + sub_info = [] + def record_sub_info(match_object,sub_info=sub_info): + sub_info.append([match_object.group(1)[0],match_object.span()]) + return match_object.group(1) + change_re.sub(record_sub_info,markers) + # process each tuple inserting our special marks that won't be + # noticed by an xml/html escaper. + for key,(begin,end) in reversed(sub_info): + text = text[0:begin]+'\0'+key+text[begin:end]+'\1'+text[end:] + text = text[2:] + # Handle case of add/delete entire line + else: + text = lines.pop(0)[2:] + # if line of text is just a newline, insert a space so there is + # something for the user to highlight and see. + if not text: + text = ' ' + # insert marks that won't be noticed by an xml/html escaper. + text = '\0' + format_key + text + '\1' + # Return line of text, first allow user's line formatter to do its + # thing (such as adding the line number) then replace the special + # marks with what the user's change markup. + return (num_lines[side],text) + + def _line_iterator(): + """Yields from/to lines of text with a change indication. + + This function is an iterator. It itself pulls lines from a + differencing iterator, processes them and yields them. When it can + it yields both a "from" and a "to" line, otherwise it will yield one + or the other. In addition to yielding the lines of from/to text, a + boolean flag is yielded to indicate if the text line(s) have + differences in them. + + Note, this function is purposefully not defined at the module scope so + that data it needs from its parent function (within whose context it + is defined) does not need to be of module scope. + """ + lines = [] + num_blanks_pending, num_blanks_to_yield = 0, 0 + while True: + # Load up next 4 lines so we can look ahead, create strings which + # are a concatenation of the first character of each of the 4 lines + # so we can do some very readable comparisons. + while len(lines) < 4: + lines.append(next(diff_lines_iterator, 'X')) + s = ''.join([line[0] for line in lines]) + if s.startswith('X'): + # When no more lines, pump out any remaining blank lines so the + # corresponding add/delete lines get a matching blank line so + # all line pairs get yielded at the next level. + num_blanks_to_yield = num_blanks_pending + elif s.startswith('-?+?'): + # simple intraline change + yield _make_line(lines,'?',0), _make_line(lines,'?',1), True + continue + elif s.startswith('--++'): + # in delete block, add block coming: we do NOT want to get + # caught up on blank lines yet, just process the delete line + num_blanks_pending -= 1 + yield _make_line(lines,'-',0), None, True + continue + elif s.startswith(('--?+', '--+', '- ')): + # in delete block and see an intraline change or unchanged line + # coming: yield the delete line and then blanks + from_line,to_line = _make_line(lines,'-',0), None + num_blanks_to_yield,num_blanks_pending = num_blanks_pending-1,0 + elif s.startswith('-+?'): + # intraline change + yield _make_line(lines,None,0), _make_line(lines,'?',1), True + continue + elif s.startswith('-?+'): + # intraline change + yield _make_line(lines,'?',0), _make_line(lines,None,1), True + continue + elif s.startswith('-'): + # delete FROM line + num_blanks_pending -= 1 + yield _make_line(lines,'-',0), None, True + continue + elif s.startswith('+--'): + # in add block, delete block coming: we do NOT want to get + # caught up on blank lines yet, just process the add line + num_blanks_pending += 1 + yield None, _make_line(lines,'+',1), True + continue + elif s.startswith(('+ ', '+-')): + # will be leaving an add block: yield blanks then add line + from_line, to_line = None, _make_line(lines,'+',1) + num_blanks_to_yield,num_blanks_pending = num_blanks_pending+1,0 + elif s.startswith('+'): + # inside an add block, yield the add line + num_blanks_pending += 1 + yield None, _make_line(lines,'+',1), True + continue + elif s.startswith(' '): + # unchanged text, yield it to both sides + yield _make_line(lines[:],None,0),_make_line(lines,None,1),False + continue + # Catch up on the blank lines so when we yield the next from/to + # pair, they are lined up. + while(num_blanks_to_yield < 0): + num_blanks_to_yield += 1 + yield None,('','\n'),True + while(num_blanks_to_yield > 0): + num_blanks_to_yield -= 1 + yield ('','\n'),None,True + if s.startswith('X'): + return + else: + yield from_line,to_line,True + + def _line_pair_iterator(): + """Yields from/to lines of text with a change indication. + + This function is an iterator. It itself pulls lines from the line + iterator. Its difference from that iterator is that this function + always yields a pair of from/to text lines (with the change + indication). If necessary it will collect single from/to lines + until it has a matching pair from/to pair to yield. + + Note, this function is purposefully not defined at the module scope so + that data it needs from its parent function (within whose context it + is defined) does not need to be of module scope. + """ + line_iterator = _line_iterator() + fromlines,tolines=[],[] + while True: + # Collecting lines of text until we have a from/to pair + while (len(fromlines)==0 or len(tolines)==0): + try: + from_line, to_line, found_diff = next(line_iterator) + except StopIteration: + return + if from_line is not None: + fromlines.append((from_line,found_diff)) + if to_line is not None: + tolines.append((to_line,found_diff)) + # Once we have a pair, remove them from the collection and yield it + from_line, fromDiff = fromlines.pop(0) + to_line, to_diff = tolines.pop(0) + yield (from_line,to_line,fromDiff or to_diff) + + # Handle case where user does not want context differencing, just yield + # them up without doing anything else with them. + line_pair_iterator = _line_pair_iterator() + if context is None: + yield from line_pair_iterator + # Handle case where user wants context differencing. We must do some + # storage of lines until we know for sure that they are to be yielded. + else: + context += 1 + lines_to_write = 0 + while True: + # Store lines up until we find a difference, note use of a + # circular queue because we only need to keep around what + # we need for context. + index, contextLines = 0, [None]*(context) + found_diff = False + while(found_diff is False): + try: + from_line, to_line, found_diff = next(line_pair_iterator) + except StopIteration: + return + i = index % context + contextLines[i] = (from_line, to_line, found_diff) + index += 1 + # Yield lines that we have collected so far, but first yield + # the user's separator. + if index > context: + yield None, None, None + lines_to_write = context + else: + lines_to_write = index + index = 0 + while(lines_to_write): + i = index % context + index += 1 + yield contextLines[i] + lines_to_write -= 1 + # Now yield the context lines after the change + lines_to_write = context-1 + try: + while(lines_to_write): + from_line, to_line, found_diff = next(line_pair_iterator) + # If another change within the context, extend the context + if found_diff: + lines_to_write = context-1 + else: + lines_to_write -= 1 + yield from_line, to_line, found_diff + except StopIteration: + # Catch exception from next() and return normally + return + + +_file_template = """ + + + + + + + + + + + + %(table)s%(legend)s + + +""" + +_styles = """ + table.diff {font-family:Courier; border:medium;} + .diff_header {background-color:#e0e0e0} + td.diff_header {text-align:right} + .diff_next {background-color:#c0c0c0} + .diff_add {background-color:#aaffaa} + .diff_chg {background-color:#ffff77} + .diff_sub {background-color:#ffaaaa}""" + +_table_template = """ + + + + %(header_row)s + +%(data_rows)s +
""" + +_legend = """ + + + + +
Legends
+ + + + +
Colors
 Added 
Changed
Deleted
+ + + + +
Links
(f)irst change
(n)ext change
(t)op
""" + +class HtmlDiff(object): + """For producing HTML side by side comparison with change highlights. + + This class can be used to create an HTML table (or a complete HTML file + containing the table) showing a side by side, line by line comparison + of text with inter-line and intra-line change highlights. The table can + be generated in either full or contextual difference mode. + + The following methods are provided for HTML generation: + + make_table -- generates HTML for a single side by side table + make_file -- generates complete HTML file with a single side by side table + + See tools/scripts/diff.py for an example usage of this class. + """ + + _file_template = _file_template + _styles = _styles + _table_template = _table_template + _legend = _legend + _default_prefix = 0 + + def __init__(self,tabsize=8,wrapcolumn=None,linejunk=None, + charjunk=IS_CHARACTER_JUNK): + """HtmlDiff instance initializer + + Arguments: + tabsize -- tab stop spacing, defaults to 8. + wrapcolumn -- column number where lines are broken and wrapped, + defaults to None where lines are not wrapped. + linejunk,charjunk -- keyword arguments passed into ndiff() (used by + HtmlDiff() to generate the side by side HTML differences). See + ndiff() documentation for argument default values and descriptions. + """ + self._tabsize = tabsize + self._wrapcolumn = wrapcolumn + self._linejunk = linejunk + self._charjunk = charjunk + + def make_file(self, fromlines, tolines, fromdesc='', todesc='', + context=False, numlines=5, *, charset='utf-8'): + """Returns HTML file of side by side comparison with change highlights + + Arguments: + fromlines -- list of "from" lines + tolines -- list of "to" lines + fromdesc -- "from" file column header string + todesc -- "to" file column header string + context -- set to True for contextual differences (defaults to False + which shows full differences). + numlines -- number of context lines. When context is set True, + controls number of lines displayed before and after the change. + When context is False, controls the number of lines to place + the "next" link anchors before the next change (so click of + "next" link jumps to just before the change). + charset -- charset of the HTML document + """ + + return (self._file_template % dict( + styles=self._styles, + legend=self._legend, + table=self.make_table(fromlines, tolines, fromdesc, todesc, + context=context, numlines=numlines), + charset=charset + )).encode(charset, 'xmlcharrefreplace').decode(charset) + + def _tab_newline_replace(self,fromlines,tolines): + """Returns from/to line lists with tabs expanded and newlines removed. + + Instead of tab characters being replaced by the number of spaces + needed to fill in to the next tab stop, this function will fill + the space with tab characters. This is done so that the difference + algorithms can identify changes in a file when tabs are replaced by + spaces and vice versa. At the end of the HTML generation, the tab + characters will be replaced with a nonbreakable space. + """ + def expand_tabs(line): + # hide real spaces + line = line.replace(' ','\0') + # expand tabs into spaces + line = line.expandtabs(self._tabsize) + # replace spaces from expanded tabs back into tab characters + # (we'll replace them with markup after we do differencing) + line = line.replace(' ','\t') + return line.replace('\0',' ').rstrip('\n') + fromlines = [expand_tabs(line) for line in fromlines] + tolines = [expand_tabs(line) for line in tolines] + return fromlines,tolines + + def _split_line(self,data_list,line_num,text): + """Builds list of text lines by splitting text lines at wrap point + + This function will determine if the input text line needs to be + wrapped (split) into separate lines. If so, the first wrap point + will be determined and the first line appended to the output + text line list. This function is used recursively to handle + the second part of the split line to further split it. + """ + # if blank line or context separator, just add it to the output list + if not line_num: + data_list.append((line_num,text)) + return + + # if line text doesn't need wrapping, just add it to the output list + size = len(text) + max = self._wrapcolumn + if (size <= max) or ((size -(text.count('\0')*3)) <= max): + data_list.append((line_num,text)) + return + + # scan text looking for the wrap point, keeping track if the wrap + # point is inside markers + i = 0 + n = 0 + mark = '' + while n < max and i < size: + if text[i] == '\0': + i += 1 + mark = text[i] + i += 1 + elif text[i] == '\1': + i += 1 + mark = '' + else: + i += 1 + n += 1 + + # wrap point is inside text, break it up into separate lines + line1 = text[:i] + line2 = text[i:] + + # if wrap point is inside markers, place end marker at end of first + # line and start marker at beginning of second line because each + # line will have its own table tag markup around it. + if mark: + line1 = line1 + '\1' + line2 = '\0' + mark + line2 + + # tack on first line onto the output list + data_list.append((line_num,line1)) + + # use this routine again to wrap the remaining text + self._split_line(data_list,'>',line2) + + def _line_wrapper(self,diffs): + """Returns iterator that splits (wraps) mdiff text lines""" + + # pull from/to data and flags from mdiff iterator + for fromdata,todata,flag in diffs: + # check for context separators and pass them through + if flag is None: + yield fromdata,todata,flag + continue + (fromline,fromtext),(toline,totext) = fromdata,todata + # for each from/to line split it at the wrap column to form + # list of text lines. + fromlist,tolist = [],[] + self._split_line(fromlist,fromline,fromtext) + self._split_line(tolist,toline,totext) + # yield from/to line in pairs inserting blank lines as + # necessary when one side has more wrapped lines + while fromlist or tolist: + if fromlist: + fromdata = fromlist.pop(0) + else: + fromdata = ('',' ') + if tolist: + todata = tolist.pop(0) + else: + todata = ('',' ') + yield fromdata,todata,flag + + def _collect_lines(self,diffs): + """Collects mdiff output into separate lists + + Before storing the mdiff from/to data into a list, it is converted + into a single line of text with HTML markup. + """ + + fromlist,tolist,flaglist = [],[],[] + # pull from/to data and flags from mdiff style iterator + for fromdata,todata,flag in diffs: + try: + # store HTML markup of the lines into the lists + fromlist.append(self._format_line(0,flag,*fromdata)) + tolist.append(self._format_line(1,flag,*todata)) + except TypeError: + # exceptions occur for lines where context separators go + fromlist.append(None) + tolist.append(None) + flaglist.append(flag) + return fromlist,tolist,flaglist + + def _format_line(self,side,flag,linenum,text): + """Returns HTML markup of "from" / "to" text lines + + side -- 0 or 1 indicating "from" or "to" text + flag -- indicates if difference on line + linenum -- line number (used for line number column) + text -- line text to be marked up + """ + try: + linenum = '%d' % linenum + id = ' id="%s%s"' % (self._prefix[side],linenum) + except TypeError: + # handle blank lines where linenum is '>' or '' + id = '' + # replace those things that would get confused with HTML symbols + text=text.replace("&","&").replace(">",">").replace("<","<") + + # make space non-breakable so they don't get compressed or line wrapped + text = text.replace(' ',' ').rstrip() + + return '%s%s' \ + % (id,linenum,text) + + def _make_prefix(self): + """Create unique anchor prefixes""" + + # Generate a unique anchor prefix so multiple tables + # can exist on the same HTML page without conflicts. + fromprefix = "from%d_" % HtmlDiff._default_prefix + toprefix = "to%d_" % HtmlDiff._default_prefix + HtmlDiff._default_prefix += 1 + # store prefixes so line format method has access + self._prefix = [fromprefix,toprefix] + + def _convert_flags(self,fromlist,tolist,flaglist,context,numlines): + """Makes list of "next" links""" + + # all anchor names will be generated using the unique "to" prefix + toprefix = self._prefix[1] + + # process change flags, generating middle column of next anchors/links + next_id = ['']*len(flaglist) + next_href = ['']*len(flaglist) + num_chg, in_change = 0, False + last = 0 + for i,flag in enumerate(flaglist): + if flag: + if not in_change: + in_change = True + last = i + # at the beginning of a change, drop an anchor a few lines + # (the context lines) before the change for the previous + # link + i = max([0,i-numlines]) + next_id[i] = ' id="difflib_chg_%s_%d"' % (toprefix,num_chg) + # at the beginning of a change, drop a link to the next + # change + num_chg += 1 + next_href[last] = 'n' % ( + toprefix,num_chg) + else: + in_change = False + # check for cases where there is no content to avoid exceptions + if not flaglist: + flaglist = [False] + next_id = [''] + next_href = [''] + last = 0 + if context: + fromlist = [' No Differences Found '] + tolist = fromlist + else: + fromlist = tolist = [' Empty File '] + # if not a change on first line, drop a link + if not flaglist[0]: + next_href[0] = 'f' % toprefix + # redo the last link to link to the top + next_href[last] = 't' % (toprefix) + + return fromlist,tolist,flaglist,next_href,next_id + + def make_table(self,fromlines,tolines,fromdesc='',todesc='',context=False, + numlines=5): + """Returns HTML table of side by side comparison with change highlights + + Arguments: + fromlines -- list of "from" lines + tolines -- list of "to" lines + fromdesc -- "from" file column header string + todesc -- "to" file column header string + context -- set to True for contextual differences (defaults to False + which shows full differences). + numlines -- number of context lines. When context is set True, + controls number of lines displayed before and after the change. + When context is False, controls the number of lines to place + the "next" link anchors before the next change (so click of + "next" link jumps to just before the change). + """ + + # make unique anchor prefixes so that multiple tables may exist + # on the same page without conflict. + self._make_prefix() + + # change tabs to spaces before it gets more difficult after we insert + # markup + fromlines,tolines = self._tab_newline_replace(fromlines,tolines) + + # create diffs iterator which generates side by side from/to data + if context: + context_lines = numlines + else: + context_lines = None + diffs = _mdiff(fromlines,tolines,context_lines,linejunk=self._linejunk, + charjunk=self._charjunk) + + # set up iterator to wrap lines that exceed desired width + if self._wrapcolumn: + diffs = self._line_wrapper(diffs) + + # collect up from/to lines and flags into lists (also format the lines) + fromlist,tolist,flaglist = self._collect_lines(diffs) + + # process change flags, generating middle column of next anchors/links + fromlist,tolist,flaglist,next_href,next_id = self._convert_flags( + fromlist,tolist,flaglist,context,numlines) + + s = [] + fmt = ' %s%s' + \ + '%s%s\n' + for i in range(len(flaglist)): + if flaglist[i] is None: + # mdiff yields None on separator lines skip the bogus ones + # generated for the first line + if i > 0: + s.append(' \n \n') + else: + s.append( fmt % (next_id[i],next_href[i],fromlist[i], + next_href[i],tolist[i])) + if fromdesc or todesc: + header_row = '%s%s%s%s' % ( + '
', + '%s' % fromdesc, + '
', + '%s' % todesc) + else: + header_row = '' + + table = self._table_template % dict( + data_rows=''.join(s), + header_row=header_row, + prefix=self._prefix[1]) + + return table.replace('\0+',''). \ + replace('\0-',''). \ + replace('\0^',''). \ + replace('\1',''). \ + replace('\t',' ') + +del re + +def restore(delta, which): + r""" + Generate one of the two sequences that generated a delta. + + Given a `delta` produced by `Differ.compare()` or `ndiff()`, extract + lines originating from file 1 or 2 (parameter `which`), stripping off line + prefixes. + + Examples: + + >>> diff = ndiff('one\ntwo\nthree\n'.splitlines(keepends=True), + ... 'ore\ntree\nemu\n'.splitlines(keepends=True)) + >>> diff = list(diff) + >>> print(''.join(restore(diff, 1)), end="") + one + two + three + >>> print(''.join(restore(diff, 2)), end="") + ore + tree + emu + """ + try: + tag = {1: "- ", 2: "+ "}[int(which)] + except KeyError: + raise ValueError('unknown delta choice (must be 1 or 2): %r' + % which) from None + prefixes = (" ", tag) + for line in delta: + if line[:2] in prefixes: + yield line[2:] + +def _test(): + import doctest, difflib + return doctest.testmod(difflib) + +if __name__ == "__main__": + _test() diff --git a/Lib/functools.py b/Lib/functools.py new file mode 100644 index 0000000000..c8b79c2a7c --- /dev/null +++ b/Lib/functools.py @@ -0,0 +1,828 @@ +"""functools.py - Tools for working with functions and callable objects +""" +# Python module wrapper for _functools C module +# to allow utilities written in Python to be added +# to the functools module. +# Written by Nick Coghlan , +# Raymond Hettinger , +# and Łukasz Langa . +# Copyright (C) 2006-2013 Python Software Foundation. +# See C source code for _functools credits/copyright + +__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES', + 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial', + 'partialmethod', 'singledispatch'] + +try: + from _functools import reduce +except ImportError: + pass +from abc import get_cache_token +from collections import namedtuple +# import types, weakref # Deferred to single_dispatch() +from reprlib import recursive_repr +from _thread import RLock + + +################################################################################ +### update_wrapper() and wraps() decorator +################################################################################ + +# update_wrapper() and wraps() are tools to help write +# wrapper functions that can handle naive introspection + +WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__', + '__annotations__') +WRAPPER_UPDATES = ('__dict__',) +def update_wrapper(wrapper, + wrapped, + assigned = WRAPPER_ASSIGNMENTS, + updated = WRAPPER_UPDATES): + """Update a wrapper function to look like the wrapped function + + wrapper is the function to be updated + wrapped is the original function + assigned is a tuple naming the attributes assigned directly + from the wrapped function to the wrapper function (defaults to + functools.WRAPPER_ASSIGNMENTS) + updated is a tuple naming the attributes of the wrapper that + are updated with the corresponding attribute from the wrapped + function (defaults to functools.WRAPPER_UPDATES) + """ + for attr in assigned: + try: + value = getattr(wrapped, attr) + except AttributeError: + pass + else: + setattr(wrapper, attr, value) + for attr in updated: + getattr(wrapper, attr).update(getattr(wrapped, attr, {})) + # Issue #17482: set __wrapped__ last so we don't inadvertently copy it + # from the wrapped function when updating __dict__ + wrapper.__wrapped__ = wrapped + # Return the wrapper so this can be used as a decorator via partial() + return wrapper + +def wraps(wrapped, + assigned = WRAPPER_ASSIGNMENTS, + updated = WRAPPER_UPDATES): + """Decorator factory to apply update_wrapper() to a wrapper function + + Returns a decorator that invokes update_wrapper() with the decorated + function as the wrapper argument and the arguments to wraps() as the + remaining arguments. Default arguments are as for update_wrapper(). + This is a convenience function to simplify applying partial() to + update_wrapper(). + """ + return partial(update_wrapper, wrapped=wrapped, + assigned=assigned, updated=updated) + + +################################################################################ +### total_ordering class decorator +################################################################################ + +# The total ordering functions all invoke the root magic method directly +# rather than using the corresponding operator. This avoids possible +# infinite recursion that could occur when the operator dispatch logic +# detects a NotImplemented result and then calls a reflected method. + +def _gt_from_lt(self, other, NotImplemented=NotImplemented): + 'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).' + op_result = self.__lt__(other) + if op_result is NotImplemented: + return op_result + return not op_result and self != other + +def _le_from_lt(self, other, NotImplemented=NotImplemented): + 'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).' + op_result = self.__lt__(other) + return op_result or self == other + +def _ge_from_lt(self, other, NotImplemented=NotImplemented): + 'Return a >= b. Computed by @total_ordering from (not a < b).' + op_result = self.__lt__(other) + if op_result is NotImplemented: + return op_result + return not op_result + +def _ge_from_le(self, other, NotImplemented=NotImplemented): + 'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).' + op_result = self.__le__(other) + if op_result is NotImplemented: + return op_result + return not op_result or self == other + +def _lt_from_le(self, other, NotImplemented=NotImplemented): + 'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).' + op_result = self.__le__(other) + if op_result is NotImplemented: + return op_result + return op_result and self != other + +def _gt_from_le(self, other, NotImplemented=NotImplemented): + 'Return a > b. Computed by @total_ordering from (not a <= b).' + op_result = self.__le__(other) + if op_result is NotImplemented: + return op_result + return not op_result + +def _lt_from_gt(self, other, NotImplemented=NotImplemented): + 'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).' + op_result = self.__gt__(other) + if op_result is NotImplemented: + return op_result + return not op_result and self != other + +def _ge_from_gt(self, other, NotImplemented=NotImplemented): + 'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).' + op_result = self.__gt__(other) + return op_result or self == other + +def _le_from_gt(self, other, NotImplemented=NotImplemented): + 'Return a <= b. Computed by @total_ordering from (not a > b).' + op_result = self.__gt__(other) + if op_result is NotImplemented: + return op_result + return not op_result + +def _le_from_ge(self, other, NotImplemented=NotImplemented): + 'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).' + op_result = self.__ge__(other) + if op_result is NotImplemented: + return op_result + return not op_result or self == other + +def _gt_from_ge(self, other, NotImplemented=NotImplemented): + 'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).' + op_result = self.__ge__(other) + if op_result is NotImplemented: + return op_result + return op_result and self != other + +def _lt_from_ge(self, other, NotImplemented=NotImplemented): + 'Return a < b. Computed by @total_ordering from (not a >= b).' + op_result = self.__ge__(other) + if op_result is NotImplemented: + return op_result + return not op_result + +_convert = { + '__lt__': [('__gt__', _gt_from_lt), + ('__le__', _le_from_lt), + ('__ge__', _ge_from_lt)], + '__le__': [('__ge__', _ge_from_le), + ('__lt__', _lt_from_le), + ('__gt__', _gt_from_le)], + '__gt__': [('__lt__', _lt_from_gt), + ('__ge__', _ge_from_gt), + ('__le__', _le_from_gt)], + '__ge__': [('__le__', _le_from_ge), + ('__gt__', _gt_from_ge), + ('__lt__', _lt_from_ge)] +} + +def total_ordering(cls): + """Class decorator that fills in missing ordering methods""" + # Find user-defined comparisons (not those inherited from object). + roots = {op for op in _convert if getattr(cls, op, None) is not getattr(object, op, None)} + if not roots: + raise ValueError('must define at least one ordering operation: < > <= >=') + root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__ + for opname, opfunc in _convert[root]: + if opname not in roots: + opfunc.__name__ = opname + setattr(cls, opname, opfunc) + return cls + + +################################################################################ +### cmp_to_key() function converter +################################################################################ + +def cmp_to_key(mycmp): + """Convert a cmp= function into a key= function""" + class K(object): + __slots__ = ['obj'] + def __init__(self, obj): + self.obj = obj + def __lt__(self, other): + return mycmp(self.obj, other.obj) < 0 + def __gt__(self, other): + return mycmp(self.obj, other.obj) > 0 + def __eq__(self, other): + return mycmp(self.obj, other.obj) == 0 + def __le__(self, other): + return mycmp(self.obj, other.obj) <= 0 + def __ge__(self, other): + return mycmp(self.obj, other.obj) >= 0 + __hash__ = None + return K + +try: + from _functools import cmp_to_key +except ImportError: + pass + + +################################################################################ +### partial() argument application +################################################################################ + +# Purely functional, no descriptor behaviour +class partial: + """New function with partial application of the given arguments + and keywords. + """ + + __slots__ = "func", "args", "keywords", "__dict__", "__weakref__" + + def __new__(*args, **keywords): + if not args: + raise TypeError("descriptor '__new__' of partial needs an argument") + if len(args) < 2: + raise TypeError("type 'partial' takes at least one argument") + cls, func, *args = args + if not callable(func): + raise TypeError("the first argument must be callable") + args = tuple(args) + + if hasattr(func, "func"): + args = func.args + args + tmpkw = func.keywords.copy() + tmpkw.update(keywords) + keywords = tmpkw + del tmpkw + func = func.func + + self = super(partial, cls).__new__(cls) + + self.func = func + self.args = args + self.keywords = keywords + return self + + def __call__(*args, **keywords): + if not args: + raise TypeError("descriptor '__call__' of partial needs an argument") + self, *args = args + newkeywords = self.keywords.copy() + newkeywords.update(keywords) + return self.func(*self.args, *args, **newkeywords) + + @recursive_repr() + def __repr__(self): + qualname = type(self).__qualname__ + args = [repr(self.func)] + args.extend(repr(x) for x in self.args) + args.extend(f"{k}={v!r}" for (k, v) in self.keywords.items()) + if type(self).__module__ == "functools": + return f"functools.{qualname}({', '.join(args)})" + return f"{qualname}({', '.join(args)})" + + def __reduce__(self): + return type(self), (self.func,), (self.func, self.args, + self.keywords or None, self.__dict__ or None) + + def __setstate__(self, state): + if not isinstance(state, tuple): + raise TypeError("argument to __setstate__ must be a tuple") + if len(state) != 4: + raise TypeError(f"expected 4 items in state, got {len(state)}") + func, args, kwds, namespace = state + if (not callable(func) or not isinstance(args, tuple) or + (kwds is not None and not isinstance(kwds, dict)) or + (namespace is not None and not isinstance(namespace, dict))): + raise TypeError("invalid partial state") + + args = tuple(args) # just in case it's a subclass + if kwds is None: + kwds = {} + elif type(kwds) is not dict: # XXX does it need to be *exactly* dict? + kwds = dict(kwds) + if namespace is None: + namespace = {} + + self.__dict__ = namespace + self.func = func + self.args = args + self.keywords = kwds + +try: + from _functools import partial +except ImportError: + pass + +# Descriptor version +class partialmethod(object): + """Method descriptor with partial application of the given arguments + and keywords. + + Supports wrapping existing descriptors and handles non-descriptor + callables as instance methods. + """ + + def __init__(self, func, *args, **keywords): + if not callable(func) and not hasattr(func, "__get__"): + raise TypeError("{!r} is not callable or a descriptor" + .format(func)) + + # func could be a descriptor like classmethod which isn't callable, + # so we can't inherit from partial (it verifies func is callable) + if isinstance(func, partialmethod): + # flattening is mandatory in order to place cls/self before all + # other arguments + # it's also more efficient since only one function will be called + self.func = func.func + self.args = func.args + args + self.keywords = func.keywords.copy() + self.keywords.update(keywords) + else: + self.func = func + self.args = args + self.keywords = keywords + + def __repr__(self): + args = ", ".join(map(repr, self.args)) + keywords = ", ".join("{}={!r}".format(k, v) + for k, v in self.keywords.items()) + format_string = "{module}.{cls}({func}, {args}, {keywords})" + return format_string.format(module=self.__class__.__module__, + cls=self.__class__.__qualname__, + func=self.func, + args=args, + keywords=keywords) + + def _make_unbound_method(self): + def _method(*args, **keywords): + call_keywords = self.keywords.copy() + call_keywords.update(keywords) + cls_or_self, *rest = args + call_args = (cls_or_self,) + self.args + tuple(rest) + return self.func(*call_args, **call_keywords) + _method.__isabstractmethod__ = self.__isabstractmethod__ + _method._partialmethod = self + return _method + + def __get__(self, obj, cls): + get = getattr(self.func, "__get__", None) + result = None + if get is not None: + new_func = get(obj, cls) + if new_func is not self.func: + # Assume __get__ returning something new indicates the + # creation of an appropriate callable + result = partial(new_func, *self.args, **self.keywords) + try: + result.__self__ = new_func.__self__ + except AttributeError: + pass + if result is None: + # If the underlying descriptor didn't do anything, treat this + # like an instance method + result = self._make_unbound_method().__get__(obj, cls) + return result + + @property + def __isabstractmethod__(self): + return getattr(self.func, "__isabstractmethod__", False) + + +################################################################################ +### LRU Cache function decorator +################################################################################ + +_CacheInfo = namedtuple("CacheInfo", ["hits", "misses", "maxsize", "currsize"]) + +class _HashedSeq(list): + """ This class guarantees that hash() will be called no more than once + per element. This is important because the lru_cache() will hash + the key multiple times on a cache miss. + + """ + + __slots__ = 'hashvalue' + + def __init__(self, tup, hash=hash): + self[:] = tup + self.hashvalue = hash(tup) + + def __hash__(self): + return self.hashvalue + +def _make_key(args, kwds, typed, + kwd_mark = (object(),), + fasttypes = {int, str, frozenset, type(None)}, + tuple=tuple, type=type, len=len): + """Make a cache key from optionally typed positional and keyword arguments + + The key is constructed in a way that is flat as possible rather than + as a nested structure that would take more memory. + + If there is only a single argument and its data type is known to cache + its hash value, then that argument is returned without a wrapper. This + saves space and improves lookup speed. + + """ + # All of code below relies on kwds preserving the order input by the user. + # Formerly, we sorted() the kwds before looping. The new way is *much* + # faster; however, it means that f(x=1, y=2) will now be treated as a + # distinct call from f(y=2, x=1) which will be cached separately. + key = args + if kwds: + key += kwd_mark + for item in kwds.items(): + key += item + if typed: + key += tuple(type(v) for v in args) + if kwds: + key += tuple(type(v) for v in kwds.values()) + elif len(key) == 1 and type(key[0]) in fasttypes: + return key[0] + return _HashedSeq(key) + +def lru_cache(maxsize=128, typed=False): + """Least-recently-used cache decorator. + + If *maxsize* is set to None, the LRU features are disabled and the cache + can grow without bound. + + If *typed* is True, arguments of different types will be cached separately. + For example, f(3.0) and f(3) will be treated as distinct calls with + distinct results. + + Arguments to the cached function must be hashable. + + View the cache statistics named tuple (hits, misses, maxsize, currsize) + with f.cache_info(). Clear the cache and statistics with f.cache_clear(). + Access the underlying function with f.__wrapped__. + + See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used + + """ + + # Users should only access the lru_cache through its public API: + # cache_info, cache_clear, and f.__wrapped__ + # The internals of the lru_cache are encapsulated for thread safety and + # to allow the implementation to change (including a possible C version). + + # Early detection of an erroneous call to @lru_cache without any arguments + # resulting in the inner function being passed to maxsize instead of an + # integer or None. + if maxsize is not None and not isinstance(maxsize, int): + raise TypeError('Expected maxsize to be an integer or None') + + def decorating_function(user_function): + wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo) + return update_wrapper(wrapper, user_function) + + return decorating_function + +def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo): + # Constants shared by all lru cache instances: + sentinel = object() # unique object used to signal cache misses + make_key = _make_key # build a key from the function arguments + PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields + + cache = {} + hits = misses = 0 + full = False + cache_get = cache.get # bound method to lookup a key or return None + cache_len = cache.__len__ # get cache size without calling len() + lock = RLock() # because linkedlist updates aren't threadsafe + root = [] # root of the circular doubly linked list + root[:] = [root, root, None, None] # initialize by pointing to self + + if maxsize == 0: + + def wrapper(*args, **kwds): + # No caching -- just a statistics update after a successful call + nonlocal misses + result = user_function(*args, **kwds) + misses += 1 + return result + + elif maxsize is None: + + def wrapper(*args, **kwds): + # Simple caching without ordering or size limit + nonlocal hits, misses + key = make_key(args, kwds, typed) + result = cache_get(key, sentinel) + if result is not sentinel: + hits += 1 + return result + result = user_function(*args, **kwds) + cache[key] = result + misses += 1 + return result + + else: + + def wrapper(*args, **kwds): + # Size limited caching that tracks accesses by recency + nonlocal root, hits, misses, full + key = make_key(args, kwds, typed) + with lock: + link = cache_get(key) + if link is not None: + # Move the link to the front of the circular queue + link_prev, link_next, _key, result = link + link_prev[NEXT] = link_next + link_next[PREV] = link_prev + last = root[PREV] + last[NEXT] = root[PREV] = link + link[PREV] = last + link[NEXT] = root + hits += 1 + return result + result = user_function(*args, **kwds) + with lock: + if key in cache: + # Getting here means that this same key was added to the + # cache while the lock was released. Since the link + # update is already done, we need only return the + # computed result and update the count of misses. + pass + elif full: + # Use the old root to store the new key and result. + oldroot = root + oldroot[KEY] = key + oldroot[RESULT] = result + # Empty the oldest link and make it the new root. + # Keep a reference to the old key and old result to + # prevent their ref counts from going to zero during the + # update. That will prevent potentially arbitrary object + # clean-up code (i.e. __del__) from running while we're + # still adjusting the links. + root = oldroot[NEXT] + oldkey = root[KEY] + oldresult = root[RESULT] + root[KEY] = root[RESULT] = None + # Now update the cache dictionary. + del cache[oldkey] + # Save the potentially reentrant cache[key] assignment + # for last, after the root and links have been put in + # a consistent state. + cache[key] = oldroot + else: + # Put result in a new link at the front of the queue. + last = root[PREV] + link = [last, root, key, result] + last[NEXT] = root[PREV] = cache[key] = link + # Use the cache_len bound method instead of the len() function + # which could potentially be wrapped in an lru_cache itself. + full = (cache_len() >= maxsize) + misses += 1 + return result + + def cache_info(): + """Report cache statistics""" + with lock: + return _CacheInfo(hits, misses, maxsize, cache_len()) + + def cache_clear(): + """Clear the cache and cache statistics""" + nonlocal hits, misses, full + with lock: + cache.clear() + root[:] = [root, root, None, None] + hits = misses = 0 + full = False + + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + return wrapper + +try: + from _functools import _lru_cache_wrapper +except ImportError: + pass + + +################################################################################ +### singledispatch() - single-dispatch generic function decorator +################################################################################ + +def _c3_merge(sequences): + """Merges MROs in *sequences* to a single MRO using the C3 algorithm. + + Adapted from http://www.python.org/download/releases/2.3/mro/. + + """ + result = [] + while True: + sequences = [s for s in sequences if s] # purge empty sequences + if not sequences: + return result + for s1 in sequences: # find merge candidates among seq heads + candidate = s1[0] + for s2 in sequences: + if candidate in s2[1:]: + candidate = None + break # reject the current head, it appears later + else: + break + if candidate is None: + raise RuntimeError("Inconsistent hierarchy") + result.append(candidate) + # remove the chosen candidate + for seq in sequences: + if seq[0] == candidate: + del seq[0] + +def _c3_mro(cls, abcs=None): + """Computes the method resolution order using extended C3 linearization. + + If no *abcs* are given, the algorithm works exactly like the built-in C3 + linearization used for method resolution. + + If given, *abcs* is a list of abstract base classes that should be inserted + into the resulting MRO. Unrelated ABCs are ignored and don't end up in the + result. The algorithm inserts ABCs where their functionality is introduced, + i.e. issubclass(cls, abc) returns True for the class itself but returns + False for all its direct base classes. Implicit ABCs for a given class + (either registered or inferred from the presence of a special method like + __len__) are inserted directly after the last ABC explicitly listed in the + MRO of said class. If two implicit ABCs end up next to each other in the + resulting MRO, their ordering depends on the order of types in *abcs*. + + """ + for i, base in enumerate(reversed(cls.__bases__)): + if hasattr(base, '__abstractmethods__'): + boundary = len(cls.__bases__) - i + break # Bases up to the last explicit ABC are considered first. + else: + boundary = 0 + abcs = list(abcs) if abcs else [] + explicit_bases = list(cls.__bases__[:boundary]) + abstract_bases = [] + other_bases = list(cls.__bases__[boundary:]) + for base in abcs: + if issubclass(cls, base) and not any( + issubclass(b, base) for b in cls.__bases__ + ): + # If *cls* is the class that introduces behaviour described by + # an ABC *base*, insert said ABC to its MRO. + abstract_bases.append(base) + for base in abstract_bases: + abcs.remove(base) + explicit_c3_mros = [_c3_mro(base, abcs=abcs) for base in explicit_bases] + abstract_c3_mros = [_c3_mro(base, abcs=abcs) for base in abstract_bases] + other_c3_mros = [_c3_mro(base, abcs=abcs) for base in other_bases] + return _c3_merge( + [[cls]] + + explicit_c3_mros + abstract_c3_mros + other_c3_mros + + [explicit_bases] + [abstract_bases] + [other_bases] + ) + +def _compose_mro(cls, types): + """Calculates the method resolution order for a given class *cls*. + + Includes relevant abstract base classes (with their respective bases) from + the *types* iterable. Uses a modified C3 linearization algorithm. + + """ + bases = set(cls.__mro__) + # Remove entries which are already present in the __mro__ or unrelated. + def is_related(typ): + return (typ not in bases and hasattr(typ, '__mro__') + and issubclass(cls, typ)) + types = [n for n in types if is_related(n)] + # Remove entries which are strict bases of other entries (they will end up + # in the MRO anyway. + def is_strict_base(typ): + for other in types: + if typ != other and typ in other.__mro__: + return True + return False + types = [n for n in types if not is_strict_base(n)] + # Subclasses of the ABCs in *types* which are also implemented by + # *cls* can be used to stabilize ABC ordering. + type_set = set(types) + mro = [] + for typ in types: + found = [] + for sub in typ.__subclasses__(): + if sub not in bases and issubclass(cls, sub): + found.append([s for s in sub.__mro__ if s in type_set]) + if not found: + mro.append(typ) + continue + # Favor subclasses with the biggest number of useful bases + found.sort(key=len, reverse=True) + for sub in found: + for subcls in sub: + if subcls not in mro: + mro.append(subcls) + return _c3_mro(cls, abcs=mro) + +def _find_impl(cls, registry): + """Returns the best matching implementation from *registry* for type *cls*. + + Where there is no registered implementation for a specific type, its method + resolution order is used to find a more generic implementation. + + Note: if *registry* does not contain an implementation for the base + *object* type, this function may return None. + + """ + mro = _compose_mro(cls, registry.keys()) + match = None + for t in mro: + if match is not None: + # If *match* is an implicit ABC but there is another unrelated, + # equally matching implicit ABC, refuse the temptation to guess. + if (t in registry and t not in cls.__mro__ + and match not in cls.__mro__ + and not issubclass(match, t)): + raise RuntimeError("Ambiguous dispatch: {} or {}".format( + match, t)) + break + if t in registry: + match = t + return registry.get(match) + +def singledispatch(func): + """Single-dispatch generic function decorator. + + Transforms a function into a generic function, which can have different + behaviours depending upon the type of its first argument. The decorated + function acts as the default implementation, and additional + implementations can be registered using the register() attribute of the + generic function. + """ + # There are many programs that use functools without singledispatch, so we + # trade-off making singledispatch marginally slower for the benefit of + # making start-up of such applications slightly faster. + import types, weakref + + registry = {} + dispatch_cache = weakref.WeakKeyDictionary() + cache_token = None + + def dispatch(cls): + """generic_func.dispatch(cls) -> + + Runs the dispatch algorithm to return the best available implementation + for the given *cls* registered on *generic_func*. + + """ + nonlocal cache_token + if cache_token is not None: + current_token = get_cache_token() + if cache_token != current_token: + dispatch_cache.clear() + cache_token = current_token + try: + impl = dispatch_cache[cls] + except KeyError: + try: + impl = registry[cls] + except KeyError: + impl = _find_impl(cls, registry) + dispatch_cache[cls] = impl + return impl + + def register(cls, func=None): + """generic_func.register(cls, func) -> func + + Registers a new implementation for the given *cls* on a *generic_func*. + + """ + nonlocal cache_token + if func is None: + if isinstance(cls, type): + return lambda f: register(cls, f) + ann = getattr(cls, '__annotations__', {}) + if not ann: + raise TypeError( + f"Invalid first argument to `register()`: {cls!r}. " + f"Use either `@register(some_class)` or plain `@register` " + f"on an annotated function." + ) + func = cls + + # only import typing if annotation parsing is necessary + from typing import get_type_hints + argname, cls = next(iter(get_type_hints(func).items())) + assert isinstance(cls, type), ( + f"Invalid annotation for {argname!r}. {cls!r} is not a class." + ) + registry[cls] = func + if cache_token is None and hasattr(cls, '__abstractmethods__'): + cache_token = get_cache_token() + dispatch_cache.clear() + return func + + def wrapper(*args, **kw): + return dispatch(args[0].__class__)(*args, **kw) + + registry[object] = func + wrapper.register = register + wrapper.dispatch = dispatch + wrapper.registry = types.MappingProxyType(registry) + wrapper._clear_cache = dispatch_cache.clear + update_wrapper(wrapper, func) + return wrapper diff --git a/Lib/linecache.py b/Lib/linecache.py new file mode 100644 index 0000000000..3afcce1f0a --- /dev/null +++ b/Lib/linecache.py @@ -0,0 +1,177 @@ +"""Cache lines from Python source files. + +This is intended to read lines from modules imported -- hence if a filename +is not found, it will look down the module search path for a file by +that name. +""" + +import functools +import sys +import os +import tokenize + +__all__ = ["getline", "clearcache", "checkcache"] + +def getline(filename, lineno, module_globals=None): + lines = getlines(filename, module_globals) + if 1 <= lineno <= len(lines): + return lines[lineno-1] + else: + return '' + + +# The cache + +# The cache. Maps filenames to either a thunk which will provide source code, +# or a tuple (size, mtime, lines, fullname) once loaded. +cache = {} + + +def clearcache(): + """Clear the cache entirely.""" + + global cache + cache = {} + + +def getlines(filename, module_globals=None): + """Get the lines for a Python source file from the cache. + Update the cache if it doesn't contain an entry for this file already.""" + + if filename in cache: + entry = cache[filename] + if len(entry) != 1: + return cache[filename][2] + + try: + return updatecache(filename, module_globals) + except MemoryError: + clearcache() + return [] + + +def checkcache(filename=None): + """Discard cache entries that are out of date. + (This is not checked upon each call!)""" + + if filename is None: + filenames = list(cache.keys()) + else: + if filename in cache: + filenames = [filename] + else: + return + + for filename in filenames: + entry = cache[filename] + if len(entry) == 1: + # lazy cache entry, leave it lazy. + continue + size, mtime, lines, fullname = entry + if mtime is None: + continue # no-op for files loaded via a __loader__ + try: + stat = os.stat(fullname) + except OSError: + del cache[filename] + continue + if size != stat.st_size or mtime != stat.st_mtime: + del cache[filename] + + +def updatecache(filename, module_globals=None): + """Update a cache entry and return its list of lines. + If something's wrong, print a message, discard the cache entry, + and return an empty list.""" + + if filename in cache: + if len(cache[filename]) != 1: + del cache[filename] + if not filename or (filename.startswith('<') and filename.endswith('>')): + return [] + + fullname = filename + try: + stat = os.stat(fullname) + except OSError: + basename = filename + + # Realise a lazy loader based lookup if there is one + # otherwise try to lookup right now. + if lazycache(filename, module_globals): + try: + data = cache[filename][0]() + except (ImportError, OSError): + pass + else: + if data is None: + # No luck, the PEP302 loader cannot find the source + # for this module. + return [] + cache[filename] = ( + len(data), None, + [line+'\n' for line in data.splitlines()], fullname + ) + return cache[filename][2] + + # Try looking through the module search path, which is only useful + # when handling a relative filename. + if os.path.isabs(filename): + return [] + + for dirname in sys.path: + try: + fullname = os.path.join(dirname, basename) + except (TypeError, AttributeError): + # Not sufficiently string-like to do anything useful with. + continue + try: + stat = os.stat(fullname) + break + except OSError: + pass + else: + return [] + try: + with tokenize.open(fullname) as fp: + lines = fp.readlines() + except OSError: + return [] + if lines and not lines[-1].endswith('\n'): + lines[-1] += '\n' + size, mtime = stat.st_size, stat.st_mtime + cache[filename] = size, mtime, lines, fullname + return lines + + +def lazycache(filename, module_globals): + """Seed the cache for filename with module_globals. + + The module loader will be asked for the source only when getlines is + called, not immediately. + + If there is an entry in the cache already, it is not altered. + + :return: True if a lazy load is registered in the cache, + otherwise False. To register such a load a module loader with a + get_source method must be found, the filename must be a cachable + filename, and the filename must not be already cached. + """ + if filename in cache: + if len(cache[filename]) == 1: + return True + else: + return False + if not filename or (filename.startswith('<') and filename.endswith('>')): + return False + # Try for a __loader__, if available + if module_globals and '__loader__' in module_globals: + name = module_globals.get('__name__') + loader = module_globals['__loader__'] + get_source = getattr(loader, 'get_source', None) + + if name and get_source: + get_lines = functools.partial(get_source, name) + cache[filename] = (get_lines,) + return True + return False diff --git a/Lib/warnings.py b/Lib/warnings.py new file mode 100644 index 0000000000..81f9864778 --- /dev/null +++ b/Lib/warnings.py @@ -0,0 +1,554 @@ +"""Python part of the warnings subsystem.""" + +import sys + + +__all__ = ["warn", "warn_explicit", "showwarning", + "formatwarning", "filterwarnings", "simplefilter", + "resetwarnings", "catch_warnings"] + +def showwarning(message, category, filename, lineno, file=None, line=None): + """Hook to write a warning to a file; replace if you like.""" + msg = WarningMessage(message, category, filename, lineno, file, line) + _showwarnmsg_impl(msg) + +def formatwarning(message, category, filename, lineno, line=None): + """Function to format a warning the standard way.""" + msg = WarningMessage(message, category, filename, lineno, None, line) + return _formatwarnmsg_impl(msg) + +def _showwarnmsg_impl(msg): + file = msg.file + if file is None: + file = sys.stderr + if file is None: + # sys.stderr is None when run with pythonw.exe: + # warnings get lost + return + text = _formatwarnmsg(msg) + try: + file.write(text) + except OSError: + # the file (probably stderr) is invalid - this warning gets lost. + pass + +def _formatwarnmsg_impl(msg): + s = ("%s:%s: %s: %s\n" + % (msg.filename, msg.lineno, msg.category.__name__, + msg.message)) + + if msg.line is None: + try: + import linecache + line = linecache.getline(msg.filename, msg.lineno) + except Exception: + # When a warning is logged during Python shutdown, linecache + # and the import machinery don't work anymore + line = None + linecache = None + else: + line = msg.line + if line: + line = line.strip() + s += " %s\n" % line + + if msg.source is not None: + try: + import tracemalloc + tb = tracemalloc.get_object_traceback(msg.source) + except Exception: + # When a warning is logged during Python shutdown, tracemalloc + # and the import machinery don't work anymore + tb = None + + if tb is not None: + s += 'Object allocated at (most recent call last):\n' + for frame in tb: + s += (' File "%s", lineno %s\n' + % (frame.filename, frame.lineno)) + + try: + if linecache is not None: + line = linecache.getline(frame.filename, frame.lineno) + else: + line = None + except Exception: + line = None + if line: + line = line.strip() + s += ' %s\n' % line + return s + +# Keep a reference to check if the function was replaced +_showwarning_orig = showwarning + +def _showwarnmsg(msg): + """Hook to write a warning to a file; replace if you like.""" + try: + sw = showwarning + except NameError: + pass + else: + if sw is not _showwarning_orig: + # warnings.showwarning() was replaced + if not callable(sw): + raise TypeError("warnings.showwarning() must be set to a " + "function or method") + + sw(msg.message, msg.category, msg.filename, msg.lineno, + msg.file, msg.line) + return + _showwarnmsg_impl(msg) + +# Keep a reference to check if the function was replaced +_formatwarning_orig = formatwarning + +def _formatwarnmsg(msg): + """Function to format a warning the standard way.""" + try: + fw = formatwarning + except NameError: + pass + else: + if fw is not _formatwarning_orig: + # warnings.formatwarning() was replaced + return fw(msg.message, msg.category, + msg.filename, msg.lineno, line=msg.line) + return _formatwarnmsg_impl(msg) + +def filterwarnings(action, message="", category=Warning, module="", lineno=0, + append=False): + """Insert an entry into the list of warnings filters (at the front). + + 'action' -- one of "error", "ignore", "always", "default", "module", + or "once" + 'message' -- a regex that the warning message must match + 'category' -- a class that the warning must be a subclass of + 'module' -- a regex that the module name must match + 'lineno' -- an integer line number, 0 matches all warnings + 'append' -- if true, append to the list of filters + """ + assert action in ("error", "ignore", "always", "default", "module", + "once"), "invalid action: %r" % (action,) + assert isinstance(message, str), "message must be a string" + assert isinstance(category, type), "category must be a class" + assert issubclass(category, Warning), "category must be a Warning subclass" + assert isinstance(module, str), "module must be a string" + assert isinstance(lineno, int) and lineno >= 0, \ + "lineno must be an int >= 0" + + if message or module: + import re + + if message: + message = re.compile(message, re.I) + else: + message = None + if module: + module = re.compile(module) + else: + module = None + + _add_filter(action, message, category, module, lineno, append=append) + +def simplefilter(action, category=Warning, lineno=0, append=False): + """Insert a simple entry into the list of warnings filters (at the front). + + A simple filter matches all modules and messages. + 'action' -- one of "error", "ignore", "always", "default", "module", + or "once" + 'category' -- a class that the warning must be a subclass of + 'lineno' -- an integer line number, 0 matches all warnings + 'append' -- if true, append to the list of filters + """ + assert action in ("error", "ignore", "always", "default", "module", + "once"), "invalid action: %r" % (action,) + assert isinstance(lineno, int) and lineno >= 0, \ + "lineno must be an int >= 0" + _add_filter(action, None, category, None, lineno, append=append) + +def _add_filter(*item, append): + # Remove possible duplicate filters, so new one will be placed + # in correct place. If append=True and duplicate exists, do nothing. + if not append: + try: + filters.remove(item) + except ValueError: + pass + filters.insert(0, item) + else: + if item not in filters: + filters.append(item) + _filters_mutated() + +def resetwarnings(): + """Clear the list of warning filters, so that no filters are active.""" + filters[:] = [] + _filters_mutated() + +class _OptionError(Exception): + """Exception used by option processing helpers.""" + pass + +# Helper to process -W options passed via sys.warnoptions +def _processoptions(args): + for arg in args: + try: + _setoption(arg) + except _OptionError as msg: + print("Invalid -W option ignored:", msg, file=sys.stderr) + +# Helper for _processoptions() +def _setoption(arg): + import re + parts = arg.split(':') + if len(parts) > 5: + raise _OptionError("too many fields (max 5): %r" % (arg,)) + while len(parts) < 5: + parts.append('') + action, message, category, module, lineno = [s.strip() + for s in parts] + action = _getaction(action) + message = re.escape(message) + category = _getcategory(category) + module = re.escape(module) + if module: + module = module + '$' + if lineno: + try: + lineno = int(lineno) + if lineno < 0: + raise ValueError + except (ValueError, OverflowError): + raise _OptionError("invalid lineno %r" % (lineno,)) from None + else: + lineno = 0 + filterwarnings(action, message, category, module, lineno) + +# Helper for _setoption() +def _getaction(action): + if not action: + return "default" + if action == "all": return "always" # Alias + for a in ('default', 'always', 'ignore', 'module', 'once', 'error'): + if a.startswith(action): + return a + raise _OptionError("invalid action: %r" % (action,)) + +# Helper for _setoption() +def _getcategory(category): + import re + if not category: + return Warning + if re.match("^[a-zA-Z0-9_]+$", category): + try: + cat = eval(category) + except NameError: + raise _OptionError("unknown warning category: %r" % (category,)) from None + else: + i = category.rfind(".") + module = category[:i] + klass = category[i+1:] + try: + m = __import__(module, None, None, [klass]) + except ImportError: + raise _OptionError("invalid module name: %r" % (module,)) from None + try: + cat = getattr(m, klass) + except AttributeError: + raise _OptionError("unknown warning category: %r" % (category,)) from None + if not issubclass(cat, Warning): + raise _OptionError("invalid warning category: %r" % (category,)) + return cat + + +def _is_internal_frame(frame): + """Signal whether the frame is an internal CPython implementation detail.""" + filename = frame.f_code.co_filename + return 'importlib' in filename and '_bootstrap' in filename + + +def _next_external_frame(frame): + """Find the next frame that doesn't involve CPython internals.""" + frame = frame.f_back + while frame is not None and _is_internal_frame(frame): + frame = frame.f_back + return frame + + +# Code typically replaced by _warnings +def warn(message, category=None, stacklevel=1, source=None): + """Issue a warning, or maybe ignore it or raise an exception.""" + # Check if message is already a Warning object + if isinstance(message, Warning): + category = message.__class__ + # Check category argument + if category is None: + category = UserWarning + if not (isinstance(category, type) and issubclass(category, Warning)): + raise TypeError("category must be a Warning subclass, " + "not '{:s}'".format(type(category).__name__)) + # Get context information + try: + if stacklevel <= 1 or _is_internal_frame(sys._getframe(1)): + # If frame is too small to care or if the warning originated in + # internal code, then do not try to hide any frames. + frame = sys._getframe(stacklevel) + else: + frame = sys._getframe(1) + # Look for one frame less since the above line starts us off. + for x in range(stacklevel-1): + frame = _next_external_frame(frame) + if frame is None: + raise ValueError + except ValueError: + globals = sys.__dict__ + lineno = 1 + else: + globals = frame.f_globals + lineno = frame.f_lineno + if '__name__' in globals: + module = globals['__name__'] + else: + module = "" + filename = globals.get('__file__') + if filename: + fnl = filename.lower() + if fnl.endswith(".pyc"): + filename = filename[:-1] + else: + if module == "__main__": + try: + filename = sys.argv[0] + except AttributeError: + # embedded interpreters don't have sys.argv, see bug #839151 + filename = '__main__' + if not filename: + filename = module + registry = globals.setdefault("__warningregistry__", {}) + warn_explicit(message, category, filename, lineno, module, registry, + globals, source) + +def warn_explicit(message, category, filename, lineno, + module=None, registry=None, module_globals=None, + source=None): + lineno = int(lineno) + if module is None: + module = filename or "" + if module[-3:].lower() == ".py": + module = module[:-3] # XXX What about leading pathname? + if registry is None: + registry = {} + if registry.get('version', 0) != _filters_version: + registry.clear() + registry['version'] = _filters_version + if isinstance(message, Warning): + text = str(message) + category = message.__class__ + else: + text = message + message = category(message) + key = (text, category, lineno) + # Quick test for common case + if registry.get(key): + return + # Search the filters + for item in filters: + action, msg, cat, mod, ln = item + if ((msg is None or msg.match(text)) and + issubclass(category, cat) and + (mod is None or mod.match(module)) and + (ln == 0 or lineno == ln)): + break + else: + action = defaultaction + # Early exit actions + if action == "ignore": + return + + # Prime the linecache for formatting, in case the + # "file" is actually in a zipfile or something. + import linecache + linecache.getlines(filename, module_globals) + + if action == "error": + raise message + # Other actions + if action == "once": + registry[key] = 1 + oncekey = (text, category) + if onceregistry.get(oncekey): + return + onceregistry[oncekey] = 1 + elif action == "always": + pass + elif action == "module": + registry[key] = 1 + altkey = (text, category, 0) + if registry.get(altkey): + return + registry[altkey] = 1 + elif action == "default": + registry[key] = 1 + else: + # Unrecognized actions are errors + raise RuntimeError( + "Unrecognized action (%r) in warnings.filters:\n %s" % + (action, item)) + # Print message and context + msg = WarningMessage(message, category, filename, lineno, source) + _showwarnmsg(msg) + + +class WarningMessage(object): + + _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", + "line", "source") + + def __init__(self, message, category, filename, lineno, file=None, + line=None, source=None): + self.message = message + self.category = category + self.filename = filename + self.lineno = lineno + self.file = file + self.line = line + self.source = source + self._category_name = category.__name__ if category else None + + def __str__(self): + return ("{message : %r, category : %r, filename : %r, lineno : %s, " + "line : %r}" % (self.message, self._category_name, + self.filename, self.lineno, self.line)) + + +class catch_warnings(object): + + """A context manager that copies and restores the warnings filter upon + exiting the context. + + The 'record' argument specifies whether warnings should be captured by a + custom implementation of warnings.showwarning() and be appended to a list + returned by the context manager. Otherwise None is returned by the context + manager. The objects appended to the list are arguments whose attributes + mirror the arguments to showwarning(). + + The 'module' argument is to specify an alternative module to the module + named 'warnings' and imported under that name. This argument is only useful + when testing the warnings module itself. + + """ + + def __init__(self, *, record=False, module=None): + """Specify whether to record warnings and if an alternative module + should be used other than sys.modules['warnings']. + + For compatibility with Python 3.0, please consider all arguments to be + keyword-only. + + """ + self._record = record + self._module = sys.modules['warnings'] if module is None else module + self._entered = False + + def __repr__(self): + args = [] + if self._record: + args.append("record=True") + if self._module is not sys.modules['warnings']: + args.append("module=%r" % self._module) + name = type(self).__name__ + return "%s(%s)" % (name, ", ".join(args)) + + def __enter__(self): + if self._entered: + raise RuntimeError("Cannot enter %r twice" % self) + self._entered = True + self._filters = self._module.filters + self._module.filters = self._filters[:] + self._module._filters_mutated() + self._showwarning = self._module.showwarning + self._showwarnmsg_impl = self._module._showwarnmsg_impl + if self._record: + log = [] + self._module._showwarnmsg_impl = log.append + # Reset showwarning() to the default implementation to make sure + # that _showwarnmsg() calls _showwarnmsg_impl() + self._module.showwarning = self._module._showwarning_orig + return log + else: + return None + + def __exit__(self, *exc_info): + if not self._entered: + raise RuntimeError("Cannot exit %r without entering first" % self) + self._module.filters = self._filters + self._module._filters_mutated() + self._module.showwarning = self._showwarning + self._module._showwarnmsg_impl = self._showwarnmsg_impl + + +# Private utility function called by _PyErr_WarnUnawaitedCoroutine +def _warn_unawaited_coroutine(coro): + msg_lines = [ + f"coroutine '{coro.__qualname__}' was never awaited\n" + ] + if coro.cr_origin is not None: + import linecache, traceback + def extract(): + for filename, lineno, funcname in reversed(coro.cr_origin): + line = linecache.getline(filename, lineno) + yield (filename, lineno, funcname, line) + msg_lines.append("Coroutine created at (most recent call last)\n") + msg_lines += traceback.format_list(list(extract())) + msg = "".join(msg_lines).rstrip("\n") + # Passing source= here means that if the user happens to have tracemalloc + # enabled and tracking where the coroutine was created, the warning will + # contain that traceback. This does mean that if they have *both* + # coroutine origin tracking *and* tracemalloc enabled, they'll get two + # partially-redundant tracebacks. If we wanted to be clever we could + # probably detect this case and avoid it, but for now we don't bother. + warn(msg, category=RuntimeWarning, stacklevel=2, source=coro) + + +# filters contains a sequence of filter 5-tuples +# The components of the 5-tuple are: +# - an action: error, ignore, always, default, module, or once +# - a compiled regex that must match the warning message +# - a class representing the warning category +# - a compiled regex that must match the module that is being warned +# - a line number for the line being warning, or 0 to mean any line +# If either if the compiled regexs are None, match anything. +try: + from _warnings import (filters, _defaultaction, _onceregistry, + warn, warn_explicit, _filters_mutated) + defaultaction = _defaultaction + onceregistry = _onceregistry + _warnings_defaults = True +except ImportError: + filters = [] + defaultaction = "default" + onceregistry = {} + + _filters_version = 1 + + def _filters_mutated(): + global _filters_version + _filters_version += 1 + + _warnings_defaults = False + + +# Module initialization +_processoptions(sys.warnoptions) +if not _warnings_defaults: + # Several warning categories are ignored by default in regular builds + if not hasattr(sys, 'gettotalrefcount'): + filterwarnings("default", category=DeprecationWarning, + module="__main__", append=1) + simplefilter("ignore", category=DeprecationWarning, append=1) + simplefilter("ignore", category=PendingDeprecationWarning, append=1) + simplefilter("ignore", category=ImportWarning, append=1) + simplefilter("ignore", category=ResourceWarning, append=1) + +del _warnings_defaults diff --git a/Lib/weakref.py b/Lib/weakref.py new file mode 100644 index 0000000000..99de2eab74 --- /dev/null +++ b/Lib/weakref.py @@ -0,0 +1,632 @@ +"""Weak reference support for Python. + +This module is an implementation of PEP 205: + +http://www.python.org/dev/peps/pep-0205/ +""" + +# Naming convention: Variables named "wr" are weak reference objects; +# they are called this instead of "ref" to avoid name collisions with +# the module-global ref() function imported from _weakref. + +from _weakref import ( + getweakrefcount, + getweakrefs, + ref, + proxy, + CallableProxyType, + ProxyType, + ReferenceType, + _remove_dead_weakref) + +from _weakrefset import WeakSet, _IterationGuard + +import _collections_abc # Import after _weakref to avoid circular import. +import sys +import itertools + +ProxyTypes = (ProxyType, CallableProxyType) + +__all__ = ["ref", "proxy", "getweakrefcount", "getweakrefs", + "WeakKeyDictionary", "ReferenceType", "ProxyType", + "CallableProxyType", "ProxyTypes", "WeakValueDictionary", + "WeakSet", "WeakMethod", "finalize"] + + +class WeakMethod(ref): + """ + A custom `weakref.ref` subclass which simulates a weak reference to + a bound method, working around the lifetime problem of bound methods. + """ + + __slots__ = "_func_ref", "_meth_type", "_alive", "__weakref__" + + def __new__(cls, meth, callback=None): + try: + obj = meth.__self__ + func = meth.__func__ + except AttributeError: + raise TypeError("argument should be a bound method, not {}" + .format(type(meth))) from None + def _cb(arg): + # The self-weakref trick is needed to avoid creating a reference + # cycle. + self = self_wr() + if self._alive: + self._alive = False + if callback is not None: + callback(self) + self = ref.__new__(cls, obj, _cb) + self._func_ref = ref(func, _cb) + self._meth_type = type(meth) + self._alive = True + self_wr = ref(self) + return self + + def __call__(self): + obj = super().__call__() + func = self._func_ref() + if obj is None or func is None: + return None + return self._meth_type(func, obj) + + def __eq__(self, other): + if isinstance(other, WeakMethod): + if not self._alive or not other._alive: + return self is other + return ref.__eq__(self, other) and self._func_ref == other._func_ref + return False + + def __ne__(self, other): + if isinstance(other, WeakMethod): + if not self._alive or not other._alive: + return self is not other + return ref.__ne__(self, other) or self._func_ref != other._func_ref + return True + + __hash__ = ref.__hash__ + + +class WeakValueDictionary(_collections_abc.MutableMapping): + """Mapping class that references values weakly. + + Entries in the dictionary will be discarded when no strong + reference to the value exists anymore + """ + # We inherit the constructor without worrying about the input + # dictionary; since it uses our .update() method, we get the right + # checks (if the other dictionary is a WeakValueDictionary, + # objects are unwrapped on the way out, and we always wrap on the + # way in). + + def __init__(*args, **kw): + if not args: + raise TypeError("descriptor '__init__' of 'WeakValueDictionary' " + "object needs an argument") + self, *args = args + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + def remove(wr, selfref=ref(self), _atomic_removal=_remove_dead_weakref): + self = selfref() + if self is not None: + if self._iterating: + self._pending_removals.append(wr.key) + else: + # Atomic removal is necessary since this function + # can be called asynchronously by the GC + _atomic_removal(d, wr.key) + self._remove = remove + # A list of keys to be removed + self._pending_removals = [] + self._iterating = set() + self.data = d = {} + self.update(*args, **kw) + + def _commit_removals(self): + l = self._pending_removals + d = self.data + # We shouldn't encounter any KeyError, because this method should + # always be called *before* mutating the dict. + while l: + key = l.pop() + _remove_dead_weakref(d, key) + + def __getitem__(self, key): + if self._pending_removals: + self._commit_removals() + o = self.data[key]() + if o is None: + raise KeyError(key) + else: + return o + + def __delitem__(self, key): + if self._pending_removals: + self._commit_removals() + del self.data[key] + + def __len__(self): + if self._pending_removals: + self._commit_removals() + return len(self.data) + + def __contains__(self, key): + if self._pending_removals: + self._commit_removals() + try: + o = self.data[key]() + except KeyError: + return False + return o is not None + + def __repr__(self): + return "<%s at %#x>" % (self.__class__.__name__, id(self)) + + def __setitem__(self, key, value): + if self._pending_removals: + self._commit_removals() + self.data[key] = KeyedRef(value, self._remove, key) + + def copy(self): + if self._pending_removals: + self._commit_removals() + new = WeakValueDictionary() + for key, wr in self.data.items(): + o = wr() + if o is not None: + new[key] = o + return new + + __copy__ = copy + + def __deepcopy__(self, memo): + from copy import deepcopy + if self._pending_removals: + self._commit_removals() + new = self.__class__() + for key, wr in self.data.items(): + o = wr() + if o is not None: + new[deepcopy(key, memo)] = o + return new + + def get(self, key, default=None): + if self._pending_removals: + self._commit_removals() + try: + wr = self.data[key] + except KeyError: + return default + else: + o = wr() + if o is None: + # This should only happen + return default + else: + return o + + def items(self): + if self._pending_removals: + self._commit_removals() + with _IterationGuard(self): + for k, wr in self.data.items(): + v = wr() + if v is not None: + yield k, v + + def keys(self): + if self._pending_removals: + self._commit_removals() + with _IterationGuard(self): + for k, wr in self.data.items(): + if wr() is not None: + yield k + + __iter__ = keys + + def itervaluerefs(self): + """Return an iterator that yields the weak references to the values. + + The references are not guaranteed to be 'live' at the time + they are used, so the result of calling the references needs + to be checked before being used. This can be used to avoid + creating references that will cause the garbage collector to + keep the values around longer than needed. + + """ + if self._pending_removals: + self._commit_removals() + with _IterationGuard(self): + yield from self.data.values() + + def values(self): + if self._pending_removals: + self._commit_removals() + with _IterationGuard(self): + for wr in self.data.values(): + obj = wr() + if obj is not None: + yield obj + + def popitem(self): + if self._pending_removals: + self._commit_removals() + while True: + key, wr = self.data.popitem() + o = wr() + if o is not None: + return key, o + + def pop(self, key, *args): + if self._pending_removals: + self._commit_removals() + try: + o = self.data.pop(key)() + except KeyError: + o = None + if o is None: + if args: + return args[0] + else: + raise KeyError(key) + else: + return o + + def setdefault(self, key, default=None): + try: + o = self.data[key]() + except KeyError: + o = None + if o is None: + if self._pending_removals: + self._commit_removals() + self.data[key] = KeyedRef(default, self._remove, key) + return default + else: + return o + + def update(*args, **kwargs): + if not args: + raise TypeError("descriptor 'update' of 'WeakValueDictionary' " + "object needs an argument") + self, *args = args + if len(args) > 1: + raise TypeError('expected at most 1 arguments, got %d' % len(args)) + dict = args[0] if args else None + if self._pending_removals: + self._commit_removals() + d = self.data + if dict is not None: + if not hasattr(dict, "items"): + dict = type({})(dict) + for key, o in dict.items(): + d[key] = KeyedRef(o, self._remove, key) + if len(kwargs): + self.update(kwargs) + + def valuerefs(self): + """Return a list of weak references to the values. + + The references are not guaranteed to be 'live' at the time + they are used, so the result of calling the references needs + to be checked before being used. This can be used to avoid + creating references that will cause the garbage collector to + keep the values around longer than needed. + + """ + if self._pending_removals: + self._commit_removals() + return list(self.data.values()) + + +class KeyedRef(ref): + """Specialized reference that includes a key corresponding to the value. + + This is used in the WeakValueDictionary to avoid having to create + a function object for each key stored in the mapping. A shared + callback object can use the 'key' attribute of a KeyedRef instead + of getting a reference to the key from an enclosing scope. + + """ + + __slots__ = "key", + + def __new__(type, ob, callback, key): + self = ref.__new__(type, ob, callback) + self.key = key + return self + + def __init__(self, ob, callback, key): + super().__init__(ob, callback) + + +class WeakKeyDictionary(_collections_abc.MutableMapping): + """ Mapping class that references keys weakly. + + Entries in the dictionary will be discarded when there is no + longer a strong reference to the key. This can be used to + associate additional data with an object owned by other parts of + an application without adding attributes to those objects. This + can be especially useful with objects that override attribute + accesses. + """ + + def __init__(self, dict=None): + self.data = {} + def remove(k, selfref=ref(self)): + self = selfref() + if self is not None: + if self._iterating: + self._pending_removals.append(k) + else: + del self.data[k] + self._remove = remove + # A list of dead weakrefs (keys to be removed) + self._pending_removals = [] + self._iterating = set() + self._dirty_len = False + if dict is not None: + self.update(dict) + + def _commit_removals(self): + # NOTE: We don't need to call this method before mutating the dict, + # because a dead weakref never compares equal to a live weakref, + # even if they happened to refer to equal objects. + # However, it means keys may already have been removed. + l = self._pending_removals + d = self.data + while l: + try: + del d[l.pop()] + except KeyError: + pass + + def _scrub_removals(self): + d = self.data + self._pending_removals = [k for k in self._pending_removals if k in d] + self._dirty_len = False + + def __delitem__(self, key): + self._dirty_len = True + del self.data[ref(key)] + + def __getitem__(self, key): + return self.data[ref(key)] + + def __len__(self): + if self._dirty_len and self._pending_removals: + # self._pending_removals may still contain keys which were + # explicitly removed, we have to scrub them (see issue #21173). + self._scrub_removals() + return len(self.data) - len(self._pending_removals) + + def __repr__(self): + return "<%s at %#x>" % (self.__class__.__name__, id(self)) + + def __setitem__(self, key, value): + self.data[ref(key, self._remove)] = value + + def copy(self): + new = WeakKeyDictionary() + for key, value in self.data.items(): + o = key() + if o is not None: + new[o] = value + return new + + __copy__ = copy + + def __deepcopy__(self, memo): + from copy import deepcopy + new = self.__class__() + for key, value in self.data.items(): + o = key() + if o is not None: + new[o] = deepcopy(value, memo) + return new + + def get(self, key, default=None): + return self.data.get(ref(key),default) + + def __contains__(self, key): + try: + wr = ref(key) + except TypeError: + return False + return wr in self.data + + def items(self): + with _IterationGuard(self): + for wr, value in self.data.items(): + key = wr() + if key is not None: + yield key, value + + def keys(self): + with _IterationGuard(self): + for wr in self.data: + obj = wr() + if obj is not None: + yield obj + + __iter__ = keys + + def values(self): + with _IterationGuard(self): + for wr, value in self.data.items(): + if wr() is not None: + yield value + + def keyrefs(self): + """Return a list of weak references to the keys. + + The references are not guaranteed to be 'live' at the time + they are used, so the result of calling the references needs + to be checked before being used. This can be used to avoid + creating references that will cause the garbage collector to + keep the keys around longer than needed. + + """ + return list(self.data) + + def popitem(self): + self._dirty_len = True + while True: + key, value = self.data.popitem() + o = key() + if o is not None: + return o, value + + def pop(self, key, *args): + self._dirty_len = True + return self.data.pop(ref(key), *args) + + def setdefault(self, key, default=None): + return self.data.setdefault(ref(key, self._remove),default) + + def update(self, dict=None, **kwargs): + d = self.data + if dict is not None: + if not hasattr(dict, "items"): + dict = type({})(dict) + for key, value in dict.items(): + d[ref(key, self._remove)] = value + if len(kwargs): + self.update(kwargs) + + +class finalize: + """Class for finalization of weakrefable objects + + finalize(obj, func, *args, **kwargs) returns a callable finalizer + object which will be called when obj is garbage collected. The + first time the finalizer is called it evaluates func(*arg, **kwargs) + and returns the result. After this the finalizer is dead, and + calling it just returns None. + + When the program exits any remaining finalizers for which the + atexit attribute is true will be run in reverse order of creation. + By default atexit is true. + """ + + # Finalizer objects don't have any state of their own. They are + # just used as keys to lookup _Info objects in the registry. This + # ensures that they cannot be part of a ref-cycle. + + __slots__ = () + _registry = {} + _shutdown = False + _index_iter = itertools.count() + _dirty = False + _registered_with_atexit = False + + class _Info: + __slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index") + + def __init__(self, obj, func, *args, **kwargs): + if not self._registered_with_atexit: + # We may register the exit function more than once because + # of a thread race, but that is harmless + import atexit + atexit.register(self._exitfunc) + finalize._registered_with_atexit = True + info = self._Info() + info.weakref = ref(obj, self) + info.func = func + info.args = args + info.kwargs = kwargs or None + info.atexit = True + info.index = next(self._index_iter) + self._registry[self] = info + finalize._dirty = True + + def __call__(self, _=None): + """If alive then mark as dead and return func(*args, **kwargs); + otherwise return None""" + info = self._registry.pop(self, None) + if info and not self._shutdown: + return info.func(*info.args, **(info.kwargs or {})) + + def detach(self): + """If alive then mark as dead and return (obj, func, args, kwargs); + otherwise return None""" + info = self._registry.get(self) + obj = info and info.weakref() + if obj is not None and self._registry.pop(self, None): + return (obj, info.func, info.args, info.kwargs or {}) + + def peek(self): + """If alive then return (obj, func, args, kwargs); + otherwise return None""" + info = self._registry.get(self) + obj = info and info.weakref() + if obj is not None: + return (obj, info.func, info.args, info.kwargs or {}) + + @property + def alive(self): + """Whether finalizer is alive""" + return self in self._registry + + @property + def atexit(self): + """Whether finalizer should be called at exit""" + info = self._registry.get(self) + return bool(info) and info.atexit + + @atexit.setter + def atexit(self, value): + info = self._registry.get(self) + if info: + info.atexit = bool(value) + + def __repr__(self): + info = self._registry.get(self) + obj = info and info.weakref() + if obj is None: + return '<%s object at %#x; dead>' % (type(self).__name__, id(self)) + else: + return '<%s object at %#x; for %r at %#x>' % \ + (type(self).__name__, id(self), type(obj).__name__, id(obj)) + + @classmethod + def _select_for_exit(cls): + # Return live finalizers marked for exit, oldest first + L = [(f,i) for (f,i) in cls._registry.items() if i.atexit] + L.sort(key=lambda item:item[1].index) + return [f for (f,i) in L] + + @classmethod + def _exitfunc(cls): + # At shutdown invoke finalizers for which atexit is true. + # This is called once all other non-daemonic threads have been + # joined. + reenable_gc = False + try: + if cls._registry: + import gc + if gc.isenabled(): + reenable_gc = True + gc.disable() + pending = None + while True: + if pending is None or finalize._dirty: + pending = cls._select_for_exit() + finalize._dirty = False + if not pending: + break + f = pending.pop() + try: + # gc is disabled, so (assuming no daemonic + # threads) the following is the only line in + # this function which might trigger creation + # of a new finalizer + f() + except Exception: + sys.excepthook(*sys.exc_info()) + assert f not in cls._registry + finally: + # prevent any more finalizers from executing during shutdown + finalize._shutdown = True + if reenable_gc: + gc.enable() diff --git a/vm/src/stdlib/weakref.rs b/vm/src/stdlib/weakref.rs index d4529599ef..58a23704d6 100644 --- a/vm/src/stdlib/weakref.rs +++ b/vm/src/stdlib/weakref.rs @@ -5,8 +5,22 @@ //! - [rust weak struct](https://doc.rust-lang.org/std/rc/struct.Weak.html) //! -use super::super::pyobject::PyObjectRef; +use crate::pyobject::PyObjectRef; use crate::vm::VirtualMachine; +use std::rc::Rc; + +fn weakref_getweakrefcount(obj: PyObjectRef, _vm: &VirtualMachine) -> usize { + Rc::weak_count(&obj) +} + +fn weakref_getweakrefs(_obj: PyObjectRef, vm: &VirtualMachine) -> PyObjectRef { + // TODO: implement this, may require a different gc + vm.ctx.new_list(vec![]) +} + +fn weakref_remove_dead_weakref(_obj: PyObjectRef, _key: PyObjectRef, _vm: &VirtualMachine) { + // TODO +} pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; @@ -14,5 +28,11 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { py_module!(vm, "_weakref", { "ref" => ctx.weakref_type(), "proxy" => ctx.weakproxy_type(), + "getweakrefcount" => ctx.new_rustfunc(weakref_getweakrefcount), + "getweakrefs" => ctx.new_rustfunc(weakref_getweakrefs), + "ReferenceType" => ctx.weakref_type(), + "ProxyType" => ctx.weakproxy_type(), + "CallableProxyType" => ctx.weakproxy_type(), + "_remove_dead_weakref" => ctx.new_rustfunc(weakref_remove_dead_weakref), }) } From 848714e18e31040a1b008960c93f062c8a4f2fb4 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Wed, 26 Jun 2019 23:40:24 +0300 Subject: [PATCH 835/884] save passed object into the first .args parameter for dict/mappingproxy KeyError --- tests/snippets/dict.py | 40 +++++++++++++++++++++++++++------- tests/snippets/mappingproxy.py | 6 +++-- tests/snippets/testutils.py | 3 +++ vm/src/dictdatatype.rs | 6 ++--- vm/src/obj/objdict.rs | 5 +++-- vm/src/obj/objmappingproxy.rs | 2 +- vm/src/obj/objset.rs | 3 ++- vm/src/obj/objstr.rs | 2 +- vm/src/stdlib/pwd.rs | 4 ++-- vm/src/vm.rs | 20 ++++++++--------- 10 files changed, 60 insertions(+), 31 deletions(-) diff --git a/tests/snippets/dict.py b/tests/snippets/dict.py index 4545f7f584..c453cff2b4 100644 --- a/tests/snippets/dict.py +++ b/tests/snippets/dict.py @@ -52,6 +52,10 @@ with assertRaises(StopIteration): next(it) +with assertRaises(KeyError) as cm: + del x[10] +assert cm.exception.args[0] == 10 + # Iterating a dictionary is just its keys: assert ['a', 'b', 'd'] == list(x) @@ -95,9 +99,6 @@ x[2] = 2 x[(5, 6)] = 5 -with assertRaises(KeyError): - x["not here"] - with assertRaises(TypeError): x[[]] # Unhashable type. @@ -167,19 +168,42 @@ def __eq__(self, other): y.update(y) assert y == {'a': 2, 'b': 12, 'c': 19, 'd': -1} # hasn't changed +# KeyError has object that used as key as an .args[0] +with assertRaises(KeyError) as cm: + x['not here'] +assert cm.exception.args[0] == "not here" +with assertRaises(KeyError) as cm: + x.pop('not here') +assert cm.exception.args[0] == "not here" + +with assertRaises(KeyError) as cm: + x[10] +assert cm.exception.args[0] == 10 +with assertRaises(KeyError) as cm: + x.pop(10) +assert cm.exception.args[0] == 10 + +class MyClass: pass +obj = MyClass() + +with assertRaises(KeyError) as cm: + x[obj] +assert cm.exception.args[0] == obj +with assertRaises(KeyError) as cm: + x.pop(obj) +assert cm.exception.args[0] == obj + x = {1: 'a', '1': None} assert x.pop(1) == 'a' assert x.pop('1') is None assert x == {} -with assertRaises(KeyError): - x.pop("not here") - x = {1: 'a'} assert (1, 'a') == x.popitem() -with assertRaises(KeyError): - x.popitem() assert x == {} +with assertRaises(KeyError) as cm: + x.popitem() +assert cm.exception.args == ('popitem(): dictionary is empty',) x = {'a': 4} assert 4 == x.setdefault('a', 0) diff --git a/tests/snippets/mappingproxy.py b/tests/snippets/mappingproxy.py index 624bf6d5f2..254f2a9251 100644 --- a/tests/snippets/mappingproxy.py +++ b/tests/snippets/mappingproxy.py @@ -9,8 +9,10 @@ def b(): assert A.__dict__['a'] == A.a -with assertRaises(KeyError): - A.__dict__['not_here'] +with assertRaises(KeyError) as cm: + A.__dict__['not here'] + +assert cm.exception.args[0] == "not here" assert 'b' in A.__dict__ assert 'c' not in A.__dict__ diff --git a/tests/snippets/testutils.py b/tests/snippets/testutils.py index ea4dd91384..21662c4614 100644 --- a/tests/snippets/testutils.py +++ b/tests/snippets/testutils.py @@ -23,6 +23,7 @@ def assert_raises(exc_type, expr, msg=None): class assertRaises: def __init__(self, expected): self.expected = expected + self.exception = None def __enter__(self): return self @@ -33,6 +34,8 @@ def __exit__(self, exc_type, exc_val, exc_tb): assert False, failmsg if not issubclass(exc_type, self.expected): return False + + self.exception = exc_val return True diff --git a/vm/src/dictdatatype.rs b/vm/src/dictdatatype.rs index ca20f20127..e52e83b8c5 100644 --- a/vm/src/dictdatatype.rs +++ b/vm/src/dictdatatype.rs @@ -129,8 +129,7 @@ impl Dict { if self.delete_if_exists(vm, key)? { Ok(()) } else { - let key_repr = vm.to_pystr(key)?; - Err(vm.new_key_error(format!("Key not found: {}", key_repr))) + Err(vm.new_key_error(key.clone())) } } @@ -247,8 +246,7 @@ impl Dict { self.unchecked_delete(index); Ok(value) } else { - let key_repr = vm.to_pystr(key)?; - Err(vm.new_key_error(format!("Key not found: {}", key_repr))) + Err(vm.new_key_error(key.clone())) } } diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index 8d2e0fdff8..7b708611e8 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -210,7 +210,7 @@ impl PyDictRef { let method = method_or_err?; return vm.invoke(method, vec![key]); } - Err(vm.new_key_error(format!("Key not found: {}", vm.to_pystr(&key)?))) + Err(vm.new_key_error(key.clone())) } fn get( @@ -266,7 +266,8 @@ impl PyDictRef { if let Some((key, value)) = entries.pop_front() { Ok(vm.ctx.new_tuple(vec![key, value])) } else { - Err(vm.new_key_error("popitem(): dictionary is empty".to_string())) + let err_msg = vm.new_str("popitem(): dictionary is empty".to_string()); + Err(vm.new_key_error(err_msg)) } } diff --git a/vm/src/obj/objmappingproxy.rs b/vm/src/obj/objmappingproxy.rs index c46591105b..243a8c89cc 100644 --- a/vm/src/obj/objmappingproxy.rs +++ b/vm/src/obj/objmappingproxy.rs @@ -28,7 +28,7 @@ impl PyMappingProxy { if let Some(value) = objtype::class_get_attr(&self.class, key.as_str()) { return Ok(value); } - Err(vm.new_key_error(format!("Key not found: {}", key))) + Err(vm.new_key_error(key.into_object())) } #[pymethod(name = "__contains__")] diff --git a/vm/src/obj/objset.rs b/vm/src/obj/objset.rs index b7f0276ced..ede6029f32 100644 --- a/vm/src/obj/objset.rs +++ b/vm/src/obj/objset.rs @@ -269,7 +269,8 @@ impl PySetInner { if let Some((key, _)) = self.content.pop_front() { Ok(key) } else { - Err(vm.new_key_error("pop from an empty set".to_string())) + let err_msg = vm.new_str("pop from an empty set".to_string()); + Err(vm.new_key_error(err_msg)) } } diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index 1b6870e149..fafd23a65d 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -1335,7 +1335,7 @@ fn perform_format( let result = match arguments.get_optional_kwarg(&keyword) { Some(argument) => call_object_format(vm, argument.clone(), &format_spec)?, None => { - return Err(vm.new_key_error(format!("'{}'", keyword))); + return Err(vm.new_key_error(vm.new_str(keyword.to_string()))); } }; get_value(&result) diff --git a/vm/src/stdlib/pwd.rs b/vm/src/stdlib/pwd.rs index 1f50925409..85cb2d3eb0 100644 --- a/vm/src/stdlib/pwd.rs +++ b/vm/src/stdlib/pwd.rs @@ -48,7 +48,7 @@ fn pwd_getpwnam(name: PyStringRef, vm: &VirtualMachine) -> PyResult { Ok(Some(passwd)) => Ok(passwd), _ => { let name_repr = vm.to_repr(name.as_object())?; - let message = format!("getpwnam(): name not found: {}", name_repr); + let message = vm.new_str(format!("getpwnam(): name not found: {}", name_repr)); Err(vm.new_key_error(message)) } } @@ -58,7 +58,7 @@ fn pwd_getpwuid(uid: u32, vm: &VirtualMachine) -> PyResult { match Passwd::from_uid(uid) { Some(passwd) => Ok(passwd), _ => { - let message = format!("getpwuid(): uid not found: {}", uid); + let message = vm.new_str(format!("getpwuid(): uid not found: {}", uid)); Err(vm.new_key_error(message)) } } diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 3aaa23dc10..4e69f33893 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -167,20 +167,20 @@ impl VirtualMachine { self.ctx.new_bool(b) } - pub fn new_empty_exception(&self, exc_type: PyClassRef) -> PyResult { + fn new_exception_obj(&self, exc_type: PyClassRef, args: Vec) -> PyResult { + // TODO: add repr of args into logging? info!("New exception created: {}", exc_type.name); - let args = PyFuncArgs::default(); self.invoke(exc_type.into_object(), args) } + pub fn new_empty_exception(&self, exc_type: PyClassRef) -> PyResult { + self.new_exception_obj(exc_type, vec![]) + } + /// Create Python instance of `exc_type` with message as first element of `args` tuple pub fn new_exception(&self, exc_type: PyClassRef, msg: String) -> PyObjectRef { - // TODO: exc_type may be user-defined exception, so we should return PyResult - // TODO: maybe there is a clearer way to create an instance: - info!("New exception created: {}('{}')", exc_type.name, msg); - let pymsg = self.new_str(msg); - let args: Vec = vec![pymsg]; - self.invoke(exc_type.into_object(), args).unwrap() + let pystr_msg = self.new_str(msg); + self.new_exception_obj(exc_type, vec![pystr_msg]).unwrap() } pub fn new_attribute_error(&self, msg: String) -> PyObjectRef { @@ -224,9 +224,9 @@ impl VirtualMachine { self.new_exception(value_error, msg) } - pub fn new_key_error(&self, msg: String) -> PyObjectRef { + pub fn new_key_error(&self, obj: PyObjectRef) -> PyObjectRef { let key_error = self.ctx.exceptions.key_error.clone(); - self.new_exception(key_error, msg) + self.new_exception_obj(key_error, vec![obj]).unwrap() } pub fn new_index_error(&self, msg: String) -> PyObjectRef { From 0a369d821d926e64e077193bc439cb1a0b7d4137 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Thu, 27 Jun 2019 19:09:35 +0200 Subject: [PATCH 836/884] Add initial hashlib module. --- tests/snippets/test_hashlib.py | 21 +++++ vm/Cargo.toml | 1 + vm/src/stdlib/hashlib.rs | 140 +++++++++++++++++++++++++++++++++ vm/src/stdlib/mod.rs | 2 + 4 files changed, 164 insertions(+) create mode 100644 tests/snippets/test_hashlib.py create mode 100644 vm/src/stdlib/hashlib.rs diff --git a/tests/snippets/test_hashlib.py b/tests/snippets/test_hashlib.py new file mode 100644 index 0000000000..f8d9e088ea --- /dev/null +++ b/tests/snippets/test_hashlib.py @@ -0,0 +1,21 @@ + +import hashlib + +# print(hashlib.md5) +h = hashlib.md5() +h.update(b'a') +print(h.hexdigest()) + +assert h.hexdigest() == '0cc175b9c0f1b6a831c399e269772661' + +h = hashlib.sha256() +h.update(b'a') +print(h.hexdigest()) + +assert h.hexdigest() == 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb' + +h = hashlib.sha512() +h.update(b'a') +print(h.hexdigest()) + +assert h.hexdigest() == '1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75' diff --git a/vm/Cargo.toml b/vm/Cargo.toml index d2dc30e42c..7bf7141a11 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -20,6 +20,7 @@ serde_json = "1.0.26" byteorder = "1.2.6" regex = "1" rustc_version_runtime = "0.1.*" +rust-crypto = "0.2.36" statrs = "0.10.0" caseless = "0.2.1" unicode-segmentation = "1.2.1" diff --git a/vm/src/stdlib/hashlib.rs b/vm/src/stdlib/hashlib.rs new file mode 100644 index 0000000000..5136d7f41d --- /dev/null +++ b/vm/src/stdlib/hashlib.rs @@ -0,0 +1,140 @@ +use crate::function::PyFuncArgs; +use crate::obj::objbytes::PyBytesRef; +use crate::obj::objtype::PyClassRef; +use crate::pyobject::{PyClassImpl, PyObjectRef, PyResult, PyValue}; +use crate::vm::VirtualMachine; +use std::cell::RefCell; +use std::fmt; + +use crypto; +use crypto::digest::Digest; + +#[pyclass(name = "hasher")] +struct PyHasher { + name: String, + buffer: Box>, +} + +impl fmt::Debug for PyHasher { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "hasher {}", self.name) + } +} + +impl PyValue for PyHasher { + fn class(vm: &VirtualMachine) -> PyClassRef { + vm.class("hashlib", "hasher") + } +} + +#[pyimpl] +impl PyHasher { + // fn new(d: D) -> Self where D: Digest, D: Sized { + fn new(name: &str, d: D) -> Self + where + D: Digest, + D: Sized, + { + /* + let d = match name { + "md5" => crypto::md5::Md5::new(), + crypto::sha2::Sha256::new() + }; + */ + + PyHasher { + name: name.to_string(), + buffer: Box::new(RefCell::new(d)), + } + } + + #[pymethod(name = "__new__")] + fn py_new(_cls: PyClassRef, _args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("md5", crypto::md5::Md5::new()) + .into_ref(vm) + .into_object()) + } + + #[pymethod(name = "update")] + fn update(&self, data: PyBytesRef, vm: &VirtualMachine) -> PyResult { + self.buffer.borrow_mut().input(data.get_value()); + Ok(vm.get_none()) + } + + #[pymethod(name = "hexdigest")] + fn hexdigest(&self, vm: &VirtualMachine) -> PyResult { + Ok(vm.new_str(self.buffer.borrow_mut().result_str())) + } +} + +fn md5(_vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("md5", crypto::md5::Md5::new())) +} + +fn sha1(_vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("sha1", crypto::sha1::Sha1::new())) +} + +fn sha224(_vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("224", crypto::sha2::Sha224::new())) +} + +fn sha256(_vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("sha256", crypto::sha2::Sha256::new())) +} + +fn sha384(_vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("sha384", crypto::sha2::Sha384::new())) +} + +fn sha512(_vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("sha512", crypto::sha2::Sha512::new())) +} + +fn sha3_224(_vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("sha3_224", crypto::sha3::Sha3::sha3_224())) +} + +fn sha3_256(_vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("sha3_256", crypto::sha3::Sha3::sha3_256())) +} + +fn sha3_384(_vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("sha3_384", crypto::sha3::Sha3::sha3_384())) +} + +fn sha3_512(_vm: &VirtualMachine) -> PyResult { + Ok(PyHasher::new("sha3_512", crypto::sha3::Sha3::sha3_512())) +} + +fn blake2b(_vm: &VirtualMachine) -> PyResult { + // TODO: handle parameters + Ok(PyHasher::new("blake2b", crypto::blake2b::Blake2b::new(0))) +} + +fn blake2s(_vm: &VirtualMachine) -> PyResult { + // TODO: handle parameters + Ok(PyHasher::new("blake2s", crypto::blake2s::Blake2s::new(0))) +} + +pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { + let ctx = &vm.ctx; + + let hasher_type = PyHasher::make_class(ctx); + + py_module!(vm, "hashlib", { + "md5" => ctx.new_rustfunc(md5), + "sha1" => ctx.new_rustfunc(sha1), + "sha224" => ctx.new_rustfunc(sha224), + "sha256" => ctx.new_rustfunc(sha256), + "sha384" => ctx.new_rustfunc(sha384), + "sha512" => ctx.new_rustfunc(sha512), + "sha3_224" => ctx.new_rustfunc(sha3_224), + "sha3_256" => ctx.new_rustfunc(sha3_256), + "sha3_384" => ctx.new_rustfunc(sha3_384), + "sha3_512" => ctx.new_rustfunc(sha3_512), + "blake2b" => ctx.new_rustfunc(blake2b), + "blake2s" => ctx.new_rustfunc(blake2s), + "hasher" => hasher_type, + }) +} diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 8329b3092c..f62d073d42 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -1,6 +1,7 @@ mod ast; mod binascii; mod dis; +mod hashlib; mod imp; mod itertools; mod json; @@ -39,6 +40,7 @@ pub fn get_module_inits() -> HashMap { "ast".to_string() => Box::new(ast::make_module) as StdlibInitFunc, "binascii".to_string() => Box::new(binascii::make_module), "dis".to_string() => Box::new(dis::make_module), + "hashlib".to_string() => Box::new(hashlib::make_module), "itertools".to_string() => Box::new(itertools::make_module), "json".to_string() => Box::new(json::make_module), "keyword".to_string() => Box::new(keyword::make_module), From 4959361d29a5247ffc72634c4bcacaaa6e94d246 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 27 Jun 2019 13:56:03 -0500 Subject: [PATCH 837/884] Give more information in the error messages for failed wasm tests --- wasm/demo/package.json | 3 ++- wasm/tests/.gitignore | 1 + wasm/tests/test_demo.py | 21 +++++++++++++++++++-- 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 wasm/tests/.gitignore diff --git a/wasm/demo/package.json b/wasm/demo/package.json index a03c80aced..edcf1f7110 100644 --- a/wasm/demo/package.json +++ b/wasm/demo/package.json @@ -5,6 +5,7 @@ "main": "index.js", "dependencies": { "codemirror": "^5.42.0", + "serve": "^11.0.2", "xterm": "^3.8.0" }, "devDependencies": { @@ -24,7 +25,7 @@ "build": "webpack", "dist": "webpack --mode production", "test": "cd ../tests; pipenv run pytest", - "ci": "start-server-and-test dev http-get://localhost:8080 test" + "ci": "start-server-and-test 'npm run build && serve dist/ -np 8080' :8080 test" }, "repository": { "type": "git", diff --git a/wasm/tests/.gitignore b/wasm/tests/.gitignore new file mode 100644 index 0000000000..283f43cb08 --- /dev/null +++ b/wasm/tests/.gitignore @@ -0,0 +1 @@ +geckodriver.log diff --git a/wasm/tests/test_demo.py b/wasm/tests/test_demo.py index 9dce406811..a9694f0716 100644 --- a/wasm/tests/test_demo.py +++ b/wasm/tests/test_demo.py @@ -1,4 +1,5 @@ import time +import sys from selenium import webdriver from selenium.webdriver.firefox.options import Options @@ -16,12 +17,23 @@ return output; """ +def print_stack(driver): + stack = driver.execute_script( + "return window.__RUSTPYTHON_ERROR_MSG + '\\n' + window.__RUSTPYTHON_ERROR_STACK" + ) + print(f"RustPython error stack:\n{stack}", file=sys.stderr) + + @pytest.fixture(scope="module") def driver(request): options = Options() options.add_argument('-headless') driver = webdriver.Firefox(options=options) - driver.get("http://localhost:8080") + try: + driver.get("http://localhost:8080") + except Exception as e: + print_stack(driver) + raise time.sleep(5) yield driver driver.close() @@ -35,4 +47,9 @@ def driver(request): ) def test_demo(driver, script, output): script = RUN_CODE_TEMPLATE.format(script) - assert driver.execute_script(script).strip() == output + try: + script_output = driver.execute_script(script) + except Exception as e: + print_stack(driver) + raise + assert script_output.strip() == output From d44350d042dac475b5978bac34f39ce4fd8fb28e Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Thu, 27 Jun 2019 13:56:34 -0500 Subject: [PATCH 838/884] Make vm.import more clear --- vm/src/vm.rs | 52 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 8656c7dd48..2f49f9d9bd 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -303,33 +303,31 @@ impl VirtualMachine { } pub fn import(&self, module: &str, from_list: &PyObjectRef, level: usize) -> PyResult { - let sys_modules = self - .get_attribute(self.sys_module.clone(), "modules") - .unwrap(); - if let Ok(module) = sys_modules.get_item(module.to_string(), self) { - Ok(module) - } else { - match self.get_attribute(self.builtins.clone(), "__import__") { - Ok(func) => self.invoke( - func, - vec![ - self.ctx.new_str(module.to_string()), - if self.current_frame().is_some() { - self.get_locals().into_object() - } else { - self.get_none() - }, - self.get_none(), - from_list.clone(), - self.ctx.new_int(level), - ], - ), - Err(_) => Err(self.new_exception( - self.ctx.exceptions.import_error.clone(), - "__import__ not found".to_string(), - )), - } - } + let sys_modules = self.get_attribute(self.sys_module.clone(), "modules")?; + sys_modules.get_item(module.to_string(), self).or_else(|_| { + let import_func = self + .get_attribute(self.builtins.clone(), "__import__") + .map_err(|_| { + self.new_exception( + self.ctx.exceptions.import_error.clone(), + "__import__ not found".to_string(), + ) + })?; + self.invoke( + import_func, + vec![ + self.ctx.new_str(module.to_string()), + if let Some(frame) = self.current_frame() { + frame.scope.get_locals().into_object() + } else { + self.get_none() + }, + self.get_none(), + from_list.clone(), + self.ctx.new_int(level), + ], + ) + }) } /// Determines if `obj` is an instance of `cls`, either directly, indirectly or virtually via From 7d2c4b6b6ef5d3a6384981d4d93d659cf1ca0c81 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 28 Jun 2019 10:19:44 +0300 Subject: [PATCH 839/884] memoryview support __getitem__ --- vm/src/obj/objmemory.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vm/src/obj/objmemory.rs b/vm/src/obj/objmemory.rs index 8c2af26f05..7e1628d13d 100644 --- a/vm/src/obj/objmemory.rs +++ b/vm/src/obj/objmemory.rs @@ -33,6 +33,11 @@ impl PyMemoryView { fn obj(&self, __vm: &VirtualMachine) -> PyObjectRef { self.obj_ref.clone() } + + #[pymethod(name = "__getitem__")] + fn getitem(&self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { + vm.call_method(&self.obj_ref, "__getitem__", vec![needle]) + } } impl PyValue for PyMemoryView { From 5a8613c78a2e5e0a4b8fff04a9b64d1440693924 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 28 Jun 2019 10:28:48 +0300 Subject: [PATCH 840/884] Add tests for memoryview --- tests/snippets/memoryview.py | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 tests/snippets/memoryview.py diff --git a/tests/snippets/memoryview.py b/tests/snippets/memoryview.py new file mode 100644 index 0000000000..ebf27a9a6f --- /dev/null +++ b/tests/snippets/memoryview.py @@ -0,0 +1,6 @@ + +obj = b"abcde" +a = memoryview(obj) +assert a.obj == obj + +assert a[2:3] == b"c" From a3dc98d1727a8443daace941f55f7c1d3b881e75 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Fri, 28 Jun 2019 10:46:55 +0300 Subject: [PATCH 841/884] Add empty _imp._fix_co_filename --- vm/src/stdlib/imp.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/vm/src/stdlib/imp.rs b/vm/src/stdlib/imp.rs index 184fc5003c..4c0f023d9b 100644 --- a/vm/src/stdlib/imp.rs +++ b/vm/src/stdlib/imp.rs @@ -77,6 +77,10 @@ fn imp_is_frozen_package(_name: PyStringRef, _vm: &VirtualMachine) -> bool { false } +fn imp_fix_co_filename(_code: PyObjectRef, _path: PyStringRef, _vm: &VirtualMachine) { + // TODO: +} + pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let ctx = &vm.ctx; let module = py_module!(vm, "_imp", { @@ -91,6 +95,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "get_frozen_object" => ctx.new_rustfunc(imp_get_frozen_object), "init_frozen" => ctx.new_rustfunc(imp_init_frozen), "is_frozen_package" => ctx.new_rustfunc(imp_is_frozen_package), + "_fix_co_filename" => ctx.new_rustfunc(imp_fix_co_filename), }); module From 1dc5daa4d8f95b2fd212037fd21fa379b4eb41fe Mon Sep 17 00:00:00 2001 From: hannut91 Date: Sat, 29 Jun 2019 14:25:49 +0900 Subject: [PATCH 842/884] Fix TestWithTempDir test to pass on Mac Fix TestWithTmpeDir test that comapre os.getcwd() and tmpdir. os.getcwd() returns the result of resolving the symbolic link. But tmpdir absolute path. Testing fails on Mac because the /tmp directory is a symbolic link. Comparing the results of resolving tempdir will pass the test. But RustPython does not implement lstat, so it does not pass, but in CPython The test passes. --- tests/snippets/stdlib_os.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/snippets/stdlib_os.py b/tests/snippets/stdlib_os.py index ef9b9111d1..d2e39c1ce5 100644 --- a/tests/snippets/stdlib_os.py +++ b/tests/snippets/stdlib_os.py @@ -244,7 +244,7 @@ def __exit__(self, exc_type, exc_val, exc_tb): with TestWithTempCurrentDir(): os.chdir(tmpdir) - assert os.getcwd() == tmpdir + assert os.getcwd() == os.path.realpath(tmpdir) os.path.exists(FILE_NAME) # supports From 1e568f1044be7c398ee6a47ec71dee57f6ba302c Mon Sep 17 00:00:00 2001 From: JimJeon Date: Sat, 29 Jun 2019 15:21:28 +0900 Subject: [PATCH 843/884] Implement int.__rmod__ Extracted inner_mod function and used in int.__mod__ and int.__rmod__ and also added test snippets of int.__mod__ and int.__rmod__. --- tests/snippets/ints.py | 10 ++++++++++ vm/src/obj/objint.rs | 26 ++++++++++++++++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/tests/snippets/ints.py b/tests/snippets/ints.py index b947797ac6..40061e7e52 100644 --- a/tests/snippets/ints.py +++ b/tests/snippets/ints.py @@ -40,6 +40,14 @@ assert (2).__pow__(3) == 8 assert (10).__pow__(-1) == 0.1 assert (2).__rpow__(3) == 9 +assert (10).__mod__(5) == 0 +assert (10).__mod__(6) == 4 +with assertRaises(ZeroDivisionError): + (10).__mod__(0) +assert (5).__rmod__(10) == 0 +assert (6).__rmod__(10) == 4 +with assertRaises(ZeroDivisionError): + (0).__rmod__(10) # real/imag attributes assert (1).real == 1 @@ -63,6 +71,8 @@ assert (2).__rtruediv__(1.0) == NotImplemented assert (2).__pow__(3.0) == NotImplemented assert (2).__rpow__(3.0) == NotImplemented +assert (2).__mod__(3.0) == NotImplemented +assert (2).__rmod__(3.0) == NotImplemented assert 10 // 4 == 2 assert -10 // 4 == -3 diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index e0f29cefcb..d369fbc6f9 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -138,6 +138,14 @@ fn inner_pow(int1: &PyInt, int2: &PyInt, vm: &VirtualMachine) -> PyResult { Ok(result) } +fn inner_mod(int1: &PyInt, int2: &PyInt, vm: &VirtualMachine) -> PyResult { + if int2.value != BigInt::zero() { + Ok(vm.ctx.new_int(&int1.value % &int2.value)) + } else { + Err(vm.new_zero_division_error("integer modulo by zero".to_string())) + } +} + #[pyimpl] impl PyInt { #[pymethod(name = "__eq__")] @@ -369,12 +377,18 @@ impl PyInt { #[pymethod(name = "__mod__")] fn mod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { - let v2 = get_value(&other); - if *v2 != BigInt::zero() { - Ok(vm.ctx.new_int((&self.value) % v2)) - } else { - Err(vm.new_zero_division_error("integer modulo by zero".to_string())) - } + let other = other.payload::().unwrap(); + inner_mod(self, &other, vm) + } else { + Ok(vm.ctx.not_implemented()) + } + } + + #[pymethod(name = "__rmod__")] + fn rmod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + if objtype::isinstance(&other, &vm.ctx.int_type()) { + let other = other.payload::().unwrap(); + inner_mod(&other, self, vm) } else { Ok(vm.ctx.not_implemented()) } From 91d901abdb3d3c9b28b84dcd5e8cc331d6d023a4 Mon Sep 17 00:00:00 2001 From: hannut91 Date: Sat, 29 Jun 2019 15:59:29 +0900 Subject: [PATCH 844/884] Add tuple.__rmul__ Add tuple.__rmul__ and uncomment test snippet of __rmul__. --- tests/snippets/tuple.py | 2 +- vm/src/obj/objtuple.rs | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/snippets/tuple.py b/tests/snippets/tuple.py index b7393fe00e..86f12fe943 100644 --- a/tests/snippets/tuple.py +++ b/tests/snippets/tuple.py @@ -9,7 +9,7 @@ assert x + y == (1, 2, 1) assert x * 3 == (1, 2, 1, 2, 1, 2) -# assert 3 * x == (1, 2, 1, 2, 1, 2) +assert 3 * x == (1, 2, 1, 2, 1, 2) assert x * 0 == () assert x * -1 == () # integers less than zero treated as 0 diff --git a/vm/src/obj/objtuple.rs b/vm/src/obj/objtuple.rs index b4f1699dd6..a6e181f3ba 100644 --- a/vm/src/obj/objtuple.rs +++ b/vm/src/obj/objtuple.rs @@ -168,6 +168,11 @@ impl PyTupleRef { vm.ctx.new_tuple(new_elements) } + fn rmul(self, counter: isize, vm: &VirtualMachine) -> PyObjectRef { + let new_elements = seq_mul(&self.elements, counter); + vm.ctx.new_tuple(new_elements) + } + fn getitem(self, needle: PyObjectRef, vm: &VirtualMachine) -> PyResult { get_item(vm, self.as_object(), &self.elements, needle.clone()) } @@ -263,6 +268,7 @@ If the argument is a tuple, the return value is the same object."; "__len__" => context.new_rustfunc(PyTupleRef::len), "__new__" => context.new_rustfunc(tuple_new), "__mul__" => context.new_rustfunc(PyTupleRef::mul), + "__rmul__" => context.new_rustfunc(PyTupleRef::rmul), "__repr__" => context.new_rustfunc(PyTupleRef::repr), "count" => context.new_rustfunc(PyTupleRef::count), "__lt__" => context.new_rustfunc(PyTupleRef::lt), From 103559101e4bb6957c412646300c5c5e85f51d30 Mon Sep 17 00:00:00 2001 From: Dong-hee Na Date: Sat, 29 Jun 2019 16:16:03 +0900 Subject: [PATCH 845/884] sys.byteorder: Implement sys.byteorder --- tests/snippets/sysmod.py | 2 ++ vm/src/sysmodule.rs | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/tests/snippets/sysmod.py b/tests/snippets/sysmod.py index 23dd70f9b5..adabb57d2b 100644 --- a/tests/snippets/sysmod.py +++ b/tests/snippets/sysmod.py @@ -14,6 +14,8 @@ assert sys.getfilesystemencoding() == 'utf-8' assert sys.getfilesystemencodeerrors().startswith('surrogate') +assert sys.byteorder == "little" or sys.byteorder == "big" + assert isinstance(sys.flags, tuple) assert type(sys.flags).__name__ == "flags" assert type(sys.flags.optimize) is int diff --git a/vm/src/sysmodule.rs b/vm/src/sysmodule.rs index b81b8db52a..c20f7b1677 100644 --- a/vm/src/sysmodule.rs +++ b/vm/src/sysmodule.rs @@ -151,6 +151,15 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef, builtins: PyObjectR "unknown".to_string() }; + // https://doc.rust-lang.org/reference/conditional-compilation.html#target_endian + let bytorder = if cfg!(target_endian = "little") { + "little".to_string() + } else if cfg!(target_endian = "big") { + "big".to_string() + } else { + "unknown".to_string() + }; + let sys_doc = "This module provides access to some objects used or maintained by the interpreter and to functions that interact strongly with the interpreter. @@ -229,6 +238,7 @@ settrace() -- set the global debug tracing function "__name__" => ctx.new_str(String::from("sys")), "argv" => argv(ctx), "builtin_module_names" => ctx.new_tuple(module_names.iter().map(|v| v.into_pyobject(vm).unwrap()).collect()), + "byteorder" => ctx.new_str(bytorder), "flags" => flags, "getrefcount" => ctx.new_rustfunc(sys_getrefcount), "getsizeof" => ctx.new_rustfunc(sys_getsizeof), From b1aa10d03efab1a0ee5335bbb0098b7875243a73 Mon Sep 17 00:00:00 2001 From: Dong-hee Na Date: Sat, 29 Jun 2019 15:38:46 +0900 Subject: [PATCH 846/884] Fix complex representation for negative imaginary number case. --- tests/snippets/builtin_complex.py | 6 ++++++ vm/src/obj/objcomplex.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/snippets/builtin_complex.py b/tests/snippets/builtin_complex.py index 06d46a9800..87e25b9b38 100644 --- a/tests/snippets/builtin_complex.py +++ b/tests/snippets/builtin_complex.py @@ -135,3 +135,9 @@ assert_raises(OverflowError, lambda: complex(10 ** 1000, 0), msg) assert_raises(OverflowError, lambda: complex(0, 10 ** 1000), msg) assert_raises(OverflowError, lambda: 0j + 10 ** 1000, msg) + +# str/repr +assert '(1+1j)' == str(1+1j) +assert '(1-1j)' == str(1-1j) +assert '(1+1j)' == repr(1+1j) +assert '(1-1j)' == repr(1-1j) diff --git a/vm/src/obj/objcomplex.rs b/vm/src/obj/objcomplex.rs index 7a6df87056..436634e9c4 100644 --- a/vm/src/obj/objcomplex.rs +++ b/vm/src/obj/objcomplex.rs @@ -204,7 +204,7 @@ impl PyComplex { if re == 0.0 { format!("{}j", im) } else { - format!("({}+{}j)", re, im) + format!("({}{:+}j)", re, im) } } From 87d866d414f7268e2d2f9eedbe6dc0cb190c214e Mon Sep 17 00:00:00 2001 From: Seo Sanghyeon Date: Sat, 29 Jun 2019 17:21:11 +0900 Subject: [PATCH 847/884] Implement os.lstat --- vm/src/stdlib/os.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/vm/src/stdlib/os.rs b/vm/src/stdlib/os.rs index 24a7a67414..11f6c16764 100644 --- a/vm/src/stdlib/os.rs +++ b/vm/src/stdlib/os.rs @@ -605,6 +605,17 @@ fn os_stat(path: PyStringRef, vm: &VirtualMachine) -> PyResult { unimplemented!(); } +fn os_lstat(path: PyStringRef, dir_fd: DirFd, vm: &VirtualMachine) -> PyResult { + os_stat( + path, + dir_fd, + FollowSymlinks { + follow_symlinks: false, + }, + vm, + ) +} + #[cfg(unix)] fn os_symlink( src: PyStringRef, @@ -788,6 +799,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "ScandirIter" => scandir_iter, "DirEntry" => dir_entry, "stat_result" => stat_result, + "lstat" => ctx.new_rustfunc(os_lstat), "getcwd" => ctx.new_rustfunc(os_getcwd), "chdir" => ctx.new_rustfunc(os_chdir), "fspath" => ctx.new_rustfunc(os_fspath), From 5172a098bf79960ae87ceced859801c5206fe409 Mon Sep 17 00:00:00 2001 From: JimJeon Date: Sat, 29 Jun 2019 17:38:01 +0900 Subject: [PATCH 848/884] Changed function name to rmod --- vm/src/obj/objint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index d369fbc6f9..7346ca44f4 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -385,7 +385,7 @@ impl PyInt { } #[pymethod(name = "__rmod__")] - fn rmod_(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + fn rmod(&self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { if objtype::isinstance(&other, &vm.ctx.int_type()) { let other = other.payload::().unwrap(); inner_mod(&other, self, vm) From f4d63c923a35aead3017081beb83867cc4dc7872 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 29 Jun 2019 14:03:19 +0200 Subject: [PATCH 849/884] Switch to better supported crypto lib. --- Cargo.lock | 83 +++++++++++++ tests/snippets/test_hashlib.py | 12 ++ vm/Cargo.toml | 10 +- vm/src/stdlib/hashlib.rs | 220 ++++++++++++++++++++++++++++----- 4 files changed, 290 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35435cabd2..68f37df224 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,17 @@ name = "bitflags" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "blake2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "blake2-rfc" version = "0.2.18" @@ -225,6 +236,15 @@ dependencies = [ "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "diff" version = "0.1.11" @@ -357,6 +377,11 @@ dependencies = [ "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hex" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "hexf" version = "0.1.0" @@ -416,6 +441,11 @@ dependencies = [ "wasm-bindgen 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "keccak" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -505,6 +535,16 @@ name = "maplit" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "md-5" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "memchr" version = "2.2.0" @@ -964,9 +1004,12 @@ version = "0.1.0" dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -974,6 +1017,7 @@ dependencies = [ "lexical 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", @@ -989,6 +1033,9 @@ dependencies = [ "rustpython_parser 0.0.1", "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", + "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "statrs 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "unicode-casing 0.1.0 (git+https://github.com/OddCoincidence/unicode-casing?rev=90d6d1f02b9cc04ffb55a5f1c3fa1455a84231fb)", "unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1091,6 +1138,17 @@ dependencies = [ "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sha-1" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "sha2" version = "0.8.0" @@ -1102,6 +1160,18 @@ dependencies = [ "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "sha3" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "siphasher" version = "0.2.3" @@ -1179,6 +1249,11 @@ name = "strsim" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "syn" version = "0.11.11" @@ -1544,6 +1619,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" "checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" +"checksum blake2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "91721a6330935673395a0607df4d49a9cb90ae12d259f1b3e0a3f6e1d486872e" "checksum blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6d530bdd2d52966a6d03b7a964add7ae1a288d25214066fd4b600f0f796400" "checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" "checksum block-padding 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6d4dc3af3ee2e12f3e5d224e5e1e3d73668abbeb69e566d361f7d5563a4fdf09" @@ -1559,6 +1635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b489034e723e7f5109fecd19b719e664f89ef925be785885252469e9822fa940" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" +"checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" "checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" @@ -1576,6 +1653,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)" = "a2037ec1c6c1c4f79557762eab1f7eae1f64f6cb418ace90fae88f0942b60139" "checksum generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3c0f28c2f5bfb5960175af447a2da7c18900693738343dc896ffbcabd9839592" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e54653cc32d838771a36532647afad59c4bf7155745eeeec406f71fd5d7e7538" "checksum hexf-impl 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "22eadcfadba76a730b2764eaa577d045f35e0ef5174b9c5b46adf1ee42b85e12" "checksum hexf-parse 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "79296f72d53a89096cbc9a88c9547ee8dfe793388674620e2207593d370550ac" @@ -1584,6 +1662,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum js-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)" = "9987e7c13a91d9cf0efe59cca48a3a7a70e2b11695d5a4640f85ae71e28f5e73" +"checksum keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lalrpop 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4e2e80bee40b22bca46665b4ef1f3cd88ed0fb043c971407eac17a0712c02572" "checksum lalrpop-util 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)" = "33b27d8490dbe1f9704b0088d61e8d46edc10d5673a8829836c6ded26a9912c7" @@ -1594,6 +1673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum maplit 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08cbb6b4fef96b6d77bfc40ec491b1690c779e77b05cd9f07f787ed376fd4c43" +"checksum md-5 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a18af3dcaf2b0219366cdb4e2af65a6101457b415c3d1a5c71dd9c2b7c77b9c8" "checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" "checksum new_debug_unreachable 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f40f005c60db6e03bae699e414c58bf9aa7ea02a2d0b9bfbcf19286cc4c82b30" "checksum nix 0.13.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4dbdc256eaac2e3bd236d93ad999d3479ef775c863dbda3068c4006a92eec51b" @@ -1651,7 +1731,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum serde-wasm-bindgen 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7ee6f12f7ed0e7ad2e55200da37dbabc2cadeb942355c5b629aa3771f5ac5636" "checksum serde_derive 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)" = "46a3223d0c9ba936b61c0d2e3e559e3217dbfb8d65d06d26e8b3c25de38bae3e" "checksum serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)" = "5a23aa71d4a4d43fdbfaac00eff68ba8a06a51759a89ac3304323e800c4dd40d" +"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" +"checksum sha3 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd26bc0e7a2e3a7c959bc494caf58b72ee0c71d67704e9520f736ca7e4853ecf" "checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" @@ -1663,6 +1745,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum string_cache_shared 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b1884d1bc09741d466d9b14e6d37ac89d6909cbcac41dd9ae982d4d063bbedfc" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum strsim 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "032c03039aae92b350aad2e3779c352e104d919cb192ba2fabbd7b831ce4f0f6" +"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" "checksum syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)" = "641e117d55514d6d918490e47102f7e08d096fdde360247e4a10f7a91a8478d3" "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" diff --git a/tests/snippets/test_hashlib.py b/tests/snippets/test_hashlib.py index f8d9e088ea..796f6aefd2 100644 --- a/tests/snippets/test_hashlib.py +++ b/tests/snippets/test_hashlib.py @@ -4,18 +4,30 @@ # print(hashlib.md5) h = hashlib.md5() h.update(b'a') +assert h.name == 'md5' print(h.hexdigest()) assert h.hexdigest() == '0cc175b9c0f1b6a831c399e269772661' +assert h.digest_size == 16 h = hashlib.sha256() h.update(b'a') +assert h.name == 'sha256' +assert h.digest_size == 32 print(h.hexdigest()) assert h.hexdigest() == 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb' h = hashlib.sha512() +assert h.name == 'sha512' h.update(b'a') print(h.hexdigest()) assert h.hexdigest() == '1f40fc92da241694750979ee6cf582f2d5d7d28e18335de05abc54d0560e0f5302860c652bf08d560252aa5e74210546f369fbbbce8c12cfc7957b2652fe9a75' + +h = hashlib.new("blake2s", b"fubar") +print(h.hexdigest()) +assert h.hexdigest() == 'a0e1ad0c123c9c65e8ef850db2ce4b5cef2c35b06527c615b0154353574d0415' +h.update(b'bla') +print(h.hexdigest()) +assert h.hexdigest() == '25738bfe4cc104131e1b45bece4dfd4e7e1d6f0dffda1211e996e9d5d3b66e81' diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 7bf7141a11..74ad46f372 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -5,6 +5,14 @@ authors = ["Shing Lyu "] edition = "2018" [dependencies] +# Crypto: +digest = "0.8" +md-5 = "0.8" +sha-1 = "0.8" +sha2 = "0.8" +sha3 = "0.8" +blake2 = "0.8" + num-complex = { version = "0.2", features = ["serde"] } num-bigint = { version = "0.2.1", features = ["serde"] } num-traits = "0.2" @@ -20,7 +28,6 @@ serde_json = "1.0.26" byteorder = "1.2.6" regex = "1" rustc_version_runtime = "0.1.*" -rust-crypto = "0.2.36" statrs = "0.10.0" caseless = "0.2.1" unicode-segmentation = "1.2.1" @@ -28,6 +35,7 @@ unicode-xid = "0.1.0" lazy_static = "^1.0.1" lexical = "2.0.0" itertools = "^0.8.0" +hex = "0.3.2" hexf = "0.1.0" indexmap = "1.0.2" crc = "^1.0.0" diff --git a/vm/src/stdlib/hashlib.rs b/vm/src/stdlib/hashlib.rs index 5136d7f41d..08906548b3 100644 --- a/vm/src/stdlib/hashlib.rs +++ b/vm/src/stdlib/hashlib.rs @@ -1,18 +1,25 @@ -use crate::function::PyFuncArgs; -use crate::obj::objbytes::PyBytesRef; +use crate::function::{OptionalArg, PyFuncArgs}; +use crate::obj::objbytes::{PyBytes, PyBytesRef}; +use crate::obj::objstr::PyStringRef; use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyClassImpl, PyObjectRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use std::cell::RefCell; +// use std::clone::Clone; +// use std::ops::Deref; use std::fmt; -use crypto; -use crypto::digest::Digest; +use blake2::{Blake2b, Blake2s}; +use digest::DynDigest; +use md5::Md5; +use sha1::Sha1; +use sha2::{Sha224, Sha256, Sha384, Sha512}; +use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512}; // TODO: , Shake128, Shake256}; #[pyclass(name = "hasher")] struct PyHasher { name: String, - buffer: Box>, + buffer: RefCell, } impl fmt::Debug for PyHasher { @@ -29,92 +36,142 @@ impl PyValue for PyHasher { #[pyimpl] impl PyHasher { - // fn new(d: D) -> Self where D: Digest, D: Sized { - fn new(name: &str, d: D) -> Self - where - D: Digest, - D: Sized, - { - /* - let d = match name { - "md5" => crypto::md5::Md5::new(), - crypto::sha2::Sha256::new() - }; - */ - + fn new(name: &str, d: HashWrapper) -> Self { PyHasher { name: name.to_string(), - buffer: Box::new(RefCell::new(d)), + buffer: RefCell::new(d), } } #[pymethod(name = "__new__")] fn py_new(_cls: PyClassRef, _args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("md5", crypto::md5::Md5::new()) + Ok(PyHasher::new("md5", HashWrapper::md5()) .into_ref(vm) .into_object()) } + #[pyproperty(name = "name")] + fn name(&self, _vm: &VirtualMachine) -> String { + self.name.clone() + } + + #[pyproperty(name = "digest_size")] + fn digest_size(&self, vm: &VirtualMachine) -> PyResult { + Ok(vm.ctx.new_int(self.buffer.borrow().digest_size())) + } + #[pymethod(name = "update")] fn update(&self, data: PyBytesRef, vm: &VirtualMachine) -> PyResult { self.buffer.borrow_mut().input(data.get_value()); Ok(vm.get_none()) } + #[pymethod(name = "digest")] + fn digest(&self, _vm: &VirtualMachine) -> PyBytes { + let result = self.get_digest(); + PyBytes::new(result) + } + #[pymethod(name = "hexdigest")] - fn hexdigest(&self, vm: &VirtualMachine) -> PyResult { - Ok(vm.new_str(self.buffer.borrow_mut().result_str())) + fn hexdigest(&self, _vm: &VirtualMachine) -> String { + let result = self.get_digest(); + hex::encode(result) + } + + fn get_digest(&self) -> Vec { + self.buffer.borrow_mut().get_digest() } } +fn hashlib_new( + name: PyStringRef, + data: OptionalArg, + vm: &VirtualMachine, +) -> PyResult { + let hasher = match name.value.as_ref() { + "md5" => Ok(PyHasher::new("md5", HashWrapper::md5())), + "sha1" => Ok(PyHasher::new("sha1", HashWrapper::sha1())), + "sha224" => Ok(PyHasher::new("sha224", HashWrapper::sha224())), + "sha256" => Ok(PyHasher::new("sha256", HashWrapper::sha256())), + "sha384" => Ok(PyHasher::new("sha384", HashWrapper::sha384())), + "sha512" => Ok(PyHasher::new("sha512", HashWrapper::sha512())), + "sha3_224" => Ok(PyHasher::new("sha3_224", HashWrapper::sha3_224())), + "sha3_256" => Ok(PyHasher::new("sha3_256", HashWrapper::sha3_256())), + "sha3_384" => Ok(PyHasher::new("sha3_384", HashWrapper::sha3_384())), + "sha3_512" => Ok(PyHasher::new("sha3_512", HashWrapper::sha3_512())), + // TODO: "shake128" => Ok(PyHasher::new("shake128", HashWrapper::shake128())), + // TODO: "shake256" => Ok(PyHasher::new("shake256", HashWrapper::shake256())), + "blake2b" => Ok(PyHasher::new("blake2b", HashWrapper::blake2b())), + "blake2s" => Ok(PyHasher::new("blake2s", HashWrapper::blake2s())), + other => Err(vm.new_value_error(format!("Unknown hashing algorithm: {}", other))), + }?; + + match data { + OptionalArg::Present(data) => hasher.update(data, vm).map(|_| ())?, + OptionalArg::Missing => (), + }; + + Ok(hasher) +} + fn md5(_vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("md5", crypto::md5::Md5::new())) + Ok(PyHasher::new("md5", HashWrapper::md5())) } fn sha1(_vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("sha1", crypto::sha1::Sha1::new())) + Ok(PyHasher::new("sha1", HashWrapper::sha1())) } fn sha224(_vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("224", crypto::sha2::Sha224::new())) + Ok(PyHasher::new("sha224", HashWrapper::sha224())) } fn sha256(_vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("sha256", crypto::sha2::Sha256::new())) + Ok(PyHasher::new("sha256", HashWrapper::sha256())) } fn sha384(_vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("sha384", crypto::sha2::Sha384::new())) + Ok(PyHasher::new("sha384", HashWrapper::sha384())) } fn sha512(_vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("sha512", crypto::sha2::Sha512::new())) + Ok(PyHasher::new("sha512", HashWrapper::sha512())) } fn sha3_224(_vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("sha3_224", crypto::sha3::Sha3::sha3_224())) + Ok(PyHasher::new("sha3_224", HashWrapper::sha3_224())) } fn sha3_256(_vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("sha3_256", crypto::sha3::Sha3::sha3_256())) + Ok(PyHasher::new("sha3_256", HashWrapper::sha3_256())) } fn sha3_384(_vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("sha3_384", crypto::sha3::Sha3::sha3_384())) + Ok(PyHasher::new("sha3_384", HashWrapper::sha3_384())) } fn sha3_512(_vm: &VirtualMachine) -> PyResult { - Ok(PyHasher::new("sha3_512", crypto::sha3::Sha3::sha3_512())) + Ok(PyHasher::new("sha3_512", HashWrapper::sha3_512())) +} + +fn shake128(vm: &VirtualMachine) -> PyResult { + Err(vm.new_not_implemented_error("shake256".to_string())) + // Ok(PyHasher::new("shake128", HashWrapper::shake128())) +} + +fn shake256(vm: &VirtualMachine) -> PyResult { + Err(vm.new_not_implemented_error("shake256".to_string())) + // TODO: Ok(PyHasher::new("shake256", HashWrapper::shake256())) } fn blake2b(_vm: &VirtualMachine) -> PyResult { // TODO: handle parameters - Ok(PyHasher::new("blake2b", crypto::blake2b::Blake2b::new(0))) + Ok(PyHasher::new("blake2b", HashWrapper::blake2b())) } fn blake2s(_vm: &VirtualMachine) -> PyResult { // TODO: handle parameters - Ok(PyHasher::new("blake2s", crypto::blake2s::Blake2s::new(0))) + Ok(PyHasher::new("blake2s", HashWrapper::blake2s())) } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { @@ -123,6 +180,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { let hasher_type = PyHasher::make_class(ctx); py_module!(vm, "hashlib", { + "new" => ctx.new_rustfunc(hashlib_new), "md5" => ctx.new_rustfunc(md5), "sha1" => ctx.new_rustfunc(sha1), "sha224" => ctx.new_rustfunc(sha224), @@ -133,8 +191,102 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { "sha3_256" => ctx.new_rustfunc(sha3_256), "sha3_384" => ctx.new_rustfunc(sha3_384), "sha3_512" => ctx.new_rustfunc(sha3_512), + "shake128" => ctx.new_rustfunc(shake128), + "shake256" => ctx.new_rustfunc(shake256), "blake2b" => ctx.new_rustfunc(blake2b), "blake2s" => ctx.new_rustfunc(blake2s), "hasher" => hasher_type, }) } + +/// Generic wrapper patching around the hashing libraries. +struct HashWrapper { + inner: Box, + data: Vec, +} + +impl HashWrapper { + fn new(d: D) -> Self + where + D: DynDigest, + D: Sized, + { + HashWrapper { + inner: Box::new(d), + data: vec![], + } + } + + fn md5() -> Self { + Self::new(Md5::default()) + } + + fn sha1() -> Self { + Self::new(Sha1::default()) + } + + fn sha224() -> Self { + Self::new(Sha224::default()) + } + + fn sha256() -> Self { + Self::new(Sha256::default()) + } + + fn sha384() -> Self { + Self::new(Sha384::default()) + } + + fn sha512() -> Self { + Self::new(Sha512::default()) + } + + fn sha3_224() -> Self { + Self::new(Sha3_224::default()) + } + + fn sha3_256() -> Self { + Self::new(Sha3_256::default()) + } + + fn sha3_384() -> Self { + Self::new(Sha3_384::default()) + } + + fn sha3_512() -> Self { + Self::new(Sha3_512::default()) + } + + /* TODO: + fn shake128() -> Self { + Self::new(Shake128::default()) + } + + fn shake256() -> Self { + Self::new(Shake256::default()) + } + */ + fn blake2b() -> Self { + Self::new(Blake2b::default()) + } + + fn blake2s() -> Self { + Self::new(Blake2s::default()) + } + + fn input(&mut self, data: &[u8]) { + // TODO: this is super not efficient. Find a better way for this. + self.data.extend(data); + } + + fn digest_size(&self) -> usize { + self.inner.output_size() + } + + fn get_digest(&mut self) -> Vec { + // TODO: this is super not efficient. Find a better way for this: + self.inner.input(&self.data); + let res = self.inner.result_reset().to_vec(); + res + } +} From 5f3e5e16028a0660708f648f44bf1fe829628343 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 29 Jun 2019 14:18:54 +0200 Subject: [PATCH 850/884] Disable code coverage on all branches except the master branch. --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index aff28cd6a6..be1a47aa23 100644 --- a/.travis.yml +++ b/.travis.yml @@ -111,6 +111,8 @@ matrix: - target script: - tests/.travis-runner.sh + # Only do code coverage on master via a cron job. + if: branch = master AND type = cron env: - TRAVIS_RUST_VERSION=nightly - REGULAR_TEST=false From ddc24d67dcb273331d2b2e8561ee3281f1357ce5 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 29 Jun 2019 14:44:39 +0200 Subject: [PATCH 851/884] Add xdrlib.py --- Lib/xdrlib.py | 241 ++++++++++++++++++++++++++++++++++ tests/snippets/test_xdrlib.py | 13 ++ 2 files changed, 254 insertions(+) create mode 100644 Lib/xdrlib.py create mode 100644 tests/snippets/test_xdrlib.py diff --git a/Lib/xdrlib.py b/Lib/xdrlib.py new file mode 100644 index 0000000000..d6e1aeb527 --- /dev/null +++ b/Lib/xdrlib.py @@ -0,0 +1,241 @@ +"""Implements (a subset of) Sun XDR -- eXternal Data Representation. + +See: RFC 1014 + +""" + +import struct +from io import BytesIO +from functools import wraps + +__all__ = ["Error", "Packer", "Unpacker", "ConversionError"] + +# exceptions +class Error(Exception): + """Exception class for this module. Use: + + except xdrlib.Error as var: + # var has the Error instance for the exception + + Public ivars: + msg -- contains the message + + """ + def __init__(self, msg): + self.msg = msg + def __repr__(self): + return repr(self.msg) + def __str__(self): + return str(self.msg) + + +class ConversionError(Error): + pass + +def raise_conversion_error(function): + """ Wrap any raised struct.errors in a ConversionError. """ + + @wraps(function) + def result(self, value): + try: + return function(self, value) + except struct.error as e: + raise ConversionError(e.args[0]) from None + return result + + +class Packer: + """Pack various data representations into a buffer.""" + + def __init__(self): + self.reset() + + def reset(self): + self.__buf = BytesIO() + + def get_buffer(self): + return self.__buf.getvalue() + # backwards compatibility + get_buf = get_buffer + + @raise_conversion_error + def pack_uint(self, x): + self.__buf.write(struct.pack('>L', x)) + + @raise_conversion_error + def pack_int(self, x): + self.__buf.write(struct.pack('>l', x)) + + pack_enum = pack_int + + def pack_bool(self, x): + if x: self.__buf.write(b'\0\0\0\1') + else: self.__buf.write(b'\0\0\0\0') + + def pack_uhyper(self, x): + try: + self.pack_uint(x>>32 & 0xffffffff) + except (TypeError, struct.error) as e: + raise ConversionError(e.args[0]) from None + try: + self.pack_uint(x & 0xffffffff) + except (TypeError, struct.error) as e: + raise ConversionError(e.args[0]) from None + + pack_hyper = pack_uhyper + + @raise_conversion_error + def pack_float(self, x): + self.__buf.write(struct.pack('>f', x)) + + @raise_conversion_error + def pack_double(self, x): + self.__buf.write(struct.pack('>d', x)) + + def pack_fstring(self, n, s): + if n < 0: + raise ValueError('fstring size must be nonnegative') + data = s[:n] + n = ((n+3)//4)*4 + data = data + (n - len(data)) * b'\0' + self.__buf.write(data) + + pack_fopaque = pack_fstring + + def pack_string(self, s): + n = len(s) + self.pack_uint(n) + self.pack_fstring(n, s) + + pack_opaque = pack_string + pack_bytes = pack_string + + def pack_list(self, list, pack_item): + for item in list: + self.pack_uint(1) + pack_item(item) + self.pack_uint(0) + + def pack_farray(self, n, list, pack_item): + if len(list) != n: + raise ValueError('wrong array size') + for item in list: + pack_item(item) + + def pack_array(self, list, pack_item): + n = len(list) + self.pack_uint(n) + self.pack_farray(n, list, pack_item) + + + +class Unpacker: + """Unpacks various data representations from the given buffer.""" + + def __init__(self, data): + self.reset(data) + + def reset(self, data): + self.__buf = data + self.__pos = 0 + + def get_position(self): + return self.__pos + + def set_position(self, position): + self.__pos = position + + def get_buffer(self): + return self.__buf + + def done(self): + if self.__pos < len(self.__buf): + raise Error('unextracted data remains') + + def unpack_uint(self): + i = self.__pos + self.__pos = j = i+4 + data = self.__buf[i:j] + if len(data) < 4: + raise EOFError + return struct.unpack('>L', data)[0] + + def unpack_int(self): + i = self.__pos + self.__pos = j = i+4 + data = self.__buf[i:j] + if len(data) < 4: + raise EOFError + return struct.unpack('>l', data)[0] + + unpack_enum = unpack_int + + def unpack_bool(self): + return bool(self.unpack_int()) + + def unpack_uhyper(self): + hi = self.unpack_uint() + lo = self.unpack_uint() + return int(hi)<<32 | lo + + def unpack_hyper(self): + x = self.unpack_uhyper() + if x >= 0x8000000000000000: + x = x - 0x10000000000000000 + return x + + def unpack_float(self): + i = self.__pos + self.__pos = j = i+4 + data = self.__buf[i:j] + if len(data) < 4: + raise EOFError + return struct.unpack('>f', data)[0] + + def unpack_double(self): + i = self.__pos + self.__pos = j = i+8 + data = self.__buf[i:j] + if len(data) < 8: + raise EOFError + return struct.unpack('>d', data)[0] + + def unpack_fstring(self, n): + if n < 0: + raise ValueError('fstring size must be nonnegative') + i = self.__pos + j = i + (n+3)//4*4 + if j > len(self.__buf): + raise EOFError + self.__pos = j + return self.__buf[i:i+n] + + unpack_fopaque = unpack_fstring + + def unpack_string(self): + n = self.unpack_uint() + return self.unpack_fstring(n) + + unpack_opaque = unpack_string + unpack_bytes = unpack_string + + def unpack_list(self, unpack_item): + list = [] + while 1: + x = self.unpack_uint() + if x == 0: break + if x != 1: + raise ConversionError('0 or 1 expected, got %r' % (x,)) + item = unpack_item() + list.append(item) + return list + + def unpack_farray(self, n, unpack_item): + list = [] + for i in range(n): + list.append(unpack_item()) + return list + + def unpack_array(self, unpack_item): + n = self.unpack_uint() + return self.unpack_farray(n, unpack_item) diff --git a/tests/snippets/test_xdrlib.py b/tests/snippets/test_xdrlib.py new file mode 100644 index 0000000000..989a603c7d --- /dev/null +++ b/tests/snippets/test_xdrlib.py @@ -0,0 +1,13 @@ + +# This probably will be superceeded by the python unittests when that works. + +import xdrlib + +p = xdrlib.Packer() +p.pack_int(1337) + +d = p.get_buffer() + +print(d) + +assert d == b'\x00\x00\x059' From 0d8d2e3bebf73116ad9f719aed0d4fe57bfeceb2 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 29 Jun 2019 17:04:31 +0300 Subject: [PATCH 852/884] Calculate import level at parsing --- compiler/src/bytecode.rs | 20 ++++++++++++++++++-- compiler/src/compile.rs | 6 ++++++ parser/src/ast.rs | 1 + parser/src/python.lalrpop | 23 ++++++++--------------- vm/src/frame.rs | 25 ++++++++++++++++--------- 5 files changed, 49 insertions(+), 26 deletions(-) diff --git a/compiler/src/bytecode.rs b/compiler/src/bytecode.rs index c87bc0ae0b..e41441c801 100644 --- a/compiler/src/bytecode.rs +++ b/compiler/src/bytecode.rs @@ -51,9 +51,11 @@ pub enum Instruction { Import { name: String, symbols: Vec, + level: usize, }, ImportStar { name: String, + level: usize, }, LoadName { name: String, @@ -327,11 +329,25 @@ impl Instruction { ($variant:ident, $var1:expr, $var2:expr) => { write!(f, "{:20} ({}, {})\n", stringify!($variant), $var1, $var2) }; + ($variant:ident, $var1:expr, $var2:expr, $var3:expr) => { + write!( + f, + "{:20} ({}, {}, {})\n", + stringify!($variant), + $var1, + $var2, + $var3 + ) + }; } match self { - Import { name, symbols } => w!(Import, name, format!("{:?}", symbols)), - ImportStar { name } => w!(ImportStar, name), + Import { + name, + symbols, + level, + } => w!(Import, name, format!("{:?}", symbols), level), + ImportStar { name, level } => w!(ImportStar, name, level), LoadName { name, scope } => w!(LoadName, name, format!("{:?}", scope)), StoreName { name, scope } => w!(StoreName, name, format!("{:?}", scope)), DeleteName { name } => w!(DeleteName, name), diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index 8b806b1d1a..c4e4a1a2db 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -252,13 +252,16 @@ impl Compiler { module, symbols, alias, + level, } in import_parts { + let level = *level; if let Some(alias) = alias { // import module as alias self.emit(Instruction::Import { name: module.clone(), symbols: vec![], + level, }); self.store_name(&alias); } else if symbols.is_empty() { @@ -266,6 +269,7 @@ impl Compiler { self.emit(Instruction::Import { name: module.clone(), symbols: vec![], + level, }); self.store_name(&module.clone()); } else { @@ -276,6 +280,7 @@ impl Compiler { // from module import * self.emit(Instruction::ImportStar { name: module.clone(), + level, }); } else { // from module import symbol @@ -292,6 +297,7 @@ impl Compiler { self.emit(Instruction::Import { name: module.clone(), symbols: symbols_strings, + level, }); names.iter().rev().for_each(|name| self.store_name(&name)); } diff --git a/parser/src/ast.rs b/parser/src/ast.rs index 8fc2ba53d4..0a32fe3bfa 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -38,6 +38,7 @@ pub struct SingleImport { pub module: String, pub alias: Option, pub symbols: Vec, + pub level: usize, } #[derive(Debug, PartialEq)] diff --git a/parser/src/python.lalrpop b/parser/src/python.lalrpop index ac8669b8eb..60a81e0203 100644 --- a/parser/src/python.lalrpop +++ b/parser/src/python.lalrpop @@ -207,7 +207,8 @@ ImportStatement: ast::LocatedStatement = { ast::SingleImport { module: n.to_string(), symbols: vec![], - alias: a.clone() + alias: a.clone(), + level: 0, }) .collect() }, @@ -219,7 +220,7 @@ ImportStatement: ast::LocatedStatement = { node: ast::Statement::Import { import_parts: vec![ ast::SingleImport { - module: n.to_string(), + module: n.0.to_string(), symbols: i.iter() .map(|(i, a)| ast::ImportSymbol { @@ -227,28 +228,20 @@ ImportStatement: ast::LocatedStatement = { alias: a.clone(), }) .collect(), - alias: None + alias: None, + level: n.1 }] }, } }, }; -ImportFromLocation: String = { +ImportFromLocation: (String, usize) = { => { - let mut r = "".to_string(); - for _dot in dots { - r.push_str("."); - } - r.push_str(&name); - r + (name, dots.len()) }, => { - let mut r = "".to_string(); - for _dot in dots { - r.push_str("."); - } - r + ("".to_string(), dots.len()) }, }; diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 7844902dc8..5656813c36 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -358,8 +358,12 @@ impl Frame { bytecode::Instruction::Import { ref name, ref symbols, - } => self.import(vm, name, symbols), - bytecode::Instruction::ImportStar { ref name } => self.import_star(vm, name), + ref level, + } => self.import(vm, name, symbols, level), + bytecode::Instruction::ImportStar { + ref name, + ref level, + } => self.import_star(vm, name, level), bytecode::Instruction::LoadName { ref name, ref scope, @@ -907,14 +911,18 @@ impl Frame { } } - fn import(&self, vm: &VirtualMachine, module: &str, symbols: &Vec) -> FrameResult { + fn import( + &self, + vm: &VirtualMachine, + module: &str, + symbols: &Vec, + level: &usize, + ) -> FrameResult { let from_list = symbols .iter() .map(|symbol| vm.ctx.new_str(symbol.to_string())) .collect(); - let level = module.chars().take_while(|char| *char == '.').count(); - let module_name = &module[level..]; - let module = vm.import(module_name, &vm.ctx.new_tuple(from_list), level)?; + let module = vm.import(module, &vm.ctx.new_tuple(from_list), *level)?; if symbols.is_empty() { self.push_value(module); @@ -929,9 +937,8 @@ impl Frame { Ok(None) } - fn import_star(&self, vm: &VirtualMachine, module: &str) -> FrameResult { - let level = module.chars().take_while(|char| *char == '.').count(); - let module = vm.import(module, &vm.ctx.new_tuple(vec![]), level)?; + fn import_star(&self, vm: &VirtualMachine, module: &str, level: &usize) -> FrameResult { + let module = vm.import(module, &vm.ctx.new_tuple(vec![]), *level)?; // Grab all the names from the module and put them in the context if let Some(dict) = &module.dict { From 8c318dcd3c47052764d91565589c2ecf488321a4 Mon Sep 17 00:00:00 2001 From: Dong-hee Na Date: Sat, 29 Jun 2019 14:14:35 +0900 Subject: [PATCH 853/884] Add "Ellipsis" for Ellipsis Currently, only "..." works for Ellipsis type. This PR will fix this code to work. ```python a = Ellipsis b = ... a is b ``` --- tests/snippets/ellipsis.py | 8 ++++++++ vm/src/builtins.rs | 1 + 2 files changed, 9 insertions(+) diff --git a/tests/snippets/ellipsis.py b/tests/snippets/ellipsis.py index 88b1c965d8..20bb3a33c6 100644 --- a/tests/snippets/ellipsis.py +++ b/tests/snippets/ellipsis.py @@ -3,6 +3,14 @@ a = ... b = ... c = type(a)() # Test singleton behavior +d = Ellipsis +e = Ellipsis assert a is b assert b is c +assert b is d +assert d is e + +assert Ellipsis is ... +Ellipsis = 2 +assert Ellipsis is not ... diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index d497b47f98..9734e40218 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -858,6 +858,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { // Constants "NotImplemented" => ctx.not_implemented(), + "Ellipsis" => vm.ctx.ellipsis.clone(), // Exceptions: "BaseException" => ctx.exceptions.base_exception_type.clone(), From e88f7536b365214db3404dbe68075cb3a96564fe Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sat, 29 Jun 2019 16:48:03 +0200 Subject: [PATCH 854/884] Improve single arm match. --- vm/src/stdlib/hashlib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vm/src/stdlib/hashlib.rs b/vm/src/stdlib/hashlib.rs index 08906548b3..c8e38374d6 100644 --- a/vm/src/stdlib/hashlib.rs +++ b/vm/src/stdlib/hashlib.rs @@ -106,10 +106,9 @@ fn hashlib_new( other => Err(vm.new_value_error(format!("Unknown hashing algorithm: {}", other))), }?; - match data { - OptionalArg::Present(data) => hasher.update(data, vm).map(|_| ())?, - OptionalArg::Missing => (), - }; + if let OptionalArg::Present(data) = data { + hasher.update(data, vm)?; + } Ok(hasher) } From d0de3be2a9b3f11c080cb298e3efb9ff553cb833 Mon Sep 17 00:00:00 2001 From: silmeth Date: Sat, 29 Jun 2019 16:50:09 +0200 Subject: [PATCH 855/884] =?UTF-8?q?fix=20float=E2=80=93int=20cross-type=20?= =?UTF-8?q?comparisons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #1069 --- tests/snippets/int_float_comparisons.py | 61 ++++++++++++++++++++++ tests/snippets/int_float_equality.py | 15 ------ vm/src/obj/objfloat.rs | 68 +++++++++++++++++++++---- 3 files changed, 119 insertions(+), 25 deletions(-) create mode 100644 tests/snippets/int_float_comparisons.py delete mode 100644 tests/snippets/int_float_equality.py diff --git a/tests/snippets/int_float_comparisons.py b/tests/snippets/int_float_comparisons.py new file mode 100644 index 0000000000..400d47d07f --- /dev/null +++ b/tests/snippets/int_float_comparisons.py @@ -0,0 +1,61 @@ +# 10**308 cannot be represented exactly in f64, thus it is not equal to 1e308 float +assert not (10**308 == 1e308) +# but the 1e308 float can be converted to big int and then it still should be equal to itself +assert int(1e308) == 1e308 + +# and the equalities should be the same when operands switch sides +assert not (1e308 == 10**308) +assert 1e308 == int(1e308) + +# floats that cannot be converted to big ints shouldn’t crash the vm +import math +assert not (10**500 == math.inf) +assert not (math.inf == 10**500) +assert not (10**500 == math.nan) +assert not (math.nan == 10**500) + +# comparisons +# floats with worse than integer precision +assert 2.**54 > 2**54 - 1 +assert 2.**54 < 2**54 + 1 +assert 2.**54 >= 2**54 - 1 +assert 2.**54 <= 2**54 + 1 +assert 2.**54 == 2**54 +assert not 2.**54 == 2**54 + 1 + +# inverse operands +assert 2**54 - 1 < 2.**54 +assert 2**54 + 1 > 2.**54 +assert 2**54 - 1 <= 2.**54 +assert 2**54 + 1 >= 2.**54 +assert 2**54 == 2.**54 +assert not 2**54 + 1 == 2.**54 + +assert not 2.**54 < 2**54 - 1 +assert not 2.**54 > 2**54 + 1 + +# sub-int numbers +assert 1.3 > 1 +assert 1.3 >= 1 +assert 2.5 > 2 +assert 2.5 >= 2 +assert -0.3 < 0 +assert -0.3 <= 0 + +# int out of float range comparisons +assert 10**500 > 2.**54 +assert -10**500 < -0.12 + +# infinity and NaN comparisons +assert math.inf > 10**500 +assert math.inf >= 10**500 +assert not math.inf < 10**500 + +assert -math.inf < -10*500 +assert -math.inf <= -10*500 +assert not -math.inf > -10*500 + +assert not math.nan > 123 +assert not math.nan < 123 +assert not math.nan >= 123 +assert not math.nan <= 123 diff --git a/tests/snippets/int_float_equality.py b/tests/snippets/int_float_equality.py deleted file mode 100644 index fb240f8d3c..0000000000 --- a/tests/snippets/int_float_equality.py +++ /dev/null @@ -1,15 +0,0 @@ -# 10**308 cannot be represented exactly in f64, thus it is not equal to 1e308 float -assert not (10**308 == 1e308) -# but the 1e308 float can be converted to big int and then it still should be equal to itself -assert int(1e308) == 1e308 - -# and the equalities should be the same when operands switch sides -assert not (1e308 == 10**308) -assert 1e308 == int(1e308) - -# floats that cannot be converted to big ints shouldn’t crash the vm -import math -assert not (10**500 == math.inf) -assert not (math.inf == 10**500) -assert not (10**500 == math.nan) -assert not (math.nan == 10**500) diff --git a/vm/src/obj/objfloat.rs b/vm/src/obj/objfloat.rs index bd17147ad1..e8e05465ae 100644 --- a/vm/src/obj/objfloat.rs +++ b/vm/src/obj/objfloat.rs @@ -14,7 +14,7 @@ use crate::vm::VirtualMachine; use hexf; use num_bigint::{BigInt, ToBigInt}; use num_rational::Ratio; -use num_traits::{float::Float, ToPrimitive, Zero}; +use num_traits::{float::Float, sign::Signed, ToPrimitive, Zero}; /// Convert a string or number to a floating point number, if possible. #[pyclass(name = "float")] @@ -79,7 +79,7 @@ fn try_to_bigint(value: f64, vm: &VirtualMachine) -> PyResult { None => { if value.is_infinite() { Err(vm.new_overflow_error( - "OverflowError: cannot convert float NaN to integer".to_string(), + "OverflowError: cannot convert float infinity to integer".to_string(), )) } else if value.is_nan() { Err(vm @@ -111,6 +111,32 @@ fn inner_divmod(v1: f64, v2: f64, vm: &VirtualMachine) -> PyResult<(f64, f64)> { } } +fn inner_lt_int(value: f64, other_int: &BigInt) -> bool { + match (value.to_bigint(), other_int.to_f64()) { + (Some(self_int), Some(other_float)) => value < other_float || self_int < *other_int, + // finite float, other_int too big for float, + // the result depends only on other_int’s sign + (Some(_), None) => other_int.is_positive(), + // infinite float must be bigger or lower than any int, depending on its sign + _ if value.is_infinite() => value.is_sign_negative(), + // NaN, always false + _ => false, + } +} + +fn inner_gt_int(value: f64, other_int: &BigInt) -> bool { + match (value.to_bigint(), other_int.to_f64()) { + (Some(self_int), Some(other_float)) => value > other_float || self_int > *other_int, + // finite float, other_int too big for float, + // the result depends only on other_int’s sign + (Some(_), None) => other_int.is_negative(), + // infinite float must be bigger or lower than any int, depending on its sign + _ if value.is_infinite() => value.is_sign_positive(), + // NaN, always false + _ => false, + } +} + #[pyimpl] impl PyFloat { #[pymethod(name = "__eq__")] @@ -139,8 +165,9 @@ impl PyFloat { if objtype::isinstance(&i2, &vm.ctx.float_type()) { vm.ctx.new_bool(v1 < get_value(&i2)) } else if objtype::isinstance(&i2, &vm.ctx.int_type()) { - vm.ctx - .new_bool(v1 < objint::get_value(&i2).to_f64().unwrap()) + let other_int = objint::get_value(&i2); + + vm.ctx.new_bool(inner_lt_int(self.value, other_int)) } else { vm.ctx.not_implemented() } @@ -152,8 +179,18 @@ impl PyFloat { if objtype::isinstance(&i2, &vm.ctx.float_type()) { vm.ctx.new_bool(v1 <= get_value(&i2)) } else if objtype::isinstance(&i2, &vm.ctx.int_type()) { - vm.ctx - .new_bool(v1 <= objint::get_value(&i2).to_f64().unwrap()) + let other_int = objint::get_value(&i2); + + let result = if let (Some(self_int), Some(other_float)) = + (self.value.to_bigint(), other_int.to_f64()) + { + self.value <= other_float && self_int <= *other_int + } else { + // certainly not equal, forward to inner_lt_int + inner_lt_int(self.value, other_int) + }; + + vm.ctx.new_bool(result) } else { vm.ctx.not_implemented() } @@ -165,8 +202,9 @@ impl PyFloat { if objtype::isinstance(&i2, &vm.ctx.float_type()) { vm.ctx.new_bool(v1 > get_value(&i2)) } else if objtype::isinstance(&i2, &vm.ctx.int_type()) { - vm.ctx - .new_bool(v1 > objint::get_value(&i2).to_f64().unwrap()) + let other_int = objint::get_value(&i2); + + vm.ctx.new_bool(inner_gt_int(self.value, other_int)) } else { vm.ctx.not_implemented() } @@ -178,8 +216,18 @@ impl PyFloat { if objtype::isinstance(&i2, &vm.ctx.float_type()) { vm.ctx.new_bool(v1 >= get_value(&i2)) } else if objtype::isinstance(&i2, &vm.ctx.int_type()) { - vm.ctx - .new_bool(v1 >= objint::get_value(&i2).to_f64().unwrap()) + let other_int = objint::get_value(&i2); + + let result = if let (Some(self_int), Some(other_float)) = + (self.value.to_bigint(), other_int.to_f64()) + { + self.value >= other_float && self_int >= *other_int + } else { + // certainly not equal, forward to inner_gt_int + inner_gt_int(self.value, other_int) + }; + + vm.ctx.new_bool(result) } else { vm.ctx.not_implemented() } From 4d5b4f5dbe9f06707d9bbb582000590c6a2d9067 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Fri, 28 Jun 2019 02:05:48 -0500 Subject: [PATCH 856/884] Use new_import_error, fix serve command --- vm/src/vm.rs | 19 ++++++++----------- wasm/demo/package.json | 2 +- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index 2f49f9d9bd..5a7e4c1db6 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -307,21 +307,18 @@ impl VirtualMachine { sys_modules.get_item(module.to_string(), self).or_else(|_| { let import_func = self .get_attribute(self.builtins.clone(), "__import__") - .map_err(|_| { - self.new_exception( - self.ctx.exceptions.import_error.clone(), - "__import__ not found".to_string(), - ) - })?; + .map_err(|_| self.new_import_error("__import__ not found".to_string()))?; + + let locals = if let Some(frame) = self.current_frame() { + frame.scope.get_locals().into_object() + } else { + self.get_none() + }; self.invoke( import_func, vec![ self.ctx.new_str(module.to_string()), - if let Some(frame) = self.current_frame() { - frame.scope.get_locals().into_object() - } else { - self.get_none() - }, + locals, self.get_none(), from_list.clone(), self.ctx.new_int(level), diff --git a/wasm/demo/package.json b/wasm/demo/package.json index edcf1f7110..6f66b120d6 100644 --- a/wasm/demo/package.json +++ b/wasm/demo/package.json @@ -25,7 +25,7 @@ "build": "webpack", "dist": "webpack --mode production", "test": "cd ../tests; pipenv run pytest", - "ci": "start-server-and-test 'npm run build && serve dist/ -np 8080' :8080 test" + "ci": "start-server-and-test 'npm run build && serve dist/ -n -p 8080' :8080 test" }, "repository": { "type": "git", From f0715fe38871d92cfaca8b93fb8149fcf50029f8 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 30 Jun 2019 09:04:57 +0200 Subject: [PATCH 857/884] Simplify travis config. --- .travis.yml | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index be1a47aa23..e41d575028 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,6 @@ language: rust rust: - stable - - beta - - nightly script: - cargo build --verbose --all @@ -34,20 +32,6 @@ matrix: - REGULAR_TEST=false - CODE_COVERAGE=false script: tests/.travis-runner.sh - - language: python - python: 3.6 - cache: - pip: true - # Because we're using the Python Travis environment, we can't use - # the built-in cargo cacher - directories: - - /home/travis/.cargo - - target - env: - - TRAVIS_RUST_VERSION=beta - - REGULAR_TEST=false - - CODE_COVERAGE=false - script: tests/.travis-runner.sh - name: rustfmt language: rust rust: stable @@ -75,7 +59,7 @@ matrix: - DEPLOY_DOC=true - name: WASM online demo language: rust - rust: nightly + rust: stable cache: cargo install: - nvm install node @@ -138,8 +122,6 @@ matrix: - REGULAR_TEST=true - TRAVIS_RUST_VERSION=stable allow_failures: - - rust: nightly - env: REGULAR_TEST=true - name: cargo-clippy deploy: From 598a0a9b793ffa4d7bc4909dfc763d635a93be1c Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 30 Jun 2019 09:13:47 +0200 Subject: [PATCH 858/884] Simplify travis config further. --- .travis.yml | 33 +++++++++++---------------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/.travis.yml b/.travis.yml index e41d575028..fc9afe7d8e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,22 @@ language: rust - -rust: - - stable - -script: - - cargo build --verbose --all - - cargo test --verbose --all - -env: - # This is used to only capture the regular nightly test in allow_failures - - REGULAR_TEST=true - +rust: stable cache: cargo matrix: fast_finish: true include: + - name: rust unittests and doctests + language: rust + rust: stable + cache: cargo + script: + - cargo build --verbose --all + - cargo test --verbose --all + # To test the snippets, we use Travis' Python environment (because # installing rust ourselves is a lot easier than installing Python) - - language: python + - name: python test snippets + language: python python: 3.6 cache: pip: true @@ -29,7 +27,6 @@ matrix: - target env: - TRAVIS_RUST_VERSION=stable - - REGULAR_TEST=false - CODE_COVERAGE=false script: tests/.travis-runner.sh - name: rustfmt @@ -45,8 +42,6 @@ matrix: # creates.) - echo > parser/src/python.rs - cargo fmt --all -- --check - env: - - REGULAR_TEST=false - name: publish documentation language: rust rust: stable @@ -55,7 +50,6 @@ matrix: - cargo doc --no-deps --all if: branch = release env: - - REGULAR_TEST=false - DEPLOY_DOC=true - name: WASM online demo language: rust @@ -71,7 +65,6 @@ matrix: - npm run dist if: branch = release env: - - REGULAR_TEST=false - DEPLOY_DEMO=true - name: cargo-clippy language: rust @@ -81,8 +74,6 @@ matrix: - rustup component add clippy script: - cargo clippy - env: - - REGULAR_TEST=true - name: Code Coverage language: python python: 3.6 @@ -99,7 +90,6 @@ matrix: if: branch = master AND type = cron env: - TRAVIS_RUST_VERSION=nightly - - REGULAR_TEST=false - CODE_COVERAGE=true - name: test WASM language: python @@ -119,7 +109,6 @@ matrix: script: - wasm/tests/.travis-runner.sh env: - - REGULAR_TEST=true - TRAVIS_RUST_VERSION=stable allow_failures: - name: cargo-clippy From 7ab5d53642aa08fcbad72fe1d4769fea203e91b1 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 30 Jun 2019 10:10:17 +0200 Subject: [PATCH 859/884] update to newer version of DynDigest trait which allows clone. --- Cargo.lock | 16 ++++++++-------- vm/Cargo.toml | 2 +- vm/src/stdlib/hashlib.rs | 21 ++++++--------------- 3 files changed, 15 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 68f37df224..4fe14d22d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -120,7 +120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -252,7 +252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "digest" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "generic-array 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -541,7 +541,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1008,7 +1008,7 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "caseless 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "hexf 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1144,7 +1144,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1155,7 +1155,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1167,7 +1167,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "keccak 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "opaque-debug 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1637,7 +1637,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" "checksum crypto-mac 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" "checksum diff 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "3c2b69f912779fbb121ceb775d74d51e915af17aaebc38d28a592843a2dd0a3a" -"checksum digest 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05f47366984d3ad862010e22c7ce81a7dbcaebbdfb37241a620f8b6596ee135c" +"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" "checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" "checksum docopt 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7f525a586d310c87df72ebcd98009e57f1cc030c8c268305287a476beb653969" "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 74ad46f372..0a1b79c6f8 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" [dependencies] # Crypto: -digest = "0.8" +digest = "0.8.1" md-5 = "0.8" sha-1 = "0.8" sha2 = "0.8" diff --git a/vm/src/stdlib/hashlib.rs b/vm/src/stdlib/hashlib.rs index c8e38374d6..719e3a3296 100644 --- a/vm/src/stdlib/hashlib.rs +++ b/vm/src/stdlib/hashlib.rs @@ -5,8 +5,6 @@ use crate::obj::objtype::PyClassRef; use crate::pyobject::{PyClassImpl, PyObjectRef, PyResult, PyValue}; use crate::vm::VirtualMachine; use std::cell::RefCell; -// use std::clone::Clone; -// use std::ops::Deref; use std::fmt; use blake2::{Blake2b, Blake2s}; @@ -79,7 +77,7 @@ impl PyHasher { } fn get_digest(&self) -> Vec { - self.buffer.borrow_mut().get_digest() + self.buffer.borrow().get_digest() } } @@ -201,7 +199,6 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { /// Generic wrapper patching around the hashing libraries. struct HashWrapper { inner: Box, - data: Vec, } impl HashWrapper { @@ -210,10 +207,7 @@ impl HashWrapper { D: DynDigest, D: Sized, { - HashWrapper { - inner: Box::new(d), - data: vec![], - } + HashWrapper { inner: Box::new(d) } } fn md5() -> Self { @@ -274,18 +268,15 @@ impl HashWrapper { } fn input(&mut self, data: &[u8]) { - // TODO: this is super not efficient. Find a better way for this. - self.data.extend(data); + self.inner.input(data); } fn digest_size(&self) -> usize { self.inner.output_size() } - fn get_digest(&mut self) -> Vec { - // TODO: this is super not efficient. Find a better way for this: - self.inner.input(&self.data); - let res = self.inner.result_reset().to_vec(); - res + fn get_digest(&self) -> Vec { + let cloned = self.inner.clone(); + cloned.result().to_vec() } } From 183415e64b7798bf8f3011e13bfb6328fad96ff5 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 30 Jun 2019 11:01:40 +0200 Subject: [PATCH 860/884] Move bytecode into own crate. --- Cargo.lock | 14 ++++++++++++++ Cargo.toml | 4 ++-- bytecode/Cargo.toml | 12 ++++++++++++ {compiler => bytecode}/src/bytecode.rs | 0 bytecode/src/lib.rs | 1 + compiler/Cargo.toml | 3 ++- compiler/src/compile.rs | 2 +- compiler/src/lib.rs | 1 - derive/Cargo.toml | 1 + derive/src/compile_bytecode.rs | 3 ++- vm/Cargo.toml | 1 + vm/src/lib.rs | 1 + 12 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 bytecode/Cargo.toml rename {compiler => bytecode}/src/bytecode.rs (100%) create mode 100644 bytecode/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 35435cabd2..917b87061a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -918,6 +918,17 @@ dependencies = [ "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "rustpython_bytecode" +version = "0.1.0" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "num-complex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustpython_parser 0.0.1", + "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "rustpython_compiler" version = "0.1.0" @@ -926,6 +937,7 @@ dependencies = [ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "rustpython_bytecode 0.1.0", "rustpython_parser 0.0.1", "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -938,6 +950,7 @@ dependencies = [ "proc-macro-hack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", + "rustpython_bytecode 0.1.0", "rustpython_compiler 0.1.0", "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -984,6 +997,7 @@ dependencies = [ "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "rustpython_bytecode 0.1.0", "rustpython_compiler 0.1.0", "rustpython_derive 0.1.0", "rustpython_parser 0.0.1", diff --git a/Cargo.toml b/Cargo.toml index 2a4fc23bfe..315b5114c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "rustpython" version = "0.0.1" -authors = ["Windel Bouwman", "Shing Lyu "] +authors = ["Windel Bouwman ", "Shing Lyu "] edition = "2018" [workspace] -members = [".", "derive", "vm", "wasm/lib", "parser", "compiler"] +members = [".", "derive", "vm", "wasm/lib", "parser", "compiler", "bytecode"] [[bench]] name = "bench" diff --git a/bytecode/Cargo.toml b/bytecode/Cargo.toml new file mode 100644 index 0000000000..a67328bcd2 --- /dev/null +++ b/bytecode/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "rustpython_bytecode" +version = "0.1.0" +authors = ["Windel Bouwman "] +edition = "2018" + +[dependencies] +bitflags = "1.1" +num-bigint = { version = "0.2", features = ["serde"] } +num-complex = { version = "0.2", features = ["serde"] } +serde = { version = "1.0", features = ["derive"] } +rustpython_parser = { path = "../parser" } diff --git a/compiler/src/bytecode.rs b/bytecode/src/bytecode.rs similarity index 100% rename from compiler/src/bytecode.rs rename to bytecode/src/bytecode.rs diff --git a/bytecode/src/lib.rs b/bytecode/src/lib.rs new file mode 100644 index 0000000000..c6e3077a91 --- /dev/null +++ b/bytecode/src/lib.rs @@ -0,0 +1 @@ +pub mod bytecode; diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index fc0d195931..d0aa092629 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "rustpython_compiler" version = "0.1.0" -authors = ["coolreader18 <33094578+coolreader18@users.noreply.github.com>"] +authors = ["coolreader18 <33094578+coolreader18@users.noreply.github.com>", "Windel Bouwman "] edition = "2018" [dependencies] bitflags = "1.1" +rustpython_bytecode = { path = "../bytecode" } rustpython_parser = { path = "../parser" } serde = { version = "1.0", features = ["derive"] } num-complex = { version = "0.2", features = ["serde"] } diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index c4e4a1a2db..b50ae3329a 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -5,10 +5,10 @@ //! https://github.com/python/cpython/blob/master/Python/compile.c //! https://github.com/micropython/micropython/blob/master/py/compile.c -use crate::bytecode::{self, CallType, CodeObject, Instruction, Varargs}; use crate::error::{CompileError, CompileErrorType}; use crate::symboltable::{make_symbol_table, statements_to_symbol_table, SymbolRole, SymbolScope}; use num_complex::Complex64; +use rustpython_bytecode::bytecode::{self, CallType, CodeObject, Instruction, Varargs}; use rustpython_parser::{ast, parser}; struct Compiler { diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 382647d157..e0e6fb2bf1 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -4,7 +4,6 @@ #[macro_use] extern crate log; -pub mod bytecode; pub mod compile; pub mod error; mod symboltable; diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 8ef8965620..bcf56dc6fa 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -12,5 +12,6 @@ syn = { version = "0.15.29", features = ["full"] } quote = "0.6.11" proc-macro2 = "0.4.27" rustpython_compiler = { path = "../compiler" } +rustpython_bytecode = { path = "../bytecode" } bincode = "1.1" proc-macro-hack = "0.5" diff --git a/derive/src/compile_bytecode.rs b/derive/src/compile_bytecode.rs index e344746e54..8fa9f0c7f1 100644 --- a/derive/src/compile_bytecode.rs +++ b/derive/src/compile_bytecode.rs @@ -17,7 +17,8 @@ use crate::{extract_spans, Diagnostic}; use bincode; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; -use rustpython_compiler::{bytecode::CodeObject, compile}; +use rustpython_bytecode::bytecode::CodeObject; +use rustpython_compiler::compile; use std::env; use std::fs; use std::path::PathBuf; diff --git a/vm/Cargo.toml b/vm/Cargo.toml index d2dc30e42c..f608546e65 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -15,6 +15,7 @@ log = "0.3" rustpython_derive = {path = "../derive"} rustpython_parser = {path = "../parser"} rustpython_compiler = {path = "../compiler"} +rustpython_bytecode = { path = "../bytecode" } serde = { version = "1.0.66", features = ["derive"] } serde_json = "1.0.26" byteorder = "1.2.6" diff --git a/vm/src/lib.rs b/vm/src/lib.rs index f60be1c7df..13fd3b5d36 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -65,6 +65,7 @@ mod vm; // pub use self::pyobject::Executor; pub use self::exceptions::print_exception; pub use self::vm::VirtualMachine; +pub use rustpython_bytecode::*; pub use rustpython_compiler::*; #[doc(hidden)] From e40c844be778e0e3aca7d958def093226b5556a8 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 30 Jun 2019 11:42:36 +0200 Subject: [PATCH 861/884] Make bytecode crate independent of parser crate. --- Cargo.lock | 4 --- bytecode/Cargo.toml | 1 - bytecode/src/bytecode.rs | 57 ++++++++++++++++++++++++---------------- compiler/Cargo.toml | 3 --- compiler/src/compile.rs | 34 +++++++++++++++++++----- vm/src/eval.rs | 2 -- vm/src/frame.rs | 6 ++--- vm/src/lib.rs | 1 - 8 files changed, 63 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 917b87061a..df12315b7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -925,7 +925,6 @@ dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rustpython_parser 0.0.1", "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -933,13 +932,10 @@ dependencies = [ name = "rustpython_compiler" version = "0.1.0" dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", - "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "rustpython_bytecode 0.1.0", "rustpython_parser 0.0.1", - "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/bytecode/Cargo.toml b/bytecode/Cargo.toml index a67328bcd2..25a9dd3a3e 100644 --- a/bytecode/Cargo.toml +++ b/bytecode/Cargo.toml @@ -9,4 +9,3 @@ bitflags = "1.1" num-bigint = { version = "0.2", features = ["serde"] } num-complex = { version = "0.2", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } -rustpython_parser = { path = "../parser" } diff --git a/bytecode/src/bytecode.rs b/bytecode/src/bytecode.rs index e41441c801..ad49ce83b3 100644 --- a/bytecode/src/bytecode.rs +++ b/bytecode/src/bytecode.rs @@ -4,11 +4,31 @@ use bitflags::bitflags; use num_bigint::BigInt; use num_complex::Complex64; -use rustpython_parser::ast; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fmt; +/// Sourcode location. +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +pub struct Location { + row: usize, + column: usize, +} + +impl Location { + pub fn new(row: usize, column: usize) -> Self { + Location { row, column } + } + + pub fn get_row(&self) -> usize { + self.row + } + + pub fn get_column(&self) -> usize { + self.column + } +} + /// Primary container of a single code object. Each python function has /// a codeobject. Also a module has a codeobject. #[derive(Clone, PartialEq, Serialize, Deserialize)] @@ -16,7 +36,7 @@ pub struct CodeObject { pub instructions: Vec, /// Jump targets. pub label_map: HashMap, - pub locations: Vec, + pub locations: Vec, pub arg_names: Vec, // Names of positional arguments pub varargs: Varargs, // *args or * pub kwonlyarg_names: Vec, @@ -45,6 +65,17 @@ pub enum NameScope { Global, } +/// Transforms a value prior to formatting it. +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum ConversionFlag { + /// Converts by calling `str()`. + Str, + /// Converts by calling `ascii()`. + Ascii, + /// Converts by calling `repr()`. + Repr, +} + /// A Single bytecode instruction. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum Instruction { @@ -181,7 +212,7 @@ pub enum Instruction { }, Unpack, FormatValue { - conversion: Option, + conversion: Option, spec: String, }, PopException, @@ -436,23 +467,3 @@ impl fmt::Debug for CodeObject { ) } } - -impl From for Varargs { - fn from(varargs: ast::Varargs) -> Varargs { - match varargs { - ast::Varargs::None => Varargs::None, - ast::Varargs::Unnamed => Varargs::Unnamed, - ast::Varargs::Named(param) => Varargs::Named(param.arg), - } - } -} - -impl<'a> From<&'a ast::Varargs> for Varargs { - fn from(varargs: &'a ast::Varargs) -> Varargs { - match varargs { - ast::Varargs::None => Varargs::None, - ast::Varargs::Unnamed => Varargs::Unnamed, - ast::Varargs::Named(ref param) => Varargs::Named(param.arg.clone()), - } - } -} diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index d0aa092629..dccc3b07e0 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -5,10 +5,7 @@ authors = ["coolreader18 <33094578+coolreader18@users.noreply.github.com>", "Win edition = "2018" [dependencies] -bitflags = "1.1" rustpython_bytecode = { path = "../bytecode" } rustpython_parser = { path = "../parser" } -serde = { version = "1.0", features = ["derive"] } num-complex = { version = "0.2", features = ["serde"] } -num-bigint = { version = "0.2", features = ["serde"] } log = "0.3" diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index b50ae3329a..7ade38bc5f 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -599,9 +599,9 @@ impl Compiler { let line_number = self.get_source_line_number(); self.code_object_stack.push(CodeObject::new( args.args.iter().map(|a| a.arg.clone()).collect(), - Varargs::from(&args.vararg), + compile_varargs(&args.vararg), args.kwonlyargs.iter().map(|a| a.arg.clone()).collect(), - Varargs::from(&args.kwarg), + compile_varargs(&args.kwarg), self.source_path.clone().unwrap(), line_number, name.to_string(), @@ -1743,7 +1743,7 @@ impl Compiler { } => { self.compile_expression(value)?; self.emit(Instruction::FormatValue { - conversion: *conversion, + conversion: conversion.map(compile_conversion_flag), spec: spec.clone(), }); } @@ -1773,7 +1773,7 @@ impl Compiler { // Low level helper functions: fn emit(&mut self, instruction: Instruction) { - let location = self.current_source_location.clone(); + let location = compile_location(&self.current_source_location); let cur_code_obj = self.current_code_object(); cur_code_obj.instructions.push(instruction); cur_code_obj.locations.push(location); @@ -1834,13 +1834,33 @@ fn get_doc(body: &[ast::LocatedStatement]) -> (&[ast::LocatedStatement], Option< (body, None) } +fn compile_location(location: &ast::Location) -> bytecode::Location { + bytecode::Location::new(location.get_row(), location.get_column()) +} + +fn compile_varargs(varargs: &ast::Varargs) -> bytecode::Varargs { + match varargs { + ast::Varargs::None => bytecode::Varargs::None, + ast::Varargs::Unnamed => bytecode::Varargs::Unnamed, + ast::Varargs::Named(param) => bytecode::Varargs::Named(param.arg.clone()), + } +} + +fn compile_conversion_flag(conversion_flag: ast::ConversionFlag) -> bytecode::ConversionFlag { + match conversion_flag { + ast::ConversionFlag::Ascii => bytecode::ConversionFlag::Ascii, + ast::ConversionFlag::Repr => bytecode::ConversionFlag::Repr, + ast::ConversionFlag::Str => bytecode::ConversionFlag::Str, + } +} + #[cfg(test)] mod tests { use super::Compiler; - use crate::bytecode::CodeObject; - use crate::bytecode::Constant::*; - use crate::bytecode::Instruction::*; use crate::symboltable::make_symbol_table; + use rustpython_bytecode::bytecode::CodeObject; + use rustpython_bytecode::bytecode::Constant::*; + use rustpython_bytecode::bytecode::Instruction::*; use rustpython_parser::parser; fn compile_exec(source: &str) -> CodeObject { diff --git a/vm/src/eval.rs b/vm/src/eval.rs index e326e300e5..eec190d2cc 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -1,5 +1,3 @@ -extern crate rustpython_parser; - use crate::compile; use crate::frame::Scope; use crate::pyobject::PyResult; diff --git a/vm/src/frame.rs b/vm/src/frame.rs index 5656813c36..d1caf16091 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -2,8 +2,6 @@ use std::cell::RefCell; use std::fmt; use std::rc::Rc; -use rustpython_parser::ast; - use crate::builtins; use crate::bytecode; use crate::function::PyFuncArgs; @@ -865,7 +863,7 @@ impl Frame { Ok(None) } bytecode::Instruction::FormatValue { conversion, spec } => { - use ast::ConversionFlag::*; + use bytecode::ConversionFlag::*; let value = match conversion { Some(Str) => vm.to_str(&self.pop_value())?.into_object(), Some(Repr) => vm.to_repr(&self.pop_value())?.into_object(), @@ -1277,7 +1275,7 @@ impl Frame { Ok(None) } - pub fn get_lineno(&self) -> ast::Location { + pub fn get_lineno(&self) -> bytecode::Location { self.code.locations[*self.lasti.borrow()].clone() } diff --git a/vm/src/lib.rs b/vm/src/lib.rs index 13fd3b5d36..bfd9a66bc1 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -23,7 +23,6 @@ extern crate log; extern crate maplit; // extern crate env_logger; -extern crate rustpython_parser; #[macro_use] extern crate rustpython_derive; From 5dddd02bca1bb0bb34e329d44fe8b7186cae0075 Mon Sep 17 00:00:00 2001 From: silmeth Date: Sun, 30 Jun 2019 14:43:36 +0200 Subject: [PATCH 862/884] improve (de)serialization: no crash on BigInts, non-string map keys --- tests/snippets/json_snippet.py | 34 +++++++++++---- tests/snippets/math_basics.py | 4 +- vm/src/py_serde.rs | 76 ++++++++++++++++------------------ 3 files changed, 64 insertions(+), 50 deletions(-) diff --git a/tests/snippets/json_snippet.py b/tests/snippets/json_snippet.py index 224fde8ca4..a8c0ecc19e 100644 --- a/tests/snippets/json_snippet.py +++ b/tests/snippets/json_snippet.py @@ -1,3 +1,4 @@ +from testutils import assert_raises import json def round_trip_test(obj): @@ -27,6 +28,12 @@ def round_trip_test(obj): assert '{}' == json.dumps({}) round_trip_test({'a': 'b'}) +# should reject non-str keys in jsons +assert_raises(json.JSONDecodeError, lambda: json.loads('{3: "abc"}')) + +# should serialize non-str keys as strings +assert json.dumps({'3': 'abc'}) == json.dumps({3: 'abc'}) + assert 1 == json.loads("1") assert -1 == json.loads("-1") assert 1.0 == json.loads("1.0") @@ -44,12 +51,23 @@ class String(str): pass assert "string" == json.loads(String('"string"')) assert '"string"' == json.dumps(String("string")) -# TODO: Uncomment and test once int/float construction is supported -# class Int(int): pass -# class Float(float): pass +class Int(int): pass +class Float(float): pass + +assert '1' == json.dumps(Int(1)) +assert '0.5' == json.dumps(Float(0.5)) + +class List(list): pass +class Tuple(tuple): pass +class Dict(dict): pass + +assert '[1]' == json.dumps(List([1])) +assert json.dumps((1, "string", 1.0, True)) == json.dumps(Tuple((1, "string", 1.0, True))) +assert json.dumps({'a': 'b'}) == json.dumps(Dict({'a': 'b'})) -# TODO: Uncomment and test once sequence/dict subclasses are supported by -# json.dumps -# class List(list): pass -# class Tuple(tuple): pass -# class Dict(dict): pass +# big ints should not crash VM +# TODO: test for correct output when actual serialization implemented and doesn’t throw +try: + json.dumps(7*500) +except: + pass diff --git a/tests/snippets/math_basics.py b/tests/snippets/math_basics.py index d950b13a93..f02ba3e60c 100644 --- a/tests/snippets/math_basics.py +++ b/tests/snippets/math_basics.py @@ -33,11 +33,11 @@ assert_raises( OverflowError, lambda: round(float('inf')), - 'OverflowError: cannot convert float NaN to integer') + 'OverflowError: cannot convert float infinity to integer') assert_raises( OverflowError, lambda: round(-float('inf')), - 'OverflowError: cannot convert float NaN to integer') + 'OverflowError: cannot convert float infinity to integer') assert pow(0, 0) == 1 assert pow(2, 2) == 4 diff --git a/vm/src/py_serde.rs b/vm/src/py_serde.rs index 10901bf7a4..5e43c43a7a 100644 --- a/vm/src/py_serde.rs +++ b/vm/src/py_serde.rs @@ -4,16 +4,11 @@ use serde; use serde::de::{DeserializeSeed, Visitor}; use serde::ser::{Serialize, SerializeMap, SerializeSeq}; -use crate::obj::{ - objbool, - objdict::PyDictRef, - objfloat, objint, objsequence, - objstr::{self, PyString}, - objtype, -}; +use crate::obj::{objbool, objdict::PyDictRef, objfloat, objint, objsequence, objstr, objtype}; use crate::pyobject::{IdProtocol, ItemProtocol, PyObjectRef, TypeProtocol}; use crate::VirtualMachine; use num_traits::cast::ToPrimitive; +use num_traits::sign::Signed; #[inline] pub fn serialize( @@ -79,9 +74,16 @@ impl<'s> serde::Serialize for PyObjectSerializer<'s> { serializer.serialize_bool(objbool::get_value(self.pyobject)) } else if objtype::isinstance(self.pyobject, &self.vm.ctx.int_type()) { let v = objint::get_value(self.pyobject); - serializer.serialize_i64(v.to_i64().unwrap()) - // Although this may seem nice, it does not give the right result: - // v.serialize(serializer) + let int_too_large = || serde::ser::Error::custom("int too large to serialize"); + // TODO: serialize BigInt when it does not fit into i64 + // BigInt implements serialization to a tuple of sign and a list of u32s, + // eg. -1 is [-1, [1]], 0 is [0, []], 12345678900000654321 is [1, [2710766577,2874452364]] + // CPython serializes big ints as long decimal integer literals + if v.is_positive() { + serializer.serialize_u64(v.to_u64().ok_or_else(int_too_large)?) + } else { + serializer.serialize_i64(v.to_i64().ok_or_else(int_too_large)?) + } } else if objtype::isinstance(self.pyobject, &self.vm.ctx.list_type()) { let elements = objsequence::get_elements_list(self.pyobject); serialize_seq_elements(serializer, &elements) @@ -138,35 +140,26 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { formatter.write_str("a type that can deserialise in Python") } - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - Ok(self.vm.ctx.new_str(value.to_string())) - } - - fn visit_string(self, value: String) -> Result + fn visit_bool(self, value: bool) -> Result where E: serde::de::Error, { - Ok(self.vm.ctx.new_str(value)) + Ok(self.vm.ctx.new_bool(value)) } + // Other signed integers delegate to this method by default, it’s the only one needed fn visit_i64(self, value: i64) -> Result where E: serde::de::Error, { - // The JSON deserializer always uses the i64/u64 deserializers, so we only need to - // implement those for now Ok(self.vm.ctx.new_int(value)) } + // Other unsigned integers delegate to this method by default, it’s the only one needed fn visit_u64(self, value: u64) -> Result where E: serde::de::Error, { - // The JSON deserializer always uses the i64/u64 deserializers, so we only need to - // implement those for now Ok(self.vm.ctx.new_int(value)) } @@ -177,11 +170,26 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { Ok(self.vm.ctx.new_float(value)) } - fn visit_bool(self, value: bool) -> Result + fn visit_str(self, value: &str) -> Result where E: serde::de::Error, { - Ok(self.vm.ctx.new_bool(value)) + // Owned value needed anyway, delegate to visit_string + self.visit_string(value.to_string()) + } + + fn visit_string(self, value: String) -> Result + where + E: serde::de::Error, + { + Ok(self.vm.ctx.new_str(value)) + } + + fn visit_unit(self) -> Result + where + E: serde::de::Error, + { + Ok(self.vm.get_none()) } fn visit_seq(self, mut access: A) -> Result @@ -200,23 +208,11 @@ impl<'de> Visitor<'de> for PyObjectDeserializer<'de> { M: serde::de::MapAccess<'de>, { let dict = self.vm.ctx.new_dict(); - // TODO: Given keys must be strings, we can probably do something more efficient - // than wrapping the given object up and then unwrapping it to determine whether or - // not it is a string + // Although JSON keys must be strings, implementation accepts any keys + // and can be reused by other deserializers without such limit while let Some((key_obj, value)) = access.next_entry_seed(self.clone(), self.clone())? { - let key: String = match key_obj.payload::() { - Some(PyString { ref value }) => value.clone(), - _ => unimplemented!("map keys must be strings"), - }; - dict.set_item(&key, value, self.vm).unwrap(); + dict.set_item(key_obj, value, self.vm).unwrap(); } Ok(dict.into_object()) } - - fn visit_unit(self) -> Result - where - E: serde::de::Error, - { - Ok(self.vm.get_none()) - } } From 49f3bf7401a3cc60a6105efebf92cb0004e26bfa Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 30 Jun 2019 20:09:26 +0300 Subject: [PATCH 863/884] Fix locals and globals in call to __import__ --- vm/src/vm.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index c8a1315523..e4ccd8866e 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -309,17 +309,20 @@ impl VirtualMachine { .get_attribute(self.builtins.clone(), "__import__") .map_err(|_| self.new_import_error("__import__ not found".to_string()))?; - let locals = if let Some(frame) = self.current_frame() { - frame.scope.get_locals().into_object() + let (locals, globals) = if let Some(frame) = self.current_frame() { + ( + frame.scope.get_locals().into_object(), + frame.scope.globals.clone().into_object(), + ) } else { - self.get_none() + (self.get_none(), self.get_none()) }; self.invoke( import_func, vec![ self.ctx.new_str(module.to_string()), + globals, locals, - self.get_none(), from_list.clone(), self.ctx.new_int(level), ], From 3954dbe716bbfa4d1802aabd9cd9e8517d3fb552 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 30 Jun 2019 20:11:40 +0200 Subject: [PATCH 864/884] Make parser and compiler optional features for vm crate. --- Cargo.lock | 2 ++ Cargo.toml | 1 + src/main.rs | 5 ++--- vm/Cargo.toml | 7 +++++-- vm/src/builtins.rs | 18 ++++++++++++++---- vm/src/eval.rs | 2 +- vm/src/import.rs | 9 +++++++-- vm/src/lib.rs | 2 +- vm/src/stdlib/mod.rs | 21 ++++++++++++++++----- vm/src/vm.rs | 8 ++++++-- wasm/lib/Cargo.toml | 1 + wasm/lib/src/vm_class.rs | 6 ++---- 12 files changed, 58 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d24a53b9c..af153b7c36 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -952,6 +952,7 @@ dependencies = [ "cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rustpython_compiler 0.1.0", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", "rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1061,6 +1062,7 @@ dependencies = [ "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rustpython_compiler 0.1.0", "rustpython_parser 0.0.1", "rustpython_vm 0.1.0", "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 315b5114c0..45c5c940d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ path = "./benchmarks/bench.rs" log="0.4.1" env_logger="0.5.10" clap = "2.31.2" +rustpython_compiler = {path = "compiler"} rustpython_parser = {path = "parser"} rustpython_vm = {path = "vm"} rustyline = "4.1.0" diff --git a/src/main.rs b/src/main.rs index 5d4d2b9e72..bcd8c9ccd3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,11 +8,9 @@ extern crate rustpython_vm; extern crate rustyline; use clap::{App, Arg}; +use rustpython_compiler::{compile, error::CompileError, error::CompileErrorType}; use rustpython_parser::error::ParseError; use rustpython_vm::{ - compile, - error::CompileError, - error::CompileErrorType, frame::Scope, import, obj::objstr, @@ -20,6 +18,7 @@ use rustpython_vm::{ pyobject::{ItemProtocol, PyResult}, util, VirtualMachine, }; + use rustyline::{error::ReadlineError, Editor}; use std::path::PathBuf; diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 6e1e800346..021e7f5b13 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -4,6 +4,9 @@ version = "0.1.0" authors = ["Shing Lyu "] edition = "2018" +[features] +default = ["rustpython_parser", "rustpython_compiler"] + [dependencies] # Crypto: digest = "0.8.1" @@ -21,8 +24,8 @@ num-rational = "0.2.1" rand = "0.5" log = "0.3" rustpython_derive = {path = "../derive"} -rustpython_parser = {path = "../parser"} -rustpython_compiler = {path = "../compiler"} +rustpython_parser = {path = "../parser", optional = true} +rustpython_compiler = {path = "../compiler", optional = true} rustpython_bytecode = { path = "../bytecode" } serde = { version = "1.0.66", features = ["derive"] } serde_json = "1.0.26" diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 9734e40218..8c76790cee 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -10,7 +10,6 @@ use std::str; use num_bigint::Sign; use num_traits::{Signed, ToPrimitive, Zero}; -use crate::compile; use crate::obj::objbool; use crate::obj::objbytes::PyBytesRef; use crate::obj::objcode::PyCodeRef; @@ -19,6 +18,8 @@ use crate::obj::objint::{self, PyIntRef}; use crate::obj::objiter; use crate::obj::objstr::{self, PyString, PyStringRef}; use crate::obj::objtype::{self, PyClassRef}; +#[cfg(feature = "rustpython_compiler")] +use rustpython_compiler::compile; use crate::frame::Scope; use crate::function::{single_or_tuple_any, Args, KwArgs, OptionalArg, PyFuncArgs}; @@ -98,6 +99,7 @@ struct CompileArgs { optimize: OptionalArg, } +#[cfg(feature = "rustpython_compiler")] fn builtin_compile(args: CompileArgs, vm: &VirtualMachine) -> PyResult { // TODO: compile::compile should probably get bytes let source = match args.source { @@ -153,6 +155,7 @@ fn builtin_divmod(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { /// Implements `eval`. /// See also: https://docs.python.org/3/library/functions.html#eval +#[cfg(feature = "rustpython_compiler")] fn builtin_eval(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: support any mapping for `locals` arg_check!( @@ -184,6 +187,7 @@ fn builtin_eval(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { /// Implements `exec` /// https://docs.python.org/3/library/functions.html#exec +#[cfg(feature = "rustpython_compiler")] fn builtin_exec(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -785,6 +789,15 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { #[cfg(not(target_arch = "wasm32"))] let open = vm.ctx.new_rustfunc(io_open); + #[cfg(feature = "rustpython_compiler")] + { + extend_module!(vm, module, { + "compile" => ctx.new_rustfunc(builtin_compile), + "eval" => ctx.new_rustfunc(builtin_eval), + "exec" => ctx.new_rustfunc(builtin_exec), + }); + } + extend_module!(vm, module, { //set __name__ fixes: https://github.com/RustPython/RustPython/issues/146 "__name__" => ctx.new_str(String::from("__main__")), @@ -799,15 +812,12 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { "callable" => ctx.new_rustfunc(builtin_callable), "chr" => ctx.new_rustfunc(builtin_chr), "classmethod" => ctx.classmethod_type(), - "compile" => ctx.new_rustfunc(builtin_compile), "complex" => ctx.complex_type(), "delattr" => ctx.new_rustfunc(builtin_delattr), "dict" => ctx.dict_type(), "divmod" => ctx.new_rustfunc(builtin_divmod), "dir" => ctx.new_rustfunc(builtin_dir), "enumerate" => ctx.enumerate_type(), - "eval" => ctx.new_rustfunc(builtin_eval), - "exec" => ctx.new_rustfunc(builtin_exec), "float" => ctx.float_type(), "frozenset" => ctx.frozenset_type(), "filter" => ctx.filter_type(), diff --git a/vm/src/eval.rs b/vm/src/eval.rs index eec190d2cc..f8ca7bb7b3 100644 --- a/vm/src/eval.rs +++ b/vm/src/eval.rs @@ -1,7 +1,7 @@ -use crate::compile; use crate::frame::Scope; use crate::pyobject::PyResult; use crate::vm::VirtualMachine; +use rustpython_compiler::compile; pub fn eval(vm: &VirtualMachine, source: &str, scope: Scope, source_path: &str) -> PyResult { match vm.compile(source, &compile::Mode::Eval, source_path.to_string()) { diff --git a/vm/src/import.rs b/vm/src/import.rs index 8b56b23b0e..d6c5e55769 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -5,12 +5,13 @@ use std::path::PathBuf; use crate::bytecode::CodeObject; -use crate::compile; use crate::frame::Scope; use crate::obj::{objcode, objsequence, objstr}; use crate::pyobject::{ItemProtocol, PyResult, PyValue}; use crate::util; use crate::vm::VirtualMachine; +#[cfg(feature = "rustpython_compiler")] +use rustpython_compiler::compile; pub fn init_importlib(vm: &VirtualMachine) -> PyResult { let importlib = import_frozen(vm, "_frozen_importlib")?; @@ -56,7 +57,7 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s import_frozen(vm, module_name) } else if vm.stdlib_inits.borrow().contains_key(module_name) { import_builtin(vm, module_name) - } else { + } else if cfg!(feature = "rustpython_compiler") { let notfound_error = &vm.ctx.exceptions.module_not_found_error; let import_error = &vm.ctx.exceptions.import_error; @@ -72,9 +73,13 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s file_path.to_str().unwrap().to_string(), source, ) + } else { + let notfound_error = &vm.ctx.exceptions.module_not_found_error; + Err(vm.new_exception(notfound_error.clone(), module_name.to_string())) } } +#[cfg(feature = "rustpython_compiler")] pub fn import_file( vm: &VirtualMachine, module_name: &str, diff --git a/vm/src/lib.rs b/vm/src/lib.rs index bfd9a66bc1..b68276b904 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -44,6 +44,7 @@ pub mod macros; mod builtins; pub mod cformat; mod dictdatatype; +#[cfg(feature = "rustpython_compiler")] pub mod eval; mod exceptions; pub mod format; @@ -65,7 +66,6 @@ mod vm; pub use self::exceptions::print_exception; pub use self::vm::VirtualMachine; pub use rustpython_bytecode::*; -pub use rustpython_compiler::*; #[doc(hidden)] pub mod __exports { diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index f62d073d42..f853598a67 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "rustpython_parser")] mod ast; mod binascii; mod dis; @@ -5,6 +6,7 @@ mod hashlib; mod imp; mod itertools; mod json; +#[cfg(feature = "rustpython_parser")] mod keyword; mod marshal; mod math; @@ -16,6 +18,7 @@ pub mod socket; mod string; mod thread; mod time_module; +#[cfg(feature = "rustpython_parser")] mod tokenize; mod warnings; mod weakref; @@ -37,13 +40,11 @@ pub type StdlibInitFunc = Box PyObjectRef>; pub fn get_module_inits() -> HashMap { #[allow(unused_mut)] let mut modules = hashmap! { - "ast".to_string() => Box::new(ast::make_module) as StdlibInitFunc, - "binascii".to_string() => Box::new(binascii::make_module), - "dis".to_string() => Box::new(dis::make_module), + "binascii".to_string() => Box::new(binascii::make_module) as StdlibInitFunc, + "dis".to_string() => Box::new(dis::make_module) as StdlibInitFunc, "hashlib".to_string() => Box::new(hashlib::make_module), "itertools".to_string() => Box::new(itertools::make_module), "json".to_string() => Box::new(json::make_module), - "keyword".to_string() => Box::new(keyword::make_module), "marshal".to_string() => Box::new(marshal::make_module), "math".to_string() => Box::new(math::make_module), "platform".to_string() => Box::new(platform::make_module), @@ -53,12 +54,22 @@ pub fn get_module_inits() -> HashMap { "struct".to_string() => Box::new(pystruct::make_module), "_thread".to_string() => Box::new(thread::make_module), "time".to_string() => Box::new(time_module::make_module), - "tokenize".to_string() => Box::new(tokenize::make_module), "_weakref".to_string() => Box::new(weakref::make_module), "_imp".to_string() => Box::new(imp::make_module), "_warnings".to_string() => Box::new(warnings::make_module), }; + // Insert parser related modules: + #[cfg(feature = "rustpython_parser")] + { + modules.insert( + "ast".to_string(), + Box::new(ast::make_module) as StdlibInitFunc, + ); + modules.insert("keyword".to_string(), Box::new(keyword::make_module)); + modules.insert("tokenize".to_string(), Box::new(tokenize::make_module)); + } + // disable some modules on WASM #[cfg(not(target_arch = "wasm32"))] { diff --git a/vm/src/vm.rs b/vm/src/vm.rs index c8a1315523..632443c8f0 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -12,8 +12,6 @@ use std::sync::{Mutex, MutexGuard}; use crate::builtins; use crate::bytecode; -use crate::compile; -use crate::error::CompileError; use crate::frame::{ExecutionResult, Frame, FrameRef, Scope}; use crate::frozen; use crate::function::PyFuncArgs; @@ -38,6 +36,10 @@ use crate::pyobject::{ use crate::stdlib; use crate::sysmodule; use num_bigint::BigInt; +#[cfg(feature = "rustpython_compiler")] +use rustpython_compiler::compile; +#[cfg(feature = "rustpython_compiler")] +use rustpython_compiler::error::CompileError; // use objects::objects; @@ -249,6 +251,7 @@ impl VirtualMachine { self.new_exception(overflow_error, msg) } + #[cfg(feature = "rustpython_compiler")] pub fn new_syntax_error(&self, error: &CompileError) -> PyObjectRef { let syntax_error_type = self.ctx.exceptions.syntax_error.clone(); let syntax_error = self.new_exception(syntax_error_type, error.to_string()); @@ -727,6 +730,7 @@ impl VirtualMachine { ) } + #[cfg(feature = "rustpython_compiler")] pub fn compile( &self, source: &str, diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 3e045ae876..35f02ba8d0 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -11,6 +11,7 @@ edition = "2018" crate-type = ["cdylib", "rlib"] [dependencies] +rustpython_compiler = { path = "../../compiler" } rustpython_parser = { path = "../../parser" } rustpython_vm = { path = "../../vm" } cfg-if = "0.1.2" diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 886a7013e2..1500304bc2 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -5,7 +5,7 @@ use std::rc::{Rc, Weak}; use js_sys::{Object, Reflect, SyntaxError, TypeError}; use wasm_bindgen::prelude::*; -use rustpython_vm::compile; +use rustpython_compiler::{compile, error::CompileErrorType}; use rustpython_vm::frame::{NameProtocol, Scope}; use rustpython_vm::function::PyFuncArgs; use rustpython_vm::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult, PyValue}; @@ -280,9 +280,7 @@ impl WASMVirtualMachine { let code = vm.compile(&source, &mode, "".to_string()); let code = code.map_err(|err| { let js_err = SyntaxError::new(&format!("Error parsing Python code: {}", err)); - if let rustpython_vm::error::CompileErrorType::Parse(ref parse_error) = - err.error - { + if let CompileErrorType::Parse(ref parse_error) = err.error { use rustpython_parser::error::ParseError; if let ParseError::EOF(Some(ref loc)) | ParseError::ExtraToken((ref loc, ..)) From f816a50110d7f5ff68400bbc7afab83afbd879ec Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Sun, 30 Jun 2019 20:23:01 +0200 Subject: [PATCH 865/884] Change get_row() into row() --- bytecode/src/bytecode.rs | 4 ++-- compiler/src/compile.rs | 4 ++-- compiler/src/error.rs | 2 +- parser/src/lexer.rs | 4 ++-- vm/src/frame.rs | 2 +- vm/src/stdlib/ast.rs | 2 +- vm/src/vm.rs | 2 +- wasm/lib/src/vm_class.rs | 23 +++++++---------------- 8 files changed, 17 insertions(+), 26 deletions(-) diff --git a/bytecode/src/bytecode.rs b/bytecode/src/bytecode.rs index ad49ce83b3..0f08e22bb7 100644 --- a/bytecode/src/bytecode.rs +++ b/bytecode/src/bytecode.rs @@ -20,11 +20,11 @@ impl Location { Location { row, column } } - pub fn get_row(&self) -> usize { + pub fn row(&self) -> usize { self.row } - pub fn get_column(&self) -> usize { + pub fn column(&self) -> usize { self.column } } diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index 7ade38bc5f..844f952ea6 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -1803,7 +1803,7 @@ impl Compiler { } fn get_source_line_number(&mut self) -> usize { - self.current_source_location.get_row() + self.current_source_location.row() } fn create_qualified_name(&self, name: &str, suffix: &str) -> String { @@ -1835,7 +1835,7 @@ fn get_doc(body: &[ast::LocatedStatement]) -> (&[ast::LocatedStatement], Option< } fn compile_location(location: &ast::Location) -> bytecode::Location { - bytecode::Location::new(location.get_row(), location.get_column()) + bytecode::Location::new(location.row(), location.column()) } fn compile_varargs(varargs: &ast::Varargs) -> bytecode::Varargs { diff --git a/compiler/src/error.rs b/compiler/src/error.rs index af763168da..bd9950c8d3 100644 --- a/compiler/src/error.rs +++ b/compiler/src/error.rs @@ -56,7 +56,7 @@ impl fmt::Display for CompileError { }?; // Print line number: - write!(f, " at line {:?}", self.location.get_row()) + write!(f, " at line {:?}", self.location.row()) } } diff --git a/parser/src/lexer.rs b/parser/src/lexer.rs index 4b47ec2d78..76bc421693 100644 --- a/parser/src/lexer.rs +++ b/parser/src/lexer.rs @@ -85,11 +85,11 @@ impl Location { Location { row, column } } - pub fn get_row(&self) -> usize { + pub fn row(&self) -> usize { self.row } - pub fn get_column(&self) -> usize { + pub fn column(&self) -> usize { self.column } } diff --git a/vm/src/frame.rs b/vm/src/frame.rs index d1caf16091..61ea0a78a9 100644 --- a/vm/src/frame.rs +++ b/vm/src/frame.rs @@ -298,7 +298,7 @@ impl Frame { trace!("Adding to traceback: {:?} {:?}", traceback, lineno); let raise_location = vm.ctx.new_tuple(vec![ vm.ctx.new_str(filename.clone()), - vm.ctx.new_int(lineno.get_row()), + vm.ctx.new_int(lineno.row()), vm.ctx.new_str(run_obj_name.clone()), ]); objlist::PyListRef::try_from_object(vm, traceback)?.append(raise_location, vm); diff --git a/vm/src/stdlib/ast.rs b/vm/src/stdlib/ast.rs index 2b79dd7069..5808e303f7 100644 --- a/vm/src/stdlib/ast.rs +++ b/vm/src/stdlib/ast.rs @@ -205,7 +205,7 @@ fn statement_to_ast( }?; // set lineno on node: - let lineno = vm.ctx.new_int(statement.location.get_row()); + let lineno = vm.ctx.new_int(statement.location.row()); vm.set_attr(node.as_object(), "lineno", lineno).unwrap(); Ok(node) diff --git a/vm/src/vm.rs b/vm/src/vm.rs index c8a1315523..e0e5435436 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -252,7 +252,7 @@ impl VirtualMachine { pub fn new_syntax_error(&self, error: &CompileError) -> PyObjectRef { let syntax_error_type = self.ctx.exceptions.syntax_error.clone(); let syntax_error = self.new_exception(syntax_error_type, error.to_string()); - let lineno = self.new_int(error.location.get_row()); + let lineno = self.new_int(error.location.row()); self.set_attr(&syntax_error, "lineno", lineno).unwrap(); syntax_error } diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 886a7013e2..dd9363c3f1 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -289,29 +289,20 @@ impl WASMVirtualMachine { | ParseError::InvalidToken(ref loc) | ParseError::UnrecognizedToken((ref loc, ..), _) = parse_error { - let _ = Reflect::set( - &js_err, - &"row".into(), - &(loc.get_row() as u32).into(), - ); - let _ = Reflect::set( - &js_err, - &"col".into(), - &(loc.get_column() as u32).into(), - ); + let _ = + Reflect::set(&js_err, &"row".into(), &(loc.row() as u32).into()); + let _ = + Reflect::set(&js_err, &"col".into(), &(loc.column() as u32).into()); } if let ParseError::ExtraToken((_, _, ref loc)) | ParseError::UnrecognizedToken((_, _, ref loc), _) = parse_error { - let _ = Reflect::set( - &js_err, - &"endrow".into(), - &(loc.get_row() as u32).into(), - ); + let _ = + Reflect::set(&js_err, &"endrow".into(), &(loc.row() as u32).into()); let _ = Reflect::set( &js_err, &"endcol".into(), - &(loc.get_column() as u32).into(), + &(loc.column() as u32).into(), ); } } From 2f1d4a8d29d8432b8df7906267e42efc3ace87be Mon Sep 17 00:00:00 2001 From: JimJeon Date: Mon, 1 Jul 2019 09:44:47 +0900 Subject: [PATCH 866/884] Fix with rustfmt --- vm/src/obj/objint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/obj/objint.rs b/vm/src/obj/objint.rs index 7346ca44f4..88e0233b2f 100644 --- a/vm/src/obj/objint.rs +++ b/vm/src/obj/objint.rs @@ -145,7 +145,7 @@ fn inner_mod(int1: &PyInt, int2: &PyInt, vm: &VirtualMachine) -> PyResult { Err(vm.new_zero_division_error("integer modulo by zero".to_string())) } } - + #[pyimpl] impl PyInt { #[pymethod(name = "__eq__")] From 307990d5dea35fba2b97bf5fa28500becf316d9b Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 1 Jul 2019 13:06:45 -0500 Subject: [PATCH 867/884] Add module listing to whats_left.sh --- tests/.gitignore | 2 +- ...footer.txt => not_impl_methods_footer.txt} | 0 ...header.txt => not_impl_methods_header.txt} | 0 tests/generator/not_impl_modules_footer.txt | 15 +++ tests/generator/not_impl_modules_header.txt | 8 ++ tests/not_impl_gen.py | 100 ++++++++++++------ whats_left.sh | 29 ++++- 7 files changed, 114 insertions(+), 40 deletions(-) rename tests/generator/{not_impl_footer.txt => not_impl_methods_footer.txt} (100%) rename tests/generator/{not_impl_header.txt => not_impl_methods_header.txt} (100%) create mode 100644 tests/generator/not_impl_modules_footer.txt create mode 100644 tests/generator/not_impl_modules_header.txt diff --git a/tests/.gitignore b/tests/.gitignore index 05ba24c43b..8829b75811 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -1 +1 @@ -snippets/whats_left_to_implement.py +snippets/whats_left_*.py diff --git a/tests/generator/not_impl_footer.txt b/tests/generator/not_impl_methods_footer.txt similarity index 100% rename from tests/generator/not_impl_footer.txt rename to tests/generator/not_impl_methods_footer.txt diff --git a/tests/generator/not_impl_header.txt b/tests/generator/not_impl_methods_header.txt similarity index 100% rename from tests/generator/not_impl_header.txt rename to tests/generator/not_impl_methods_header.txt diff --git a/tests/generator/not_impl_modules_footer.txt b/tests/generator/not_impl_modules_footer.txt new file mode 100644 index 0000000000..1faffd0e5b --- /dev/null +++ b/tests/generator/not_impl_modules_footer.txt @@ -0,0 +1,15 @@ + +rustpymods = set( + map( + lambda mod: mod[0], + filter( + lambda mod: mod[1] == "" or mod[1] == ".py", + map(os.path.splitext, os.listdir(libdir)), + ), + ) +) +rustpymods |= set(sys.builtin_module_names) + +for mod in cpymods - rustpymods: + print(mod) + diff --git a/tests/generator/not_impl_modules_header.txt b/tests/generator/not_impl_modules_header.txt new file mode 100644 index 0000000000..8879230d40 --- /dev/null +++ b/tests/generator/not_impl_modules_header.txt @@ -0,0 +1,8 @@ +# WARNING: THIS IS AN AUTOMATICALLY GENERATED FILE +# EDIT tests/not_impl_mods_gen.sh, NOT THIS FILE. +# RESULTS OF THIS TEST DEPEND ON THE CPYTHON +# VERSION AND PYTHON ENVIRONMENT USED +# TO RUN not_impl_mods_gen.py + +import sys +import os diff --git a/tests/not_impl_gen.py b/tests/not_impl_gen.py index 2f1df44a2f..dba3df8277 100644 --- a/tests/not_impl_gen.py +++ b/tests/not_impl_gen.py @@ -1,20 +1,6 @@ -objects = [ - bool, - bytearray, - bytes, - complex, - dict, - float, - frozenset, - int, - list, - memoryview, - range, - set, - str, - tuple, - object, -] +import pkgutil +import os +import sys def attr_is_not_inherited(type_, attr): @@ -22,27 +8,71 @@ def attr_is_not_inherited(type_, attr): returns True if type_'s attr is not inherited from any of its base classes """ - bases = obj.__mro__[1:] + bases = type_.__mro__[1:] - return getattr(obj, attr) not in ( - getattr(base, attr, None) for base in bases) + return getattr(type_, attr) not in (getattr(base, attr, None) for base in bases) -header = open("generator/not_impl_header.txt") -footer = open("generator/not_impl_footer.txt") -output = open("snippets/whats_left_to_implement.py", "w") +def gen_methods(header, footer, output): + objects = [ + bool, + bytearray, + bytes, + complex, + dict, + float, + frozenset, + int, + list, + memoryview, + range, + set, + str, + tuple, + object, + ] -output.write(header.read()) -output.write("expected_methods = {\n") + output.write(header.read()) + output.write("expected_methods = {\n") -for obj in objects: - output.write(f" '{obj.__name__}': ({obj.__name__}, [\n") - output.write("\n".join( - f" {attr!r}," - for attr in dir(obj) - if attr_is_not_inherited(obj, attr) - )) - output.write("\n ])," + ("\n" if objects[-1] == obj else "\n\n")) + for obj in objects: + output.write(f" '{obj.__name__}': ({obj.__name__}, [\n") + output.write( + "\n".join( + f" {attr!r}," + for attr in dir(obj) + if attr_is_not_inherited(obj, attr) + ) + ) + output.write("\n ])," + ("\n" if objects[-1] == obj else "\n\n")) + + output.write("}\n\n") + output.write(footer.read()) + + +def gen_modules(header, footer, output): + output.write(header.read()) + + modules = set(map(lambda mod: mod.name, pkgutil.iter_modules())) + + print( + f""" +cpymods = {modules!r} +libdir = {os.path.abspath("../Lib/")!r} +""", + file=output, + ) + + output.write(footer.read()) + + +gen_funcs = {"methods": gen_methods, "modules": gen_modules} + + +for name, gen_func in gen_funcs.items(): + gen_func( + header=open(f"generator/not_impl_{name}_header.txt"), + footer=open(f"generator/not_impl_{name}_footer.txt"), + output=open(f"snippets/whats_left_{name}.py", "w"), + ) -output.write("}\n\n") -output.write(footer.read()) diff --git a/whats_left.sh b/whats_left.sh index 66e64f1a37..72601c4e23 100755 --- a/whats_left.sh +++ b/whats_left.sh @@ -1,11 +1,32 @@ #!/bin/sh set -e +GREEN='' +BOLD='' +NC='(B' + +h() { + # uppercase input + header_name=$(echo "$@" | tr "[:lower:]" "[:upper:]") + echo "$GREEN$BOLD===== $header_name =====$NC" +} + cd "$(dirname "$0")" -cd tests -python3 not_impl_gen.py +( + cd tests + # -I means isolate from environment; we don't want any pip packages to be listed + python3 -I not_impl_gen.py +) + +# show the building first, so people aren't confused why it's taking so long to +# run whats_left_to_implement +cargo build -cd .. +whats_left_section() { + h "$1" + cargo run -q -- tests/snippets/whats_left_"$1".py +} -cargo run -- tests/snippets/whats_left_to_implement.py \ No newline at end of file +whats_left_section methods +whats_left_section modules From 982b2f2c14460f2303136b77200e312b10994ec2 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 1 Jul 2019 13:11:43 -0500 Subject: [PATCH 868/884] Add comment to not_impl_gen.py --- tests/not_impl_gen.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/not_impl_gen.py b/tests/not_impl_gen.py index dba3df8277..d8a8f400f7 100644 --- a/tests/not_impl_gen.py +++ b/tests/not_impl_gen.py @@ -1,3 +1,7 @@ +# It's recommended to run this with `python3 -I not_impl_gen.py`, to make sure +# that nothing in your global Python environment interferes with what's being +# extracted here. + import pkgutil import os import sys From f8cf39ebbb71bb8461d6fdabb5aa61d7aa67194c Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 1 Jul 2019 13:18:05 -0500 Subject: [PATCH 869/884] Change sys.path in not_impl_gen.py --- tests/not_impl_gen.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/not_impl_gen.py b/tests/not_impl_gen.py index d8a8f400f7..203efcabb8 100644 --- a/tests/not_impl_gen.py +++ b/tests/not_impl_gen.py @@ -6,6 +6,13 @@ import os import sys +sys.path = list( + filter( + lambda path: "site-packages" not in path and "dist-packages" not in path, + sys.path, + ) +) + def attr_is_not_inherited(type_, attr): """ From 92625857ba54ab2e0d23cb9e7a78884f9c774863 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 1 Jul 2019 13:28:55 -0500 Subject: [PATCH 870/884] Allow specifying sections for whats_left.sh --- whats_left.sh | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/whats_left.sh b/whats_left.sh index 72601c4e23..05cc8d6444 100755 --- a/whats_left.sh +++ b/whats_left.sh @@ -1,6 +1,8 @@ -#!/bin/sh +#!/bin/bash set -e +ALL_SECTIONS=(methods modules) + GREEN='' BOLD='' NC='(B' @@ -23,10 +25,19 @@ cd "$(dirname "$0")" # run whats_left_to_implement cargo build -whats_left_section() { - h "$1" - cargo run -q -- tests/snippets/whats_left_"$1".py -} +if [ $# -eq 0 ]; then + sections=(${ALL_SECTIONS[@]}) +else + sections=($@) +fi -whats_left_section methods -whats_left_section modules +for section in "${sections[@]}"; do + section=$(echo "$section" | tr "[:upper:]" "[:lower:]") + snippet=tests/snippets/whats_left_$section.py + if ! [[ -f $snippet ]]; then + echo "Invalid section $section" >&2 + continue + fi + h "$section" >&2 + cargo run -q -- "$snippet" +done From bedbad284e157bf3909a4ba5f8f1f939cec731ff Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Mon, 1 Jul 2019 21:05:29 +0200 Subject: [PATCH 871/884] Change underscore into hyphen --- Cargo.lock | 38 +++++++++++++++++++------------------- Cargo.toml | 6 +++--- bytecode/Cargo.toml | 2 +- compiler/Cargo.toml | 6 +++--- derive/Cargo.toml | 6 +++--- parser/Cargo.toml | 2 +- parser/src/parser.rs | 1 - src/main.rs | 2 -- vm/Cargo.toml | 12 ++++++------ vm/src/builtins.rs | 10 +++++----- vm/src/import.rs | 6 +++--- vm/src/lib.rs | 2 +- vm/src/stdlib/mod.rs | 8 ++++---- vm/src/vm.rs | 10 ++++------ wasm/lib/Cargo.toml | 6 +++--- 15 files changed, 56 insertions(+), 61 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2d61467b91..c01dc468ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -957,15 +957,15 @@ dependencies = [ "cpython 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.5.13 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", - "rustpython_compiler 0.1.0", - "rustpython_parser 0.0.1", - "rustpython_vm 0.1.0", + "rustpython-compiler 0.1.0", + "rustpython-parser 0.0.1", + "rustpython-vm 0.1.0", "rustyline 4.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "xdg 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "rustpython_bytecode" +name = "rustpython-bytecode" version = "0.1.0" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -975,30 +975,30 @@ dependencies = [ ] [[package]] -name = "rustpython_compiler" +name = "rustpython-compiler" version = "0.1.0" dependencies = [ "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", "num-complex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rustpython_bytecode 0.1.0", - "rustpython_parser 0.0.1", + "rustpython-bytecode 0.1.0", + "rustpython-parser 0.0.1", ] [[package]] -name = "rustpython_derive" +name = "rustpython-derive" version = "0.1.0" dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro-hack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)", - "rustpython_bytecode 0.1.0", - "rustpython_compiler 0.1.0", + "rustpython-bytecode 0.1.0", + "rustpython-compiler 0.1.0", "syn 0.15.35 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "rustpython_parser" +name = "rustpython-parser" version = "0.0.1" dependencies = [ "lalrpop 0.16.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1014,7 +1014,7 @@ dependencies = [ ] [[package]] -name = "rustpython_vm" +name = "rustpython-vm" version = "0.1.0" dependencies = [ "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1043,10 +1043,10 @@ dependencies = [ "rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version_runtime 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rustpython_bytecode 0.1.0", - "rustpython_compiler 0.1.0", - "rustpython_derive 0.1.0", - "rustpython_parser 0.0.1", + "rustpython-bytecode 0.1.0", + "rustpython-compiler 0.1.0", + "rustpython-derive 0.1.0", + "rustpython-parser 0.0.1", "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)", "sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1069,9 +1069,9 @@ dependencies = [ "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", "js-sys 0.3.22 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rustpython_compiler 0.1.0", - "rustpython_parser 0.0.1", - "rustpython_vm 0.1.0", + "rustpython-compiler 0.1.0", + "rustpython-parser 0.0.1", + "rustpython-vm 0.1.0", "serde 1.0.92 (registry+https://github.com/rust-lang/crates.io-index)", "serde-wasm-bindgen 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "wasm-bindgen 0.2.45 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 45c5c940d3..76d5c56319 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,9 +16,9 @@ path = "./benchmarks/bench.rs" log="0.4.1" env_logger="0.5.10" clap = "2.31.2" -rustpython_compiler = {path = "compiler"} -rustpython_parser = {path = "parser"} -rustpython_vm = {path = "vm"} +rustpython-compiler = {path = "compiler"} +rustpython-parser = {path = "parser"} +rustpython-vm = {path = "vm"} rustyline = "4.1.0" xdg = "2.2.0" diff --git a/bytecode/Cargo.toml b/bytecode/Cargo.toml index 25a9dd3a3e..2fc7b8d242 100644 --- a/bytecode/Cargo.toml +++ b/bytecode/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rustpython_bytecode" +name = "rustpython-bytecode" version = "0.1.0" authors = ["Windel Bouwman "] edition = "2018" diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index dccc3b07e0..9056260ae1 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "rustpython_compiler" +name = "rustpython-compiler" version = "0.1.0" authors = ["coolreader18 <33094578+coolreader18@users.noreply.github.com>", "Windel Bouwman "] edition = "2018" [dependencies] -rustpython_bytecode = { path = "../bytecode" } -rustpython_parser = { path = "../parser" } +rustpython-bytecode = { path = "../bytecode" } +rustpython-parser = { path = "../parser" } num-complex = { version = "0.2", features = ["serde"] } log = "0.3" diff --git a/derive/Cargo.toml b/derive/Cargo.toml index bcf56dc6fa..8356c81859 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rustpython_derive" +name = "rustpython-derive" version = "0.1.0" authors = ["Joey "] edition = "2018" @@ -11,7 +11,7 @@ proc-macro = true syn = { version = "0.15.29", features = ["full"] } quote = "0.6.11" proc-macro2 = "0.4.27" -rustpython_compiler = { path = "../compiler" } -rustpython_bytecode = { path = "../bytecode" } +rustpython-compiler = { path = "../compiler" } +rustpython-bytecode = { path = "../bytecode" } bincode = "1.1" proc-macro-hack = "0.5" diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 5d10344204..89ed24aa01 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rustpython_parser" +name = "rustpython-parser" version = "0.0.1" authors = [ "Shing Lyu", "Windel Bouwman" ] build = "build.rs" diff --git a/parser/src/parser.rs b/parser/src/parser.rs index dd58bd11db..eee4193105 100644 --- a/parser/src/parser.rs +++ b/parser/src/parser.rs @@ -44,7 +44,6 @@ pub fn parse_statement(source: &str) -> Result, Parse /// # Example /// ``` /// extern crate num_bigint; -/// extern crate rustpython_parser; /// use num_bigint::BigInt; /// use rustpython_parser::{parser, ast}; /// let expr = parser::parse_expression("1+2").unwrap(); diff --git a/src/main.rs b/src/main.rs index bcd8c9ccd3..ba6f097ec9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,8 +3,6 @@ extern crate clap; extern crate env_logger; #[macro_use] extern crate log; -extern crate rustpython_parser; -extern crate rustpython_vm; extern crate rustyline; use clap::{App, Arg}; diff --git a/vm/Cargo.toml b/vm/Cargo.toml index 77df865c83..a06bb1b6bd 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -1,11 +1,11 @@ [package] -name = "rustpython_vm" +name = "rustpython-vm" version = "0.1.0" authors = ["Shing Lyu "] edition = "2018" [features] -default = ["rustpython_parser", "rustpython_compiler"] +default = ["rustpython-parser", "rustpython-compiler"] [dependencies] # Crypto: @@ -23,10 +23,10 @@ num-integer = "0.1.39" num-rational = "0.2.1" rand = "0.5" log = "0.3" -rustpython_derive = {path = "../derive"} -rustpython_parser = {path = "../parser", optional = true} -rustpython_compiler = {path = "../compiler", optional = true} -rustpython_bytecode = { path = "../bytecode" } +rustpython-derive = {path = "../derive"} +rustpython-parser = {path = "../parser", optional = true} +rustpython-compiler = {path = "../compiler", optional = true} +rustpython-bytecode = { path = "../bytecode" } serde = { version = "1.0.66", features = ["derive"] } serde_json = "1.0.26" byteorder = "1.2.6" diff --git a/vm/src/builtins.rs b/vm/src/builtins.rs index 8c76790cee..fa05febf68 100644 --- a/vm/src/builtins.rs +++ b/vm/src/builtins.rs @@ -18,7 +18,7 @@ use crate::obj::objint::{self, PyIntRef}; use crate::obj::objiter; use crate::obj::objstr::{self, PyString, PyStringRef}; use crate::obj::objtype::{self, PyClassRef}; -#[cfg(feature = "rustpython_compiler")] +#[cfg(feature = "rustpython-compiler")] use rustpython_compiler::compile; use crate::frame::Scope; @@ -99,7 +99,7 @@ struct CompileArgs { optimize: OptionalArg, } -#[cfg(feature = "rustpython_compiler")] +#[cfg(feature = "rustpython-compiler")] fn builtin_compile(args: CompileArgs, vm: &VirtualMachine) -> PyResult { // TODO: compile::compile should probably get bytes let source = match args.source { @@ -155,7 +155,7 @@ fn builtin_divmod(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { /// Implements `eval`. /// See also: https://docs.python.org/3/library/functions.html#eval -#[cfg(feature = "rustpython_compiler")] +#[cfg(feature = "rustpython-compiler")] fn builtin_eval(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { // TODO: support any mapping for `locals` arg_check!( @@ -187,7 +187,7 @@ fn builtin_eval(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { /// Implements `exec` /// https://docs.python.org/3/library/functions.html#exec -#[cfg(feature = "rustpython_compiler")] +#[cfg(feature = "rustpython-compiler")] fn builtin_exec(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { arg_check!( vm, @@ -789,7 +789,7 @@ pub fn make_module(vm: &VirtualMachine, module: PyObjectRef) { #[cfg(not(target_arch = "wasm32"))] let open = vm.ctx.new_rustfunc(io_open); - #[cfg(feature = "rustpython_compiler")] + #[cfg(feature = "rustpython-compiler")] { extend_module!(vm, module, { "compile" => ctx.new_rustfunc(builtin_compile), diff --git a/vm/src/import.rs b/vm/src/import.rs index d6c5e55769..10d2175054 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -10,7 +10,7 @@ use crate::obj::{objcode, objsequence, objstr}; use crate::pyobject::{ItemProtocol, PyResult, PyValue}; use crate::util; use crate::vm::VirtualMachine; -#[cfg(feature = "rustpython_compiler")] +#[cfg(feature = "rustpython-compiler")] use rustpython_compiler::compile; pub fn init_importlib(vm: &VirtualMachine) -> PyResult { @@ -57,7 +57,7 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s import_frozen(vm, module_name) } else if vm.stdlib_inits.borrow().contains_key(module_name) { import_builtin(vm, module_name) - } else if cfg!(feature = "rustpython_compiler") { + } else if cfg!(feature = "rustpython-compiler") { let notfound_error = &vm.ctx.exceptions.module_not_found_error; let import_error = &vm.ctx.exceptions.import_error; @@ -79,7 +79,7 @@ pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &s } } -#[cfg(feature = "rustpython_compiler")] +#[cfg(feature = "rustpython-compiler")] pub fn import_file( vm: &VirtualMachine, module_name: &str, diff --git a/vm/src/lib.rs b/vm/src/lib.rs index b68276b904..5754d98249 100644 --- a/vm/src/lib.rs +++ b/vm/src/lib.rs @@ -44,7 +44,7 @@ pub mod macros; mod builtins; pub mod cformat; mod dictdatatype; -#[cfg(feature = "rustpython_compiler")] +#[cfg(feature = "rustpython-compiler")] pub mod eval; mod exceptions; pub mod format; diff --git a/vm/src/stdlib/mod.rs b/vm/src/stdlib/mod.rs index 7ce1bed0c8..458a8cfb5e 100644 --- a/vm/src/stdlib/mod.rs +++ b/vm/src/stdlib/mod.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "rustpython_parser")] +#[cfg(feature = "rustpython-parser")] mod ast; mod binascii; mod dis; @@ -6,7 +6,7 @@ mod hashlib; mod imp; mod itertools; mod json; -#[cfg(feature = "rustpython_parser")] +#[cfg(feature = "rustpython-parser")] mod keyword; mod marshal; mod math; @@ -18,7 +18,7 @@ pub mod socket; mod string; mod thread; mod time_module; -#[cfg(feature = "rustpython_parser")] +#[cfg(feature = "rustpython-parser")] mod tokenize; mod unicodedata; mod warnings; @@ -62,7 +62,7 @@ pub fn get_module_inits() -> HashMap { }; // Insert parser related modules: - #[cfg(feature = "rustpython_parser")] + #[cfg(feature = "rustpython-parser")] { modules.insert( "ast".to_string(), diff --git a/vm/src/vm.rs b/vm/src/vm.rs index c6176450e5..bd0beba5ba 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -36,10 +36,8 @@ use crate::pyobject::{ use crate::stdlib; use crate::sysmodule; use num_bigint::BigInt; -#[cfg(feature = "rustpython_compiler")] -use rustpython_compiler::compile; -#[cfg(feature = "rustpython_compiler")] -use rustpython_compiler::error::CompileError; +#[cfg(feature = "rustpython-compiler")] +use rustpython_compiler::{compile, error::CompileError}; // use objects::objects; @@ -251,7 +249,7 @@ impl VirtualMachine { self.new_exception(overflow_error, msg) } - #[cfg(feature = "rustpython_compiler")] + #[cfg(feature = "rustpython-compiler")] pub fn new_syntax_error(&self, error: &CompileError) -> PyObjectRef { let syntax_error_type = self.ctx.exceptions.syntax_error.clone(); let syntax_error = self.new_exception(syntax_error_type, error.to_string()); @@ -733,7 +731,7 @@ impl VirtualMachine { ) } - #[cfg(feature = "rustpython_compiler")] + #[cfg(feature = "rustpython-compiler")] pub fn compile( &self, source: &str, diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index 35f02ba8d0..f3e0a42f4f 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -11,9 +11,9 @@ edition = "2018" crate-type = ["cdylib", "rlib"] [dependencies] -rustpython_compiler = { path = "../../compiler" } -rustpython_parser = { path = "../../parser" } -rustpython_vm = { path = "../../vm" } +rustpython-compiler = { path = "../../compiler" } +rustpython-parser = { path = "../../parser" } +rustpython-vm = { path = "../../vm" } cfg-if = "0.1.2" wasm-bindgen = "0.2" wasm-bindgen-futures = "0.3" From 6a58b76ef78301bc9ab4922795f1eb6c0d6474a0 Mon Sep 17 00:00:00 2001 From: Windel Bouwman Date: Mon, 1 Jul 2019 21:14:07 +0200 Subject: [PATCH 872/884] Change authors to team name. --- Cargo.toml | 2 +- bytecode/Cargo.toml | 2 +- compiler/Cargo.toml | 2 +- derive/Cargo.toml | 2 +- parser/Cargo.toml | 2 +- vm/Cargo.toml | 2 +- wasm/lib/Cargo.toml | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 76d5c56319..b92473d6db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustpython" version = "0.0.1" -authors = ["Windel Bouwman ", "Shing Lyu "] +authors = ["RustPython Team"] edition = "2018" [workspace] diff --git a/bytecode/Cargo.toml b/bytecode/Cargo.toml index 2fc7b8d242..fad46c1757 100644 --- a/bytecode/Cargo.toml +++ b/bytecode/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustpython-bytecode" version = "0.1.0" -authors = ["Windel Bouwman "] +authors = ["RustPython Team"] edition = "2018" [dependencies] diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 9056260ae1..8fdb37871a 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustpython-compiler" version = "0.1.0" -authors = ["coolreader18 <33094578+coolreader18@users.noreply.github.com>", "Windel Bouwman "] +authors = ["RustPython Team"] edition = "2018" [dependencies] diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 8356c81859..fa304d0bdd 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustpython-derive" version = "0.1.0" -authors = ["Joey "] +authors = ["RustPython Team"] edition = "2018" [lib] diff --git a/parser/Cargo.toml b/parser/Cargo.toml index 89ed24aa01..af0b19d39f 100644 --- a/parser/Cargo.toml +++ b/parser/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustpython-parser" version = "0.0.1" -authors = [ "Shing Lyu", "Windel Bouwman" ] +authors = [ "RustPython Team" ] build = "build.rs" edition = "2018" diff --git a/vm/Cargo.toml b/vm/Cargo.toml index a06bb1b6bd..6c2533cb3f 100644 --- a/vm/Cargo.toml +++ b/vm/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustpython-vm" version = "0.1.0" -authors = ["Shing Lyu "] +authors = ["RustPython Team"] edition = "2018" [features] diff --git a/wasm/lib/Cargo.toml b/wasm/lib/Cargo.toml index f3e0a42f4f..9c78a02904 100644 --- a/wasm/lib/Cargo.toml +++ b/wasm/lib/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "rustpython_wasm" version = "0.1.0-pre-alpha.2" -authors = ["Ryan Liddle "] +authors = ["RustPython Team"] license = "MIT" description = "A Python-3 (CPython >= 3.5.0) Interpreter written in Rust, compiled to WASM" repository = "https://github.com/RustPython/RustPython/tree/master/wasm/lib" From 3668250c127e4b537a70a5103a241154e37c5b25 Mon Sep 17 00:00:00 2001 From: coolreader18 <33094578+coolreader18@users.noreply.github.com> Date: Mon, 1 Jul 2019 14:21:29 -0500 Subject: [PATCH 873/884] Show module attributes missing --- tests/generator/not_impl_modules_footer.txt | 27 +++++++++++++++++---- tests/not_impl_gen.py | 14 ++++++++++- whats_left.sh | 4 +-- 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/tests/generator/not_impl_modules_footer.txt b/tests/generator/not_impl_modules_footer.txt index 1faffd0e5b..c475ca1782 100644 --- a/tests/generator/not_impl_modules_footer.txt +++ b/tests/generator/not_impl_modules_footer.txt @@ -1,15 +1,32 @@ -rustpymods = set( + +rustpymods = list( map( lambda mod: mod[0], filter( - lambda mod: mod[1] == "" or mod[1] == ".py", + lambda mod: (mod[1] == "" or mod[1] == ".py") and "LICENSE" not in mod[0], map(os.path.splitext, os.listdir(libdir)), ), ) ) -rustpymods |= set(sys.builtin_module_names) +rustpymods += list(sys.builtin_module_names) + +rustpymods = dict(map( + lambda mod: ( + mod, + set(dir(__import__(mod))) + if mod not in ("this", "antigravity") + else None, + ), + rustpymods +)) -for mod in cpymods - rustpymods: - print(mod) +for modname, cpymod in cpymods.items(): + if modname in rustpymods: + rustpymod = rustpymods[modname] + if rustpymod: + for item in cpymod - rustpymod: + print(f"{modname}.{item}") + else: + print(f"{modname} (entire module)") diff --git a/tests/not_impl_gen.py b/tests/not_impl_gen.py index 203efcabb8..4689eca867 100644 --- a/tests/not_impl_gen.py +++ b/tests/not_impl_gen.py @@ -64,7 +64,19 @@ def gen_methods(header, footer, output): def gen_modules(header, footer, output): output.write(header.read()) - modules = set(map(lambda mod: mod.name, pkgutil.iter_modules())) + modules = dict( + map( + lambda mod: ( + mod.name, + # check name b/c modules listed have side effects on import, + # e.g. printing something or opening a webpage + set(dir(__import__(mod.name))) + if mod.name not in ("this", "antigravity") + else None, + ), + pkgutil.iter_modules(), + ) + ) print( f""" diff --git a/whats_left.sh b/whats_left.sh index 05cc8d6444..84ae3a03ca 100755 --- a/whats_left.sh +++ b/whats_left.sh @@ -23,7 +23,7 @@ cd "$(dirname "$0")" # show the building first, so people aren't confused why it's taking so long to # run whats_left_to_implement -cargo build +cargo build --release if [ $# -eq 0 ]; then sections=(${ALL_SECTIONS[@]}) @@ -39,5 +39,5 @@ for section in "${sections[@]}"; do continue fi h "$section" >&2 - cargo run -q -- "$snippet" + cargo run --release -q -- "$snippet" done From 8162b87a0010c988ae0988a537ebcc4dd8cb9b04 Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Wed, 3 Jul 2019 16:13:16 +0300 Subject: [PATCH 874/884] Do not pollute stack when if-expression condition evaluated to False --- compiler/src/compile.rs | 6 +++++- tests/snippets/if_expressions.py | 26 ++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/snippets/if_expressions.py diff --git a/compiler/src/compile.rs b/compiler/src/compile.rs index 844f952ea6..877200f809 100644 --- a/compiler/src/compile.rs +++ b/compiler/src/compile.rs @@ -1426,11 +1426,15 @@ impl Compiler { ast::Expression::IfExpression { test, body, orelse } => { let no_label = self.new_label(); let end_label = self.new_label(); - self.compile_test(test, None, Some(no_label), EvalContext::Expression)?; + self.compile_test(test, None, None, EvalContext::Expression)?; + self.emit(Instruction::JumpIfFalse { target: no_label }); + // True case self.compile_expression(body)?; self.emit(Instruction::Jump { target: end_label }); + // False case self.set_label(no_label); self.compile_expression(orelse)?; + // End self.set_label(end_label); } } diff --git a/tests/snippets/if_expressions.py b/tests/snippets/if_expressions.py new file mode 100644 index 0000000000..143cb64580 --- /dev/null +++ b/tests/snippets/if_expressions.py @@ -0,0 +1,26 @@ +def ret(expression): + return expression + + +assert ret("0" if True else "1") == "0" +assert ret("0" if False else "1") == "1" + +assert ret("0" if False else ("1" if True else "2")) == "1" +assert ret("0" if False else ("1" if False else "2")) == "2" + +assert ret(("0" if True else "1") if True else "2") == "0" +assert ret(("0" if False else "1") if True else "2") == "1" + +a = True +b = False +assert ret("0" if a or b else "1") == "0" +assert ret("0" if a and b else "1") == "1" + + +def func1(): + return 0 + +def func2(): + return 20 + +assert ret(func1() or func2()) == 20 From 090cfed5930067f729dcfe2c27b5bdfc9753e6a4 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Wed, 3 Jul 2019 18:57:49 +0300 Subject: [PATCH 875/884] Use importlib import in WASM --- src/main.rs | 2 +- vm/src/import.rs | 9 ++++++--- wasm/lib/src/vm_class.rs | 3 ++- wasm/lib/src/wasm_builtins.rs | 27 --------------------------- 4 files changed, 9 insertions(+), 32 deletions(-) diff --git a/src/main.rs b/src/main.rs index ba6f097ec9..c9f74fea0a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,7 +51,7 @@ fn main() { // Construct vm: let vm = VirtualMachine::new(); - let res = import::init_importlib(&vm); + let res = import::init_importlib(&vm, true); handle_exception(&vm, res); // Figure out if a -c option was given: diff --git a/vm/src/import.rs b/vm/src/import.rs index 10d2175054..37fcca3385 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -13,15 +13,18 @@ use crate::vm::VirtualMachine; #[cfg(feature = "rustpython-compiler")] use rustpython_compiler::compile; -pub fn init_importlib(vm: &VirtualMachine) -> PyResult { +pub fn init_importlib(vm: &VirtualMachine, external: bool) -> PyResult { let importlib = import_frozen(vm, "_frozen_importlib")?; let impmod = import_builtin(vm, "_imp")?; let install = vm.get_attribute(importlib.clone(), "_install")?; vm.invoke(install, vec![vm.sys_module.clone(), impmod])?; vm.import_func .replace(vm.get_attribute(importlib.clone(), "__import__")?); - let install_external = vm.get_attribute(importlib.clone(), "_install_external_importers")?; - vm.invoke(install_external, vec![])?; + if external { + let install_external = + vm.get_attribute(importlib.clone(), "_install_external_importers")?; + vm.invoke(install_external, vec![])?; + } Ok(vm.get_none()) } diff --git a/wasm/lib/src/vm_class.rs b/wasm/lib/src/vm_class.rs index 2bb156ef8c..1437af4668 100644 --- a/wasm/lib/src/vm_class.rs +++ b/wasm/lib/src/vm_class.rs @@ -8,6 +8,7 @@ use wasm_bindgen::prelude::*; use rustpython_compiler::{compile, error::CompileErrorType}; use rustpython_vm::frame::{NameProtocol, Scope}; use rustpython_vm::function::PyFuncArgs; +use rustpython_vm::import; use rustpython_vm::pyobject::{PyObject, PyObjectPayload, PyObjectRef, PyResult, PyValue}; use rustpython_vm::VirtualMachine; @@ -43,7 +44,7 @@ impl StoredVirtualMachine { setup_browser_module(&vm); } - *vm.import_func.borrow_mut() = vm.ctx.new_rustfunc(wasm_builtins::builtin_import); + import::init_importlib(&vm, false); StoredVirtualMachine { vm, diff --git a/wasm/lib/src/wasm_builtins.rs b/wasm/lib/src/wasm_builtins.rs index 6a89f5e35f..e2fed62c2b 100644 --- a/wasm/lib/src/wasm_builtins.rs +++ b/wasm/lib/src/wasm_builtins.rs @@ -8,7 +8,6 @@ use js_sys::{self, Array}; use web_sys::{self, console}; use rustpython_vm::function::{Args, KwArgs, PyFuncArgs}; -use rustpython_vm::import; use rustpython_vm::obj::{ objstr::{self, PyStringRef}, objtype, @@ -80,29 +79,3 @@ pub fn builtin_print_console(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult console::log(&arr); Ok(vm.get_none()) } - -pub fn builtin_import( - module_name: PyStringRef, - _args: Args, - _kwargs: KwArgs, - vm: &VirtualMachine, -) -> PyResult { - let module_name = module_name.as_str(); - - let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); - - // First, see if we already loaded the module: - if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) { - Ok(module) - } else if vm.frozen.borrow().contains_key(module_name) { - import::import_frozen(vm, module_name) - } else if vm.stdlib_inits.borrow().contains_key(module_name) { - import::import_builtin(vm, module_name) - } else { - let notfound_error = vm.context().exceptions.module_not_found_error.clone(); - Err(vm.new_exception( - notfound_error, - format!("Module {:?} not found", module_name), - )) - } -} From 9ce5240598f186e66c5e5d765042294fe95b4efa Mon Sep 17 00:00:00 2001 From: Maxim Kurnikov Date: Wed, 3 Jul 2019 22:15:02 +0300 Subject: [PATCH 876/884] add name, path, msg attributes to ImportError --- tests/snippets/exceptions.py | 20 ++++++++++++++++++++ tests/snippets/import.py | 5 +++++ vm/src/exceptions.rs | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) diff --git a/tests/snippets/exceptions.py b/tests/snippets/exceptions.py index 1349f46296..cb178fc066 100644 --- a/tests/snippets/exceptions.py +++ b/tests/snippets/exceptions.py @@ -1,3 +1,4 @@ +# KeyError empty_exc = KeyError() assert str(empty_exc) == '' assert repr(empty_exc) == 'KeyError()' @@ -23,3 +24,22 @@ def __str__(self): exc = KeyError(A()) assert str(exc) == 'repr' assert repr(exc) == 'KeyError(repr,)' + +# ImportError / ModuleNotFoundError +exc = ImportError() +assert exc.name is None +assert exc.path is None +assert exc.msg is None +assert exc.args == () + +exc = ImportError('hello') +assert exc.name is None +assert exc.path is None +assert exc.msg == 'hello' +assert exc.args == ('hello',) + +exc = ImportError('hello', name='name', path='path') +assert exc.name == 'name' +assert exc.path == 'path' +assert exc.msg == 'hello' +assert exc.args == ('hello',) diff --git a/tests/snippets/import.py b/tests/snippets/import.py index 99322a2ea9..7c730c2659 100644 --- a/tests/snippets/import.py +++ b/tests/snippets/import.py @@ -25,6 +25,11 @@ except ImportError: pass +try: + import mymodule +except ModuleNotFoundError as exc: + assert exc.name == 'mymodule' + test = __import__("import_target") assert test.X == import_target.X diff --git a/vm/src/exceptions.rs b/vm/src/exceptions.rs index b3d03ea806..cfd611c3ff 100644 --- a/vm/src/exceptions.rs +++ b/vm/src/exceptions.rs @@ -312,6 +312,35 @@ impl ExceptionZoo { } } +fn import_error_init(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { + // TODO: call super().__init__(*args) instead + exception_init(vm, args.clone())?; + + let exc_self = args.args[0].clone(); + vm.set_attr( + &exc_self, + "name", + args.kwargs + .get("name") + .cloned() + .unwrap_or_else(|| vm.get_none()), + )?; + vm.set_attr( + &exc_self, + "path", + args.kwargs + .get("path") + .cloned() + .unwrap_or_else(|| vm.get_none()), + )?; + vm.set_attr( + &exc_self, + "msg", + args.args.get(1).cloned().unwrap_or_else(|| vm.get_none()), + )?; + Ok(vm.get_none()) +} + pub fn init(context: &PyContext) { let base_exception_type = &context.exceptions.base_exception_type; extend_class!(context, base_exception_type, { @@ -323,4 +352,9 @@ pub fn init(context: &PyContext) { "__str__" => context.new_rustfunc(exception_str), "__repr__" => context.new_rustfunc(exception_repr), }); + + let import_error_type = &context.exceptions.import_error; + extend_class!(context, import_error_type, { + "__init__" => context.new_rustfunc(import_error_init) + }); } From ee4ee5456fd248dbe6ba00fa7e3fb8224154d123 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sun, 30 Jun 2019 20:26:42 +0300 Subject: [PATCH 877/884] Use vm.import when running with -m --- src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index c9f74fea0a..d1b33ab4e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,8 +98,7 @@ fn run_command(vm: &VirtualMachine, mut source: String) -> PyResult { fn run_module(vm: &VirtualMachine, module: &str) -> PyResult { debug!("Running module {}", module); - let current_path = PathBuf::from("."); - import::import_module(vm, current_path, module) + vm.import(module, &vm.ctx.new_tuple(vec![]), 0) } fn run_script(vm: &VirtualMachine, script_file: &str) -> PyResult { From 42d17138ebb56fdb1b37e4e6ac21437c5c01e01c Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Wed, 3 Jul 2019 19:06:36 +0300 Subject: [PATCH 878/884] Remove old import --- vm/src/import.rs | 64 +---------------------------------------- vm/src/stdlib/io.rs | 5 +--- vm/src/stdlib/thread.rs | 4 +-- 3 files changed, 3 insertions(+), 70 deletions(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index 37fcca3385..4d94a3f108 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -2,13 +2,10 @@ * Import mechanics */ -use std::path::PathBuf; - use crate::bytecode::CodeObject; use crate::frame::Scope; -use crate::obj::{objcode, objsequence, objstr}; +use crate::obj::objcode; use crate::pyobject::{ItemProtocol, PyResult, PyValue}; -use crate::util; use crate::vm::VirtualMachine; #[cfg(feature = "rustpython-compiler")] use rustpython_compiler::compile; @@ -49,39 +46,6 @@ pub fn import_builtin(vm: &VirtualMachine, module_name: &str) -> PyResult { }) } -pub fn import_module(vm: &VirtualMachine, current_path: PathBuf, module_name: &str) -> PyResult { - // Cached modules: - let sys_modules = vm.get_attribute(vm.sys_module.clone(), "modules").unwrap(); - - // First, see if we already loaded the module: - if let Ok(module) = sys_modules.get_item(module_name.to_string(), vm) { - Ok(module) - } else if vm.frozen.borrow().contains_key(module_name) { - import_frozen(vm, module_name) - } else if vm.stdlib_inits.borrow().contains_key(module_name) { - import_builtin(vm, module_name) - } else if cfg!(feature = "rustpython-compiler") { - let notfound_error = &vm.ctx.exceptions.module_not_found_error; - let import_error = &vm.ctx.exceptions.import_error; - - // Time to search for module in any place: - let file_path = find_source(vm, current_path, module_name) - .map_err(|e| vm.new_exception(notfound_error.clone(), e))?; - let source = util::read_file(file_path.as_path()) - .map_err(|e| vm.new_exception(import_error.clone(), e.to_string()))?; - - import_file( - vm, - module_name, - file_path.to_str().unwrap().to_string(), - source, - ) - } else { - let notfound_error = &vm.ctx.exceptions.module_not_found_error; - Err(vm.new_exception(notfound_error.clone(), module_name.to_string())) - } -} - #[cfg(feature = "rustpython-compiler")] pub fn import_file( vm: &VirtualMachine, @@ -118,29 +82,3 @@ pub fn import_codeobj( )?; Ok(module) } - -fn find_source(vm: &VirtualMachine, current_path: PathBuf, name: &str) -> Result { - let sys_path = vm.get_attribute(vm.sys_module.clone(), "path").unwrap(); - let mut paths: Vec = objsequence::get_elements_list(&sys_path) - .iter() - .map(|item| PathBuf::from(objstr::get_value(item))) - .collect(); - - paths.insert(0, current_path); - - let rel_name = name.replace('.', "/"); - let suffixes = [".py", "/__init__.py"]; - let mut file_paths = vec![]; - for path in paths { - for suffix in suffixes.iter() { - let mut file_path = path.clone(); - file_path.push(format!("{}{}", rel_name, suffix)); - file_paths.push(file_path); - } - } - - match file_paths.iter().find(|p| p.exists()) { - Some(path) => Ok(path.to_path_buf()), - None => Err(format!("No module named '{}'", name)), - } -} diff --git a/vm/src/stdlib/io.rs b/vm/src/stdlib/io.rs index 2d0b2286c1..f092953f30 100644 --- a/vm/src/stdlib/io.rs +++ b/vm/src/stdlib/io.rs @@ -6,14 +6,11 @@ use std::io::prelude::*; use std::io::Cursor; use std::io::SeekFrom; -use std::path::PathBuf; - use num_bigint::ToBigInt; use num_traits::ToPrimitive; use super::os; use crate::function::{OptionalArg, PyFuncArgs}; -use crate::import; use crate::obj::objbytearray::PyByteArray; use crate::obj::objbytes; use crate::obj::objbytes::PyBytes; @@ -532,7 +529,7 @@ pub fn io_open(vm: &VirtualMachine, args: PyFuncArgs) -> PyResult { } }; - let io_module = import::import_module(vm, PathBuf::default(), "io").unwrap(); + let io_module = vm.import("_io", &vm.ctx.new_tuple(vec![]), 0)?; // Construct a FileIO (subclass of RawIOBase) // This is subsequently consumed by a Buffered Class. diff --git a/vm/src/stdlib/thread.rs b/vm/src/stdlib/thread.rs index 6155365490..d6df4cb761 100644 --- a/vm/src/stdlib/thread.rs +++ b/vm/src/stdlib/thread.rs @@ -2,10 +2,8 @@ /// support threading use super::super::pyobject::PyObjectRef; use crate::function::PyFuncArgs; -use crate::import; use crate::pyobject::PyResult; use crate::vm::VirtualMachine; -use std::path::PathBuf; fn rlock_acquire(vm: &VirtualMachine, _args: PyFuncArgs) -> PyResult { Ok(vm.get_none()) @@ -38,7 +36,7 @@ fn get_ident(_vm: &VirtualMachine) -> u32 { } fn allocate_lock(vm: &VirtualMachine) -> PyResult { - let module = import::import_module(vm, PathBuf::default(), "_thread")?; + let module = vm.import("_thread", &vm.ctx.new_tuple(vec![]), 0)?; let lock_class = vm.get_attribute(module.clone(), "RLock")?; vm.invoke(lock_class, vec![]) } From bb30d0fce0c8042f8e076eb256f44ea8b55dbc54 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 4 Jul 2019 17:36:27 +0300 Subject: [PATCH 879/884] Don't install external imports if there is no compiler --- vm/src/import.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index 4d94a3f108..28727b6bfd 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -17,7 +17,7 @@ pub fn init_importlib(vm: &VirtualMachine, external: bool) -> PyResult { vm.invoke(install, vec![vm.sys_module.clone(), impmod])?; vm.import_func .replace(vm.get_attribute(importlib.clone(), "__import__")?); - if external { + if external && cfg!(feature = "rustpython-compiler") { let install_external = vm.get_attribute(importlib.clone(), "_install_external_importers")?; vm.invoke(install_external, vec![])?; From f740845babf0c8d3d18ae67487c371153cf3781f Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 4 Jul 2019 17:38:55 +0300 Subject: [PATCH 880/884] Use vm.class --- vm/src/stdlib/thread.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vm/src/stdlib/thread.rs b/vm/src/stdlib/thread.rs index d6df4cb761..414e7d72e7 100644 --- a/vm/src/stdlib/thread.rs +++ b/vm/src/stdlib/thread.rs @@ -36,9 +36,8 @@ fn get_ident(_vm: &VirtualMachine) -> u32 { } fn allocate_lock(vm: &VirtualMachine) -> PyResult { - let module = vm.import("_thread", &vm.ctx.new_tuple(vec![]), 0)?; - let lock_class = vm.get_attribute(module.clone(), "RLock")?; - vm.invoke(lock_class, vec![]) + let lock_class = vm.class("_thread", "RLock"); + vm.invoke(lock_class.into_object(), vec![]) } pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { From 2edba36de7eb2987549943e37274cc7616fdde6b Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 4 Jul 2019 23:58:01 +0300 Subject: [PATCH 881/884] Allow skipping of unknown modules --- tests/not_impl_gen.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/not_impl_gen.py b/tests/not_impl_gen.py index 4689eca867..7e0c029e9f 100644 --- a/tests/not_impl_gen.py +++ b/tests/not_impl_gen.py @@ -60,6 +60,12 @@ def gen_methods(header, footer, output): output.write("}\n\n") output.write(footer.read()) +def get_module_methods(name): + try: + return set(dir(__import__(name))) if name not in ("this", "antigravity") else None + except ModuleNotFoundError: + return None + def gen_modules(header, footer, output): output.write(header.read()) @@ -70,9 +76,7 @@ def gen_modules(header, footer, output): mod.name, # check name b/c modules listed have side effects on import, # e.g. printing something or opening a webpage - set(dir(__import__(mod.name))) - if mod.name not in ("this", "antigravity") - else None, + get_module_methods(mod.name) ), pkgutil.iter_modules(), ) From 6239fe0fbc1cde65df643ff6e66a43a2b98b7f8c Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Thu, 4 Jul 2019 19:20:34 +0300 Subject: [PATCH 882/884] Remove importlib frames from traceback --- vm/src/import.rs | 36 ++++++++++++++++++++++++++++++++-- vm/src/vm.rs | 50 ++++++++++++++++++++++++++---------------------- 2 files changed, 61 insertions(+), 25 deletions(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index 28727b6bfd..f6c777c448 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -4,8 +4,8 @@ use crate::bytecode::CodeObject; use crate::frame::Scope; -use crate::obj::objcode; -use crate::pyobject::{ItemProtocol, PyResult, PyValue}; +use crate::obj::{objcode, objsequence, objstr, objtype}; +use crate::pyobject::{ItemProtocol, PyObjectRef, PyResult, PyValue}; use crate::vm::VirtualMachine; #[cfg(feature = "rustpython-compiler")] use rustpython_compiler::compile; @@ -82,3 +82,35 @@ pub fn import_codeobj( )?; Ok(module) } + +pub fn remove_importlib_frames(vm: &VirtualMachine, exc: &PyObjectRef) -> PyObjectRef { + let always_trim = objtype::isinstance(exc, &vm.ctx.exceptions.import_error); + + if let Ok(tb) = vm.get_attribute(exc.clone(), "__traceback__") { + if objtype::isinstance(&tb, &vm.ctx.list_type()) { + let mut tb_entries = objsequence::get_elements_list(&tb).to_vec(); + tb_entries.reverse(); + let mut new_tb = Vec::with_capacity(tb_entries.len()); + + for tb_entry in tb_entries.iter() { + let mut current_chunk = vec![]; + let location_attrs = objsequence::get_elements_tuple(&tb_entry); + let file_name = objstr::get_value(&location_attrs[0]); + if file_name != "_frozen_importlib" && file_name != "_frozen_importlib_external" { + current_chunk.clear(); + new_tb.push(tb_entry.clone()) + } else { + current_chunk.push(tb_entry.clone()); + let run_obj_name = objstr::get_value(&location_attrs[0]); + if run_obj_name == "_call_with_frames_removed" || always_trim { + new_tb.append(&mut current_chunk); + } + }; + } + new_tb.reverse(); + vm.set_attr(exc, "__traceback__", vm.ctx.new_list(new_tb)) + .unwrap(); + } + } + exc.clone() +} diff --git a/vm/src/vm.rs b/vm/src/vm.rs index bd0beba5ba..f117b83f31 100644 --- a/vm/src/vm.rs +++ b/vm/src/vm.rs @@ -15,6 +15,7 @@ use crate::bytecode; use crate::frame::{ExecutionResult, Frame, FrameRef, Scope}; use crate::frozen; use crate::function::PyFuncArgs; +use crate::import; use crate::obj::objbool; use crate::obj::objbuiltinfunc::PyBuiltinFunction; use crate::obj::objcode::{PyCode, PyCodeRef}; @@ -305,30 +306,33 @@ impl VirtualMachine { pub fn import(&self, module: &str, from_list: &PyObjectRef, level: usize) -> PyResult { let sys_modules = self.get_attribute(self.sys_module.clone(), "modules")?; - sys_modules.get_item(module.to_string(), self).or_else(|_| { - let import_func = self - .get_attribute(self.builtins.clone(), "__import__") - .map_err(|_| self.new_import_error("__import__ not found".to_string()))?; - - let (locals, globals) = if let Some(frame) = self.current_frame() { - ( - frame.scope.get_locals().into_object(), - frame.scope.globals.clone().into_object(), + sys_modules + .get_item(module.to_string(), self) + .or_else(|_| { + let import_func = self + .get_attribute(self.builtins.clone(), "__import__") + .map_err(|_| self.new_import_error("__import__ not found".to_string()))?; + + let (locals, globals) = if let Some(frame) = self.current_frame() { + ( + frame.scope.get_locals().into_object(), + frame.scope.globals.clone().into_object(), + ) + } else { + (self.get_none(), self.get_none()) + }; + self.invoke( + import_func, + vec![ + self.ctx.new_str(module.to_string()), + globals, + locals, + from_list.clone(), + self.ctx.new_int(level), + ], ) - } else { - (self.get_none(), self.get_none()) - }; - self.invoke( - import_func, - vec![ - self.ctx.new_str(module.to_string()), - globals, - locals, - from_list.clone(), - self.ctx.new_int(level), - ], - ) - }) + }) + .map_err(|exc| import::remove_importlib_frames(self, &exc)) } /// Determines if `obj` is an instance of `cls`, either directly, indirectly or virtually via From c70748fbaa557c537cb05ca36a29baa5090629d9 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 6 Jul 2019 09:12:33 +0300 Subject: [PATCH 883/884] Use filter --- vm/src/import.rs | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/vm/src/import.rs b/vm/src/import.rs index f6c777c448..1103dfc9ad 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -88,26 +88,31 @@ pub fn remove_importlib_frames(vm: &VirtualMachine, exc: &PyObjectRef) -> PyObje if let Ok(tb) = vm.get_attribute(exc.clone(), "__traceback__") { if objtype::isinstance(&tb, &vm.ctx.list_type()) { - let mut tb_entries = objsequence::get_elements_list(&tb).to_vec(); - tb_entries.reverse(); - let mut new_tb = Vec::with_capacity(tb_entries.len()); - - for tb_entry in tb_entries.iter() { - let mut current_chunk = vec![]; - let location_attrs = objsequence::get_elements_tuple(&tb_entry); - let file_name = objstr::get_value(&location_attrs[0]); - if file_name != "_frozen_importlib" && file_name != "_frozen_importlib_external" { - current_chunk.clear(); - new_tb.push(tb_entry.clone()) - } else { - current_chunk.push(tb_entry.clone()); - let run_obj_name = objstr::get_value(&location_attrs[0]); - if run_obj_name == "_call_with_frames_removed" || always_trim { - new_tb.append(&mut current_chunk); + let tb_entries = objsequence::get_elements_list(&tb).to_vec(); + let mut in_importlib = false; + let new_tb = tb_entries + .iter() + .filter(|tb_entry| { + let location_attrs = objsequence::get_elements_tuple(&tb_entry); + let file_name = objstr::get_value(&location_attrs[0]); + if file_name == "_frozen_importlib" || file_name == "_frozen_importlib_external" + { + let run_obj_name = objstr::get_value(&location_attrs[2]); + if run_obj_name == "_call_with_frames_removed" { + in_importlib = true; + } + if always_trim || in_importlib { + false + } else { + true + } + } else { + in_importlib = false; + true } - }; - } - new_tb.reverse(); + }) + .map(|x| x.clone()) + .collect(); vm.set_attr(exc, "__traceback__", vm.ctx.new_list(new_tb)) .unwrap(); } From 251e33af222935054857f4ba78392d6e6a97ebe7 Mon Sep 17 00:00:00 2001 From: Aviv Palivoda Date: Sat, 6 Jul 2019 09:13:17 +0300 Subject: [PATCH 884/884] Add TODO on verbose --- vm/src/import.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/src/import.rs b/vm/src/import.rs index 1103dfc9ad..2c2ac87289 100644 --- a/vm/src/import.rs +++ b/vm/src/import.rs @@ -83,6 +83,7 @@ pub fn import_codeobj( Ok(module) } +// TODO: This function should do nothing on verbose mode. pub fn remove_importlib_frames(vm: &VirtualMachine, exc: &PyObjectRef) -> PyObjectRef { let always_trim = objtype::isinstance(exc, &vm.ctx.exceptions.import_error);