From 42ff6234738fc1fe444bcc9a49d9a5542d7a9491 Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Tue, 31 Oct 2023 15:06:25 -0400 Subject: [PATCH 01/11] Add %aA for printf and friends --- .../src/main/java/org/jruby/util/Sprintf.java | 239 +++++++++++++++--- spec/tags/ruby/core/file/printf_tags.txt | 6 - spec/tags/ruby/core/kernel/printf_tags.txt | 12 - spec/tags/ruby/core/kernel/sprintf_tags.txt | 12 - spec/tags/ruby/core/string/modulo_tags.txt | 6 - spec/tags/ruby/core/string/percent_tags.txt | 6 - .../ruby/library/stringio/printf_tags.txt | 6 - 7 files changed, 207 insertions(+), 80 deletions(-) diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index 23dff7eb394..37393994039 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -40,7 +40,6 @@ import org.jcodings.specific.UTF8Encoding; import org.jruby.*; import org.jruby.common.IRubyWarnings.ID; -import org.jruby.runtime.ClassIndex; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.util.io.EncodingUtils; @@ -606,6 +605,81 @@ else if ((flags & FLAG_MINUS) != 0) { incomplete = false; break; } + + + case 'a': + case 'A': { + arg = args.getArg(); + + final boolean positive = isPositive(arg); + double fval = RubyKernel.new_float(runtime, arg).getDoubleValue(); + boolean negative = fval < 0.0d || (fval == 0.0d && Double.doubleToLongBits(fval) == Double.doubleToLongBits(-0.0)); + boolean isnan = Double.isNaN(fval); + boolean isinf = fval == Double.POSITIVE_INFINITY || fval == Double.NEGATIVE_INFINITY; + + if (isnan || isinf) { + printSpecialValue(buf, flags, width, isnan, negative); + + offset++; + incomplete = false; + break; + } + + long exponent = getExponent(arg); + final byte[] mantissaBytes = getMantissaBytes(arg); + + if (!positive) { + buf.append('-'); + } else if ((flags & FLAG_PLUS) != 0) { + buf.append('+'); + } else if ((flags & FLAG_SPACE) != 0) { + buf.append(' '); + } + buf.append('0'); + buf.append(fchar == 'a' ? 'x' : 'X'); + if (mantissaBytes[0] == 0) { + exponent = 0; + buf.append('0'); + if (precision > 0 || (flags & FLAG_SPACE) != 0) { + buf.append('.'); + while (precision > 0) { + buf.append('0'); + precision--; + } + } + } else { + int i = 0; + int digit = getDigit(i++, mantissaBytes); + if (digit == 0) { + digit = getDigit(i++, mantissaBytes); + } + assert digit == 1; + buf.append('1'); + int digits = getNumberOfDigits(mantissaBytes); + if (i < digits || (flags & FLAG_SPACE) != 0 || precision > 0) { + buf.append('.'); + } + + if ((flags & FLAG_PRECISION) == 0) { + precision = -1; + } + + while ((precision < 0 && i < digits) || precision > 0) { + digit = getDigit(i++, mantissaBytes); + buf.append((fchar == 'a' ? HEX_DIGITS : HEX_DIGITS_UPPER_CASE)[digit]); + precision--; + } + } + + buf.append(fchar == 'a' ? 'p' : 'P'); + if (exponent >= 0) { + buf.append('+'); + } + buf.append(Long.toString(exponent).getBytes()); + offset++; + incomplete = false; + break; + } case 'p': case 's': { // format_s: arg = args.getArg(); @@ -933,8 +1007,6 @@ else if (precision > 0) { case 'e': case 'G': case 'g': - //case 'a': - //case 'A': float_value: { arg = args.getArg(); @@ -951,35 +1023,7 @@ else if (precision > 0) { byte sign; if (isnan || isinf) { - if (isnan) { - digits = NAN_VALUE; - len = NAN_VALUE.length; - } else { - digits = INFINITY_VALUE; - len = INFINITY_VALUE.length; - } - if (negative) { - sign = '-'; - width--; - } else if ((flags & FLAG_PLUS) != 0) { - sign = '+'; - width--; - } else if ((flags & FLAG_SPACE) != 0) { - sign = ' '; - width--; - } else { - sign = 0; - } - width -= len; - - if (width > 0 && (flags & FLAG_MINUS) == 0) { - buf.fill(' ', width); - width = 0; - } - if (sign != 0) buf.append(sign); - - buf.append(digits); - if (width > 0) buf.fill(' ', width); + printSpecialValue(buf, flags, width, isnan, negative); offset++; incomplete = false; @@ -1480,6 +1524,42 @@ else if (precision > 0) { } } + // prints nan or inf + private static void printSpecialValue(ByteList buf, int flags, int width, boolean isnan, boolean negative) { + byte sign; + byte[] digits; + int len; + if (isnan) { + digits = NAN_VALUE; + len = NAN_VALUE.length; + } else { + digits = INFINITY_VALUE; + len = INFINITY_VALUE.length; + } + if (negative) { + sign = '-'; + width--; + } else if ((flags & FLAG_PLUS) != 0) { + sign = '+'; + width--; + } else if ((flags & FLAG_SPACE) != 0) { + sign = ' '; + width--; + } else { + sign = 0; + } + width -= len; + + if (width > 0 && (flags & FLAG_MINUS) == 0) { + buf.fill(' ', width); + width = 0; + } + if (sign != 0) buf.append(sign); + + buf.append(digits); + if (width > 0) buf.fill(' ', width); + } + public static NumberFormat getNumberFormat(Locale locale) { Map numberFormats = LOCALE_NUMBER_FORMATS.get(); if (numberFormats == null) { @@ -1759,4 +1839,99 @@ private static byte[] stringToBytes(CharSequence s) { return ByteList.plain(s); } + private static int getNumberOfDigits(byte[] bytes) { + int digits = bytes.length * 2; + if (getDigit(digits - 1, bytes) == 0) { + digits--; + } + return digits; + } + + private static byte getDigit(int position, byte[] bytes) { + int index = position / 2; + if (index < bytes.length) { + byte twoDigits = bytes[index]; + if (position % 2 == 0) { + return (byte) ((twoDigits >> 4) & 0xf); + } else { + return (byte) (twoDigits & 0xf); + } + } else { + return 0; + } + } + + private static boolean isPositive(Object value) { + if (value instanceof RubyFloat) { + final long bits = Double.doubleToRawLongBits(((RubyFloat) value).getDoubleValue()); + return (bits & SIGN_MASK) == 0; + } else if (value instanceof RubyFixnum) { + return ((RubyFixnum) value).getLongValue() >= 0; + } else if (value instanceof RubyBignum) { + return ((RubyBignum) value).signum() >= 0; + } + return true; + } + + private static final long SIGN_MASK = 1L << 63; + private static final long BIASED_EXP_MASK = 0x7ffL << 52; + private static final long MANTISSA_MASK = ~(SIGN_MASK | BIASED_EXP_MASK); + private static final byte[] HEX_DIGITS = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + private static final byte[] HEX_DIGITS_UPPER_CASE = new byte[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; + + private static byte[] getMantissaBytes(IRubyObject value) { + BigInteger bi; + if (value instanceof RubyFloat) { + final long bits = Double.doubleToRawLongBits(((RubyFloat) value).getDoubleValue()); + long biasedExp = ((bits & BIASED_EXP_MASK) >> 52); + long mantissaBits = bits & MANTISSA_MASK; + if (biasedExp > 0) { + mantissaBits = mantissaBits | 0x10000000000000L; + } + bi = BigInteger.valueOf(mantissaBits); + } else if (value instanceof RubyFixnum) { + bi = BigInteger.valueOf(((RubyFixnum) value).getLongValue()); + } else if (value instanceof RubyBignum) { + bi = ((RubyBignum) value).getValue(); + } else { + bi = BigInteger.ZERO; + } + bi = bi.abs(); + if (BigInteger.ZERO.equals(bi)) { + return new byte[1]; + } + + // Shift things to get rid of all the trailing zeros. + bi = bi.shiftRight(bi.getLowestSetBit() - 1); + + // We want the bit length to be 4n + 1 so that things line up nicely for the printing routine. + int bitLength = bi.bitLength() % 4; + if (bitLength != 1) { + bi = bi.shiftLeft(5 - bitLength); + } + return bi.toByteArray(); + } + + private static long getExponent(IRubyObject value) { + if (value instanceof RubyBignum) { + return ((RubyBignum) value).getValue().abs().bitLength() - 1; + } else if (value instanceof RubyFixnum) { + long lval = ((RubyFixnum) value).getLongValue(); + return lval == Long.MIN_VALUE ? 63 : 63 - Long.numberOfLeadingZeros(Math.abs(lval)); + } else if (value instanceof RubyFloat) { + final long bits = Double.doubleToRawLongBits(((RubyFloat) value).getDoubleValue()); + long biasedExp = ((bits & BIASED_EXP_MASK) >> 52); + long mantissaBits = bits & MANTISSA_MASK; + if (biasedExp == 0) { + // Sub normal cases are a little special. + // Find the most significant bit in the mantissa + final int lz = Long.numberOfLeadingZeros(mantissaBits); + // Adjust the exponent to reflect this. + biasedExp = biasedExp - (lz - 12); + } + return biasedExp - 1023; + } + return 0; + } + } diff --git a/spec/tags/ruby/core/file/printf_tags.txt b/spec/tags/ruby/core/file/printf_tags.txt index a4313055836..4f055b5c4cc 100644 --- a/spec/tags/ruby/core/file/printf_tags.txt +++ b/spec/tags/ruby/core/file/printf_tags.txt @@ -1,10 +1,4 @@ fails:File#printf integer formats u converts argument as a decimal number -fails:File#printf float formats a converts floating point argument as [-]0xh.hhhhp[+-]dd -fails:File#printf float formats a displays Float::INFINITY as Inf -fails:File#printf float formats a displays Float::NAN as NaN -fails:File#printf float formats A converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P -fails:File#printf float formats A displays Float::INFINITY as Inf -fails:File#printf float formats A displays Float::NAN as NaN fails:File#printf flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers fails:File#printf flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers fails:File#printf flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one diff --git a/spec/tags/ruby/core/kernel/printf_tags.txt b/spec/tags/ruby/core/kernel/printf_tags.txt index dc14f19f9f9..4d23faea5d4 100644 --- a/spec/tags/ruby/core/kernel/printf_tags.txt +++ b/spec/tags/ruby/core/kernel/printf_tags.txt @@ -1,10 +1,4 @@ fails:Kernel.printf formatting io is specified integer formats u converts argument as a decimal number -fails:Kernel.printf formatting io is specified float formats a converts floating point argument as [-]0xh.hhhhp[+-]dd -fails:Kernel.printf formatting io is specified float formats a displays Float::INFINITY as Inf -fails:Kernel.printf formatting io is specified float formats a displays Float::NAN as NaN -fails:Kernel.printf formatting io is specified float formats A converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P -fails:Kernel.printf formatting io is specified float formats A displays Float::INFINITY as Inf -fails:Kernel.printf formatting io is specified float formats A displays Float::NAN as NaN fails:Kernel.printf formatting io is specified flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers fails:Kernel.printf formatting io is specified flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers fails:Kernel.printf formatting io is specified flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one @@ -24,12 +18,6 @@ fails:Kernel.printf formatting io is specified width specifies the minimum numbe fails:Kernel.printf formatting io is specified precision float types controls the number of decimal places displayed in fraction part fails:Kernel.printf formatting io is specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is not specified integer formats u converts argument as a decimal number -fails:Kernel.printf formatting io is not specified float formats a converts floating point argument as [-]0xh.hhhhp[+-]dd -fails:Kernel.printf formatting io is not specified float formats a displays Float::INFINITY as Inf -fails:Kernel.printf formatting io is not specified float formats a displays Float::NAN as NaN -fails:Kernel.printf formatting io is not specified float formats A converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P -fails:Kernel.printf formatting io is not specified float formats A displays Float::INFINITY as Inf -fails:Kernel.printf formatting io is not specified float formats A displays Float::NAN as NaN fails:Kernel.printf formatting io is not specified flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers fails:Kernel.printf formatting io is not specified flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers fails:Kernel.printf formatting io is not specified flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one diff --git a/spec/tags/ruby/core/kernel/sprintf_tags.txt b/spec/tags/ruby/core/kernel/sprintf_tags.txt index a7b30427cb8..8def9004a9b 100644 --- a/spec/tags/ruby/core/kernel/sprintf_tags.txt +++ b/spec/tags/ruby/core/kernel/sprintf_tags.txt @@ -1,11 +1,5 @@ fails(compiler):Kernel#sprintf passes some tests for negative %u fails:Kernel#sprintf integer formats u converts argument as a decimal number -fails:Kernel#sprintf float formats a converts floating point argument as [-]0xh.hhhhp[+-]dd -fails:Kernel#sprintf float formats a displays Float::INFINITY as Inf -fails:Kernel#sprintf float formats a displays Float::NAN as NaN -fails:Kernel#sprintf float formats A converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P -fails:Kernel#sprintf float formats A displays Float::INFINITY as Inf -fails:Kernel#sprintf float formats A displays Float::NAN as NaN fails:Kernel#sprintf flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers fails:Kernel#sprintf flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers fails:Kernel#sprintf flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one @@ -25,12 +19,6 @@ fails:Kernel#sprintf width specifies the minimum number of characters that will fails:Kernel#sprintf precision float types controls the number of decimal places displayed in fraction part fails:Kernel#sprintf reference by name %{name} style supports flags, width and precision fails:Kernel.sprintf integer formats u converts argument as a decimal number -fails:Kernel.sprintf float formats a converts floating point argument as [-]0xh.hhhhp[+-]dd -fails:Kernel.sprintf float formats a displays Float::INFINITY as Inf -fails:Kernel.sprintf float formats a displays Float::NAN as NaN -fails:Kernel.sprintf float formats A converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P -fails:Kernel.sprintf float formats A displays Float::INFINITY as Inf -fails:Kernel.sprintf float formats A displays Float::NAN as NaN fails:Kernel.sprintf flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers fails:Kernel.sprintf flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers fails:Kernel.sprintf flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one diff --git a/spec/tags/ruby/core/string/modulo_tags.txt b/spec/tags/ruby/core/string/modulo_tags.txt index c0b21505b93..78604dec7e0 100644 --- a/spec/tags/ruby/core/string/modulo_tags.txt +++ b/spec/tags/ruby/core/string/modulo_tags.txt @@ -1,10 +1,4 @@ fails:String#% integer formats u converts argument as a decimal number -fails:String#% float formats a converts floating point argument as [-]0xh.hhhhp[+-]dd -fails:String#% float formats a displays Float::INFINITY as Inf -fails:String#% float formats a displays Float::NAN as NaN -fails:String#% float formats A converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P -fails:String#% float formats A displays Float::INFINITY as Inf -fails:String#% float formats A displays Float::NAN as NaN fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one diff --git a/spec/tags/ruby/core/string/percent_tags.txt b/spec/tags/ruby/core/string/percent_tags.txt index 1107f1262d9..2991d17bec2 100644 --- a/spec/tags/ruby/core/string/percent_tags.txt +++ b/spec/tags/ruby/core/string/percent_tags.txt @@ -1,12 +1,6 @@ fails:String#% integer formats u converts argument as a decimal number fails:String#% float formats g otherwise cuts fraction part to have only 6 digits at all fails:String#% float formats G otherwise cuts fraction part to have only 6 digits at all -fails:String#% float formats a converts floating point argument as [-]0xh.hhhhp[+-]dd -fails:String#% float formats a displays Float::INFINITY as Inf -fails:String#% float formats a displays Float::NAN as NaN -fails:String#% float formats A converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P -fails:String#% float formats A displays Float::INFINITY as Inf -fails:String#% float formats A displays Float::NAN as NaN fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one diff --git a/spec/tags/ruby/library/stringio/printf_tags.txt b/spec/tags/ruby/library/stringio/printf_tags.txt index a0c4adbf3ef..caae8616f09 100644 --- a/spec/tags/ruby/library/stringio/printf_tags.txt +++ b/spec/tags/ruby/library/stringio/printf_tags.txt @@ -1,10 +1,4 @@ fails:StringIO#printf formatting integer formats u converts argument as a decimal number -fails:StringIO#printf formatting float formats a converts floating point argument as [-]0xh.hhhhp[+-]dd -fails:StringIO#printf formatting float formats a displays Float::INFINITY as Inf -fails:StringIO#printf formatting float formats a displays Float::NAN as NaN -fails:StringIO#printf formatting float formats A converts floating point argument as [-]0xh.hhhhp[+-]dd and use uppercase X and P -fails:StringIO#printf formatting float formats A displays Float::INFINITY as Inf -fails:StringIO#printf formatting float formats A displays Float::NAN as NaN fails:StringIO#printf formatting flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers fails:StringIO#printf formatting flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers fails:StringIO#printf formatting flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one From 7eff4eb9313f0e675d50de9d6a14477bf2df3d7c Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Tue, 31 Oct 2023 16:02:47 -0400 Subject: [PATCH 02/11] More fixes for %aA --- .../src/main/java/org/jruby/util/Sprintf.java | 139 ++++++++++-------- spec/tags/ruby/core/file/printf_tags.txt | 14 -- spec/tags/ruby/core/kernel/printf_tags.txt | 28 ---- spec/tags/ruby/core/kernel/sprintf_tags.txt | 28 ---- spec/tags/ruby/core/string/modulo_tags.txt | 14 -- spec/tags/ruby/core/string/percent_tags.txt | 21 --- .../ruby/library/stringio/printf_tags.txt | 14 -- 7 files changed, 79 insertions(+), 179 deletions(-) delete mode 100644 spec/tags/ruby/core/string/percent_tags.txt diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index 37393994039..3395f9f3473 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -607,78 +607,97 @@ else if ((flags & FLAG_MINUS) != 0) { } - case 'a': - case 'A': { - arg = args.getArg(); + case 'a': + case 'A': { + arg = args.getArg(); - final boolean positive = isPositive(arg); - double fval = RubyKernel.new_float(runtime, arg).getDoubleValue(); - boolean negative = fval < 0.0d || (fval == 0.0d && Double.doubleToLongBits(fval) == Double.doubleToLongBits(-0.0)); - boolean isnan = Double.isNaN(fval); - boolean isinf = fval == Double.POSITIVE_INFINITY || fval == Double.NEGATIVE_INFINITY; + ByteList bytes = new ByteList(); + final boolean positive = isPositive(arg); + double fval = RubyKernel.new_float(runtime, arg).getDoubleValue(); + boolean negative = fval < 0.0d || (fval == 0.0d && Double.doubleToLongBits(fval) == Double.doubleToLongBits(-0.0)); + boolean isnan = Double.isNaN(fval); + boolean isinf = fval == Double.POSITIVE_INFINITY || fval == Double.NEGATIVE_INFINITY; - if (isnan || isinf) { - printSpecialValue(buf, flags, width, isnan, negative); + if (isnan || isinf) { + printSpecialValue(buf, flags, width, isnan, negative); - offset++; - incomplete = false; - break; + offset++; + incomplete = false; + break; + } + + long exponent = getExponent(arg); + final byte[] mantissaBytes = getMantissaBytes(arg); + + if (!positive) { + bytes.append('-'); + } else if ((flags & FLAG_PLUS) != 0) { + bytes.append('+'); + } else if ((flags & FLAG_SPACE) != 0) { + bytes.append(' '); + } + bytes.append('0'); + bytes.append(fchar == 'a' ? 'x' : 'X'); + if (mantissaBytes[0] == 0) { + exponent = 0; + bytes.append('0'); + if (precision > 0 || (flags & FLAG_SPACE) != 0) { + bytes.append('.'); + while (precision > 0) { + bytes.append('0'); + precision--; + } + } + } else { + int i = 0; + int digit = getDigit(i++, mantissaBytes); + if (digit == 0) { + digit = getDigit(i++, mantissaBytes); + } + assert digit == 1; + bytes.append('1'); + int digits = getNumberOfDigits(mantissaBytes); + if (i < digits || (flags & FLAG_SPACE) != 0 || precision > 0) { + bytes.append('.'); } - long exponent = getExponent(arg); - final byte[] mantissaBytes = getMantissaBytes(arg); + if ((flags & FLAG_PRECISION) == 0) { + precision = -1; + } - if (!positive) { - buf.append('-'); - } else if ((flags & FLAG_PLUS) != 0) { - buf.append('+'); - } else if ((flags & FLAG_SPACE) != 0) { - buf.append(' '); + while ((precision < 0 && i < digits) || precision > 0) { + digit = getDigit(i++, mantissaBytes); + bytes.append((fchar == 'a' ? HEX_DIGITS : HEX_DIGITS_UPPER_CASE)[digit]); + precision--; } - buf.append('0'); - buf.append(fchar == 'a' ? 'x' : 'X'); - if (mantissaBytes[0] == 0) { - exponent = 0; - buf.append('0'); - if (precision > 0 || (flags & FLAG_SPACE) != 0) { - buf.append('.'); - while (precision > 0) { - buf.append('0'); - precision--; - } - } - } else { - int i = 0; - int digit = getDigit(i++, mantissaBytes); - if (digit == 0) { - digit = getDigit(i++, mantissaBytes); - } - assert digit == 1; - buf.append('1'); - int digits = getNumberOfDigits(mantissaBytes); - if (i < digits || (flags & FLAG_SPACE) != 0 || precision > 0) { - buf.append('.'); - } + } - if ((flags & FLAG_PRECISION) == 0) { - precision = -1; - } + bytes.append(fchar == 'a' ? 'p' : 'P'); + if (exponent >= 0) { + bytes.append('+'); + } + bytes.append(Long.toString(exponent).getBytes()); - while ((precision < 0 && i < digits) || precision > 0) { - digit = getDigit(i++, mantissaBytes); - buf.append((fchar == 'a' ? HEX_DIGITS : HEX_DIGITS_UPPER_CASE)[digit]); - precision--; + int bytesLength = bytes.length(); // We know numbers will be 7 bit ascii. + if (width > bytesLength) { + if ((flags & FLAG_MINUS) == 0) { + if ((flags & FLAG_PRECISION) != 0 || ((flags & FLAG_ZERO) != 0)) { + buf.fill('0', width - bytesLength); + } else { + buf.fill(' ', width - bytesLength); } } + } - buf.append(fchar == 'a' ? 'p' : 'P'); - if (exponent >= 0) { - buf.append('+'); - } - buf.append(Long.toString(exponent).getBytes()); - offset++; - incomplete = false; - break; + buf.append(bytes); + + if (width > bytesLength && ((flags & FLAG_MINUS) != 0)) { + buf.fill(' ', width - bytesLength); + } + + offset++; + incomplete = false; + break; } case 'p': case 's': { // format_s: diff --git a/spec/tags/ruby/core/file/printf_tags.txt b/spec/tags/ruby/core/file/printf_tags.txt index 4f055b5c4cc..4aebf2d0e21 100644 --- a/spec/tags/ruby/core/file/printf_tags.txt +++ b/spec/tags/ruby/core/file/printf_tags.txt @@ -1,22 +1,8 @@ fails:File#printf integer formats u converts argument as a decimal number -fails:File#printf flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers -fails:File#printf flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers -fails:File#printf flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one -fails:File#printf flags (digit)$ specifies the absolute argument number for this field fails:File#printf flags # applies to format o does nothing for negative argument -fails:File#printf flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow fails:File#printf flags # applies to gG does not remove trailing zeros -fails:File#printf flags + applies to numeric formats bBdiouxXaAeEfgG adds a leading plus sign to non-negative numbers -fails:File#printf flags - left-justifies the result of conversion if width is specified fails:File#printf flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces -fails:File#printf flags * uses the previous argument as the field width -fails:File#printf flags * left-justifies the result if width is negative -fails:File#printf flags * uses the specified argument as the width if * is followed by a number and $ -fails:File#printf flags * left-justifies the result if specified with $ argument is negative fails:File#printf flags * raises ArgumentError when is mixed with width -fails:File#printf width specifies the minimum number of characters that will be written to the result -fails:File#printf precision float types controls the number of decimal places displayed in fraction part fails:File#printf reference by name %{name} style supports flags, width and precision fails:File#printf other formats c raises TypeError if argument is nil -fails:File#printf other formats c raises TypeError if converting to String with to_str returns non-String fails:File#printf other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/core/kernel/printf_tags.txt b/spec/tags/ruby/core/kernel/printf_tags.txt index 4d23faea5d4..a9b52062103 100644 --- a/spec/tags/ruby/core/kernel/printf_tags.txt +++ b/spec/tags/ruby/core/kernel/printf_tags.txt @@ -1,44 +1,16 @@ fails:Kernel.printf formatting io is specified integer formats u converts argument as a decimal number -fails:Kernel.printf formatting io is specified flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers -fails:Kernel.printf formatting io is specified flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers -fails:Kernel.printf formatting io is specified flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one -fails:Kernel.printf formatting io is specified flags (digit)$ specifies the absolute argument number for this field fails:Kernel.printf formatting io is specified flags # applies to format o does nothing for negative argument -fails:Kernel.printf formatting io is specified flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow fails:Kernel.printf formatting io is specified flags # applies to gG does not remove trailing zeros -fails:Kernel.printf formatting io is specified flags + applies to numeric formats bBdiouxXaAeEfgG adds a leading plus sign to non-negative numbers -fails:Kernel.printf formatting io is specified flags - left-justifies the result of conversion if width is specified fails:Kernel.printf formatting io is specified flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces -fails:Kernel.printf formatting io is specified flags * uses the previous argument as the field width -fails:Kernel.printf formatting io is specified flags * left-justifies the result if width is negative -fails:Kernel.printf formatting io is specified flags * uses the specified argument as the width if * is followed by a number and $ -fails:Kernel.printf formatting io is specified flags * left-justifies the result if specified with $ argument is negative fails:Kernel.printf formatting io is specified flags * raises ArgumentError when is mixed with width -fails:Kernel.printf formatting io is specified width specifies the minimum number of characters that will be written to the result -fails:Kernel.printf formatting io is specified precision float types controls the number of decimal places displayed in fraction part fails:Kernel.printf formatting io is specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is not specified integer formats u converts argument as a decimal number -fails:Kernel.printf formatting io is not specified flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers -fails:Kernel.printf formatting io is not specified flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers -fails:Kernel.printf formatting io is not specified flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one -fails:Kernel.printf formatting io is not specified flags (digit)$ specifies the absolute argument number for this field fails:Kernel.printf formatting io is not specified flags # applies to format o does nothing for negative argument -fails:Kernel.printf formatting io is not specified flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow fails:Kernel.printf formatting io is not specified flags # applies to gG does not remove trailing zeros -fails:Kernel.printf formatting io is not specified flags + applies to numeric formats bBdiouxXaAeEfgG adds a leading plus sign to non-negative numbers -fails:Kernel.printf formatting io is not specified flags - left-justifies the result of conversion if width is specified fails:Kernel.printf formatting io is not specified flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces -fails:Kernel.printf formatting io is not specified flags * uses the previous argument as the field width -fails:Kernel.printf formatting io is not specified flags * left-justifies the result if width is negative -fails:Kernel.printf formatting io is not specified flags * uses the specified argument as the width if * is followed by a number and $ -fails:Kernel.printf formatting io is not specified flags * left-justifies the result if specified with $ argument is negative fails:Kernel.printf formatting io is not specified flags * raises ArgumentError when is mixed with width -fails:Kernel.printf formatting io is not specified width specifies the minimum number of characters that will be written to the result -fails:Kernel.printf formatting io is not specified precision float types controls the number of decimal places displayed in fraction part fails:Kernel.printf formatting io is not specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is specified other formats c raises TypeError if argument is nil -fails:Kernel.printf formatting io is specified other formats c raises TypeError if converting to String with to_str returns non-String fails:Kernel.printf formatting io is specified other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:Kernel.printf formatting io is not specified other formats c raises TypeError if argument is nil -fails:Kernel.printf formatting io is not specified other formats c raises TypeError if converting to String with to_str returns non-String fails:Kernel.printf formatting io is not specified other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/core/kernel/sprintf_tags.txt b/spec/tags/ruby/core/kernel/sprintf_tags.txt index 8def9004a9b..a428031c037 100644 --- a/spec/tags/ruby/core/kernel/sprintf_tags.txt +++ b/spec/tags/ruby/core/kernel/sprintf_tags.txt @@ -1,47 +1,19 @@ fails(compiler):Kernel#sprintf passes some tests for negative %u fails:Kernel#sprintf integer formats u converts argument as a decimal number -fails:Kernel#sprintf flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers -fails:Kernel#sprintf flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers -fails:Kernel#sprintf flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one -fails:Kernel#sprintf flags (digit)$ specifies the absolute argument number for this field fails:Kernel#sprintf flags # applies to format o does nothing for negative argument -fails:Kernel#sprintf flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow fails:Kernel#sprintf flags # applies to gG does not remove trailing zeros -fails:Kernel#sprintf flags + applies to numeric formats bBdiouxXaAeEfgG adds a leading plus sign to non-negative numbers -fails:Kernel#sprintf flags - left-justifies the result of conversion if width is specified fails:Kernel#sprintf flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces -fails:Kernel#sprintf flags * uses the previous argument as the field width -fails:Kernel#sprintf flags * left-justifies the result if width is negative -fails:Kernel#sprintf flags * uses the specified argument as the width if * is followed by a number and $ -fails:Kernel#sprintf flags * left-justifies the result if specified with $ argument is negative fails:Kernel#sprintf flags * raises ArgumentError when is mixed with width -fails:Kernel#sprintf width specifies the minimum number of characters that will be written to the result -fails:Kernel#sprintf precision float types controls the number of decimal places displayed in fraction part fails:Kernel#sprintf reference by name %{name} style supports flags, width and precision fails:Kernel.sprintf integer formats u converts argument as a decimal number -fails:Kernel.sprintf flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers -fails:Kernel.sprintf flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers -fails:Kernel.sprintf flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one -fails:Kernel.sprintf flags (digit)$ specifies the absolute argument number for this field fails:Kernel.sprintf flags # applies to format o does nothing for negative argument -fails:Kernel.sprintf flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow fails:Kernel.sprintf flags # applies to gG does not remove trailing zeros -fails:Kernel.sprintf flags + applies to numeric formats bBdiouxXaAeEfgG adds a leading plus sign to non-negative numbers -fails:Kernel.sprintf flags - left-justifies the result of conversion if width is specified fails:Kernel.sprintf flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces -fails:Kernel.sprintf flags * uses the previous argument as the field width -fails:Kernel.sprintf flags * left-justifies the result if width is negative -fails:Kernel.sprintf flags * uses the specified argument as the width if * is followed by a number and $ -fails:Kernel.sprintf flags * left-justifies the result if specified with $ argument is negative fails:Kernel.sprintf flags * raises ArgumentError when is mixed with width -fails:Kernel.sprintf width specifies the minimum number of characters that will be written to the result -fails:Kernel.sprintf precision float types controls the number of decimal places displayed in fraction part fails:Kernel.sprintf reference by name %{name} style supports flags, width and precision fails:Kernel#sprintf other formats c raises TypeError if argument is nil -fails:Kernel#sprintf other formats c raises TypeError if converting to String with to_str returns non-String fails:Kernel#sprintf other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:Kernel.sprintf other formats c raises TypeError if argument is nil -fails:Kernel.sprintf other formats c raises TypeError if converting to String with to_str returns non-String fails:Kernel.sprintf other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:Kernel#sprintf %c raises error when a codepoint isn't representable in an encoding of a format string fails:Kernel.sprintf %c raises error when a codepoint isn't representable in an encoding of a format string diff --git a/spec/tags/ruby/core/string/modulo_tags.txt b/spec/tags/ruby/core/string/modulo_tags.txt index 78604dec7e0..ee0909e3c89 100644 --- a/spec/tags/ruby/core/string/modulo_tags.txt +++ b/spec/tags/ruby/core/string/modulo_tags.txt @@ -1,23 +1,9 @@ fails:String#% integer formats u converts argument as a decimal number -fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers -fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers -fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one -fails:String#% flags (digit)$ specifies the absolute argument number for this field fails:String#% flags # applies to format o does nothing for negative argument -fails:String#% flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow fails:String#% flags # applies to gG does not remove trailing zeros -fails:String#% flags + applies to numeric formats bBdiouxXaAeEfgG adds a leading plus sign to non-negative numbers -fails:String#% flags - left-justifies the result of conversion if width is specified fails:String#% flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces -fails:String#% flags * uses the previous argument as the field width -fails:String#% flags * left-justifies the result if width is negative -fails:String#% flags * uses the specified argument as the width if * is followed by a number and $ -fails:String#% flags * left-justifies the result if specified with $ argument is negative fails:String#% flags * raises ArgumentError when is mixed with width -fails:String#% width specifies the minimum number of characters that will be written to the result -fails:String#% precision float types controls the number of decimal places displayed in fraction part fails:String#% reference by name %{name} style supports flags, width and precision fails:String#% other formats c raises TypeError if argument is nil -fails:String#% other formats c raises TypeError if converting to String with to_str returns non-String fails:String#% other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:String#% %c raises error when a codepoint isn't representable in an encoding of a format string diff --git a/spec/tags/ruby/core/string/percent_tags.txt b/spec/tags/ruby/core/string/percent_tags.txt deleted file mode 100644 index 2991d17bec2..00000000000 --- a/spec/tags/ruby/core/string/percent_tags.txt +++ /dev/null @@ -1,21 +0,0 @@ -fails:String#% integer formats u converts argument as a decimal number -fails:String#% float formats g otherwise cuts fraction part to have only 6 digits at all -fails:String#% float formats G otherwise cuts fraction part to have only 6 digits at all -fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers -fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers -fails:String#% flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one -fails:String#% flags (digit)$ specifies the absolute argument number for this field -fails:String#% flags # applies to format o does nothing for negative argument -fails:String#% flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow -fails:String#% flags # applies to gG does not remove trailing zeros -fails:String#% flags + applies to numeric formats bBdiouxXaAeEfgG adds a leading plus sign to non-negative numbers -fails:String#% flags - left-justifies the result of conversion if width is specified -fails:String#% flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces -fails:String#% flags * uses the previous argument as the field width -fails:String#% flags * left-justifies the result if width is negative -fails:String#% flags * uses the specified argument as the width if * is followed by a number and $ -fails:String#% flags * left-justifies the result if specified with $ argument is negative -fails:String#% flags * raises ArgumentError when is mixed with width -fails:String#% width specifies the minimum number of characters that will be written to the result -fails:String#% precision float types controls the number of decimal places displayed in fraction part -fails:String#% reference by name %{name} style supports flags, width and precision diff --git a/spec/tags/ruby/library/stringio/printf_tags.txt b/spec/tags/ruby/library/stringio/printf_tags.txt index caae8616f09..f0480a515e2 100644 --- a/spec/tags/ruby/library/stringio/printf_tags.txt +++ b/spec/tags/ruby/library/stringio/printf_tags.txt @@ -1,22 +1,8 @@ fails:StringIO#printf formatting integer formats u converts argument as a decimal number -fails:StringIO#printf formatting flags space applies to numeric formats bBdiouxXeEfgGaA leaves a space at the start of non-negative numbers -fails:StringIO#printf formatting flags space applies to numeric formats bBdiouxXeEfgGaA does not leave a space at the start of negative numbers -fails:StringIO#printf formatting flags space applies to numeric formats bBdiouxXeEfgGaA treats several white spaces as one -fails:StringIO#printf formatting flags (digit)$ specifies the absolute argument number for this field fails:StringIO#printf formatting flags # applies to format o does nothing for negative argument -fails:StringIO#printf formatting flags # applies to formats aAeEfgG forces a decimal point to be added, even if no digits follow fails:StringIO#printf formatting flags # applies to gG does not remove trailing zeros -fails:StringIO#printf formatting flags + applies to numeric formats bBdiouxXaAeEfgG adds a leading plus sign to non-negative numbers -fails:StringIO#printf formatting flags - left-justifies the result of conversion if width is specified fails:StringIO#printf formatting flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces -fails:StringIO#printf formatting flags * uses the previous argument as the field width -fails:StringIO#printf formatting flags * left-justifies the result if width is negative -fails:StringIO#printf formatting flags * uses the specified argument as the width if * is followed by a number and $ -fails:StringIO#printf formatting flags * left-justifies the result if specified with $ argument is negative fails:StringIO#printf formatting flags * raises ArgumentError when is mixed with width -fails:StringIO#printf formatting width specifies the minimum number of characters that will be written to the result -fails:StringIO#printf formatting precision float types controls the number of decimal places displayed in fraction part fails:StringIO#printf formatting reference by name %{name} style supports flags, width and precision fails:StringIO#printf formatting other formats c raises TypeError if argument is nil -fails:StringIO#printf formatting other formats c raises TypeError if converting to String with to_str returns non-String fails:StringIO#printf formatting other formats c raises TypeError if converting to Integer with to_int returns non-Integer From 74d7599bdad109a798d4b7b8d5b11cf8dc6d9e45 Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Tue, 31 Oct 2023 16:46:10 -0400 Subject: [PATCH 03/11] really hacky fixes (this is going to get replaced in future anyways) for %aA --- .../src/main/java/org/jruby/util/Sprintf.java | 46 +++++++++++++------ spec/tags/ruby/core/file/printf_tags.txt | 1 - spec/tags/ruby/core/kernel/printf_tags.txt | 2 - spec/tags/ruby/core/kernel/sprintf_tags.txt | 2 - .../ruby/library/stringio/printf_tags.txt | 1 - 5 files changed, 32 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index 3395f9f3473..399e6144454 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -629,15 +629,18 @@ else if ((flags & FLAG_MINUS) != 0) { long exponent = getExponent(arg); final byte[] mantissaBytes = getMantissaBytes(arg); - if (!positive) { - bytes.append('-'); - } else if ((flags & FLAG_PLUS) != 0) { - bytes.append('+'); - } else if ((flags & FLAG_SPACE) != 0) { - bytes.append(' '); + if ((flags & FLAG_MINUS) != 0) { + if (!positive) { + bytes.append('-'); + } else if ((flags & FLAG_PLUS) != 0) { + bytes.append('+'); + } else if ((flags & FLAG_SPACE) != 0) { + bytes.append(' '); + } + + bytes.append('0'); + bytes.append(fchar == 'a' ? 'x' : 'X'); } - bytes.append('0'); - bytes.append(fchar == 'a' ? 'x' : 'X'); if (mantissaBytes[0] == 0) { exponent = 0; bytes.append('0'); @@ -679,14 +682,29 @@ else if ((flags & FLAG_MINUS) != 0) { bytes.append(Long.toString(exponent).getBytes()); int bytesLength = bytes.length(); // We know numbers will be 7 bit ascii. + if ((flags & FLAG_MINUS) == 0) { + if (!positive) { + buf.append('-'); + } else if ((flags & FLAG_PLUS) != 0) { + buf.append('+'); + } else if ((flags & FLAG_SPACE) != 0) { + buf.append(' '); + } + } + if (width > bytesLength) { - if ((flags & FLAG_MINUS) == 0) { - if ((flags & FLAG_PRECISION) != 0 || ((flags & FLAG_ZERO) != 0)) { - buf.fill('0', width - bytesLength); - } else { - buf.fill(' ', width - bytesLength); - } + if ((flags & FLAG_ZERO) != 0) { + buf.append('0'); + buf.append(fchar == 'a' ? 'x' : 'X'); + buf.fill('0', width - bytesLength - 2); + } else { + buf.fill(' ', width - bytesLength - 2); + buf.append('0'); + buf.append(fchar == 'a' ? 'x' : 'X'); } + } else if ((flags & FLAG_MINUS) == 0) { + buf.append('0'); + buf.append(fchar == 'a' ? 'x' : 'X'); } buf.append(bytes); diff --git a/spec/tags/ruby/core/file/printf_tags.txt b/spec/tags/ruby/core/file/printf_tags.txt index 4aebf2d0e21..8ced7cbddfd 100644 --- a/spec/tags/ruby/core/file/printf_tags.txt +++ b/spec/tags/ruby/core/file/printf_tags.txt @@ -1,7 +1,6 @@ fails:File#printf integer formats u converts argument as a decimal number fails:File#printf flags # applies to format o does nothing for negative argument fails:File#printf flags # applies to gG does not remove trailing zeros -fails:File#printf flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces fails:File#printf flags * raises ArgumentError when is mixed with width fails:File#printf reference by name %{name} style supports flags, width and precision fails:File#printf other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/core/kernel/printf_tags.txt b/spec/tags/ruby/core/kernel/printf_tags.txt index a9b52062103..9aa233160cc 100644 --- a/spec/tags/ruby/core/kernel/printf_tags.txt +++ b/spec/tags/ruby/core/kernel/printf_tags.txt @@ -1,13 +1,11 @@ fails:Kernel.printf formatting io is specified integer formats u converts argument as a decimal number fails:Kernel.printf formatting io is specified flags # applies to format o does nothing for negative argument fails:Kernel.printf formatting io is specified flags # applies to gG does not remove trailing zeros -fails:Kernel.printf formatting io is specified flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces fails:Kernel.printf formatting io is specified flags * raises ArgumentError when is mixed with width fails:Kernel.printf formatting io is specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is not specified integer formats u converts argument as a decimal number fails:Kernel.printf formatting io is not specified flags # applies to format o does nothing for negative argument fails:Kernel.printf formatting io is not specified flags # applies to gG does not remove trailing zeros -fails:Kernel.printf formatting io is not specified flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces fails:Kernel.printf formatting io is not specified flags * raises ArgumentError when is mixed with width fails:Kernel.printf formatting io is not specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is specified other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/core/kernel/sprintf_tags.txt b/spec/tags/ruby/core/kernel/sprintf_tags.txt index a428031c037..c33a2c0a60d 100644 --- a/spec/tags/ruby/core/kernel/sprintf_tags.txt +++ b/spec/tags/ruby/core/kernel/sprintf_tags.txt @@ -2,13 +2,11 @@ fails(compiler):Kernel#sprintf passes some tests for negative %u fails:Kernel#sprintf integer formats u converts argument as a decimal number fails:Kernel#sprintf flags # applies to format o does nothing for negative argument fails:Kernel#sprintf flags # applies to gG does not remove trailing zeros -fails:Kernel#sprintf flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces fails:Kernel#sprintf flags * raises ArgumentError when is mixed with width fails:Kernel#sprintf reference by name %{name} style supports flags, width and precision fails:Kernel.sprintf integer formats u converts argument as a decimal number fails:Kernel.sprintf flags # applies to format o does nothing for negative argument fails:Kernel.sprintf flags # applies to gG does not remove trailing zeros -fails:Kernel.sprintf flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces fails:Kernel.sprintf flags * raises ArgumentError when is mixed with width fails:Kernel.sprintf reference by name %{name} style supports flags, width and precision fails:Kernel#sprintf other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/library/stringio/printf_tags.txt b/spec/tags/ruby/library/stringio/printf_tags.txt index f0480a515e2..1e0745e46ab 100644 --- a/spec/tags/ruby/library/stringio/printf_tags.txt +++ b/spec/tags/ruby/library/stringio/printf_tags.txt @@ -1,7 +1,6 @@ fails:StringIO#printf formatting integer formats u converts argument as a decimal number fails:StringIO#printf formatting flags # applies to format o does nothing for negative argument fails:StringIO#printf formatting flags # applies to gG does not remove trailing zeros -fails:StringIO#printf formatting flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces fails:StringIO#printf formatting flags * raises ArgumentError when is mixed with width fails:StringIO#printf formatting reference by name %{name} style supports flags, width and precision fails:StringIO#printf formatting other formats c raises TypeError if argument is nil From ed535570acac43ec0f6baecd86e6d33e701e37e2 Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Wed, 1 Nov 2023 11:46:28 -0400 Subject: [PATCH 04/11] last %aA failures. double width should error --- .../src/main/java/org/jruby/util/Sprintf.java | 107 +++++++++--------- spec/tags/ruby/core/file/printf_tags.txt | 1 - spec/tags/ruby/core/kernel/printf_tags.txt | 2 - spec/tags/ruby/core/string/modulo_tags.txt | 2 - .../ruby/library/stringio/printf_tags.txt | 1 - 5 files changed, 55 insertions(+), 58 deletions(-) diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index 399e6144454..cafa65d12d5 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -460,7 +460,7 @@ private static void rubySprintfToBuffer(final ByteList buf, final CharSequence c offset++; break; } - // CHECK_FOR_WIDTH(flags); + if ((flags & FLAG_WIDTH) != 0) raiseArgumentError(args,"width given twice"); width = number; flags |= FLAG_WIDTH; break; @@ -509,9 +509,7 @@ private static void rubySprintfToBuffer(final ByteList buf, final CharSequence c } case '*': - if ((flags & FLAG_WIDTH) != 0) { - raiseArgumentError(args,"width given twice"); - } + if ((flags & FLAG_WIDTH) != 0) raiseArgumentError(args,"width given twice"); flags |= FLAG_WIDTH; int[] p_width = GETASTER(args, format, offset, length, true); offset = p_width[0]; width = p_width[1]; @@ -626,9 +624,6 @@ else if ((flags & FLAG_MINUS) != 0) { break; } - long exponent = getExponent(arg); - final byte[] mantissaBytes = getMantissaBytes(arg); - if ((flags & FLAG_MINUS) != 0) { if (!positive) { bytes.append('-'); @@ -641,47 +636,9 @@ else if ((flags & FLAG_MINUS) != 0) { bytes.append('0'); bytes.append(fchar == 'a' ? 'x' : 'X'); } - if (mantissaBytes[0] == 0) { - exponent = 0; - bytes.append('0'); - if (precision > 0 || (flags & FLAG_SPACE) != 0) { - bytes.append('.'); - while (precision > 0) { - bytes.append('0'); - precision--; - } - } - } else { - int i = 0; - int digit = getDigit(i++, mantissaBytes); - if (digit == 0) { - digit = getDigit(i++, mantissaBytes); - } - assert digit == 1; - bytes.append('1'); - int digits = getNumberOfDigits(mantissaBytes); - if (i < digits || (flags & FLAG_SPACE) != 0 || precision > 0) { - bytes.append('.'); - } - - if ((flags & FLAG_PRECISION) == 0) { - precision = -1; - } - - while ((precision < 0 && i < digits) || precision > 0) { - digit = getDigit(i++, mantissaBytes); - bytes.append((fchar == 'a' ? HEX_DIGITS : HEX_DIGITS_UPPER_CASE)[digit]); - precision--; - } - } - - bytes.append(fchar == 'a' ? 'p' : 'P'); - if (exponent >= 0) { - bytes.append('+'); - } - bytes.append(Long.toString(exponent).getBytes()); - + precision = generateBinaryFloat(flags, precision, fchar, bytes, arg); int bytesLength = bytes.length(); // We know numbers will be 7 bit ascii. + if ((flags & FLAG_MINUS) == 0) { if (!positive) { buf.append('-'); @@ -692,19 +649,19 @@ else if ((flags & FLAG_MINUS) != 0) { } } - if (width > bytesLength) { + if ((flags & FLAG_MINUS) == 0 && width <= bytesLength) { + buf.append('0'); + buf.append(fchar == 'a' ? 'x' : 'X'); + } else if (width > bytesLength) { if ((flags & FLAG_ZERO) != 0) { buf.append('0'); buf.append(fchar == 'a' ? 'x' : 'X'); buf.fill('0', width - bytesLength - 2); - } else { + } else if ((flags & FLAG_MINUS) == 0) { buf.fill(' ', width - bytesLength - 2); buf.append('0'); buf.append(fchar == 'a' ? 'x' : 'X'); } - } else if ((flags & FLAG_MINUS) == 0) { - buf.append('0'); - buf.append(fchar == 'a' ? 'x' : 'X'); } buf.append(bytes); @@ -1561,6 +1518,52 @@ else if (precision > 0) { } } + private static int generateBinaryFloat(int flags, int precision, byte fchar, ByteList bytes, IRubyObject arg) { + long exponent = getExponent(arg); + final byte[] mantissaBytes = getMantissaBytes(arg); + + if (mantissaBytes[0] == 0) { + exponent = 0; + bytes.append('0'); + if (precision > 0 || (flags & FLAG_SPACE) != 0) { + bytes.append('.'); + while (precision > 0) { + bytes.append('0'); + precision--; + } + } + } else { + int i = 0; + int digit = getDigit(i++, mantissaBytes); + if (digit == 0) { + digit = getDigit(i++, mantissaBytes); + } + assert digit == 1; + bytes.append('1'); + int digits = getNumberOfDigits(mantissaBytes); + if (i < digits || (flags & FLAG_SPACE) != 0 || precision > 0) { + bytes.append('.'); + } + + if ((flags & FLAG_PRECISION) == 0) { + precision = -1; + } + + while ((precision < 0 && i < digits) || precision > 0) { + digit = getDigit(i++, mantissaBytes); + bytes.append((fchar == 'a' ? HEX_DIGITS : HEX_DIGITS_UPPER_CASE)[digit]); + precision--; + } + } + + bytes.append(fchar == 'a' ? 'p' : 'P'); + if (exponent >= 0) { + bytes.append('+'); + } + bytes.append(Long.toString(exponent).getBytes()); + return precision; + } + // prints nan or inf private static void printSpecialValue(ByteList buf, int flags, int width, boolean isnan, boolean negative) { byte sign; diff --git a/spec/tags/ruby/core/file/printf_tags.txt b/spec/tags/ruby/core/file/printf_tags.txt index 8ced7cbddfd..b25664b380f 100644 --- a/spec/tags/ruby/core/file/printf_tags.txt +++ b/spec/tags/ruby/core/file/printf_tags.txt @@ -1,7 +1,6 @@ fails:File#printf integer formats u converts argument as a decimal number fails:File#printf flags # applies to format o does nothing for negative argument fails:File#printf flags # applies to gG does not remove trailing zeros -fails:File#printf flags * raises ArgumentError when is mixed with width fails:File#printf reference by name %{name} style supports flags, width and precision fails:File#printf other formats c raises TypeError if argument is nil fails:File#printf other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/core/kernel/printf_tags.txt b/spec/tags/ruby/core/kernel/printf_tags.txt index 9aa233160cc..1c6ac3eab30 100644 --- a/spec/tags/ruby/core/kernel/printf_tags.txt +++ b/spec/tags/ruby/core/kernel/printf_tags.txt @@ -1,12 +1,10 @@ fails:Kernel.printf formatting io is specified integer formats u converts argument as a decimal number fails:Kernel.printf formatting io is specified flags # applies to format o does nothing for negative argument fails:Kernel.printf formatting io is specified flags # applies to gG does not remove trailing zeros -fails:Kernel.printf formatting io is specified flags * raises ArgumentError when is mixed with width fails:Kernel.printf formatting io is specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is not specified integer formats u converts argument as a decimal number fails:Kernel.printf formatting io is not specified flags # applies to format o does nothing for negative argument fails:Kernel.printf formatting io is not specified flags # applies to gG does not remove trailing zeros -fails:Kernel.printf formatting io is not specified flags * raises ArgumentError when is mixed with width fails:Kernel.printf formatting io is not specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is specified other formats c raises TypeError if argument is nil fails:Kernel.printf formatting io is specified other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/core/string/modulo_tags.txt b/spec/tags/ruby/core/string/modulo_tags.txt index ee0909e3c89..0da41863911 100644 --- a/spec/tags/ruby/core/string/modulo_tags.txt +++ b/spec/tags/ruby/core/string/modulo_tags.txt @@ -1,8 +1,6 @@ fails:String#% integer formats u converts argument as a decimal number fails:String#% flags # applies to format o does nothing for negative argument fails:String#% flags # applies to gG does not remove trailing zeros -fails:String#% flags 0 (zero) applies to numeric formats bBdiouxXaAeEfgG and width is specified pads with zeros, not spaces -fails:String#% flags * raises ArgumentError when is mixed with width fails:String#% reference by name %{name} style supports flags, width and precision fails:String#% other formats c raises TypeError if argument is nil fails:String#% other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/library/stringio/printf_tags.txt b/spec/tags/ruby/library/stringio/printf_tags.txt index 1e0745e46ab..74d1558c31b 100644 --- a/spec/tags/ruby/library/stringio/printf_tags.txt +++ b/spec/tags/ruby/library/stringio/printf_tags.txt @@ -1,7 +1,6 @@ fails:StringIO#printf formatting integer formats u converts argument as a decimal number fails:StringIO#printf formatting flags # applies to format o does nothing for negative argument fails:StringIO#printf formatting flags # applies to gG does not remove trailing zeros -fails:StringIO#printf formatting flags * raises ArgumentError when is mixed with width fails:StringIO#printf formatting reference by name %{name} style supports flags, width and precision fails:StringIO#printf formatting other formats c raises TypeError if argument is nil fails:StringIO#printf formatting other formats c raises TypeError if converting to Integer with to_int returns non-Integer From fde114dc5169746a493eac12e96c239de0dc7f22 Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Thu, 2 Nov 2023 13:17:46 -0400 Subject: [PATCH 05/11] %o does not 0-prefix with negative values --- core/src/main/java/org/jruby/util/Sprintf.java | 2 +- spec/tags/ruby/core/file/printf_tags.txt | 1 - spec/tags/ruby/core/kernel/printf_tags.txt | 2 -- spec/tags/ruby/core/string/modulo_tags.txt | 1 - spec/tags/ruby/library/stringio/printf_tags.txt | 1 - 5 files changed, 1 insertion(+), 6 deletions(-) diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index cafa65d12d5..e48c58e6087 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -804,7 +804,7 @@ else if ((flags & FLAG_MINUS) != 0) { if ((flags & FLAG_SHARP) != 0) { if (!zero || usePrefixForZero) { switch (fchar) { - case 'o': prefix = PREFIX_OCTAL; break; + case 'o': if (!negative) prefix = PREFIX_OCTAL; break; case 'x': prefix = PREFIX_HEX_LC; break; case 'X': prefix = PREFIX_HEX_UC; break; case 'b': prefix = PREFIX_BINARY_LC; break; diff --git a/spec/tags/ruby/core/file/printf_tags.txt b/spec/tags/ruby/core/file/printf_tags.txt index b25664b380f..7f57c10f430 100644 --- a/spec/tags/ruby/core/file/printf_tags.txt +++ b/spec/tags/ruby/core/file/printf_tags.txt @@ -1,5 +1,4 @@ fails:File#printf integer formats u converts argument as a decimal number -fails:File#printf flags # applies to format o does nothing for negative argument fails:File#printf flags # applies to gG does not remove trailing zeros fails:File#printf reference by name %{name} style supports flags, width and precision fails:File#printf other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/core/kernel/printf_tags.txt b/spec/tags/ruby/core/kernel/printf_tags.txt index 1c6ac3eab30..7cf9748642b 100644 --- a/spec/tags/ruby/core/kernel/printf_tags.txt +++ b/spec/tags/ruby/core/kernel/printf_tags.txt @@ -1,9 +1,7 @@ fails:Kernel.printf formatting io is specified integer formats u converts argument as a decimal number -fails:Kernel.printf formatting io is specified flags # applies to format o does nothing for negative argument fails:Kernel.printf formatting io is specified flags # applies to gG does not remove trailing zeros fails:Kernel.printf formatting io is specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is not specified integer formats u converts argument as a decimal number -fails:Kernel.printf formatting io is not specified flags # applies to format o does nothing for negative argument fails:Kernel.printf formatting io is not specified flags # applies to gG does not remove trailing zeros fails:Kernel.printf formatting io is not specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is specified other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/core/string/modulo_tags.txt b/spec/tags/ruby/core/string/modulo_tags.txt index 0da41863911..f72a088d0f8 100644 --- a/spec/tags/ruby/core/string/modulo_tags.txt +++ b/spec/tags/ruby/core/string/modulo_tags.txt @@ -1,5 +1,4 @@ fails:String#% integer formats u converts argument as a decimal number -fails:String#% flags # applies to format o does nothing for negative argument fails:String#% flags # applies to gG does not remove trailing zeros fails:String#% reference by name %{name} style supports flags, width and precision fails:String#% other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/library/stringio/printf_tags.txt b/spec/tags/ruby/library/stringio/printf_tags.txt index 74d1558c31b..fdf803ac224 100644 --- a/spec/tags/ruby/library/stringio/printf_tags.txt +++ b/spec/tags/ruby/library/stringio/printf_tags.txt @@ -1,5 +1,4 @@ fails:StringIO#printf formatting integer formats u converts argument as a decimal number -fails:StringIO#printf formatting flags # applies to format o does nothing for negative argument fails:StringIO#printf formatting flags # applies to gG does not remove trailing zeros fails:StringIO#printf formatting reference by name %{name} style supports flags, width and precision fails:StringIO#printf formatting other formats c raises TypeError if argument is nil From d628ce963c056fe4d68a484cd8834649e7d84f90 Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Thu, 2 Nov 2023 13:41:10 -0400 Subject: [PATCH 06/11] printing with neg value with %u was doing wrong neg value check --- core/src/main/java/org/jruby/util/Sprintf.java | 8 +------- spec/tags/ruby/core/kernel/printf_tags.txt | 2 -- spec/tags/ruby/core/kernel/sprintf_tags.txt | 6 ------ spec/tags/ruby/core/string/modulo_tags.txt | 1 - spec/tags/ruby/library/stringio/printf_tags.txt | 1 - 5 files changed, 1 insertion(+), 17 deletions(-) diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index e48c58e6087..7844e78466f 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -1836,13 +1836,7 @@ private static byte[] getBignumBytes(final BigInteger val, int base, boolean sig } private static byte[] getUnsignedNegativeBytes(final long val) { - // calculation for negatives when %u specified - // for values >= Integer.MIN_VALUE * 2, MRI uses (the equivalent of) - // long neg_u = (((long)Integer.MAX_VALUE + 1) << 1) + val - // for smaller values, BigInteger math is required to conform to MRI's - // result. - // relatively cheap test for 32-bit values - if (val >= Long.MIN_VALUE << 1) { + if (val < 0) { return ConvertBytes.longToCharBytes(((Long.MAX_VALUE + 1L) << 1) + val); } return getUnsignedNegativeBytes(BigInteger.valueOf(val)); diff --git a/spec/tags/ruby/core/kernel/printf_tags.txt b/spec/tags/ruby/core/kernel/printf_tags.txt index 7cf9748642b..e2ac608ae47 100644 --- a/spec/tags/ruby/core/kernel/printf_tags.txt +++ b/spec/tags/ruby/core/kernel/printf_tags.txt @@ -1,7 +1,5 @@ -fails:Kernel.printf formatting io is specified integer formats u converts argument as a decimal number fails:Kernel.printf formatting io is specified flags # applies to gG does not remove trailing zeros fails:Kernel.printf formatting io is specified reference by name %{name} style supports flags, width and precision -fails:Kernel.printf formatting io is not specified integer formats u converts argument as a decimal number fails:Kernel.printf formatting io is not specified flags # applies to gG does not remove trailing zeros fails:Kernel.printf formatting io is not specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is specified other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/core/kernel/sprintf_tags.txt b/spec/tags/ruby/core/kernel/sprintf_tags.txt index c33a2c0a60d..1635c224089 100644 --- a/spec/tags/ruby/core/kernel/sprintf_tags.txt +++ b/spec/tags/ruby/core/kernel/sprintf_tags.txt @@ -1,13 +1,7 @@ fails(compiler):Kernel#sprintf passes some tests for negative %u -fails:Kernel#sprintf integer formats u converts argument as a decimal number -fails:Kernel#sprintf flags # applies to format o does nothing for negative argument fails:Kernel#sprintf flags # applies to gG does not remove trailing zeros -fails:Kernel#sprintf flags * raises ArgumentError when is mixed with width fails:Kernel#sprintf reference by name %{name} style supports flags, width and precision -fails:Kernel.sprintf integer formats u converts argument as a decimal number -fails:Kernel.sprintf flags # applies to format o does nothing for negative argument fails:Kernel.sprintf flags # applies to gG does not remove trailing zeros -fails:Kernel.sprintf flags * raises ArgumentError when is mixed with width fails:Kernel.sprintf reference by name %{name} style supports flags, width and precision fails:Kernel#sprintf other formats c raises TypeError if argument is nil fails:Kernel#sprintf other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/core/string/modulo_tags.txt b/spec/tags/ruby/core/string/modulo_tags.txt index f72a088d0f8..a1c7fcd0baa 100644 --- a/spec/tags/ruby/core/string/modulo_tags.txt +++ b/spec/tags/ruby/core/string/modulo_tags.txt @@ -1,4 +1,3 @@ -fails:String#% integer formats u converts argument as a decimal number fails:String#% flags # applies to gG does not remove trailing zeros fails:String#% reference by name %{name} style supports flags, width and precision fails:String#% other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/library/stringio/printf_tags.txt b/spec/tags/ruby/library/stringio/printf_tags.txt index fdf803ac224..e98dcff48a2 100644 --- a/spec/tags/ruby/library/stringio/printf_tags.txt +++ b/spec/tags/ruby/library/stringio/printf_tags.txt @@ -1,4 +1,3 @@ -fails:StringIO#printf formatting integer formats u converts argument as a decimal number fails:StringIO#printf formatting flags # applies to gG does not remove trailing zeros fails:StringIO#printf formatting reference by name %{name} style supports flags, width and precision fails:StringIO#printf formatting other formats c raises TypeError if argument is nil From 8c3bce1bf402d6e82c9d4aff029fad3daeacbd48 Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Thu, 2 Nov 2023 14:01:51 -0400 Subject: [PATCH 07/11] %gG were not substracting precision it had already handled leading to doubling 0s with # modifier --- core/src/main/java/org/jruby/util/Sprintf.java | 1 + spec/tags/ruby/core/kernel/printf_tags.txt | 2 -- spec/tags/ruby/core/kernel/sprintf_tags.txt | 2 -- spec/tags/ruby/core/string/modulo_tags.txt | 1 - spec/tags/ruby/library/stringio/printf_tags.txt | 1 - 5 files changed, 1 insertion(+), 6 deletions(-) diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index 7844e78466f..a07068d94df 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -1327,6 +1327,7 @@ else if (precision > 0) { } if ((flags & FLAG_SHARP) != 0 && precision > 0) { buf.fill('0', precision); + precision = 0; } } if ((flags & FLAG_SHARP) != 0 && precision > 0) buf.fill('0', precision); diff --git a/spec/tags/ruby/core/kernel/printf_tags.txt b/spec/tags/ruby/core/kernel/printf_tags.txt index e2ac608ae47..c4156f5ddd0 100644 --- a/spec/tags/ruby/core/kernel/printf_tags.txt +++ b/spec/tags/ruby/core/kernel/printf_tags.txt @@ -1,6 +1,4 @@ -fails:Kernel.printf formatting io is specified flags # applies to gG does not remove trailing zeros fails:Kernel.printf formatting io is specified reference by name %{name} style supports flags, width and precision -fails:Kernel.printf formatting io is not specified flags # applies to gG does not remove trailing zeros fails:Kernel.printf formatting io is not specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is specified other formats c raises TypeError if argument is nil fails:Kernel.printf formatting io is specified other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/core/kernel/sprintf_tags.txt b/spec/tags/ruby/core/kernel/sprintf_tags.txt index 1635c224089..94691eb2c55 100644 --- a/spec/tags/ruby/core/kernel/sprintf_tags.txt +++ b/spec/tags/ruby/core/kernel/sprintf_tags.txt @@ -1,7 +1,5 @@ fails(compiler):Kernel#sprintf passes some tests for negative %u -fails:Kernel#sprintf flags # applies to gG does not remove trailing zeros fails:Kernel#sprintf reference by name %{name} style supports flags, width and precision -fails:Kernel.sprintf flags # applies to gG does not remove trailing zeros fails:Kernel.sprintf reference by name %{name} style supports flags, width and precision fails:Kernel#sprintf other formats c raises TypeError if argument is nil fails:Kernel#sprintf other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/core/string/modulo_tags.txt b/spec/tags/ruby/core/string/modulo_tags.txt index a1c7fcd0baa..66b4bfa65cf 100644 --- a/spec/tags/ruby/core/string/modulo_tags.txt +++ b/spec/tags/ruby/core/string/modulo_tags.txt @@ -1,4 +1,3 @@ -fails:String#% flags # applies to gG does not remove trailing zeros fails:String#% reference by name %{name} style supports flags, width and precision fails:String#% other formats c raises TypeError if argument is nil fails:String#% other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/library/stringio/printf_tags.txt b/spec/tags/ruby/library/stringio/printf_tags.txt index e98dcff48a2..fa72b7c0382 100644 --- a/spec/tags/ruby/library/stringio/printf_tags.txt +++ b/spec/tags/ruby/library/stringio/printf_tags.txt @@ -1,4 +1,3 @@ -fails:StringIO#printf formatting flags # applies to gG does not remove trailing zeros fails:StringIO#printf formatting reference by name %{name} style supports flags, width and precision fails:StringIO#printf formatting other formats c raises TypeError if argument is nil fails:StringIO#printf formatting other formats c raises TypeError if converting to Integer with to_int returns non-Integer From e93639cfd3747f026834d31494f4da219387380c Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Thu, 2 Nov 2023 16:15:15 -0400 Subject: [PATCH 08/11] if no specifier after printf %{name} format then process width/precision as if a string --- .../src/main/java/org/jruby/util/Sprintf.java | 48 ++++++++++++++++++- spec/tags/ruby/core/kernel/printf_tags.txt | 2 - spec/tags/ruby/core/kernel/sprintf_tags.txt | 2 - spec/tags/ruby/core/string/modulo_tags.txt | 1 - .../ruby/library/stringio/printf_tags.txt | 1 - 5 files changed, 46 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index a07068d94df..4cf12983427 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -502,8 +502,48 @@ private static void rubySprintfToBuffer(final ByteList buf, final CharSequence c if (nameEnd == nameStart) raiseArgumentError(args, ERR_MALFORMED_NAME); ByteList localName = new ByteList(format, nameStart, nameEnd - nameStart, encoding, false); - buf.append(args.getHashValue(localName, '{', '}').asString().getByteList()); - incomplete = false; + IRubyObject value = args.getHashValue(localName, '{', '}'); + RubyString str = (RubyString) TypeConverter.convertToType(value, runtime.getString(), "to_s"); + ByteList bytes = str.getByteList(); + + // peek to see if it has a format specifier. If not we need to handle it now. + if (offset < length && !isFormatSpecifier(format[offset]) || offset == length) { + int len = bytes.length(); + Encoding enc = RubyString.checkEncoding(runtime, buf, bytes); + if ((flags & (FLAG_PRECISION|FLAG_WIDTH)) != 0) { + int strLen = str.strLength(); + if ((flags & FLAG_PRECISION) != 0 && precision < strLen) { + strLen = precision; + len = StringSupport.nth(enc, bytes.getUnsafeBytes(), bytes.begin(), bytes.begin() + bytes.getRealSize(), precision); + if (len == -1) len = 0; // we might return -1 but MRI's rb_enc_nth does 0 for not-found + len = len - bytes.begin(); + } + /* need to adjust multi-byte string pos */ + if ((flags & FLAG_WIDTH) != 0 && width > strLen) { + width -= strLen; + if ((flags & FLAG_MINUS) == 0) { + buf.fill(' ', width); + width = 0; + } + buf.append(bytes.getUnsafeBytes(), bytes.begin(), len); + if ((flags & FLAG_MINUS) != 0) { + buf.fill(' ', width); + } + buf.setEncoding(enc); + + offset++; + incomplete = false; + break; + } + } else { + buf.append(bytes); + incomplete = false; + } + } else { + buf.append(bytes); + incomplete = false; + + } break; } @@ -1519,6 +1559,10 @@ else if (precision > 0) { } } + private static boolean isFormatSpecifier(byte b) { + return "aAbBcdeEfgGiopsuxX".contains(""+b); + } + private static int generateBinaryFloat(int flags, int precision, byte fchar, ByteList bytes, IRubyObject arg) { long exponent = getExponent(arg); final byte[] mantissaBytes = getMantissaBytes(arg); diff --git a/spec/tags/ruby/core/kernel/printf_tags.txt b/spec/tags/ruby/core/kernel/printf_tags.txt index c4156f5ddd0..299a2436ed4 100644 --- a/spec/tags/ruby/core/kernel/printf_tags.txt +++ b/spec/tags/ruby/core/kernel/printf_tags.txt @@ -1,5 +1,3 @@ -fails:Kernel.printf formatting io is specified reference by name %{name} style supports flags, width and precision -fails:Kernel.printf formatting io is not specified reference by name %{name} style supports flags, width and precision fails:Kernel.printf formatting io is specified other formats c raises TypeError if argument is nil fails:Kernel.printf formatting io is specified other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:Kernel.printf formatting io is not specified other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/core/kernel/sprintf_tags.txt b/spec/tags/ruby/core/kernel/sprintf_tags.txt index 94691eb2c55..c823b1e4036 100644 --- a/spec/tags/ruby/core/kernel/sprintf_tags.txt +++ b/spec/tags/ruby/core/kernel/sprintf_tags.txt @@ -1,6 +1,4 @@ fails(compiler):Kernel#sprintf passes some tests for negative %u -fails:Kernel#sprintf reference by name %{name} style supports flags, width and precision -fails:Kernel.sprintf reference by name %{name} style supports flags, width and precision fails:Kernel#sprintf other formats c raises TypeError if argument is nil fails:Kernel#sprintf other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:Kernel.sprintf other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/core/string/modulo_tags.txt b/spec/tags/ruby/core/string/modulo_tags.txt index 66b4bfa65cf..1e2762df768 100644 --- a/spec/tags/ruby/core/string/modulo_tags.txt +++ b/spec/tags/ruby/core/string/modulo_tags.txt @@ -1,4 +1,3 @@ -fails:String#% reference by name %{name} style supports flags, width and precision fails:String#% other formats c raises TypeError if argument is nil fails:String#% other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:String#% %c raises error when a codepoint isn't representable in an encoding of a format string diff --git a/spec/tags/ruby/library/stringio/printf_tags.txt b/spec/tags/ruby/library/stringio/printf_tags.txt index fa72b7c0382..af7401c5aba 100644 --- a/spec/tags/ruby/library/stringio/printf_tags.txt +++ b/spec/tags/ruby/library/stringio/printf_tags.txt @@ -1,3 +1,2 @@ -fails:StringIO#printf formatting reference by name %{name} style supports flags, width and precision fails:StringIO#printf formatting other formats c raises TypeError if argument is nil fails:StringIO#printf formatting other formats c raises TypeError if converting to Integer with to_int returns non-Integer From d1ea659f7f37dd0b35ddbbd491f466bb4241e4f8 Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Thu, 2 Nov 2023 16:34:57 -0400 Subject: [PATCH 09/11] Fix goofy exact string match for typeerror for %c --- core/src/main/java/org/jruby/util/Sprintf.java | 3 ++- spec/tags/ruby/core/kernel/printf_tags.txt | 2 -- spec/tags/ruby/core/kernel/sprintf_tags.txt | 2 -- spec/tags/ruby/core/string/modulo_tags.txt | 1 - spec/tags/ruby/library/stringio/printf_tags.txt | 1 - 5 files changed, 2 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index 4cf12983427..271f45c8876 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -612,7 +612,8 @@ private static void rubySprintfToBuffer(final ByteList buf, final CharSequence c } else { // unsigned bits - c = (int) arg.convertToInteger().getLongValue() & 0xFFFFFFFF; + RubyInteger intValue = (RubyInteger) TypeConverter.convertToType(arg, runtime.getInteger(), "to_int"); + c = (int) intValue.getLongValue() & 0xFFFFFFFF; try { n = StringSupport.codeLength(encoding, c); } catch (EncodingException e) { diff --git a/spec/tags/ruby/core/kernel/printf_tags.txt b/spec/tags/ruby/core/kernel/printf_tags.txt index 299a2436ed4..1b78d59184a 100644 --- a/spec/tags/ruby/core/kernel/printf_tags.txt +++ b/spec/tags/ruby/core/kernel/printf_tags.txt @@ -1,4 +1,2 @@ fails:Kernel.printf formatting io is specified other formats c raises TypeError if argument is nil -fails:Kernel.printf formatting io is specified other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:Kernel.printf formatting io is not specified other formats c raises TypeError if argument is nil -fails:Kernel.printf formatting io is not specified other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/core/kernel/sprintf_tags.txt b/spec/tags/ruby/core/kernel/sprintf_tags.txt index c823b1e4036..b1e9882e46d 100644 --- a/spec/tags/ruby/core/kernel/sprintf_tags.txt +++ b/spec/tags/ruby/core/kernel/sprintf_tags.txt @@ -1,7 +1,5 @@ fails(compiler):Kernel#sprintf passes some tests for negative %u fails:Kernel#sprintf other formats c raises TypeError if argument is nil -fails:Kernel#sprintf other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:Kernel.sprintf other formats c raises TypeError if argument is nil -fails:Kernel.sprintf other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:Kernel#sprintf %c raises error when a codepoint isn't representable in an encoding of a format string fails:Kernel.sprintf %c raises error when a codepoint isn't representable in an encoding of a format string diff --git a/spec/tags/ruby/core/string/modulo_tags.txt b/spec/tags/ruby/core/string/modulo_tags.txt index 1e2762df768..841a96165f3 100644 --- a/spec/tags/ruby/core/string/modulo_tags.txt +++ b/spec/tags/ruby/core/string/modulo_tags.txt @@ -1,3 +1,2 @@ fails:String#% other formats c raises TypeError if argument is nil -fails:String#% other formats c raises TypeError if converting to Integer with to_int returns non-Integer fails:String#% %c raises error when a codepoint isn't representable in an encoding of a format string diff --git a/spec/tags/ruby/library/stringio/printf_tags.txt b/spec/tags/ruby/library/stringio/printf_tags.txt index af7401c5aba..346d48ee6e8 100644 --- a/spec/tags/ruby/library/stringio/printf_tags.txt +++ b/spec/tags/ruby/library/stringio/printf_tags.txt @@ -1,2 +1 @@ fails:StringIO#printf formatting other formats c raises TypeError if argument is nil -fails:StringIO#printf formatting other formats c raises TypeError if converting to Integer with to_int returns non-Integer From 1c29bb76295794fef1b8e2d67ac24db05479e216 Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Thu, 2 Nov 2023 16:56:07 -0400 Subject: [PATCH 10/11] Last typeerror string mismatches for %c fixes --- core/src/main/java/org/jruby/RubyNumeric.java | 3 ++- core/src/main/java/org/jruby/util/Sprintf.java | 4 +--- spec/tags/ruby/core/file/printf_tags.txt | 5 ----- spec/tags/ruby/core/kernel/printf_tags.txt | 2 -- spec/tags/ruby/core/kernel/sprintf_tags.txt | 2 -- spec/tags/ruby/core/string/modulo_tags.txt | 1 - spec/tags/ruby/library/stringio/printf_tags.txt | 1 - 7 files changed, 3 insertions(+), 15 deletions(-) delete mode 100644 spec/tags/ruby/core/file/printf_tags.txt delete mode 100644 spec/tags/ruby/core/kernel/printf_tags.txt delete mode 100644 spec/tags/ruby/library/stringio/printf_tags.txt diff --git a/core/src/main/java/org/jruby/RubyNumeric.java b/core/src/main/java/org/jruby/RubyNumeric.java index eaff288007b..3a69997ac81 100644 --- a/core/src/main/java/org/jruby/RubyNumeric.java +++ b/core/src/main/java/org/jruby/RubyNumeric.java @@ -224,7 +224,8 @@ private static long other2long(IRubyObject arg) throws RaiseException { if (arg.isNil()) { throw arg.getRuntime().newTypeError("no implicit conversion from nil to integer"); } - return arg.convertToInteger().getLongValue(); + + return ((RubyInteger) TypeConverter.convertToType(arg, arg.getRuntime().getInteger(), "to_int")).getLongValue(); } public static long float2long(RubyFloat flt) { diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index 271f45c8876..aca521e7efc 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -611,9 +611,7 @@ private static void rubySprintfToBuffer(final ByteList buf, final CharSequence c n = StringSupport.codeLength(bl.getEncoding(), c); } else { - // unsigned bits - RubyInteger intValue = (RubyInteger) TypeConverter.convertToType(arg, runtime.getInteger(), "to_int"); - c = (int) intValue.getLongValue() & 0xFFFFFFFF; + c = (int) RubyNumeric.num2long(arg) & 0xFFFFFFFF; try { n = StringSupport.codeLength(encoding, c); } catch (EncodingException e) { diff --git a/spec/tags/ruby/core/file/printf_tags.txt b/spec/tags/ruby/core/file/printf_tags.txt deleted file mode 100644 index 7f57c10f430..00000000000 --- a/spec/tags/ruby/core/file/printf_tags.txt +++ /dev/null @@ -1,5 +0,0 @@ -fails:File#printf integer formats u converts argument as a decimal number -fails:File#printf flags # applies to gG does not remove trailing zeros -fails:File#printf reference by name %{name} style supports flags, width and precision -fails:File#printf other formats c raises TypeError if argument is nil -fails:File#printf other formats c raises TypeError if converting to Integer with to_int returns non-Integer diff --git a/spec/tags/ruby/core/kernel/printf_tags.txt b/spec/tags/ruby/core/kernel/printf_tags.txt deleted file mode 100644 index 1b78d59184a..00000000000 --- a/spec/tags/ruby/core/kernel/printf_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Kernel.printf formatting io is specified other formats c raises TypeError if argument is nil -fails:Kernel.printf formatting io is not specified other formats c raises TypeError if argument is nil diff --git a/spec/tags/ruby/core/kernel/sprintf_tags.txt b/spec/tags/ruby/core/kernel/sprintf_tags.txt index b1e9882e46d..4195a92a340 100644 --- a/spec/tags/ruby/core/kernel/sprintf_tags.txt +++ b/spec/tags/ruby/core/kernel/sprintf_tags.txt @@ -1,5 +1,3 @@ fails(compiler):Kernel#sprintf passes some tests for negative %u -fails:Kernel#sprintf other formats c raises TypeError if argument is nil -fails:Kernel.sprintf other formats c raises TypeError if argument is nil fails:Kernel#sprintf %c raises error when a codepoint isn't representable in an encoding of a format string fails:Kernel.sprintf %c raises error when a codepoint isn't representable in an encoding of a format string diff --git a/spec/tags/ruby/core/string/modulo_tags.txt b/spec/tags/ruby/core/string/modulo_tags.txt index 841a96165f3..ea022223d56 100644 --- a/spec/tags/ruby/core/string/modulo_tags.txt +++ b/spec/tags/ruby/core/string/modulo_tags.txt @@ -1,2 +1 @@ -fails:String#% other formats c raises TypeError if argument is nil fails:String#% %c raises error when a codepoint isn't representable in an encoding of a format string diff --git a/spec/tags/ruby/library/stringio/printf_tags.txt b/spec/tags/ruby/library/stringio/printf_tags.txt deleted file mode 100644 index 346d48ee6e8..00000000000 --- a/spec/tags/ruby/library/stringio/printf_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:StringIO#printf formatting other formats c raises TypeError if argument is nil From 917474d56b4a7ff8d1a316d65120bd1b07680560 Mon Sep 17 00:00:00 2001 From: "Thomas E. Enebo" Date: Thu, 2 Nov 2023 17:12:50 -0400 Subject: [PATCH 11/11] need proper ruby exceptions when passing invalid encoding to format strings encoding for %c --- core/src/main/java/org/jruby/util/Sprintf.java | 7 ++++--- spec/tags/ruby/core/kernel/sprintf_tags.txt | 2 -- spec/tags/ruby/core/string/modulo_tags.txt | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) delete mode 100644 spec/tags/ruby/core/kernel/sprintf_tags.txt delete mode 100644 spec/tags/ruby/core/string/modulo_tags.txt diff --git a/core/src/main/java/org/jruby/util/Sprintf.java b/core/src/main/java/org/jruby/util/Sprintf.java index aca521e7efc..4b07298ce40 100644 --- a/core/src/main/java/org/jruby/util/Sprintf.java +++ b/core/src/main/java/org/jruby/util/Sprintf.java @@ -599,6 +599,7 @@ private static void rubySprintfToBuffer(final ByteList buf, final CharSequence c case 'c': { arg = args.getArg(); + ThreadContext context = runtime.getCurrentContext(); int c; int n; IRubyObject tmp = arg.checkStringType(); @@ -623,19 +624,19 @@ private static void rubySprintfToBuffer(final ByteList buf, final CharSequence c } if ((flags & FLAG_WIDTH) == 0) { buf.ensure(buf.length() + n); - EncodingUtils.encMbcput(c, buf.unsafeBytes(), buf.realSize(), encoding); + EncodingUtils.encMbcput(context, c, buf.unsafeBytes(), buf.realSize(), encoding); buf.realSize(buf.realSize() + n); } else if ((flags & FLAG_MINUS) != 0) { buf.ensure(buf.length() + n); - EncodingUtils.encMbcput(c, buf.unsafeBytes(), buf.realSize(), encoding); + EncodingUtils.encMbcput(context, c, buf.unsafeBytes(), buf.realSize(), encoding); buf.realSize(buf.realSize() + n); buf.fill(' ', width - 1); } else { buf.fill(' ', width - 1); buf.ensure(buf.length() + n); - EncodingUtils.encMbcput(c, buf.unsafeBytes(), buf.realSize(), encoding); + EncodingUtils.encMbcput(context, c, buf.unsafeBytes(), buf.realSize(), encoding); buf.realSize(buf.realSize() + n); } offset++; diff --git a/spec/tags/ruby/core/kernel/sprintf_tags.txt b/spec/tags/ruby/core/kernel/sprintf_tags.txt deleted file mode 100644 index 588543ed749..00000000000 --- a/spec/tags/ruby/core/kernel/sprintf_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Kernel#sprintf %c raises error when a codepoint isn't representable in an encoding of a format string -fails:Kernel.sprintf %c raises error when a codepoint isn't representable in an encoding of a format string diff --git a/spec/tags/ruby/core/string/modulo_tags.txt b/spec/tags/ruby/core/string/modulo_tags.txt deleted file mode 100644 index ea022223d56..00000000000 --- a/spec/tags/ruby/core/string/modulo_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:String#% %c raises error when a codepoint isn't representable in an encoding of a format string