[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