Skip to content

Commit f61b2b0

Browse files
committed
Enable more tests from test_format and test_long
* implement __format__ for integers * proper impl. of complex and float format with no type specifier * support also _ as explicit "thousands" separator (with group size 4 for oct/hex/bin) * improve compatibility with CPython in validation of format strings * basic implementation of locale aware formatting by delegating to JDK
1 parent b1d349f commit f61b2b0

File tree

17 files changed

+538
-309
lines changed

17 files changed

+538
-309
lines changed

graalpython/com.oracle.graal.python.test/src/tests/test_formatting.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ def test_formatting():
8484
# because the object is considered as a mapping...
8585
assert " " % MyPseudoMapping() == " "
8686

87+
# Localized format still honors the sign specifier
88+
assert format(1234.5, "+n").startswith("+")
89+
90+
91+
class MyComplex(complex):
92+
def __repr__(self):
93+
return 'wrong answer'
94+
def __str__(self):
95+
return '42'
96+
8797

8898
def test_complex_formatting():
8999
assert format(3+2j, ">20,.4f") == " 3.0000+2.0000j"
@@ -102,6 +112,8 @@ def test_complex_formatting():
102112
assert format(1+2j, "") == "(1+2j)"
103113
assert format(complex(1, float("NaN")), "") == "(1+nanj)"
104114
assert format(complex(1, float("Inf")), "") == "(1+infj)"
115+
assert format(MyComplex(3j), "") == "42"
116+
assert format(MyComplex(3j), " <5") == "3j "
105117

106118

107119
class AnyRepr:
@@ -147,11 +159,29 @@ def test_formatting_errors(self):
147159
self.assertRaises(ValueError, lambda: format(3+2j, "0=30,.4f"))
148160

149161

150-
class MyFloat(float):
151-
def __str__(self):
152-
return "__str__ overridden for float"
162+
def test_overridden_str():
163+
class MyInt(int):
164+
def __str__(self):
165+
return '42'
153166

167+
class MyFloat(float):
168+
def __str__(self):
169+
return "__str__ overridden for float"
154170

155-
def test_overridden_str():
171+
# floats w/o type specifier, but with other flags should produce
172+
# something like __str__ but not actually call __str__. Only when
173+
# the formatting string is empty it calls actual __str__.
156174
assert "{}".format(MyFloat(2)) == "__str__ overridden for float"
157175
assert "{0:10}".format(MyFloat(2)) == " 2.0"
176+
assert format(MyFloat(2), "") == "__str__ overridden for float"
177+
assert format(MyFloat(2), "5") == " 2.0"
178+
179+
assert format(10000.0, '_') == "10_000.0"
180+
assert format(10000.0, '') == "10000.0"
181+
# if precision is set use '%g' instead of the __str__ like formatting:
182+
assert format(10000.0, "+,.3") == "+1e+04"
183+
184+
assert "{}".format(MyInt(2)) == "42"
185+
assert "{0:10}".format(MyInt(2)) == " 2"
186+
assert format(MyInt(2), "") == "42"
187+
assert format(MyInt(2), "5") == " 2"

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_float.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
*graalpython.lib-python.3.test.test_float.FormatTestCase.test_issue35560
66
*graalpython.lib-python.3.test.test_float.FormatTestCase.test_issue5864
77
*graalpython.lib-python.3.test.test_float.GeneralFloatCases.test_float_containment
8-
*graalpython.lib-python.3.test.test_float.GeneralFloatCases.test_float_with_comma
98
*graalpython.lib-python.3.test.test_float.GeneralFloatCases.test_floatasratio
109
*graalpython.lib-python.3.test.test_float.GeneralFloatCases.test_keyword_args
1110
*graalpython.lib-python.3.test.test_float.HexFloatTestCase.test_ends

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_format.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
*graalpython.lib-python.3.test.test_format.FormatTest.test_str_format
33
*graalpython.lib-python.3.test.test_format.FormatTest.test_bytes_and_bytearray_format
44
*graalpython.lib-python.3.test.test_format.FormatTest.test_nul
5+
*graalpython.lib-python.3.test.test_format.FormatTest.test_non_ascii
6+
*graalpython.lib-python.3.test.test_format.FormatTest.test_locale
57
*graalpython.lib-python.3.test.test_format.FormatTest.test_optimisations
68
*graalpython.lib-python.3.test.test_format.FormatTest.test_precision
79
*graalpython.lib-python.3.test.test_format.FormatTest.test_precision_c_limits

graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_long.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*graalpython.lib-python.3.test.test_long.LongTest.test_karatsuba
1616
*graalpython.lib-python.3.test.test_long.LongTest.test_logs
1717
*graalpython.lib-python.3.test.test_long.LongTest.test_long
18+
*graalpython.lib-python.3.test.test_long.LongTest.test_format
1819
*graalpython.lib-python.3.test.test_long.LongTest.test_lshift_of_zero
1920
*graalpython.lib-python.3.test.test_long.LongTest.test_mod_division
2021
*graalpython.lib-python.3.test.test_long.LongTest.test_nan_inf

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/LocaleModuleBuiltins.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ public static Locale fromPosix(String posixLocaleId) {
102102

103103
int posVariantSep = posixLocaleId.indexOf('.');
104104
if (posVariantSep < 0) {
105-
country = posixLocaleId.substring(posCountrySep, len);
105+
country = posixLocaleId.substring(posCountrySep + 1, len);
106106
} else {
107107
country = posixLocaleId.substring(posCountrySep + 1, posVariantSep);
108108
variant = posixLocaleId.substring(posVariantSep + 1, len);
@@ -174,9 +174,11 @@ public PDict localeconv() {
174174
Currency currency = numberFormat.getCurrency();
175175

176176
DecimalFormatSymbols decimalFormatSymbols;
177+
int groupSize = -1;
177178
if (numberFormat instanceof DecimalFormat) {
178179
DecimalFormat decimalFormat = (DecimalFormat) numberFormat;
179180
decimalFormatSymbols = decimalFormat.getDecimalFormatSymbols();
181+
groupSize = decimalFormat.getGroupingSize();
180182
} else {
181183
decimalFormatSymbols = new DecimalFormatSymbols(locale);
182184
}
@@ -185,7 +187,11 @@ public PDict localeconv() {
185187
dict.put("decimal_point", String.valueOf(decimalFormatSymbols.getDecimalSeparator()));
186188
dict.put("thousands_sep", String.valueOf(decimalFormatSymbols.getGroupingSeparator()));
187189
// TODO: set the proper grouping
188-
dict.put("grouping", factory().createList());
190+
if (groupSize != -1) {
191+
dict.put("grouping", factory().createList(new Object[]{groupSize, 0}));
192+
} else {
193+
dict.put("grouping", factory().createList());
194+
}
189195

190196
// LC_MONETARY
191197
dict.put("int_curr_symbol", decimalFormatSymbols.getInternationalCurrencySymbol());

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/complex/ComplexBuiltins.java

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,7 @@
7070
import static com.oracle.graal.python.runtime.exception.PythonErrorType.OverflowError;
7171
import static com.oracle.graal.python.runtime.exception.PythonErrorType.ValueError;
7272
import static com.oracle.graal.python.runtime.exception.PythonErrorType.ZeroDivisionError;
73-
import static com.oracle.graal.python.runtime.formatting.FormattingUtils.prepareSpecForFloat;
74-
import static com.oracle.graal.python.runtime.formatting.FormattingUtils.shouldBeAsStr;
73+
import static com.oracle.graal.python.runtime.formatting.FormattingUtils.validateAndPrepareForFloat;
7574

7675
import java.util.List;
7776

@@ -84,6 +83,7 @@
8483
import com.oracle.graal.python.builtins.objects.ints.PInt;
8584
import com.oracle.graal.python.builtins.objects.tuple.PTuple;
8685
import com.oracle.graal.python.nodes.ErrorMessages;
86+
import com.oracle.graal.python.nodes.call.special.LookupAndCallUnaryNode;
8787
import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode;
8888
import com.oracle.graal.python.nodes.function.PythonBuiltinNode;
8989
import com.oracle.graal.python.nodes.function.builtins.PythonBinaryBuiltinNode;
@@ -94,7 +94,6 @@
9494
import com.oracle.graal.python.runtime.PythonCore;
9595
import com.oracle.graal.python.runtime.exception.PythonErrorType;
9696
import com.oracle.graal.python.runtime.formatting.ComplexFormatter;
97-
import com.oracle.graal.python.runtime.formatting.FloatFormatter;
9897
import com.oracle.graal.python.runtime.formatting.InternalFormat;
9998
import com.oracle.graal.python.runtime.formatting.InternalFormat.Spec;
10099
import com.oracle.graal.python.runtime.object.PythonObjectFactory;
@@ -728,25 +727,9 @@ String repr(PComplex self) {
728727
}
729728

730729
private static String repr(PComplex self, PythonCore core) {
731-
if (self.getReal() == 0 && Math.copySign(1.0, self.getReal()) == 1.0) {
732-
// Real part is +0: just output the imaginary part and do not include parens
733-
return format(self.getImag(), core) + 'j';
734-
} else {
735-
// real without '+' sign, but imaginary always with sign
736-
return '(' + format(self.getReal(), core) + format(self.getImag(), '+', core) + "j)";
737-
}
738-
}
739-
740-
private static String format(double value, PythonCore core) {
741-
return format(value, InternalFormat.Spec.NONE, core);
742-
}
743-
744-
private static String format(double value, char sign, PythonCore core) {
745-
// CPython uses "r" type, but also some internal flags that cause that integer values
746-
// are printed without the decimal part, which is mostly what "g" does
747-
InternalFormat.Spec spec = new InternalFormat.Spec(' ', '>', sign, false, InternalFormat.Spec.UNSPECIFIED, false, -1, 'g');
748-
FloatFormatter f = new FloatFormatter(core, spec);
749-
return f.format(value).getResult();
730+
ComplexFormatter formatter = new ComplexFormatter(core, new Spec(-1, Spec.NONE));
731+
formatter.format(self);
732+
return formatter.pad().getResult();
750733
}
751734
}
752735

@@ -759,16 +742,19 @@ abstract static class StrNode extends ReprNode {
759742
@GenerateNodeFactory
760743
@TypeSystemReference(PythonArithmeticTypes.class)
761744
abstract static class FormatNode extends PythonBinaryBuiltinNode {
762-
@Specialization
745+
746+
@Specialization(guards = "formatString.isEmpty()")
747+
Object emptyFormat(VirtualFrame frame, Object self, @SuppressWarnings("unused") String formatString,
748+
@Cached("create(__STR__)") LookupAndCallUnaryNode strCall) {
749+
return strCall.executeObject(frame, self);
750+
}
751+
752+
@Specialization(guards = "!formatString.isEmpty()")
763753
@TruffleBoundary
764-
String format(PComplex self, String formatString,
765-
@Cached("createBinaryProfile()") ConditionProfile strProfile) {
766-
if (strProfile.profile(shouldBeAsStr(formatString))) {
767-
return ReprNode.repr(self, getCore());
768-
}
754+
String format(PComplex self, String formatString) {
769755
InternalFormat.Spec spec = InternalFormat.fromText(getCore(), formatString, __FORMAT__);
770756
validateSpec(spec);
771-
ComplexFormatter formatter = new ComplexFormatter(getCore(), prepareSpecForFloat(spec, getCore(), "complex"));
757+
ComplexFormatter formatter = new ComplexFormatter(getCore(), validateAndPrepareForFloat(spec, getCore(), "complex"));
772758
formatter.format(self);
773759
return formatter.pad().getResult();
774760
}
@@ -780,12 +766,12 @@ Object doOther(@SuppressWarnings("unused") Object self, Object format) {
780766

781767
private void validateSpec(Spec spec) {
782768
if (spec.getFill(' ') == '0') {
783-
raise(ValueError, ErrorMessages.ZERO_PADDING_NOT_ALLOWED_FOR_COMPLEX_FMT);
769+
throw raise(ValueError, ErrorMessages.ZERO_PADDING_NOT_ALLOWED_FOR_COMPLEX_FMT);
784770
}
785771

786772
char align = spec.getAlign('>');
787773
if (align == '=') {
788-
raise(ValueError, ErrorMessages.S_ALIGNMENT_FLAG_NOT_ALLOWED_FOR_COMPLEX_FMT, align);
774+
throw raise(ValueError, ErrorMessages.S_ALIGNMENT_FLAG_NOT_ALLOWED_FOR_COMPLEX_FMT, align);
789775
}
790776
}
791777
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/floats/FloatBuiltins.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@
6060
import static com.oracle.graal.python.nodes.SpecialMethodNames.__SUB__;
6161
import static com.oracle.graal.python.nodes.SpecialMethodNames.__TRUEDIV__;
6262
import static com.oracle.graal.python.nodes.SpecialMethodNames.__TRUNC__;
63-
import static com.oracle.graal.python.runtime.formatting.FormattingUtils.prepareSpecForFloat;
63+
import static com.oracle.graal.python.runtime.formatting.FormattingUtils.validateAndPrepareForFloat;
6464

6565
import java.math.BigDecimal;
6666
import java.math.BigInteger;
@@ -98,8 +98,8 @@
9898
import com.oracle.graal.python.runtime.PythonContext;
9999
import com.oracle.graal.python.runtime.exception.PythonErrorType;
100100
import com.oracle.graal.python.runtime.formatting.FloatFormatter;
101-
import com.oracle.graal.python.runtime.formatting.FormattingUtils;
102101
import com.oracle.graal.python.runtime.formatting.InternalFormat;
102+
import com.oracle.graal.python.runtime.formatting.InternalFormat.Spec;
103103
import com.oracle.truffle.api.CompilerDirectives;
104104
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
105105
import com.oracle.truffle.api.dsl.Cached;
@@ -135,7 +135,7 @@ public static double asDouble(boolean right) {
135135
abstract static class StrNode extends PythonUnaryBuiltinNode {
136136
@Specialization
137137
String str(double self) {
138-
InternalFormat.Spec spec = new InternalFormat.Spec(' ', '>', InternalFormat.Spec.NONE, false, InternalFormat.Spec.UNSPECIFIED, false, 0, 'r');
138+
Spec spec = new Spec(' ', '>', Spec.NONE, false, Spec.UNSPECIFIED, Spec.NONE, 0, 'r');
139139
FloatFormatter f = new FloatFormatter(getCore(), spec);
140140
f.setMinFracDigits(1);
141141
return doFormat(self, f);
@@ -168,20 +168,19 @@ abstract static class ReprNode extends StrNode {
168168
@Builtin(name = __FORMAT__, minNumOfPositionalArgs = 2)
169169
@GenerateNodeFactory
170170
@TypeSystemReference(PythonArithmeticTypes.class)
171-
@ImportStatic(FormattingUtils.class)
172171
abstract static class FormatNode extends PythonBinaryBuiltinNode {
173172

174-
@Specialization(guards = "shouldBeAsStr(formatString)")
173+
@Specialization(guards = "formatString.isEmpty()")
175174
Object emptyFormat(VirtualFrame frame, Object self, @SuppressWarnings("unused") String formatString,
176175
@Cached("create(__STR__)") LookupAndCallUnaryNode strCall) {
177176
return strCall.executeObject(frame, self);
178177
}
179178

180-
@Specialization(guards = "!shouldBeAsStr(formatString)")
179+
@Specialization(guards = "!formatString.isEmpty()")
181180
@TruffleBoundary
182181
String format(double self, String formatString) {
183182
InternalFormat.Spec spec = InternalFormat.fromText(getCore(), formatString, __FORMAT__);
184-
FloatFormatter formatter = new FloatFormatter(getCore(), prepareSpecForFloat(spec, getCore(), "float"));
183+
FloatFormatter formatter = new FloatFormatter(getCore(), validateAndPrepareForFloat(spec, getCore(), "float"));
185184
formatter.format(self);
186185
return formatter.pad().getResult();
187186
}

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/objects/ints/IntBuiltins.java

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@
4040
*/
4141
package com.oracle.graal.python.builtins.objects.ints;
4242

43+
import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError;
44+
import static com.oracle.graal.python.nodes.SpecialMethodNames.__FORMAT__;
4345
import static com.oracle.graal.python.nodes.SpecialMethodNames.__LT__;
4446
import static com.oracle.graal.python.runtime.exception.PythonErrorType.OverflowError;
4547
import static com.oracle.graal.python.runtime.exception.PythonErrorType.ValueError;
@@ -89,7 +91,12 @@
8991
import com.oracle.graal.python.nodes.object.GetClassNode;
9092
import com.oracle.graal.python.nodes.truffle.PythonArithmeticTypes;
9193
import com.oracle.graal.python.runtime.PythonContext;
94+
import com.oracle.graal.python.runtime.PythonCore;
9295
import com.oracle.graal.python.runtime.exception.PythonErrorType;
96+
import com.oracle.graal.python.runtime.formatting.FloatFormatter;
97+
import com.oracle.graal.python.runtime.formatting.IntegerFormatter;
98+
import com.oracle.graal.python.runtime.formatting.InternalFormat;
99+
import com.oracle.graal.python.runtime.formatting.InternalFormat.Spec;
93100
import com.oracle.graal.python.util.OverflowException;
94101
import com.oracle.graal.python.util.PythonUtils;
95102
import com.oracle.truffle.api.CompilerDirectives;
@@ -2373,6 +2380,84 @@ private static String doHash(Object object) {
23732380
abstract static class ReprNode extends StrNode {
23742381
}
23752382

2383+
@Builtin(name = __FORMAT__, minNumOfPositionalArgs = 2)
2384+
@GenerateNodeFactory
2385+
@TypeSystemReference(PythonArithmeticTypes.class)
2386+
abstract static class FormatNode extends PythonBinaryBuiltinNode {
2387+
2388+
@Specialization(guards = "formatString.isEmpty()")
2389+
Object emptyFormat(VirtualFrame frame, Object self, @SuppressWarnings("unused") String formatString,
2390+
@Cached("create(__STR__)") LookupAndCallUnaryNode strCall) {
2391+
return strCall.executeObject(frame, self);
2392+
}
2393+
2394+
@Specialization(guards = "!formatString.isEmpty()")
2395+
@TruffleBoundary
2396+
String formatI(int self, String formatString) {
2397+
PythonCore core = getCore();
2398+
Spec spec = getSpec(formatString, core);
2399+
if (isDoubleSpec(spec)) {
2400+
return formatDouble(core, spec, self);
2401+
}
2402+
validateIntegerSpec(core, spec);
2403+
IntegerFormatter formatter = new IntegerFormatter(core, spec);
2404+
formatter.format(self);
2405+
return formatter.pad().getResult();
2406+
}
2407+
2408+
@Specialization(guards = "!formatString.isEmpty()")
2409+
String formatL(long self, String formatString) {
2410+
return formatPI(factory().createInt(self), formatString);
2411+
}
2412+
2413+
@Specialization(guards = "!formatString.isEmpty()")
2414+
@TruffleBoundary
2415+
String formatPI(PInt self, String formatString) {
2416+
PythonCore core = getCore();
2417+
Spec spec = getSpec(formatString, core);
2418+
if (isDoubleSpec(spec)) {
2419+
// Note: this should really call PyNumber_Float
2420+
double doubleVal = PythonObjectLibrary.getUncached().asJavaDouble(self);
2421+
return formatDouble(core, spec, doubleVal);
2422+
}
2423+
validateIntegerSpec(core, spec);
2424+
IntegerFormatter formatter = new IntegerFormatter(core, spec);
2425+
formatter.format(self.getValue());
2426+
return formatter.pad().getResult();
2427+
}
2428+
2429+
@Fallback
2430+
Object doOther(@SuppressWarnings("unused") Object self, Object format) {
2431+
throw raise(TypeError, ErrorMessages.ARG_D_MUST_BE_S_NOT_P, "format()", 2, "str", format);
2432+
}
2433+
2434+
private static Spec getSpec(String formatString, PythonCore core) {
2435+
Spec spec = InternalFormat.fromText(core, formatString, __FORMAT__);
2436+
return spec.withDefaults(Spec.NUMERIC);
2437+
}
2438+
2439+
private static boolean isDoubleSpec(Spec spec) {
2440+
return spec.type == 'e' || spec.type == 'E' || spec.type == 'f' || //
2441+
spec.type == 'F' || spec.type == 'g' || //
2442+
spec.type == 'G' || spec.type == '%';
2443+
}
2444+
2445+
private static String formatDouble(PythonCore core, Spec spec, double value) {
2446+
FloatFormatter formatter = new FloatFormatter(core, spec);
2447+
formatter.format(value);
2448+
return formatter.pad().getResult();
2449+
}
2450+
2451+
private static void validateIntegerSpec(PythonCore core, Spec spec) {
2452+
if (Spec.specified(spec.precision)) {
2453+
throw core.raise(ValueError, ErrorMessages.PRECISION_NOT_ALLOWED_FOR_INT);
2454+
}
2455+
if (spec.type == 'c' && Spec.specified(spec.sign)) {
2456+
throw core.raise(ValueError, ErrorMessages.SIGN_NOT_ALLOWED_WITH_C_FOR_INT);
2457+
}
2458+
}
2459+
}
2460+
23762461
@Builtin(name = SpecialMethodNames.__HASH__, minNumOfPositionalArgs = 1)
23772462
@GenerateNodeFactory
23782463
@TypeSystemReference(PythonArithmeticTypes.class)

graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,8 @@ public abstract class ErrorMessages {
129129
public static final String CANNOT_RELEASE_UNAQUIRED_LOCK = "cannot release un-acquired lock";
130130
public static final String CANNOT_RESIZE_BUFFER = "cannot resize buffer";
131131
public static final String CANNOT_SPECIFY_FILTERS = "Cannot specify filters except with FORMAT_RAW";
132+
public static final String CANNOT_SPECIFY_BOTH_COMMA_AND_UNDERSCORE = "Cannot specify both ',' and '_'.";
133+
public static final String CANNOT_SPECIFY_C_WITH_C = "Cannot specify '%c' with '%c'.";
132134
public static final String CANNOT_SPECIFY_MEM_LIMIT = "Cannot specify memory limit with FORMAT_RAW";
133135
public static final String CANNOT_SPECIFY_PREST_AND_FILTER_CHAIN = "Cannot specify both preset and filter chain";
134136
public static final String CANNOT_USE_TO_INITIALIZE_ARRAY = "cannot use a %p to initialize an array with typecode '%s'";
@@ -518,4 +520,6 @@ public abstract class ErrorMessages {
518520
public static final String ZERO_PADDING_NOT_ALLOWED_FOR_COMPLEX_FMT = "Zero padding is not allowed in complex format specifier";
519521
public static final String POW_THIRD_ARG_CANNOT_BE_ZERO = "pow() 3rd argument cannot be 0";
520522
public static final String CANNOT_ENCODE_DOCSTR = "'utf-8' codec can't encode docstring '%s'";
523+
public static final String PRECISION_NOT_ALLOWED_FOR_INT = "Precision not allowed in integer format specifier";
524+
public static final String SIGN_NOT_ALLOWED_WITH_C_FOR_INT = "Sign not allowed with integer format specifier 'c'";
521525
}

0 commit comments

Comments
 (0)