[Jython-checkins] jython: Fix str.__complex__() to accept all valid inputs.

jeff.allen jython-checkins at python.org
Wed Dec 31 02:41:06 CET 2014


https://hg.python.org/jython/rev/5409ad3e93f4
changeset:   7482:5409ad3e93f4
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Sat Dec 20 09:51:58 2014 +0000
summary:
  Fix str.__complex__() to accept all valid inputs.

Fixes a number of test failures in test_complex.

files:
  Lib/test/test_complex.py          |  220 -------------
  src/org/python/core/PyString.java |  302 ++++++++---------
  2 files changed, 149 insertions(+), 373 deletions(-)


diff --git a/Lib/test/test_complex.py b/Lib/test/test_complex.py
--- a/Lib/test/test_complex.py
+++ b/Lib/test/test_complex.py
@@ -220,7 +220,6 @@
     def test_conjugate(self):
         self.assertClose(complex(5.3, 9.8).conjugate(), 5.3-9.8j)
 
-    @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython")
     def test_constructor(self):
         class OS:
             def __init__(self, value): self.value = value
@@ -264,24 +263,16 @@
         self.assertAlmostEqual(complex(),  0)
         self.assertAlmostEqual(complex("-1"), -1)
         self.assertAlmostEqual(complex("+1"), +1)
-        #FIXME: these are not working in Jython.
         self.assertAlmostEqual(complex("(1+2j)"), 1+2j)
         self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j)
-        # ]
         self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j)
-        #FIXME: these are not working in Jython.
         self.assertAlmostEqual(complex(" ( +3.14-6J )"), 3.14-6j)
         self.assertAlmostEqual(complex(" ( +3.14-J )"), 3.14-1j)
         self.assertAlmostEqual(complex(" ( +3.14+j )"), 3.14+1j)
-        # ]
         self.assertAlmostEqual(complex("J"), 1j)
-        #FIXME: this is not working in Jython.
         self.assertAlmostEqual(complex("( j )"), 1j)
-        # ]
         self.assertAlmostEqual(complex("+J"), 1j)
-        #FIXME: this is not working in Jython.
         self.assertAlmostEqual(complex("( -j)"), -1j)
-        # ]
         self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j)
         self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j)
         self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j)
@@ -301,9 +292,7 @@
             return atan2(x, -1.)
 
         self.assertEqual(split_zeros(complex(1., 0.).imag), split_zeros(0.))
-        #FIXME: this is not working in Jython.
         self.assertEqual(split_zeros(complex(1., -0.).imag), split_zeros(-0.))
-        # ]
         self.assertEqual(split_zeros(complex(0., 1.).real), split_zeros(0.))
         self.assertEqual(split_zeros(complex(-0., 1.).real), split_zeros(-0.))
 
@@ -340,199 +329,19 @@
         self.assertRaises(ValueError, complex, "(1+2j)123")
         if test_support.have_unicode:
             self.assertRaises(ValueError, complex, unicode("x"))
-        #FIXME: these are raising wrong errors in Jython.
         self.assertRaises(ValueError, complex, "1j+2")
         self.assertRaises(ValueError, complex, "1e1ej")
         self.assertRaises(ValueError, complex, "1e++1ej")
         self.assertRaises(ValueError, complex, ")1+2j(")
-        # ]
 
         # the following three are accepted by Python 2.6
-        #FIXME: these are raising wrong errors in Jython.
         self.assertRaises(ValueError, complex, "1..1j")
         self.assertRaises(ValueError, complex, "1.11.1j")
         self.assertRaises(ValueError, complex, "1e1.1j")
-        # ]
 
-        #FIXME: not working in Jython.
         if test_support.have_unicode:
             # check that complex accepts long unicode strings
             self.assertEqual(type(complex(unicode("1"*500))), complex)
-        # ]
-
-        class EvilExc(Exception):
-            pass
-
-        class evilcomplex:
-            def __complex__(self):
-                raise EvilExc
-
-        self.assertRaises(EvilExc, complex, evilcomplex())
-
-        class float2:
-            def __init__(self, value):
-                self.value = value
-            def __float__(self):
-                return self.value
-
-        self.assertAlmostEqual(complex(float2(42.)), 42)
-        self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j)
-        self.assertRaises(TypeError, complex, float2(None))
-
-        class complex0(complex):
-            """Test usage of __complex__() when inheriting from 'complex'"""
-            def __complex__(self):
-                return 42j
-
-        class complex1(complex):
-            """Test usage of __complex__() with a __new__() method"""
-            def __new__(self, value=0j):
-                return complex.__new__(self, 2*value)
-            def __complex__(self):
-                return self
-
-        class complex2(complex):
-            """Make sure that __complex__() calls fail if anything other than a
-            complex is returned"""
-            def __complex__(self):
-                return None
-
-        self.assertAlmostEqual(complex(complex0(1j)), 42j)
-        self.assertAlmostEqual(complex(complex1(1j)), 2j)
-        self.assertRaises(TypeError, complex, complex2(1j))
-
-    def test_constructor_jy(self):
-        # These are the parts of test_constructor that work in Jython.
-        # Delete this test when test_constructor skip is removed.
-        class OS:
-            def __init__(self, value): self.value = value
-            def __complex__(self): return self.value
-        class NS(object):
-            def __init__(self, value): self.value = value
-            def __complex__(self): return self.value
-        self.assertEqual(complex(OS(1+10j)), 1+10j)
-        self.assertEqual(complex(NS(1+10j)), 1+10j)
-        self.assertRaises(TypeError, complex, OS(None))
-        self.assertRaises(TypeError, complex, NS(None))
-
-        self.assertAlmostEqual(complex("1+10j"), 1+10j)
-        self.assertAlmostEqual(complex(10), 10+0j)
-        self.assertAlmostEqual(complex(10.0), 10+0j)
-        self.assertAlmostEqual(complex(10L), 10+0j)
-        self.assertAlmostEqual(complex(10+0j), 10+0j)
-        self.assertAlmostEqual(complex(1,10), 1+10j)
-        self.assertAlmostEqual(complex(1,10L), 1+10j)
-        self.assertAlmostEqual(complex(1,10.0), 1+10j)
-        self.assertAlmostEqual(complex(1L,10), 1+10j)
-        self.assertAlmostEqual(complex(1L,10L), 1+10j)
-        self.assertAlmostEqual(complex(1L,10.0), 1+10j)
-        self.assertAlmostEqual(complex(1.0,10), 1+10j)
-        self.assertAlmostEqual(complex(1.0,10L), 1+10j)
-        self.assertAlmostEqual(complex(1.0,10.0), 1+10j)
-        self.assertAlmostEqual(complex(3.14+0j), 3.14+0j)
-        self.assertAlmostEqual(complex(3.14), 3.14+0j)
-        self.assertAlmostEqual(complex(314), 314.0+0j)
-        self.assertAlmostEqual(complex(314L), 314.0+0j)
-        self.assertAlmostEqual(complex(3.14+0j, 0j), 3.14+0j)
-        self.assertAlmostEqual(complex(3.14, 0.0), 3.14+0j)
-        self.assertAlmostEqual(complex(314, 0), 314.0+0j)
-        self.assertAlmostEqual(complex(314L, 0L), 314.0+0j)
-        self.assertAlmostEqual(complex(0j, 3.14j), -3.14+0j)
-        self.assertAlmostEqual(complex(0.0, 3.14j), -3.14+0j)
-        self.assertAlmostEqual(complex(0j, 3.14), 3.14j)
-        self.assertAlmostEqual(complex(0.0, 3.14), 3.14j)
-        self.assertAlmostEqual(complex("1"), 1+0j)
-        self.assertAlmostEqual(complex("1j"), 1j)
-        self.assertAlmostEqual(complex(),  0)
-        self.assertAlmostEqual(complex("-1"), -1)
-        self.assertAlmostEqual(complex("+1"), +1)
-        #FIXME: these are not working in Jython.
-        #self.assertAlmostEqual(complex("(1+2j)"), 1+2j)
-        #self.assertAlmostEqual(complex("(1.3+2.2j)"), 1.3+2.2j)
-        self.assertAlmostEqual(complex("3.14+1J"), 3.14+1j)
-        #FIXME: these are not working in Jython.
-        #self.assertAlmostEqual(complex(" ( +3.14-6J )"), 3.14-6j)
-        #self.assertAlmostEqual(complex(" ( +3.14-J )"), 3.14-1j)
-        #self.assertAlmostEqual(complex(" ( +3.14+j )"), 3.14+1j)
-        self.assertAlmostEqual(complex("J"), 1j)
-        #FIXME: this is not working in Jython.
-        #self.assertAlmostEqual(complex("( j )"), 1j)
-        self.assertAlmostEqual(complex("+J"), 1j)
-        #FIXME: this is not working in Jython.
-        #self.assertAlmostEqual(complex("( -j)"), -1j)
-        self.assertAlmostEqual(complex('1e-500'), 0.0 + 0.0j)
-        self.assertAlmostEqual(complex('-1e-500j'), 0.0 - 0.0j)
-        self.assertAlmostEqual(complex('-1e-500+1e-500j'), -0.0 + 0.0j)
-
-        class complex2(complex): pass
-        self.assertAlmostEqual(complex(complex2(1+1j)), 1+1j)
-        self.assertAlmostEqual(complex(real=17, imag=23), 17+23j)
-        self.assertAlmostEqual(complex(real=17+23j), 17+23j)
-        self.assertAlmostEqual(complex(real=17+23j, imag=23), 17+46j)
-        self.assertAlmostEqual(complex(real=1+2j, imag=3+4j), -3+5j)
-
-        # check that the sign of a zero in the real or imaginary part
-        # is preserved when constructing from two floats.  (These checks
-        # are harmless on systems without support for signed zeros.)
-        def split_zeros(x):
-            """Function that produces different results for 0. and -0."""
-            return atan2(x, -1.)
-
-        self.assertEqual(split_zeros(complex(1., 0.).imag), split_zeros(0.))
-        #FIXME: this is not working in Jython.
-        #self.assertEqual(split_zeros(complex(1., -0.).imag), split_zeros(-0.))
-        self.assertEqual(split_zeros(complex(0., 1.).real), split_zeros(0.))
-        self.assertEqual(split_zeros(complex(-0., 1.).real), split_zeros(-0.))
-
-        c = 3.14 + 1j
-        self.assertTrue(complex(c) is c)
-        del c
-
-        self.assertRaises(TypeError, complex, "1", "1")
-        self.assertRaises(TypeError, complex, 1, "1")
-
-        if test_support.have_unicode:
-            self.assertEqual(complex(unicode("  3.14+J  ")), 3.14+1j)
-
-        # SF bug 543840:  complex(string) accepts strings with \0
-        # Fixed in 2.3.
-        self.assertRaises(ValueError, complex, '1+1j\0j')
-
-        self.assertRaises(TypeError, int, 5+3j)
-        self.assertRaises(TypeError, long, 5+3j)
-        self.assertRaises(TypeError, float, 5+3j)
-        self.assertRaises(ValueError, complex, "")
-        self.assertRaises(TypeError, complex, None)
-        self.assertRaises(ValueError, complex, "\0")
-        self.assertRaises(ValueError, complex, "3\09")
-        self.assertRaises(TypeError, complex, "1", "2")
-        self.assertRaises(TypeError, complex, "1", 42)
-        self.assertRaises(TypeError, complex, 1, "2")
-        self.assertRaises(ValueError, complex, "1+")
-        self.assertRaises(ValueError, complex, "1+1j+1j")
-        self.assertRaises(ValueError, complex, "--")
-        self.assertRaises(ValueError, complex, "(1+2j")
-        self.assertRaises(ValueError, complex, "1+2j)")
-        self.assertRaises(ValueError, complex, "1+(2j)")
-        self.assertRaises(ValueError, complex, "(1+2j)123")
-        if test_support.have_unicode:
-            self.assertRaises(ValueError, complex, unicode("x"))
-        #FIXME: these are raising wrong errors in Jython.
-        #self.assertRaises(ValueError, complex, "1j+2")
-        #self.assertRaises(ValueError, complex, "1e1ej")
-        #self.assertRaises(ValueError, complex, "1e++1ej")
-        #self.assertRaises(ValueError, complex, ")1+2j(")
-
-        # the following three are accepted by Python 2.6
-        #FIXME: these are raising wrong errors in Jython.
-        #self.assertRaises(ValueError, complex, "1..1j")
-        #self.assertRaises(ValueError, complex, "1.11.1j")
-        #self.assertRaises(ValueError, complex, "1e1.1j")
-
-        #FIXME: not working in Jython.
-        #if test_support.have_unicode:
-        #    # check that complex accepts long unicode strings
-        #    self.assertEqual(type(complex(unicode("1"*500))), complex)
 
         class EvilExc(Exception):
             pass
@@ -641,7 +450,6 @@
         for num in nums:
             self.assertAlmostEqual((num.real**2 + num.imag**2)  ** 0.5, abs(num))
 
-    @unittest.skipIf(test_support.is_jython, "FIXME: str.__complex__ not working in Jython")
     def test_repr(self):
         self.assertEqual(repr(1+6j), '(1+6j)')
         self.assertEqual(repr(1-6j), '(1-6j)')
@@ -665,32 +473,6 @@
         self.assertEqual(repr(complex(0, -INF)), "-infj")
         self.assertEqual(repr(complex(0, NAN)), "nanj")
 
-    def test_repr_jy(self):
-        # These are just the cases that Jython can do from test_repr
-        # Delete this test when test_repr passes
-        self.assertEqual(repr(1+6j), '(1+6j)')
-        self.assertEqual(repr(1-6j), '(1-6j)')
-
-        self.assertNotEqual(repr(-(1+0j)), '(-1+-0j)')
-
-        # Fails to round-trip:
-#         self.assertEqual(1-6j,complex(repr(1-6j)))
-#         self.assertEqual(1+6j,complex(repr(1+6j)))
-#         self.assertEqual(-6j,complex(repr(-6j)))
-#         self.assertEqual(6j,complex(repr(6j)))
-
-        self.assertEqual(repr(complex(1., INF)), "(1+infj)")
-        self.assertEqual(repr(complex(1., -INF)), "(1-infj)")
-        self.assertEqual(repr(complex(INF, 1)), "(inf+1j)")
-        self.assertEqual(repr(complex(-INF, INF)), "(-inf+infj)")
-        self.assertEqual(repr(complex(NAN, 1)), "(nan+1j)")
-        self.assertEqual(repr(complex(1, NAN)), "(1+nanj)")
-        self.assertEqual(repr(complex(NAN, NAN)), "(nan+nanj)")
-
-        self.assertEqual(repr(complex(0, INF)), "infj")
-        self.assertEqual(repr(complex(0, -INF)), "-infj")
-        self.assertEqual(repr(complex(0, NAN)), "nanj")
-
     def test_neg(self):
         self.assertEqual(-(1+6j), -1-6j)
 
@@ -729,7 +511,6 @@
 
     @unittest.skipUnless(float.__getformat__("double").startswith("IEEE"),
                          "test requires IEEE 754 doubles")
-    @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython")
     def test_overflow(self):
         self.assertEqual(complex("1e500"), complex(INF, 0.0))
         self.assertEqual(complex("-1e500j"), complex(0.0, -INF))
@@ -737,7 +518,6 @@
 
     @unittest.skipUnless(float.__getformat__("double").startswith("IEEE"),
                          "test requires IEEE 754 doubles")
-    @unittest.skipIf(test_support.is_jython, "FIXME: not working in Jython")
     def test_repr_roundtrip(self):
         vals = [0.0, 1e-500, 1e-315, 1e-200, 0.0123, 3.1415, 1e50, INF, NAN]
         vals += [-v for v in vals]
diff --git a/src/org/python/core/PyString.java b/src/org/python/core/PyString.java
--- a/src/org/python/core/PyString.java
+++ b/src/org/python/core/PyString.java
@@ -1021,141 +1021,9 @@
         throw Py.TypeError("bad operand type for unary ~");
     }
 
-    @SuppressWarnings("fallthrough")
     @Override
     public PyComplex __complex__() {
-        boolean got_re = false;
-        boolean got_im = false;
-        boolean done = false;
-        boolean sw_error = false;
-
-        int s = 0;
-        int n = getString().length();
-        while (s < n && Character.isSpaceChar(getString().charAt(s))) {
-            s++;
-        }
-
-        if (s == n) {
-            throw Py.ValueError("empty string for complex()");
-        }
-
-        double z = -1.0;
-        double x = 0.0;
-        double y = 0.0;
-
-        int sign = 1;
-        do {
-            char c = getString().charAt(s);
-            switch (c) {
-                case '-':
-                    sign = -1;
-                    /* Fallthrough */
-                case '+':
-                    if (done || s + 1 == n) {
-                        sw_error = true;
-                        break;
-                    }
-                    // a character is guaranteed, but it better be a digit
-                    // or J or j
-                    c = getString().charAt(++s);  // eat the sign character
-                    // and check the next
-                    if (!Character.isDigit(c) && c != 'J' && c != 'j') {
-                        sw_error = true;
-                    }
-                    break;
-
-                case 'J':
-                case 'j':
-                    if (got_im || done) {
-                        sw_error = true;
-                        break;
-                    }
-                    if (z < 0.0) {
-                        y = sign;
-                    } else {
-                        y = sign * z;
-                    }
-                    got_im = true;
-                    done = got_re;
-                    sign = 1;
-                    s++; // eat the J or j
-                    break;
-
-                case ' ':
-                    while (s < n && Character.isSpaceChar(getString().charAt(s))) {
-                        s++;
-                    }
-                    if (s != n) {
-                        sw_error = true;
-                    }
-                    break;
-
-                default:
-                    boolean digit_or_dot = (c == '.' || Character.isDigit(c));
-                    if (!digit_or_dot) {
-                        sw_error = true;
-                        break;
-                    }
-                    int end = endDouble(getString(), s);
-                    z = Double.valueOf(getString().substring(s, end)).doubleValue();
-                    if (z == Double.POSITIVE_INFINITY) {
-                        throw Py.ValueError(String.format("float() out of range: %.150s",
-                                getString()));
-                    }
-
-                    s = end;
-                    if (s < n) {
-                        c = getString().charAt(s);
-                        if (c == 'J' || c == 'j') {
-                            break;
-                        }
-                    }
-                    if (got_re) {
-                        sw_error = true;
-                        break;
-                    }
-
-                    /* accept a real part */
-                    x = sign * z;
-                    got_re = true;
-                    done = got_im;
-                    z = -1.0;
-                    sign = 1;
-                    break;
-
-            } /* end of switch */
-
-        } while (s < n && !sw_error);
-
-        if (sw_error) {
-            throw Py.ValueError("malformed string for complex() " + getString().substring(s));
-        }
-
-        return new PyComplex(x, y);
-    }
-
-    private int endDouble(String string, int s) {
-        int n = string.length();
-        while (s < n) {
-            char c = string.charAt(s++);
-            if (Character.isDigit(c)) {
-                continue;
-            }
-            if (c == '.') {
-                continue;
-            }
-            if (c == 'e' || c == 'E') {
-                if (s < n) {
-                    c = string.charAt(s);
-                    if (c == '+' || c == '-') {
-                        s++;
-                    }
-                    continue;
-                }
-            }
-            return s - 1;
-        }
-        return s;
+        return atocx();
     }
 
     // Add in methods from string module
@@ -2718,45 +2586,80 @@
      * @return the value
      */
     public double atof() {
-        String bogus = null;
         double x = 0.0;
-        Matcher m = atofPattern.matcher(getString());
-
-        if (m.matches()) {
-            // Might be a valid float
+        Matcher m = getFloatPattern().matcher(getString());
+        boolean valid = m.matches();
+
+        if (valid) {
+            // Might be a valid float: trimmed of white space in group 1.
+            String number = m.group(1);
             try {
-                if (m.group(3) == null) {
-                    // No numeric part was found: it's something like "-Inf" or "hOrsE"
+                char lastChar = number.charAt(number.length() - 1);
+                if (Character.isLetter(lastChar)) {
+                    // It's something like "nan", "-Inf" or "+nifty"
                     x = atofSpecials(m.group(1));
                 } else {
                     // A numeric part was present, try to convert the whole
                     x = Double.parseDouble(m.group(1));
                 }
             } catch (NumberFormatException e) {
-                bogus = m.group(1);
+                valid = false;
             }
-        } else {
-            // This doesn't match the pattern for a float value
-            bogus = getString().trim();
         }
 
-        // At this point, bogus will have been set to the trimmed string if there was a problem.
-        if (bogus == null) {
+        // At this point, valid will have been cleared if there was a problem.
+        if (valid) {
             return x;
         } else {
-            String fmt = "could not convert string to float: %s";
-            throw Py.ValueError(String.format(fmt, bogus));
+            String fmt = "invalid literal for float: %s";
+            throw Py.ValueError(String.format(fmt, getString().trim()));
         }
 
     }
 
     /**
-     * Regular expression that includes all valid a Python float() arguments, in which group 1
-     * captures the whole, stripped of white space, and group 3 will be present only if the form is
-     * numeric. Invalid non numerics are accepted ("+hOrsE" as "-inf").
+     * Regular expression for an unsigned Python float, accepting also any sequence of the letters
+     * that belong to "NaN" or "Infinity" in whatever case. This is used within the regular
+     * expression patterns that define a priori acceptable strings in the float and complex
+     * constructors. The expression contributes no capture groups.
      */
-    private static Pattern atofPattern = Pattern
-            .compile("\\s*([+-]?(((\\d+(\\.\\d*)?|\\.\\d+)([eE][+-]?\\d+)?)|\\p{Alpha}+))\\s*");
+    private static final String UF_RE =
+            "(?:(?:(?:\\d+\\.?|\\.\\d)\\d*(?:[eE][+-]?\\d+)?)|[infatyINFATY]+)";
+
+    /**
+     * Return the (lazily) compiled regular expression that matches all valid a Python float()
+     * arguments, in which Group 1 captures the number, stripped of white space. Various invalid
+     * non-numerics are provisionally accepted (e.g. "+inanity" or "-faint").
+     */
+    private static synchronized Pattern getFloatPattern() {
+        if (floatPattern == null) {
+            floatPattern = Pattern.compile("\\s*([+-]?" + UF_RE + ")\\s*");
+        }
+        return floatPattern;
+    }
+
+    /** Access only through {@link #getFloatPattern()}. */
+    private static Pattern floatPattern = null;
+
+    /**
+     * Return the (lazily) compiled regular expression for a Python complex number. This is used
+     * within the regular expression patterns that define a priori acceptable strings in the complex
+     * constructors. The expression contributes five named capture groups a, b, x, y and j. x and y
+     * are the two floats encountered, and if j is present, one of them is the imaginary part.
+     * a and b are the optional parentheses. They must either both be present or both omitted.
+     */
+    private static synchronized Pattern getComplexPattern() {
+        if (complexPattern == null) {
+            complexPattern = Pattern.compile("\\s*(?<a>\\(\\s*)?" // Parenthesis <a>
+                    + "(?<x>[+-]?" + UF_RE + "?)" // <x>
+                    + "(?<y>[+-]" + UF_RE + "?)?(?<j>[jJ])?" // + <y> <j>
+                    + "\\s*(?<b>\\)\\s*)?"); // Parenthesis <b>
+        }
+        return complexPattern;
+    }
+
+    /** Access only through {@link #getComplexPattern()} */
+    private static Pattern complexPattern = null;
 
     /**
      * Conversion for non-numeric floats, accepting signed or unsigned "inf" and "nan", in any case.
@@ -2784,6 +2687,99 @@
         }
     }
 
+    /**
+     * Convert this PyString to a complex value according to Python rules.
+     *
+     * @return the value
+     */
+    private PyComplex atocx() {
+        double x = 0.0, y = 0.0;
+        Matcher m = getComplexPattern().matcher(getString());
+        boolean valid = m.matches();
+
+        if (valid) {
+            // Passes a priori, but we have some checks to make. Brackets: both or neither.
+            if ((m.group("a") != null) != (m.group("b") != null)) {
+                valid = false;
+
+            } else {
+                try {
+                    // Pick up the two numbers [+-]? <x> [+-] <y> j?
+                    String xs = m.group("x"), ys = m.group("y");
+
+                    if (m.group("j") != null) {
+                        // There is a 'j', so there is an imaginary part.
+                        if (ys != null) {
+                            // There were two numbers, so the second is the imaginary part.
+                            y = toComplexPart(ys);
+                            // And the first is the real part
+                            x = toComplexPart(xs);
+                        } else if (xs != null) {
+                            // There was only one number (and a 'j')so it is the imaginary part.
+                            y = toComplexPart(xs);
+                            // x = 0.0;
+                        } else {
+                            // There were no numbers, just the 'j'. (Impossible return?)
+                            y = 1.0;
+                            // x = 0.0;
+                        }
+
+                    } else {
+                        // There is no 'j' so can only be one number, the real part.
+                        x = Double.parseDouble(xs);
+                        if (ys != null) {
+                            // Something like "123 +" or "123 + 456" but no 'j'.
+                            throw new NumberFormatException();
+                        }
+                    }
+
+                } catch (NumberFormatException e) {
+                    valid = false;
+                }
+            }
+        }
+
+        // At this point, valid will have been cleared if there was a problem.
+        if (valid) {
+            return new PyComplex(x, y);
+        } else {
+            String fmt = "complex() arg is a malformed string: %s";
+            throw Py.ValueError(String.format(fmt, getString().trim()));
+        }
+
+    }
+
+    /**
+     * Helper for interpreting each part (real and imaginary) of a complex number expressed as a
+     * string in {@link #atocx(String)}. It deals with numbers, inf, nan and their variants, and
+     * with the "implied one" in +j or 10-j.
+     *
+     * @param s to interpret
+     * @return value of s
+     * @throws NumberFormatException if the number is invalid
+     */
+    private static double toComplexPart(String s) throws NumberFormatException {
+        if (s.length() == 0) {
+            // Empty string (occurs only as 'j')
+            return 1.0;
+        } else {
+            char lastChar = s.charAt(s.length() - 1);
+            if (Character.isLetter(lastChar)) {
+                // Possibly a sign, then letters that ought to be "nan" or "inf[inity]"
+                return atofSpecials(s);
+            } else if (lastChar == '+') {
+                // Occurs only as "+j"
+                return 1.0;
+            } else if (lastChar == '-') {
+                // Occurs only as "-j"
+                return -1.0;
+            } else {
+                // Possibly a sign then an unsigned float
+                return Double.parseDouble(s);
+            }
+        }
+    }
+
     private BigInteger asciiToBigInteger(int base, boolean isLong) {
         String str = getString();
 

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list