Skip to content

Commit 24e2553

Browse files
committed
Refactor int.__format__ and long.__format__ to use stringlib.IntegerFormatter.
Also reworks StringFormatterTest.
1 parent 763e119 commit 24e2553

6 files changed

Lines changed: 438 additions & 239 deletions

File tree

src/org/python/core/PyInteger.java

Lines changed: 49 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,10 @@
44

55
import java.io.Serializable;
66
import java.math.BigInteger;
7-
import java.text.NumberFormat;
8-
import java.util.Locale;
97

108
import org.python.core.stringlib.IntegerFormatter;
9+
import org.python.core.stringlib.InternalFormat;
1110
import org.python.core.stringlib.InternalFormat.Spec;
12-
import org.python.core.stringlib.InternalFormatSpec;
13-
import org.python.core.stringlib.InternalFormatSpecParser;
1411
import org.python.expose.ExposedGet;
1512
import org.python.expose.ExposedMethod;
1613
import org.python.expose.ExposedNew;
@@ -1022,151 +1019,68 @@ public PyObject __format__(PyObject formatSpec) {
10221019
return int___format__(formatSpec);
10231020
}
10241021

1022+
@SuppressWarnings("fallthrough")
10251023
@ExposedMethod(doc = BuiltinDocs.int___format___doc)
10261024
final PyObject int___format__(PyObject formatSpec) {
1027-
return formatImpl(getValue(), formatSpec);
1028-
}
1029-
1030-
static PyObject formatImpl(Object value, PyObject formatSpec) {
1031-
if (!(formatSpec instanceof PyString)) {
1032-
throw Py.TypeError("__format__ requires str or unicode");
1033-
}
1034-
1035-
PyString formatSpecStr = (PyString)formatSpec;
1036-
String result;
1037-
try {
1038-
String specString = formatSpecStr.getString();
1039-
InternalFormatSpec spec = new InternalFormatSpecParser(specString).parse();
1040-
result = formatIntOrLong(value, spec);
1041-
} catch (IllegalArgumentException e) {
1042-
throw Py.ValueError(e.getMessage());
1043-
}
1044-
return formatSpecStr.createInstance(result);
1025+
// Get a formatter for the specification
1026+
IntegerFormatter f = prepareFormatter(formatSpec);
1027+
// Convert as per specification.
1028+
f.format(value);
1029+
// Return a result that has the same type (str or unicode) as the formatSpec argument.
1030+
return f.pad().getPyResult();
10451031
}
10461032

10471033
/**
1048-
* Formats an integer or long number according to a PEP-3101 format specification.
1034+
* Common code for PyInteger and PyLong to prepare an IntegerFormatter. This object has an
1035+
* overloaded format method {@link IntegerFormatter#format(int)} and
1036+
* {@link IntegerFormatter#format(BigInteger)} to support the two types.
10491037
*
1050-
* @param value Integer or BigInteger object specifying the value to format.
1051-
* @param spec parsed PEP-3101 format specification.
1052-
* @return result of the formatting.
1038+
* @param formatSpec PEP-3101 format specification.
1039+
* @return a formatter ready to use.
1040+
* @throws PyException(ValueError) if the specification is faulty.
10531041
*/
1054-
public static String formatIntOrLong(Object value, InternalFormatSpec spec) {
1055-
if (spec.precision != -1) {
1056-
throw new IllegalArgumentException("Precision not allowed in integer format specifier");
1057-
}
1058-
1059-
int sign;
1060-
if (value instanceof Integer) {
1061-
int intValue = (Integer)value;
1062-
sign = intValue < 0 ? -1 : intValue == 0 ? 0 : 1;
1063-
} else {
1064-
sign = ((BigInteger)value).signum();
1065-
}
1042+
static IntegerFormatter prepareFormatter(PyObject formatSpec) throws PyException {
10661043

1067-
String strValue;
1068-
String strPrefix = "";
1069-
String strSign = "";
1044+
// Parse the specification
1045+
Spec spec = InternalFormat.fromText(formatSpec, "__format__");
1046+
IntegerFormatter f;
10701047

1071-
if (spec.type == 'c') {
1072-
if (spec.sign != '\0') {
1073-
throw new IllegalArgumentException("Sign not allowed with integer format "
1074-
+ "specifier 'c'");
1075-
}
1076-
if (value instanceof Integer) {
1077-
int intValue = (Integer)value;
1078-
if (intValue > 0xffff) {
1079-
throw new IllegalArgumentException("%c arg not in range(0x10000)");
1080-
}
1081-
strValue = Character.toString((char)intValue);
1082-
} else {
1083-
BigInteger bigInt = (BigInteger)value;
1084-
if (bigInt.intValue() > 0xffff || bigInt.bitCount() > 16) {
1085-
throw new IllegalArgumentException("%c arg not in range(0x10000)");
1086-
}
1087-
strValue = Character.toString((char)bigInt.intValue());
1088-
}
1089-
} else {
1090-
int radix = 10;
1091-
if (spec.type == 'o') {
1092-
radix = 8;
1093-
} else if (spec.type == 'x' || spec.type == 'X') {
1094-
radix = 16;
1095-
} else if (spec.type == 'b') {
1096-
radix = 2;
1097-
}
1098-
1099-
if (spec.type == 'n') {
1100-
strValue = NumberFormat.getNumberInstance().format(value);
1101-
} else if (spec.thousands_separators) {
1102-
NumberFormat format = NumberFormat.getNumberInstance(Locale.US);
1103-
format.setGroupingUsed(true);
1104-
strValue = format.format(value);
1105-
} else if (value instanceof BigInteger) {
1106-
switch (radix) {
1107-
case 2:
1108-
strValue = toBinString((BigInteger)value);
1109-
break;
1110-
case 8:
1111-
strValue = toOctString((BigInteger)value);
1112-
break;
1113-
case 16:
1114-
strValue = toHexString((BigInteger)value);
1115-
break;
1116-
default:
1117-
// General case (v.slow in known implementations up to Java 7).
1118-
strValue = ((BigInteger)value).toString(radix);
1119-
break;
1120-
}
1121-
} else {
1122-
strValue = Integer.toString((Integer)value, radix);
1123-
}
1124-
1125-
if (spec.alternate) {
1126-
switch (radix) {
1127-
case 2:
1128-
strPrefix = "0b";
1129-
break;
1130-
case 8:
1131-
strPrefix = "0o";
1132-
break;
1133-
case 16:
1134-
strPrefix = "0x";
1135-
break;
1136-
}
1137-
1138-
if (sign < 0) {
1139-
assert (strValue.startsWith("-"));
1140-
strSign = "-";
1141-
strValue = strValue.substring(1);
1142-
}
1143-
}
1144-
1145-
if (spec.type == 'X') {
1146-
strPrefix = strPrefix.toUpperCase();
1147-
strValue = strValue.toUpperCase();
1148-
}
1149-
1150-
if (sign >= 0) {
1151-
switch (spec.sign) {
1152-
case '+':
1153-
case ' ':
1154-
strSign = Character.toString(spec.sign);
1155-
break;
1156-
}
1157-
}
1048+
// Check for disallowed parts of the specification
1049+
if (Spec.specified(spec.precision)) {
1050+
throw IntegerFormatter.precisionNotAllowed("integer");
11581051
}
11591052

1160-
if (spec.align == '=' && (spec.sign == '-' || spec.sign == '+' || spec.sign == ' ')) {
1161-
assert (strSign.length() == 1);
1162-
return strSign + strPrefix + spec.pad(strValue, '>', 1 + strPrefix.length());
1163-
}
1053+
// Slight differences between format types
1054+
switch (spec.type) {
1055+
case 'c':
1056+
// Character data
1057+
if (Spec.specified(spec.sign)) {
1058+
throw IntegerFormatter.notAllowed("Sign", "integer", spec.type);
1059+
} else if (spec.alternate) {
1060+
throw IntegerFormatter.alternateFormNotAllowed("integer", spec.type);
1061+
}
1062+
// Fall through
1063+
1064+
case Spec.NONE:
1065+
case 'd':
1066+
case 'x':
1067+
case 'X':
1068+
case 'o':
1069+
case 'b':
1070+
case 'n':
1071+
// spec may be incomplete. The defaults are those commonly used for numeric formats.
1072+
spec = spec.withDefaults(Spec.NUMERIC);
1073+
// Get a formatter for the spec.
1074+
f = new IntegerFormatter(spec, 1);
1075+
// Bytes mode if formatSpec argument is not unicode.
1076+
f.setBytes(!(formatSpec instanceof PyUnicode));
1077+
break;
11641078

1165-
if (spec.fill_char == 0) {
1166-
return spec.pad(strSign + strPrefix + strValue, '>', 0);
1079+
default:
1080+
throw IntegerFormatter.unknownFormat(spec.type, "integer");
11671081
}
11681082

1169-
return strSign + strPrefix + spec.pad(strValue, '>', strSign.length() + strPrefix.length());
1083+
return f;
11701084
}
11711085

11721086
/**

src/org/python/core/PyLong.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1062,7 +1062,12 @@ public PyObject __format__(PyObject formatSpec) {
10621062

10631063
@ExposedMethod(doc = BuiltinDocs.long___format___doc)
10641064
final PyObject long___format__(PyObject formatSpec) {
1065-
return PyInteger.formatImpl(getValue(), formatSpec);
1065+
// Get a formatter for the specification
1066+
IntegerFormatter f = PyInteger.prepareFormatter(formatSpec);
1067+
// Convert as per specification (note this supports BigDecimal).
1068+
f.format(value);
1069+
// Return a result that has the same type (str or unicode) as the formatSpec argument.
1070+
return f.pad().getPyResult();
10661071
}
10671072

10681073
@Override

src/org/python/core/stringlib/FloatFormatter.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,9 @@ public FloatFormatter format(double value, String positivePrefix) {
174174
* By default, the prefix of a positive number is "", but the format specifier may override
175175
* it, and the built-in type complex needs to override the format.
176176
*/
177-
if (positivePrefix == null && Spec.specified(spec.sign) && spec.sign != '-') {
178-
positivePrefix = Character.toString(spec.sign);
177+
char sign = spec.sign;
178+
if (positivePrefix == null && Spec.specified(sign) && sign != '-') {
179+
positivePrefix = Character.toString(sign);
179180
}
180181

181182
// Different process for each format type, ignoring case for now.

0 commit comments

Comments
 (0)