Skip to content

Commit

Permalink
Showing 2 changed files with 244 additions and 125 deletions.
4 changes: 0 additions & 4 deletions spec/truffle/tags/core/kernel/sprintf_tags.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,2 @@
fails:Kernel#sprintf passes some tests for positive %x
fails:Kernel#sprintf passes some tests for negative %x
fails:Kernel#sprintf passes kstephens's tests
fails:Kernel#sprintf returns a String in the same encoding as the format String if compatible
fails:Kernel#sprintf returns a String in the argument encoding if format encoding is more restrictive
fails:Kernel#sprintf with negative values with format %x precedes the number with '..'
Original file line number Diff line number Diff line change
@@ -6,6 +6,13 @@
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*
* Some of the code in this class is modified from org.jruby.util.Sprintf,
* licensed under the same EPL1.0/GPL 2.0/LGPL 2.1 used throughout.
*
* Contains code modified from Sprintf.java
*
* Copyright (C) 2007 William N Dortch <bill.dortch@gmail.com>
*/
package org.jruby.truffle.core.format.format;

@@ -18,20 +25,28 @@
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.format.FormatNode;
import org.jruby.truffle.core.format.printf.PrintfSimpleTreeBuilder;
import org.jruby.truffle.util.StringUtils;
import org.jruby.util.ByteList;
import org.jruby.util.ConvertBytes;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.Locale;

@NodeChildren({
@NodeChild(value = "width", type = FormatNode.class),
@NodeChild(value = "width", type = FormatNode.class),
@NodeChild(value = "precision", type = FormatNode.class),
@NodeChild(value = "value", type = FormatNode.class),
@NodeChild(value = "value", type = FormatNode.class),
})
public abstract class FormatIntegerNode extends FormatNode {

private static final byte[] PREFIX_OCTAL = {'0'};
private static final byte[] PREFIX_HEX_LC = {'0', 'x'};
private static final byte[] PREFIX_HEX_UC = {'0', 'X'};
private static final byte[] PREFIX_BINARY_LC = {'0', 'b'};
private static final byte[] PREFIX_BINARY_UC = {'0', 'B'};

private static final byte[] PREFIX_NEGATIVE = {'.', '.'};

private final char format;
private final boolean hasSpaceFlag;
private final boolean hasZeroFlag;
@@ -51,157 +66,265 @@ public FormatIntegerNode(RubyContext context, char format, boolean hasSpaceFlag,

@TruffleBoundary
@Specialization
public byte[] format(int width, int precision, int value) {
String formatted;
switch (this.format) {
case 'X':
formatted = Integer.toString(value, 16).toUpperCase();
if (hasFSharp && value > 0) {
formatted = "0X" + formatted;
}
if (value < 0) {
formatted = "..F" + Integer.toString(-value, 16).toUpperCase();
}
break;
case 'x':
formatted = Integer.toString(value, 16);
if (hasFSharp && value > 0) {
formatted = "0x" + formatted;
}
if (value < 0) {
formatted = "..f" + Integer.toString(-value, 16);
}
break;
case 'o':
formatted = Integer.toString(value, 8);
if (hasFSharp && value > 0) {
formatted = "0" + formatted;
}
if (value < 0) {
formatted = "..7" + StringUtils.create(elide(ConvertBytes.intToOctalBytes(value), format, precision));
;
}
break;
case 'd':
case 'i':
case 'u':
if (value < 0) {
formatted = Long.toString(-value);
} else {
formatted = Long.toString(value);
}
break;
default:
throw new UnsupportedOperationException();
}

return formatStart(width, precision, formatted, value < 0);
public byte[] format(int width, int precision, int arg) {
return format(width, precision, (long) arg);
}

@TruffleBoundary
@Specialization
public byte[] format(int width, int precision, long value) {
String formatted;
switch (this.format) {
case 'X':
formatted = Long.toString(value, 16).toUpperCase();
if (hasFSharp && value > 0) {
formatted = "0X" + formatted;
}
if (value < 0) {
formatted = "..F" + Long.toString(-value, 16).toUpperCase();
}
public byte[] format(int width, int precision, long arg) {

ByteList buf = new ByteList();

boolean usePrefixForZero = false;

boolean hasMinusFlag = this.hasMinusFlag;
if (width == PrintfSimpleTreeBuilder.DEFAULT) {
width = 0;
} else if (width < 0) {
hasMinusFlag = true;
width = -width;
}

if (precision < 0) {
precision = PrintfSimpleTreeBuilder.DEFAULT;
}

byte[] bytes = null;
int first = 0;
byte[] prefix = null;
boolean sign;
boolean negative;
byte signChar = 0;
byte leadChar = 0;
int base;

char fchar = this.format;

// 'd' and 'i' are the same
if (fchar == 'i') fchar = 'd';

// 'u' with space or plus flags is same as 'd'
if (fchar == 'u' && (hasSpaceFlag || hasPlusFlag)) {
fchar = 'd';
}
sign = (fchar == 'd' || (hasSpaceFlag || hasPlusFlag));

switch (fchar) {
case 'o':
base = 8;
break;
case 'x':
formatted = Long.toString(value, 16);
if (hasFSharp && value > 0) {
formatted = "0x" + formatted;
}
if (value < 0) {
formatted = "..f" + Long.toString(-value, 16);
}
case 'X':
base = 16;
break;
case 'o':
formatted = Long.toString(value, 8);
if (hasFSharp && value > 0) {
formatted = "0" + formatted;
}
if (value < 0) {
formatted = "..7" + StringUtils.create(elide(ConvertBytes.longToOctalBytes(value), format, precision));
}
case 'b':
case 'B':
base = 2;
break;
case 'd':
case 'i':
case 'u':
if (value < 0) {
formatted = Long.toString(-value);
} else {
formatted = Long.toString(value);
}
break;
case 'd':
default:
throw new UnsupportedOperationException();
base = 10;
break;
}
return formatStart(width, precision, formatted, value < 0);
}

private static byte[] elide(byte[] bytes, char format, int precision) {
// TODO BJF need to implement the skipping of bytes per format type when value is negative
return bytes;
}
boolean zero;

private byte[] formatStart(int width, int precision, String formatted, boolean isNegative) {
boolean leftJustified = hasMinusFlag;
if (width < 0 && width != PrintfSimpleTreeBuilder.DEFAULT) { // TODO handle default width better
width = -width;
leftJustified = true;
negative = arg < 0;
zero = arg == 0;
if (negative && fchar == 'u') {
bytes = getUnsignedNegativeBytes(arg);
} else {
bytes = getFixnumBytes(arg, base, sign, fchar == 'X');
}

final boolean addNegative = isNegative && (format == 'd' || format == 'i' || format == 'u');

if ((hasZeroFlag && !hasMinusFlag) || precision != PrintfSimpleTreeBuilder.DEFAULT) {
int padZeros;
if (precision != PrintfSimpleTreeBuilder.DEFAULT) {
padZeros = precision;
} else {
padZeros = width;
if (addNegative) {
padZeros -= 1;
if (hasFSharp) {
if (!zero || usePrefixForZero) {
switch (fchar) {
case 'o':
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;
case 'B':
prefix = PREFIX_BINARY_UC;
break;
}
}
if (prefix != null) width -= prefix.length;
}
int len = 0;
if (sign) {
if (negative) {
signChar = '-';
width--;
first = 1; // skip '-' in bytes, will add where appropriate
} else if (hasPlusFlag) {
signChar = '+';
width--;
} else if (hasSpaceFlag) {
signChar = ' ';
width--;
}
} else if (negative) {
if (base == 10) {
// warning(ID.NEGATIVE_NUMBER_FOR_U, args, "negative number for %u specifier");
leadChar = '.';
len += 2;
} else {
if (!hasZeroFlag && precision == PrintfSimpleTreeBuilder.DEFAULT) len += 2; // ..

while (formatted.length() < padZeros) {
formatted = "0" + formatted;
first = skipSignBits(bytes, base);
switch (fchar) {
case 'b':
case 'B':
leadChar = '1';
break;
case 'o':
leadChar = '7';
break;
case 'x':
leadChar = 'f';
break;
case 'X':
leadChar = 'F';
break;
}
if (leadChar != 0) len++;
}
}
int numlen = bytes.length - first;
len += numlen;


boolean hasPrecisionFlag = precision != PrintfSimpleTreeBuilder.DEFAULT;

if (addNegative) {
formatted = "-" + formatted;
// if ((flags & (FLAG_ZERO|FLAG_PRECISION)) == FLAG_ZERO) {
if (hasZeroFlag && !hasPrecisionFlag) {
precision = width;
width = 0;
} else {
if (precision < len) precision = len;

width -= precision;
}
if (!hasMinusFlag) {
buf.fill(' ', width);
width = 0;
}
if (signChar != 0) buf.append(signChar);
if (prefix != null) buf.append(prefix);

while (formatted.length() < width) {
if (leftJustified) {
formatted = formatted + " ";
if (len < precision) {
if (leadChar == 0) {
if (fchar != 'd' || usePrefixForZero || !negative ||
hasPrecisionFlag ||
(hasZeroFlag && !hasMinusFlag)) {
buf.fill('0', precision - len);
}
} else if (leadChar == '.') {
buf.fill(leadChar, precision - len);
buf.append(PREFIX_NEGATIVE);
} else if (!usePrefixForZero) {
buf.append(PREFIX_NEGATIVE);
buf.fill(leadChar, precision - len - 1);
} else {
formatted = " " + formatted;
buf.fill(leadChar, precision - len + 1); // the 1 is for the stripped sign char
}
} else if (leadChar != 0) {
if (((!hasZeroFlag && precision == PrintfSimpleTreeBuilder.DEFAULT) && usePrefixForZero) ||
(!usePrefixForZero && "xXbBo".indexOf(fchar) != -1)) {
buf.append(PREFIX_NEGATIVE);
}
if (leadChar != '.') buf.append(leadChar);
}
buf.append(bytes, first, numlen);

if (width > 0) buf.fill(' ', width);
if (len < precision && fchar == 'd' && negative &&
!usePrefixForZero && hasMinusFlag) {
buf.fill(' ', precision - len);
}
return buf.bytes();
}


if (!isNegative) {
if (hasSpaceFlag || hasPlusFlag) {
if (!hasMinusFlag) {
if (hasPlusFlag) {
formatted = "+" + formatted;
} else {
formatted = " " + formatted;
}
private static byte[] getFixnumBytes(long arg, int base, boolean sign, boolean upper) {
long val = arg;

// limit the length of negatives if possible (also faster)
if (val >= Integer.MIN_VALUE && val <= Integer.MAX_VALUE) {
if (sign) {
return ConvertBytes.intToByteArray((int) val, base, upper);
} else {
switch (base) {
case 2:
return ConvertBytes.intToBinaryBytes((int) val);
case 8:
return ConvertBytes.intToOctalBytes((int) val);
case 10:
default:
return ConvertBytes.intToCharBytes((int) val);
case 16:
return ConvertBytes.intToHexBytes((int) val, upper);
}
}
} else {
if (sign) {
return ConvertBytes.longToByteArray(val, base, upper);
} else {
switch (base) {
case 2:
return ConvertBytes.longToBinaryBytes(val);
case 8:
return ConvertBytes.longToOctalBytes(val);
case 10:
default:
return ConvertBytes.longToCharBytes(val);
case 16:
return ConvertBytes.longToHexBytes(val, upper);
}
}
}
}

return formatted.getBytes(StandardCharsets.US_ASCII);
private static int skipSignBits(byte[] bytes, int base) {
int skip = 0;
int length = bytes.length;
byte b;
switch (base) {
case 2:
for (; skip < length && bytes[skip] == '1'; skip++) {
}
break;
case 8:
if (length > 0 && bytes[0] == '3') skip++;
for (; skip < length && bytes[skip] == '7'; skip++) {
}
break;
case 10:
if (length > 0 && bytes[0] == '-') skip++;
break;
case 16:
for (; skip < length && ((b = bytes[skip]) == 'f' || b == 'F'); skip++) {
}
}
return skip;
}

private static byte[] getUnsignedNegativeBytes(long arg) {
return ConvertBytes.longToCharBytes(((Long.MAX_VALUE + 1L) << 1) + arg);
}


@TruffleBoundary
@Specialization(guards = "isRubyBignum(value)")
public byte[] format(int width, int precision, DynamicObject value) {

0 comments on commit 84f8d03

Please sign in to comment.