[Jython-checkins] jython: More PEP 3101 work and related changes (#1718).

nicholas.riley jython-checkins at python.org
Wed Mar 21 21:34:59 CET 2012


http://hg.python.org/jython/rev/78c49afd03c9
changeset:   6462:78c49afd03c9
user:        Nicholas Riley <njriley at illinois.edu>
date:        Wed Mar 21 16:34:52 2012 -0400
summary:
  More PEP 3101 work and related changes (#1718).

- Allow auto-numbered replacement fields in format strings (http://bugs.python.org/issue5237).

- Use 'nan', 'inf' and '-inf', not Unicode equivalents produced by Java number formatters.

- Limit recursion level for recursive format specs.

- Check some more error conditions.

All of test_str should pass now except the tests which require the as-yet-unimplemented float/datetime __format__.

files:
  src/org/python/core/PyFloat.java                  |   7 +-
  src/org/python/core/PyString.java                 |  81 ++++++---
  src/org/python/core/stringlib/MarkupIterator.java |  38 ++++
  3 files changed, 93 insertions(+), 33 deletions(-)


diff --git a/src/org/python/core/PyFloat.java b/src/org/python/core/PyFloat.java
--- a/src/org/python/core/PyFloat.java
+++ b/src/org/python/core/PyFloat.java
@@ -122,9 +122,12 @@
     }
 
     private String formatDouble(int precision) {
-        if (Double.isNaN(getValue())) {
+        if (Double.isNaN(value))
             return "nan";
-        }
+        else if (value == Double.NEGATIVE_INFINITY)
+            return "-inf";
+        else if (value == Double.POSITIVE_INFINITY)
+            return "inf";
 
         String result = String.format("%%.%dg", precision);
         result = Py.newString(result).__mod__(this).toString();
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
@@ -13,6 +13,8 @@
 import org.python.expose.MethodType;
 
 import java.math.BigInteger;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
 
 /**
  * A builtin python string.
@@ -2549,15 +2551,15 @@
     @ExposedMethod(doc = BuiltinDocs.str_format_doc)
     final PyObject str_format(PyObject[] args, String[] keywords) {
         try {
-            return new PyString(buildFormattedString(getString(), args, keywords));
+            return new PyString(buildFormattedString(getString(), args, keywords, null));
         } catch (IllegalArgumentException e) {
             throw Py.ValueError(e.getMessage());
         }
     }
 
-    private String buildFormattedString(String value, PyObject[] args, String[] keywords) {
+    private String buildFormattedString(String value, PyObject[] args, String[] keywords, MarkupIterator enclosingIterator) {
         StringBuilder result = new StringBuilder();
-        MarkupIterator it = new MarkupIterator(value);
+        MarkupIterator it = new MarkupIterator(value, enclosingIterator);
         while (true) {
             MarkupIterator.Chunk chunk = it.nextChunk();
             if (chunk == null) {
@@ -2565,32 +2567,29 @@
             }
             result.append(chunk.literalText);
             if (chunk.fieldName.length() > 0) {
-                outputMarkup(result, chunk, args, keywords);
+                PyObject fieldObj = getFieldObject(chunk.fieldName, args, keywords);
+                if (fieldObj == null) {
+                    continue;
+                }
+                if ("r".equals(chunk.conversion)) {
+                    fieldObj = fieldObj.__repr__();
+                } else if ("s".equals(chunk.conversion)) {
+                    fieldObj = fieldObj.__str__();
+                } else if (chunk.conversion != null) {
+                    throw Py.ValueError("Unknown conversion specifier " + chunk.conversion);
+                }
+                String formatSpec = chunk.formatSpec;
+                if (chunk.formatSpecNeedsExpanding) {
+                    if (enclosingIterator != null) // PEP 3101 says only 2 levels
+                        throw Py.ValueError("Max string recursion exceeded");
+                    formatSpec = buildFormattedString(formatSpec, args, keywords, it);
+                }
+                renderField(fieldObj, formatSpec, result);
             }
         }
         return result.toString();
     }
 
-    private void outputMarkup(StringBuilder result, MarkupIterator.Chunk chunk,
-                              PyObject[] args, String[] keywords) {
-        PyObject fieldObj = getFieldObject(chunk.fieldName, args, keywords);
-        if (fieldObj == null) {
-            return;
-        }
-        if ("r".equals(chunk.conversion)) {
-            fieldObj = fieldObj.__repr__();
-        } else if ("s".equals(chunk.conversion)) {
-            fieldObj = fieldObj.__str__();
-        } else if (chunk.conversion != null) {
-            throw Py.ValueError("Unknown conversion specifier " + chunk.conversion);
-        }
-        String formatSpec = chunk.formatSpec;
-        if (chunk.formatSpecNeedsExpanding) {
-            formatSpec = buildFormattedString(formatSpec, args, keywords);
-        }
-        renderField(fieldObj, formatSpec, result);
-    }
-
     private PyObject getFieldObject(String fieldName, PyObject[] args, String[] keywords) {
         FieldNameIterator iterator = new FieldNameIterator(fieldName);
         Object head = iterator.head();
@@ -2672,6 +2671,12 @@
      * @return the result of the formatting
      */
     public static String formatString(String text, InternalFormatSpec spec) {
+        if (spec.sign != '\0')
+            throw new IllegalArgumentException("Sign not allowed in string format specifier");
+        if (spec.alternate)
+            throw new IllegalArgumentException("Alternate form (#) not allowed in string format specifier");
+        if (spec.align == '=')
+            throw new IllegalArgumentException("'=' alignment not allowed in string format specifier");
         if (spec.precision >= 0 && text.length() > spec.precision) {
             text = text.substring(0, spec.precision);
         }
@@ -2957,10 +2962,23 @@
         }
     }
 
+    static class DecimalFormatTemplate {
+        static DecimalFormat template;
+        static {
+            template = new DecimalFormat("#,##0.#####");
+            DecimalFormatSymbols symbols = template.getDecimalFormatSymbols();
+            symbols.setNaN("nan");
+            symbols.setInfinity("inf");
+            template.setDecimalFormatSymbols(symbols);
+            template.setGroupingUsed(false);
+        }
+    }
+    private static final DecimalFormat getDecimalFormat() {
+        return (DecimalFormat)DecimalFormatTemplate.template.clone();
+    }
+
     private String formatFloatDecimal(double v, boolean truncate) {
         checkPrecision("decimal");
-        java.text.NumberFormat numberFormat = java.text.NumberFormat.getInstance(
-                                           java.util.Locale.US);
         int prec = precision;
         if (prec == -1)
             prec = 6;
@@ -2968,11 +2986,12 @@
             v = -v;
             negative = true;
         }
-        numberFormat.setMaximumFractionDigits(prec);
-        numberFormat.setMinimumFractionDigits(truncate ? 0 : prec);
-        numberFormat.setGroupingUsed(false);
-
-        String ret = numberFormat.format(v);
+
+        DecimalFormat decimalFormat = getDecimalFormat();
+        decimalFormat.setMaximumFractionDigits(prec);
+        decimalFormat.setMinimumFractionDigits(truncate ? 0 : prec);
+
+        String ret = decimalFormat.format(v);
         return ret;
     }
 
diff --git a/src/org/python/core/stringlib/MarkupIterator.java b/src/org/python/core/stringlib/MarkupIterator.java
--- a/src/org/python/core/stringlib/MarkupIterator.java
+++ b/src/org/python/core/stringlib/MarkupIterator.java
@@ -18,9 +18,18 @@
 
     private final String markup;
     private int index;
+    private final FieldNumbering numbering;
 
     public MarkupIterator(String markup) {
+        this(markup, null);
+    }
+
+    public MarkupIterator(String markup, MarkupIterator enclosingIterator) {
         this.markup = markup;
+        if (enclosingIterator != null)
+            numbering = enclosingIterator.numbering;
+        else
+            numbering = new FieldNumbering();
     }
 
     @Override
@@ -140,6 +149,17 @@
         } else {
             result.fieldName = fieldMarkup;
         }
+        if (result.fieldName.isEmpty()) {
+            result.fieldName = numbering.nextAutomaticFieldNumber();
+            return;
+        }
+        char c = result.fieldName.charAt(0);
+        if (c == '.' || c == '[') {
+            result.fieldName = numbering.nextAutomaticFieldNumber() + result.fieldName;
+            return;
+        }
+        if (Character.isDigit(c))
+            numbering.useManualFieldNumbering();
     }
 
     private int indexOfFirst(String s, int start, char c1, char c2) {
@@ -154,6 +174,24 @@
         return Math.min(i1, i2);
     }
 
+    static final class FieldNumbering {
+        private boolean manualFieldNumberSpecified;
+        private int automaticFieldNumber = 0;
+
+        String nextAutomaticFieldNumber() {
+            if (manualFieldNumberSpecified)
+                throw new IllegalArgumentException("cannot switch from manual field specification to automatic field numbering");
+            return Integer.toString(automaticFieldNumber++);
+        }
+        void useManualFieldNumbering() {
+            if (manualFieldNumberSpecified)
+                return;
+            if (automaticFieldNumber != 0)
+                throw new IllegalArgumentException("cannot switch from automatic field numbering to manual field specification");
+            manualFieldNumberSpecified = true;
+        }
+    }
+
     public static final class Chunk {
         public String literalText;
         public String fieldName;

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


More information about the Jython-checkins mailing list