Author: Carl Friedrich Bolz-Tereick <cfb...@gmx.de> Branch: py3.6 Changeset: r94689:44c25f062a8c Date: 2018-05-26 14:58 +0200 http://bitbucket.org/pypy/pypy/changeset/44c25f062a8c/
Log: support for underscores in float and complex literals. tests and approach taken from CPython diff --git a/pypy/objspace/std/complexobject.py b/pypy/objspace/std/complexobject.py --- a/pypy/objspace/std/complexobject.py +++ b/pypy/objspace/std/complexobject.py @@ -13,7 +13,7 @@ from pypy.interpreter.gateway import WrappedDefault, interp2app, unwrap_spec from pypy.interpreter.typedef import GetSetProperty, TypeDef from pypy.objspace.std import newformat -from pypy.objspace.std.floatobject import _hash_float +from pypy.objspace.std.floatobject import _hash_float, _remove_underscores from pypy.objspace.std.unicodeobject import unicode_to_decimal_w HASH_IMAG = 1000003 @@ -285,6 +285,11 @@ " arg if first is a string") unistr = unicode_to_decimal_w(space, w_real) try: + unistr = _remove_underscores(unistr) + except ValueError: + raise oefmt(space.w_ValueError, + "complex() arg is a malformed string") + try: realstr, imagstr = _split_complex(unistr) except ValueError: raise oefmt(space.w_ValueError, diff --git a/pypy/objspace/std/floatobject.py b/pypy/objspace/std/floatobject.py --- a/pypy/objspace/std/floatobject.py +++ b/pypy/objspace/std/floatobject.py @@ -205,10 +205,16 @@ def descr__new__(space, w_floattype, w_x): def _string_to_float(space, w_source, string): try: - return rfloat.string_to_float(string) - except ParseStringError as e: - raise oefmt(space.w_ValueError, - "could not convert string to float: %R", w_source) + string = _remove_underscores(string) + except ValueError: + pass + else: + try: + return rfloat.string_to_float(string) + except ParseStringError as e: + pass + raise oefmt(space.w_ValueError, + "could not convert string to float: %R", w_source) w_value = w_x # 'x' is the keyword argument name in CPython if space.lookup(w_value, "__float__") is not None: @@ -728,6 +734,26 @@ hex = interp2app(W_FloatObject.descr_hex), ) +def _remove_underscores(string): + i = 0 + prev = '?' + res = [] + for i in range(len(string)): + c = string[i] + if c == '_': + # undercores can only come after digits + if not ord('0') <= ord(prev) <= ord('9'): + raise ValueError + else: + res.append(c) + # undercores can only come before digits + if prev == '_' and not ord('0') <= ord(c) <= ord('9'): + raise ValueError + prev = c + if prev == "_": # not allowed at end + raise ValueError + return "".join(res) + def _hash_float(space, v): if not isfinite(v): diff --git a/pypy/objspace/std/test/test_complexobject.py b/pypy/objspace/std/test/test_complexobject.py --- a/pypy/objspace/std/test/test_complexobject.py +++ b/pypy/objspace/std/test/test_complexobject.py @@ -370,6 +370,46 @@ assert self.almost_equal(complex(real=float2(17.), imag=float2(23.)), 17+23j) raises(TypeError, complex, float2(None)) + def test_complex_string_underscores(self): + valid = [ + '1_00_00j', + '1_00_00.5j', + '1_00_00e5_1j', + '.1_4j', + '(1_2.5+3_3j)', + '(.5_6j)', + ] + for s in valid: + assert complex(s) == complex(s.replace("_", "")) + assert eval(s) == eval(s.replace("_", "")) + + invalid = [ + # Trailing underscores: + '1.4j_', + # Multiple consecutive underscores: + '0.1__4j', + '1e1__0j', + # Underscore right before a dot: + '1_.4j', + # Underscore right after a dot: + '1._4j', + '._5j', + # Underscore right after a sign: + '1.0e+_1j', + # Underscore right before j: + '1.4e5_j', + # Underscore right before e: + '1.4_e1j', + # Underscore right after e: + '1.4e_1j', + # Complex cases with parens: + '(1+1.5_j_)', + '(1+1.5_j)', + ] + for s in invalid: + raises(ValueError, complex, s) + raises(SyntaxError, eval, s) + @py.test.mark.skipif("not config.option.runappdirect and sys.maxunicode == 0xffff") def test_constructor_unicode(self): b1 = '\N{MATHEMATICAL BOLD DIGIT ONE}' # 𝟏 diff --git a/pypy/objspace/std/test/test_floatobject.py b/pypy/objspace/std/test/test_floatobject.py --- a/pypy/objspace/std/test/test_floatobject.py +++ b/pypy/objspace/std/test/test_floatobject.py @@ -1,9 +1,10 @@ # -*- encoding: utf-8 -*- +import pytest import sys import py -from pypy.objspace.std.floatobject import W_FloatObject +from pypy.objspace.std.floatobject import W_FloatObject, _remove_underscores class TestW_FloatObject: @@ -59,6 +60,74 @@ finally: W_LongObject.fromfloat = saved + def test_remove_undercores(self): + valid = [ + '0_0_0', + '4_2', + '1_0000_0000', + '0b1001_0100', + '0o5_7_7', + '1_00_00.5', + '1_00_00.5e5', + '1_00_00e5_1', + '1e1_0', + '.1_4', + '.1_4e1', + '1_00_00j', + '1_00_00.5j', + '1_00_00e5_1j', + '.1_4j', + '(1_2.5+3_3j)', + '(.5_6j)', + ] + for s in valid: + assert _remove_underscores(s) == s.replace("_", "") + + invalid = [ + # Trailing underscores: + '0_', + '42_', + '1.4j_', + # Multiple consecutive underscores: + '4_______2', + '0.1__4', + '0.1__4j', + '0b1001__0100', + '0xffff__ffff', + '0x___', + '0o5__77', + '1e1__0', + '1e1__0j', + # Underscore right before a dot: + '1_.4', + '1_.4j', + # Underscore right after a dot: + '1._4', + '1._4j', + '._5', + '._5j', + # Underscore right after a sign: + '1.0e+_1', + '1.0e+_1j', + # Underscore right before j: + '1.4_j', + '1.4e5_j', + # Underscore right before e: + '1_e1', + '1.4_e1', + '1.4_e1j', + # Underscore right after e: + '1e_1', + '1.4e_1', + '1.4e_1j', + # Complex cases with parens: + '(1+1.5_j_)', + '(1+1.5_j)', + ] + for s in invalid: + pytest.raises(ValueError, _remove_underscores, s) + + class AppTestAppFloatTest: spaceconfig = dict(usemodules=['binascii', 'time']) @@ -151,6 +220,53 @@ raises(UnicodeEncodeError, float, u"\ud800") + def test_float_string_underscores(self): + valid = [ + '0_0_0', + '4_2', + '1_0000_0000', + '1_00_00.5', + '1_00_00.5e5', + '1_00_00e5_1', + '1e1_0', + '.1_4', + '.1_4e1', + ] + for s in valid: + assert float(s) == float(s.replace("_", "")) + assert eval(s) == eval(s.replace("_", "")) + + invalid = [ + # Trailing underscores: + '0_', + '42_', + # Multiple consecutive underscores: + '4_______2', + '0.1__4', + '0.1__4j', + '0b1001__0100', + '0xffff__ffff', + '0x___', + '0o5__77', + '1e1__0', + # Underscore right before a dot: + '1_.4', + # Underscore right after a dot: + '1._4', + '._5', + # Underscore right after a sign: + '1.0e+_1', + # Underscore right before e: + '1_e1', + '1.4_e1', + # Underscore right after e: + '1e_1', + '1.4e_1', + ] + for s in invalid: + raises(ValueError, float, s) + raises(SyntaxError, eval, s) + def test_float_unicode(self): # u00A0 and u2000 are some kind of spaces assert 42.75 == float(chr(0x00A0)+str("42.75")+chr(0x2000)) _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit