diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e3cf7668295..0a3fac5cd3b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,6 +1,6 @@ on: push: - branches: [master, release] + pull_request: name: CI diff --git a/README.md b/README.md index f7a5832da09..fe1ed44c941 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ A Python-3 (CPython >= 3.5.0) Interpreter written in Rust :snake: :scream: [![WAPM package](https://wapm.io/package/rustpython/badge.svg?style=flat)](https://wapm.io/package/rustpython) [![Open in Gitpod](https://img.shields.io/static/v1?label=Open%20in&message=Gitpod&color=1aa6e4&logo=gitpod)](https://gitpod.io#https://github.com/RustPython/RustPython) +[![Open in Gitpod](https://img.shields.io/static/v1?label=Open%20in&message=Gitpod&color=1aa6e4&logo=gitpod)](https://gitpod.io#https://github.com/TheAnyKey/RustPython) + +For this Fork +[![Open in Gitpod](https://img.shields.io/static/v1?label=Open%20in&message=Gitpod&color=1aa6e4&logo=gitpod)](https://gitpod.io#https://github.com/TheAnyKey/RustPython) + ## Usage #### Check out our [online demo](https://rustpython.github.io/demo/) running on WebAssembly. diff --git a/tests/snippets/strings.py b/tests/snippets/strings.py index 7471b700501..7ceb653c1c3 100644 --- a/tests/snippets/strings.py +++ b/tests/snippets/strings.py @@ -1,4 +1,4 @@ -from testutils import assert_raises, AssertRaises +from testutils import assert_raises, AssertRaises, skip_if_unsupported assert "".__eq__(1) == NotImplemented assert "a" == 'a' @@ -471,3 +471,73 @@ def try_mutate_str(): assert '{:e}'.format(float('inf')) == 'inf' assert '{:e}'.format(float('-inf')) == '-inf' assert '{:E}'.format(float('inf')) == 'INF' + + +# remove*fix test +def test_removeprefix(): + s='foobarfoo' + s_ref='foobarfoo' + assert s.removeprefix('f') == s_ref[1:] + assert s.removeprefix('fo') == s_ref[2:] + assert s.removeprefix('foo') == s_ref[3:] + + assert s.removeprefix('') == s_ref + assert s.removeprefix('bar') == s_ref + assert s.removeprefix('lol') == s_ref + assert s.removeprefix('_foo') == s_ref + assert s.removeprefix('-foo') == s_ref + assert s.removeprefix('afoo') == s_ref + assert s.removeprefix('*foo') == s_ref + + assert s==s_ref, 'undefined test fail' + +def test_removeprefix_types(): + s='0123456' + s_ref='0123456' + others=[0,['012']] + found=False + for o in others: + try: + s.removeprefix(o) + except: + found=True + + assert found, f'Removeprefix accepts other type: {type(o)}: {o=}' + +def test_removesuffix(): + s='foobarfoo' + s_ref='foobarfoo' + assert s.removesuffix('o') == s_ref[:-1] + assert s.removesuffix('oo') == s_ref[:-2] + assert s.removesuffix('foo') == s_ref[:-3] + + assert s.removesuffix('') == s_ref + assert s.removesuffix('bar') == s_ref + assert s.removesuffix('lol') == s_ref + assert s.removesuffix('foo_') == s_ref + assert s.removesuffix('foo-') == s_ref + assert s.removesuffix('foo*') == s_ref + assert s.removesuffix('fooa') == s_ref + + assert s==s_ref, 'undefined test fail' + +def test_removesuffix_types(): + s='0123456' + s_ref='0123456' + others=[0,6,['6']] + found=False + for o in others: + try: + s.removesuffix(o) + except: + found=True + + assert found, f'Removesuffix accepts other type: {type(o)}: {o=}' + + +skip_if_unsupported(3,9,test_removeprefix) +skip_if_unsupported(3,9,test_removeprefix_types) +skip_if_unsupported(3,9,test_removesuffix) +skip_if_unsupported(3,9,test_removesuffix_types) + + diff --git a/tests/snippets/testutils.py b/tests/snippets/testutils.py index 8a9fdddb2fa..c779d2c8982 100644 --- a/tests/snippets/testutils.py +++ b/tests/snippets/testutils.py @@ -1,3 +1,6 @@ +import platform +import sys + def assert_raises(expected, *args, _msg=None, **kw): if args: f, f_args = args[0], args[1:] @@ -67,3 +70,26 @@ def assert_isinstance(obj, klass): def assert_in(a, b): _assert_print(lambda: a in b, [a, 'in', b]) + +def skip_if_unsupported(req_maj_vers, req_min_vers, test_fct): + def exec(): + test_fct() + + if platform.python_implementation() == 'RustPython': + exec() + elif sys.version_info.major>=req_maj_vers and sys.version_info.minor>=req_min_vers: + exec() + else: + print(f'Skipping test as a higher python version is required. Using {platform.python_implementation()} {platform.python_version()}') + +def fail_if_unsupported(req_maj_vers, req_min_vers, test_fct): + def exec(): + test_fct() + + if platform.python_implementation() == 'RustPython': + exec() + elif sys.version_info.major>=req_maj_vers and sys.version_info.minor>=req_min_vers: + exec() + else: + assert False, f'Test cannot performed on this python version. {platform.python_implementation()} {platform.python_version()}' + diff --git a/vm/src/obj/objdict.rs b/vm/src/obj/objdict.rs index a5330bdbcfd..e2cdb108db4 100644 --- a/vm/src/obj/objdict.rs +++ b/vm/src/obj/objdict.rs @@ -104,6 +104,17 @@ impl PyDictRef { Ok(()) } + fn merge_dict( + dict: &DictContentType, + dict_other: PyDictRef, + vm: &VirtualMachine, + ) -> PyResult<()> { + for (key, value) in dict_other { + dict.insert(vm, &key, value)?; + } + Ok(()) + } + #[pyclassmethod] fn fromkeys( class: PyClassRef, @@ -320,6 +331,38 @@ impl PyDictRef { PyDictRef::merge(&self.entries, dict_obj, kwargs, vm) } + #[pymethod(name = "__ior__")] + fn ior(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let dicted: Result = other.clone().downcast(); + if let Ok(other) = dicted { + PyDictRef::merge_dict(&self.entries, other, vm)?; + return Ok(self.into_object()); + } + Err(vm.new_type_error("__ior__ not implemented for non-dict type".to_owned())) + } + + #[pymethod(name = "__ror__")] + fn ror(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let dicted: Result = other.clone().downcast(); + if let Ok(other) = dicted { + let other_cp = other.copy(); + PyDictRef::merge_dict(&other_cp.entries, self, vm)?; + return Ok(other_cp); + } + Err(vm.new_type_error("__ror__ not implemented for non-dict type".to_owned())) + } + + #[pymethod(name = "__or__")] + fn or(self, other: PyObjectRef, vm: &VirtualMachine) -> PyResult { + let dicted: Result = other.clone().downcast(); + if let Ok(other) = dicted { + let self_cp = self.copy(); + PyDictRef::merge_dict(&self_cp.entries, other, vm)?; + return Ok(self_cp); + } + Err(vm.new_type_error("__or__ not implemented for non-dict type".to_owned())) + } + #[pymethod] fn pop( self, diff --git a/vm/src/obj/objstr.rs b/vm/src/obj/objstr.rs index eb3a434db88..0b1747affdc 100644 --- a/vm/src/obj/objstr.rs +++ b/vm/src/obj/objstr.rs @@ -532,6 +532,22 @@ impl PyString { ) } + #[pymethod] + fn removeprefix(&self, pref: PyStringRef) -> PyResult { + if self.value.as_str().starts_with(&pref.value) { + return Ok(self.value[pref.len()..].to_string()); + } + Ok(self.value.to_string()) + } + + #[pymethod] + fn removesuffix(&self, suff: PyStringRef) -> PyResult { + if self.value.as_str().ends_with(&suff.value) { + return Ok(self.value[..self.value.len() - suff.len()].to_string()); + } + Ok(self.value.to_string()) + } + #[pymethod] fn isalnum(&self) -> bool { !self.value.is_empty() && self.value.chars().all(char::is_alphanumeric) diff --git a/vm/src/stdlib/math.rs b/vm/src/stdlib/math.rs index 6873e4352e9..1ddbdf28934 100644 --- a/vm/src/stdlib/math.rs +++ b/vm/src/stdlib/math.rs @@ -9,9 +9,9 @@ use statrs::function::gamma::{gamma, ln_gamma}; use num_bigint::BigInt; use num_traits::{One, Zero}; -use crate::function::OptionalArg; +use crate::function::{OptionalArg, PyFuncArgs}; use crate::obj::objfloat::{self, IntoPyFloat, PyFloatRef}; -use crate::obj::objint::{self, PyIntRef}; +use crate::obj::objint::{self, PyInt, PyIntRef}; use crate::obj::objtype; use crate::pyobject::{Either, PyObjectRef, PyResult, TypeProtocol}; use crate::vm::VirtualMachine; @@ -272,9 +272,55 @@ fn math_ldexp( Ok(value * (2_f64).powf(objint::try_float(i.as_bigint(), vm)?)) } -fn math_gcd(a: PyIntRef, b: PyIntRef) -> BigInt { +fn math_perf_arb_len_int_op( + args: PyFuncArgs, + vm: &VirtualMachine, + op: F, + default: BigInt, +) -> PyResult +where + F: Fn(&BigInt, &PyInt) -> BigInt, +{ + if !args.kwargs.is_empty() { + Err(vm.new_type_error("Takes no keyword arguments".to_owned())) + } else if args.args.is_empty() { + Ok(default) + } else if args.args.len() == 1 { + let a: PyObjectRef = args.args[0].clone(); + if let Some(aa) = a.payload_if_subclass::(vm) { + let res = op(aa.as_bigint(), aa); + Ok(res) + } else { + Err(vm.new_type_error("Only integer arguments are supported".to_owned())) + } + } else { + let a = args.args[0].clone(); + if let Some(aa) = a.payload_if_subclass::(vm) { + let mut res = aa.as_bigint().clone(); + for b in args.args[1..].iter() { + if let Some(bb) = b.payload_if_subclass::(vm) { + res = op(&res, bb); + } else { + return Err( + vm.new_type_error("Only integer arguments are supported".to_owned()) + ); + } + } + Ok(res) + } else { + Err(vm.new_type_error("Only integer arguments are supported".to_owned())) + } + } +} + +fn math_gcd(args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { + use num_integer::Integer; + math_perf_arb_len_int_op(args, vm, |x, y| x.gcd(y.as_bigint()), BigInt::zero()) +} + +fn math_lcm(args: PyFuncArgs, vm: &VirtualMachine) -> PyResult { use num_integer::Integer; - a.as_bigint().gcd(b.as_bigint()) + math_perf_arb_len_int_op(args, vm, |x, y| x.lcm(y.as_bigint()), BigInt::one()) } fn math_factorial(value: PyIntRef, vm: &VirtualMachine) -> PyResult { @@ -436,6 +482,7 @@ pub fn make_module(vm: &VirtualMachine) -> PyObjectRef { // Gcd function "gcd" => ctx.new_function(math_gcd), + "lcm" => ctx.new_function(math_lcm), // Factorial function "factorial" => ctx.new_function(math_factorial),