[Jython-checkins] jython: Float formatting - comment changes only (and insignificant code chage).

jeff.allen jython-checkins at python.org
Thu Apr 24 00:04:04 CEST 2014


http://hg.python.org/jython/rev/67fd5ccc094e
changeset:   7213:67fd5ccc094e
parent:      7197:68e2098b8f12
user:        Jeff Allen <ja.py at farowl.co.uk>
date:        Thu Apr 03 08:39:42 2014 +0100
summary:
  Float formatting - comment changes only (and insignificant code chage).
Added comments to clarify the contract and logic of core.stringlib.Formatter and
related classes ahead of improvements. Largely automatic formatting changes to
code (import list, indents, trailing space, universal curly brackets, etc.).

files:
  src/org/python/core/PyString.java                           |   69 +-
  src/org/python/core/stringlib/Formatter.java                |  301 +++++++--
  src/org/python/core/stringlib/InternalFormatSpec.java       |   58 +-
  src/org/python/core/stringlib/InternalFormatSpecParser.java |   29 +-
  4 files changed, 375 insertions(+), 82 deletions(-)


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
@@ -3766,6 +3766,16 @@
         }
     }
 
+    /**
+     * Implements PEP-3101 {}-formatting methods <code>str.format()</code> and
+     * <code>unicode.format()</code>.
+     *
+     * @param value the format string
+     * @param args to be interpolated into the string
+     * @param keywords for the trailing args
+     * @param enclosingIterator when used nested
+     * @return the formatted string based on the arguments
+     */
     protected String buildFormattedString(String value, PyObject[] args, String[] keywords,
             MarkupIterator enclosingIterator) {
         StringBuilder result = new StringBuilder();
@@ -3775,12 +3785,20 @@
             if (chunk == null) {
                 break;
             }
+            // A Chunk encapsulates a literal part ...
             result.append(chunk.literalText);
+            // ... and the parsed form of the replacement field that followed it (if any)
             if (chunk.fieldName.length() > 0) {
+                // The grammar of the replacement field is:
+                // "{" [field_name] ["!" conversion] [":" format_spec] "}"
+
+                // Get the object referred to by the field name (which may be omitted).
                 PyObject fieldObj = getFieldObject(chunk.fieldName, args, keywords);
                 if (fieldObj == null) {
                     continue;
                 }
+
+                // The conversion specifier is s = __str__ or r = __repr__.
                 if ("r".equals(chunk.conversion)) {
                     fieldObj = fieldObj.__repr__();
                 } else if ("s".equals(chunk.conversion)) {
@@ -3788,12 +3806,15 @@
                 } else if (chunk.conversion != null) {
                     throw Py.ValueError("Unknown conversion specifier " + chunk.conversion);
                 }
+
+                // The format_spec may be simple, or contained nested replacement fields.
                 String formatSpec = chunk.formatSpec;
                 if (chunk.formatSpecNeedsExpanding) {
                     if (enclosingIterator != null) {
                         // PEP 3101 says only 2 levels
                         throw Py.ValueError("Max string recursion exceeded");
                     }
+                    // Recursively interpolate further args into chunk.formatSpec
                     formatSpec = buildFormattedString(formatSpec, args, keywords, it);
                 }
                 renderField(fieldObj, formatSpec, result);
@@ -3802,6 +3823,15 @@
         return result.toString();
     }
 
+    /**
+     * Return the object referenced by a given field name, interpreted in the context of the given
+     * argument list, containing positional and keyword arguments.
+     *
+     * @param fieldName to interpret.
+     * @param args argument list (positional then keyword arguments).
+     * @param keywords naming the keyword arguments.
+     * @return the object designated or <code>null</code>.
+     */
     private PyObject getFieldObject(String fieldName, PyObject[] args, String[] keywords) {
         FieldNameIterator iterator = new FieldNameIterator(fieldName);
         Object head = iterator.head();
@@ -3814,6 +3844,7 @@
                 throw Py.IndexError("tuple index out of range");
             }
             obj = args[index];
+
         } else {
             for (int i = 0; i < keywords.length; i++) {
                 if (keywords[i].equals(head)) {
@@ -3825,6 +3856,7 @@
                 throw Py.KeyError((String)head);
             }
         }
+
         if (obj != null) {
             while (true) {
                 FieldNameIterator.Chunk chunk = iterator.nextChunk();
@@ -3844,9 +3876,18 @@
                 }
             }
         }
+
         return obj;
     }
 
+    /**
+     * Append to a formatting result, the presentation of one object, according to a given format
+     * specification and the object's <code>__format__</code> method.
+     *
+     * @param fieldObj to format.
+     * @param formatSpec specification to apply.
+     * @param result to which the result will be appended.
+     */
     private void renderField(PyObject fieldObj, String formatSpec, StringBuilder result) {
         PyString formatSpecStr = formatSpec == null ? Py.EmptyString : new PyString(formatSpec);
         result.append(fieldObj.__format__(formatSpecStr).asString());
@@ -3876,10 +3917,12 @@
     }
 
     /**
-     * Internal implementation of str.__format__()
+     * Format the given text according to a parsed PEP 3101 formatting specification, as during
+     * <code>text.__format__(format_spec)</code> or <code>"{:s}".format(text)</code> where
+     * <code>text</code> is a Python string.
      *
-     * @param text the text to format
-     * @param spec the PEP 3101 formatting specification
+     * @param text to format
+     * @param spec the parsed PEP 3101 formatting specification
      * @return the result of the formatting
      */
     public static String formatString(String text, InternalFormatSpec spec) {
@@ -3954,6 +3997,9 @@
 }
 
 
+/**
+ * Interpreter for %-format strings. (Note visible across the core package.)
+ */
 final class StringFormatter {
 
     int index;
@@ -3985,6 +4031,12 @@
         this(format, false);
     }
 
+    /**
+     * Initialise the interpreter with the given format string, ready for {@link #format(PyObject)}.
+     *
+     * @param format string to interpret
+     * @param unicodeCoercion to indicate a <code>PyUnicode</code> result is expected
+     */
     public StringFormatter(String format, boolean unicodeCoercion) {
         index = 0;
         this.format = format;
@@ -3995,16 +4047,11 @@
     PyObject getarg() {
         PyObject ret = null;
         switch (argIndex) {
-        // special index indicating a mapping
-            case -3:
+            case -3: // special index indicating a mapping
                 return args;
-                // special index indicating a single item that has already been
-                // used
-            case -2:
+            case -2: // special index indicating a single item that has already been used
                 break;
-            // special index indicating a single item that has not yet been
-            // used
-            case -1:
+            case -1: // special index indicating a single item that has not yet been used
                 argIndex = -2;
                 return args;
             default:
diff --git a/src/org/python/core/stringlib/Formatter.java b/src/org/python/core/stringlib/Formatter.java
--- a/src/org/python/core/stringlib/Formatter.java
+++ b/src/org/python/core/stringlib/Formatter.java
@@ -1,20 +1,44 @@
 package org.python.core.stringlib;
-import org.python.core.*;
-import org.python.core.util.ExtraMath;
 
-import java.math.BigInteger;
 import java.text.DecimalFormat;
 import java.text.DecimalFormatSymbols;
 
+import org.python.core.Py;
+import org.python.core.util.ExtraMath;
 
+/**
+ * This class provides an approximate equivalent to corresponding parts of CPython's
+ * "~/Objects/stringlib/formatter.h", by concentrating in one place the formatting capabilities of
+ * built-in numeric types float and complex.
+ */
 public class Formatter {
 
+    /**
+     * Format a floating-point value according to a conversion specification (created by
+     * {@link InternalFormatSpecParser#parse()}), the type of which must be one of
+     * <code>{efgEFG%}</code>, including padding to width.
+     *
+     * @param value to convert
+     * @param spec for a floating-point conversion
+     * @return formatted result
+     */
     public static String formatFloat(double value, InternalFormatSpec spec) {
         InternalFormatter f = new InternalFormatter(spec);
         String string = f.format(value);
         return spec.pad(string, '>', 0);
     }
 
+    /**
+     * Format a complex value according to a conversion specification (created by
+     * {@link InternalFormatSpecParser#parse()}), the type of which must be one of
+     * <code>{efgEFG}</code>, including padding to width. The operation is effectively the
+     * application of the floating-point format to the real an imaginary parts, then the addition of
+     * padding once.
+     *
+     * @param value to convert
+     * @param spec for a floating-point conversion
+     * @return formatted result
+     */
     public static String formatComplex(double real, double imag, InternalFormatSpec spec) {
         String string;
         InternalFormatter f = new InternalFormatter(spec);
@@ -29,44 +53,76 @@
     }
 }
 
-//Adapted from PyString's StringFormatter class.
+
+/**
+ * A class that provides the implementation of floating-point formatting, and holds a conversion
+ * specification (created by {@link InternalFormatSpecParser#parse()}), a derived precision, and the
+ * sign of the number being converted.
+ */
+// Adapted from PyString's StringFormatter class.
 final class InternalFormatter {
+
     InternalFormatSpec spec;
     boolean negative;
     int precision;
 
+    /**
+     * Construct the formatter from a specification: default missing {@link #precision} to 6.
+     *
+     * @param spec parsed conversion specification
+     */
     public InternalFormatter(InternalFormatSpec spec) {
         this.spec = spec;
         this.precision = spec.precision;
-        if (this.precision == -1)
+        if (this.precision == -1) {
             this.precision = 6;
+        }
     }
 
+    /**
+     * If {@link #precision} exceeds an implementation limit, raise {@link Py#OverflowError}.
+     *
+     * @param type to name as the type being formatted
+     */
     private void checkPrecision(String type) {
-        if(precision > 250) {
+        if (precision > 250) {
             // A magic number. Larger than in CPython.
             throw Py.OverflowError("formatted " + type + " is too long (precision too long?)");
         }
-        
+
     }
 
-    private String formatExp(long v, int radix) {
+    /**
+     * Format <code>abs(e)</code> (in the given radix) with zero-padding to 2 decimal places, and
+     * store <code>sgn(e)</code> in {@link #negative}.
+     *
+     * @param e to convert
+     * @param radix in which to express
+     * @return string value of <code>abs(e)</code> base <code>radix</code>.
+     */
+    private String formatExp(long e, int radix) {
         checkPrecision("integer");
-        if (v < 0) {
+        if (e < 0) {
             negative = true;
-            v = -v;
+            e = -e;
         }
-        String s = Long.toString(v, radix);
+        String s = Long.toString(e, radix);
         while (s.length() < 2) {
-            s = "0"+s;
+            s = "0" + s;
         }
         return s;
     }
 
+    /**
+     * Holds in its {@link #template} member, a {@link DecimalFormat} initialised for fixed point
+     * float formatting.
+     */
     static class DecimalFormatTemplate {
+
         static DecimalFormat template;
         static {
-            template = new DecimalFormat("#,##0.#####", new DecimalFormatSymbols(java.util.Locale.US));
+            template =
+                    new DecimalFormat("#,##0.#####", new DecimalFormatSymbols(java.util.Locale.US));
             DecimalFormatSymbols symbols = template.getDecimalFormatSymbols();
             symbols.setNaN("nan");
             symbols.setInfinity("inf");
@@ -75,14 +131,26 @@
         }
     }
 
+    /**
+     * Return a copy of the pre-configured {@link DecimalFormatTemplate#template}, which may be
+     * further customised by the client.
+     *
+     * @return the template
+     */
     private static final DecimalFormat getDecimalFormat() {
         return (DecimalFormat)DecimalFormatTemplate.template.clone();
     }
 
+    /**
+     * Holds in its {@link #template} member, a {@link DecimalFormat} initialised for fixed point
+     * float formatting with percentage scaling and furniture.
+     */
     static class PercentageFormatTemplate {
+
         static DecimalFormat template;
         static {
-            template = new DecimalFormat("#,##0.#####%", new DecimalFormatSymbols(java.util.Locale.US));
+            template =
+                    new DecimalFormat("#,##0.#####%", new DecimalFormatSymbols(java.util.Locale.US));
             DecimalFormatSymbols symbols = template.getDecimalFormatSymbols();
             symbols.setNaN("nan");
             symbols.setInfinity("inf");
@@ -91,35 +159,72 @@
         }
     }
 
+    /**
+     * Return a copy of the pre-configured {@link PercentageFormatTemplate#template}, which may be
+     * further customised by the client.
+     *
+     * @return the template
+     */
     private static final DecimalFormat getPercentageFormat() {
         return (DecimalFormat)PercentageFormatTemplate.template.clone();
     }
 
+    /**
+     * Format <code>abs(v)</code> in <code>'{f}'</code> format to {@link #precision} (places after
+     * decimal point), and store <code>sgn(v)</code> in {@link #negative}. Truncation is provided
+     * for that will remove trailing zeros and the decimal point (e.g. <code>1.200</code> becomes
+     * <code>1.2</code>, and <code>4.000</code> becomes <code>4</code>. This treatment is to support
+     * <code>'{g}'</code> format. (Also potentially <code>'%g'</code> format.) Truncation is not
+     * used (cannot validly be specified) for <code>'{f}'</code> format.
+     *
+     * @param v to convert
+     * @param truncate if <code>true</code> strip trailing zeros and decimal point
+     * @return converted value
+     */
     private String formatFloatDecimal(double v, boolean truncate) {
+
         checkPrecision("decimal");
+
+        // Separate the sign from v
         if (v < 0) {
             v = -v;
             negative = true;
         }
 
+        // Configure a DecimalFormat: express truncation via minimumFractionDigits
         DecimalFormat decimalFormat = getDecimalFormat();
         decimalFormat.setMaximumFractionDigits(precision);
         decimalFormat.setMinimumFractionDigits(truncate ? 0 : precision);
 
+        // The DecimalFormat is already configured to group by comma at group size 3.
         if (spec.thousands_separators) {
             decimalFormat.setGroupingUsed(true);
         }
+
         String ret = decimalFormat.format(v);
         return ret;
     }
 
+    /**
+     * Format <code>100*abs(v)</code> to {@link #precision} (places after decimal point), with a '%'
+     * (percent) sign following, and store <code>sgn(v)</code> in {@link #negative}.
+     *
+     * @param v to convert
+     * @param truncate if <code>true</code> strip trailing zeros
+     * @return converted value
+     */
     private String formatPercentage(double v, boolean truncate) {
+
         checkPrecision("decimal");
+
+        // Separate the sign from v
         if (v < 0) {
             v = -v;
             negative = true;
         }
 
+        // Configure a DecimalFormat: express truncation via minimumFractionDigits
+        // XXX but truncation cannot be specified with % format!
         DecimalFormat decimalFormat = getPercentageFormat();
         decimalFormat.setMaximumFractionDigits(precision);
         decimalFormat.setMinimumFractionDigits(truncate ? 0 : precision);
@@ -128,26 +233,53 @@
         return ret;
     }
 
+    /**
+     * Format <code>abs(v)</code> in <code>'{e}'</code> format to {@link #precision} (places after
+     * decimal point), and store <code>sgn(v)</code> in {@link #negative}. Truncation is provided
+     * for that will remove trailing zeros and the decimal point before the exponential part (e.g.
+     * <code>1.200e+04</code> becomes <code>1.2e+04</code>, and <code>4.000e+05</code> becomes
+     * <code>4e+05</code>. This treatment is to support <code>'{g}'</code> format. (Also potentially
+     * <code>'%g'</code> format.) Truncation is not used (cannot validly be specified) for
+     * <code>'{e}'</code> format.
+     *
+     * @param v to convert
+     * @param truncate if <code>true</code> strip trailing zeros and decimal point
+     * @return converted value
+     */
     private String formatFloatExponential(double v, char e, boolean truncate) {
-        StringBuilder buf = new StringBuilder();
+
+        // Separate the sign from v
         boolean isNegative = false;
         if (v < 0) {
             v = -v;
             isNegative = true;
         }
+
+        /*
+         * Separate treatment is given to the exponent (I think) because java.text.DecimalFormat
+         * will insert a sign in a positive exponent, as in 1.234e+45 where Java writes 1.234E45.
+         */
+
+        // Power of 10 that will be the exponent.
         double power = 0.0;
-        if (v > 0)
+        if (v > 0) {
+            // That is, if not zero (or NaN)
             power = ExtraMath.closeFloor(Math.log10(v));
+        }
+
+        // Get exponent (as text)
         String exp = formatExp((long)power, 10);
         if (negative) {
+            // This is the sign of the power-of-ten *exponent*
             negative = false;
-            exp = '-'+exp;
-        }
-        else {
+            exp = '-' + exp;
+        } else {
             exp = '+' + exp;
         }
 
-        double base = v/Math.pow(10, power);
+        // Format the mantissa as a fixed point number
+        double base = v / Math.pow(10, power);
+        StringBuilder buf = new StringBuilder();
         buf.append(formatFloatDecimal(base, truncate));
         buf.append(e);
 
@@ -157,22 +289,40 @@
         return buf.toString();
     }
 
+    /**
+     * Format a floating-point number according to the specification represented by this
+     * <code>InternalFormatter</code>. The conversion type, precision, and flags for grouping or
+     * percentage are dealt with here. At the point this is used, we know the {@link #spec} has type
+     * in <code>{efgEFG}</code>.
+     *
+     * @param value to convert
+     * @return formatted version
+     */
     @SuppressWarnings("fallthrough")
     public String format(double value) {
-        String string;
+
+        // XXX Possible duplication in handling NaN and upper/lower case here when methiods
+        // floatFormatDecimal, formatFloatExponential, etc. appear to do those things.
+
+        String string;  // return value
 
         if (spec.alternate) {
+            // XXX in %g, but not {:g} alternate form means always include a decimal point
             throw Py.ValueError("Alternate form (#) not allowed in float format specifier");
         }
+
         int sign = Double.compare(value, 0.0d);
 
         if (Double.isNaN(value)) {
+            // Express NaN cased according to the conversion type.
             if (spec.type == 'E' || spec.type == 'F' || spec.type == 'G') {
                 string = "NAN";
             } else {
                 string = "nan";
             }
+
         } else if (Double.isInfinite(value)) {
+            // Express signed infinity cased according to the conversion type.
             if (spec.type == 'E' || spec.type == 'F' || spec.type == 'G') {
                 if (value > 0) {
                     string = "INF";
@@ -186,52 +336,75 @@
                     string = "-inf";
                 }
             }
+
         } else {
 
-            switch(spec.type) {
-            case 'e':
-            case 'E':
-                string = formatFloatExponential(value, spec.type, false);
-                if (spec.type == 'E') {
-                    string = string.toUpperCase();
-                }
-                break;
-            case 'f':
-            case 'F':
-                string = formatFloatDecimal(value, false);
-                if (spec.type == 'F') {
-                    string = string.toUpperCase();
-                }
-                break;
-            case 'g':
-            case 'G':
-                int exponent = (int)ExtraMath.closeFloor(Math.log10(Math.abs(value == 0 ? 1 : value)));
-                int origPrecision = precision;
-                if (exponent >= -4 && exponent < precision) {
-                    precision -= exponent + 1;
-                    string = formatFloatDecimal(value, !spec.alternate);
-                } else {
-                    // Exponential precision is the number of digits after the decimal
-                    // point, whereas 'g' precision is the number of significant digits --
-                    // and exponential always provides one significant digit before the
-                    // decimal point
-                    precision--;
-                    string = formatFloatExponential(value, (char)(spec.type-2), !spec.alternate);
-                }
-                if (spec.type == 'G') {
-                    string = string.toUpperCase();
-                }
-                precision = origPrecision;
-                break;
-            case '%':
-                string = formatPercentage(value, false);
-                break;
-            default:
-                //Should never get here, since this was checked in PyFloat.
-                throw Py.ValueError(String.format("Unknown format code '%c' for object of type 'float'",
-                                    spec.type));
+            switch (spec.type) {
+                case 'e':
+                case 'E':
+                    // Exponential case: 1.23e-45
+                    string = formatFloatExponential(value, spec.type, false);
+                    if (spec.type == 'E') {
+                        string = string.toUpperCase();
+                    }
+                    break;
+
+                case 'f':
+                case 'F':
+                    // Fixed case: 123.45
+                    string = formatFloatDecimal(value, false);
+                    if (spec.type == 'F') {
+                        string = string.toUpperCase();
+                    }
+                    break;
+
+                case 'g':
+                case 'G':
+                    // Mixed "general" case: e or f format according to exponent.
+                    // XXX technique not wholly effective, for example on 0.0000999999999999995.
+                    int exponent =
+                            (int)ExtraMath.closeFloor(Math.log10(Math.abs(value == 0 ? 1 : value)));
+                    int origPrecision = precision;
+                    /*
+                     * (Python docs) Suppose formatting with presentation type 'e' and precision p-1
+                     * would give exponent exp. Then if -4 <= exp < p, ...
+                     */
+                    if (exponent >= -4 && exponent < precision) {
+                        /*
+                         * ... the number is formatted with presentation type 'f' and precision
+                         * p-1-exp.
+                         */
+                        precision -= exponent + 1;
+                        string = formatFloatDecimal(value, !spec.alternate);
+                    } else {
+                        /*
+                         * ... Otherwise, the number is formatted with presentation type 'e' and
+                         * precision p-1.
+                         */
+                        precision--;
+                        string =
+                                formatFloatExponential(value, (char)(spec.type - 2),
+                                        !spec.alternate);
+                    }
+                    if (spec.type == 'G') {
+                        string = string.toUpperCase();
+                    }
+                    precision = origPrecision;
+                    break;
+
+                case '%':
+                    // Multiplies by 100 and displays in f-format, followed by a percent sign.
+                    string = formatPercentage(value, false);
+                    break;
+
+                default:
+                    // Should never get here, since this was checked in PyFloat.
+                    throw Py.ValueError(String.format(
+                            "Unknown format code '%c' for object of type 'float'", spec.type));
             }
         }
+
+        // If positive, deal with mandatory sign, or mandatory space.
         if (sign >= 0) {
             if (spec.sign == '+') {
                 string = "+" + string;
@@ -239,6 +412,8 @@
                 string = " " + string;
             }
         }
+
+        // If negative, insert a minus sign where needed, and we haven't already (e.g. "-inf").
         if (sign < 0 && string.charAt(0) != '-') {
             string = "-" + string;
         }
diff --git a/src/org/python/core/stringlib/InternalFormatSpec.java b/src/org/python/core/stringlib/InternalFormatSpec.java
--- a/src/org/python/core/stringlib/InternalFormatSpec.java
+++ b/src/org/python/core/stringlib/InternalFormatSpec.java
@@ -1,42 +1,88 @@
 package org.python.core.stringlib;
 
 /**
- * Parsed PEP-3101 format specification of a single field.
+ * Parsed PEP-3101 format specification of a single field. This class holds the several attributes
+ * that might be decoded from a format specifier. It provides a method
+ * {@link #pad(String, char, int)} for adjusting a string using those attributes related to padding
+ * to a string assumed to be the result of formatting to the given precision.
+ * <p>
+ * This structure is returned by {@link InternalFormatSpecParser#parse()} and having public members
+ * is freely used by {@link InternalFormatSpecParser}, {@link Formatter} and the __format__ methods
+ * of client object types.
+ * <p>
+ * The fields correspond to the elements of a format specification. The grammar of a format
+ * specification is:
+ *
+ * <pre>
+ * [[fill]align][sign][#][0][width][,][.precision][type]
+ * </pre>
  */
 public final class InternalFormatSpec {
+
+    /** The fill specified in the grammar. */
     public char fill_char;
+    /** Alignment indicator is 0, or one of {<code>'<', '^', '>', '='</code> . */
     public char align;
+    /** The alternative format flag '#' was given. */
     public boolean alternate;
+    /** Sign-handling flag, one of <code>'+'</code>, <code>'-'</code>, or <code>' '</code>. */
     public char sign;
+    /** Width to which to pad the resault in {@link #pad(String, char, int)}. */
     public int width = -1;
+    /** Insert the grouping separator (which in Python always indicates a group-size of 3). */
     public boolean thousands_separators;
+    /** Precision decoded from the format. */
     public int precision = -1;
+    /** Type key from the format. */
     public char type;
 
+    /**
+     * Pad value, using {@link #fill_char} (or <code>' '</code>) before and after, to {@link #width}
+     * <code>-leaveWidth</code>, aligned according to {@link #align} (or according to
+     * <code>defaultAlign</code>).
+     *
+     * @param value to pad
+     * @param defaultAlign to use if <code>this.align</code>=0 (one of <code>'<'</code>,
+     *            <code>'^'</code>, <code>'>'</code>, or <code>'='</code>).
+     * @param leaveWidth to reduce effective <code>this.width</code> by
+     * @return
+     */
     public String pad(String value, char defaultAlign, int leaveWidth) {
+
+        // We'll need this many pad characters (if>0)
         int remaining = width - value.length() - leaveWidth;
         if (remaining <= 0) {
             return value;
         }
-        StringBuilder result = new StringBuilder();
-        int leading = remaining;
+
+        // Use this.align or defaultAlign
         int useAlign = align;
         if (useAlign == 0) {
             useAlign = defaultAlign;
         }
+
+        // By default all padding is leading padding ('<' case or '=')
+        int leading = remaining;
         if (useAlign == '^') {
-            leading = remaining/2;
+            // Half the padding before
+            leading = remaining / 2;
         } else if (useAlign == '<') {
+            // All the padding after
             leading = 0;
         }
+
+        // Now build the result
+        StringBuilder result = new StringBuilder();
         char fill = fill_char != 0 ? fill_char : ' ';
-        for (int i = 0; i < leading; i++) {
+
+        for (int i = 0; i < leading; i++) { // before
             result.append(fill);
         }
         result.append(value);
-        for (int i = 0; i < remaining - leading; i++) {
+        for (int i = 0; i < remaining - leading; i++) { // after
             result.append(fill);
         }
+
         return result.toString();
     }
 }
diff --git a/src/org/python/core/stringlib/InternalFormatSpecParser.java b/src/org/python/core/stringlib/InternalFormatSpecParser.java
--- a/src/org/python/core/stringlib/InternalFormatSpecParser.java
+++ b/src/org/python/core/stringlib/InternalFormatSpecParser.java
@@ -1,19 +1,26 @@
 package org.python.core.stringlib;
 
 /**
- * Parser for PEP-3101 field format specifications.
+ * Parser for PEP-3101 field format specifications. This class provides a {@link #parse()} method
+ * that translates the format specification into an <code>InternalFormatSpec</code> object.
  */
 public class InternalFormatSpecParser {
+
     private String spec;
     private int index;
 
+    /**
+     * Constructor simply holds the specification streang ahead of the {@link #parse()} operation.
+     *
+     * @param spec format specifier to parse (e.g. "<+12.3f")
+     */
     public InternalFormatSpecParser(String spec) {
         this.spec = spec;
         this.index = 0;
     }
 
     private static boolean isAlign(char c) {
-        switch(c) {
+        switch (c) {
             case '<':
             case '>':
             case '=':
@@ -24,6 +31,24 @@
         }
     }
 
+    /**
+     * Parse the specification with which this object was initialised into an
+     * {@link InternalFormatSpec}, which is an object encapsulating the format for use by formatting
+     * methods. This parser deals only with the format specifiers themselves, as accepted by the
+     * <code>__format__</code> method of a type, or the <code>format()</code> built-in, not format
+     * strings in general as accepted by <code>str.format()</code>. A typical idiom is:
+     *
+     * <pre>
+     * InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse();
+     * </pre>
+     *
+     * @return the <code>InternalFormatSpec</code> equivalent to the constructor argument
+     */
+    /*
+     * This method is the equivalent of CPython's parse_internal_render_format_spec() in
+     * ~/Objects/stringlib/formatter.h.
+     */
+    // XXX Better encapsulated as a constructor of InternalFormatSpec?
     public InternalFormatSpec parse() {
         InternalFormatSpec result = new InternalFormatSpec();
         if (spec.length() >= 1 && isAlign(spec.charAt(0))) {

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


More information about the Jython-checkins mailing list