[pypy-commit] pypy py3.6: support for underscores in float and complex literals. tests and approach taken

cfbolz pypy.commits at gmail.com
Sat May 26 09:28:50 EDT 2018


Author: Carl Friedrich Bolz-Tereick <cfbolz at 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))


More information about the pypy-commit mailing list