Skip to content

Commit

Permalink
Showing 13 changed files with 1,158 additions and 529 deletions.
3 changes: 0 additions & 3 deletions spec/truffle/tags/core/kernel/sprintf_tags.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
fails:Kernel#sprintf passes some tests for positive %x
fails:Kernel#sprintf passes some tests for negative %x
fails:Kernel#sprintf passes some tests for negative %u
fails:Kernel#sprintf passes some tests for positive %u
fails:Kernel#sprintf passes some tests for positive %d
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
35 changes: 0 additions & 35 deletions spec/truffle/tags/core/string/modulo_tags.txt
Original file line number Diff line number Diff line change
@@ -1,44 +1,9 @@
fails:String#% formats single % characters before a newline or NULL as literal %s
fails:String#% raises an ArgumentError for unused arguments when $DEBUG is true
fails:String#% replaces trailing absolute argument specifier without type with percent sign
fails:String#% raises an ArgumentError when multiple positional argument tokens are given for one format specifier
fails:String#% raises an ArgumentError when absolute and relative argument numbers are mixed
fails:String#% allows positional arguments for width star and precision star arguments
fails:String#% allows negative width to imply '-' flag
fails:String#% ignores negative precision
fails:String#% allows a star to take an argument number to use as the width
fails:String#% calls to_int on width star and precision star tokens
fails:String#% does not call #to_a to convert the argument
fails:String#% doesn't return subclass instances when called on a subclass
fails:String#% always taints the result when the format string is tainted
fails:String#% supports character formats using %c
fails:String#% supports single character strings as argument for %c
fails:String#% raises an exception for multiple character strings as argument for %c
fails:String#% calls to_str on argument for %c formats
fails:String#% calls #to_ary on argument for %c formats
fails:String#% calls #to_int on argument for %c formats, if the argument does not respond to #to_ary
fails:String#% supports integer formats using %d
fails:String#% supports negative integers using %d
fails:String#% supports negative integers using %d, giving priority to `-`
fails:String#% supports integer formats using %i
fails:String#% supports negative integers using %i
fails:String#% supports negative integers using %i, giving priority to `-`
fails:String#% supports float formats using %e
fails:String#% supports float formats using %E, but Inf, -Inf, and NaN are not floats
fails:String#% supports float formats using %E
fails:String#% supports float formats using %f
fails:String#% supports float formats using %g
fails:String#% supports float formats using %G
fails:String#% supports octal formats using %o for positive numbers
fails:String#% supports octal formats using %o for negative numbers
fails:String#% supports inspect formats using %p
fails:String#% supports string formats using %s
fails:String#% taints result for %s when argument is tainted
fails:String#% raises an ArgumentError for huge precisions for %s
fails:String#% supports unsigned formats using %u
fails:String#% supports hex formats using %x for positive numbers
fails:String#% supports hex formats using %x for negative numbers
fails:String#% supports hex formats using %X for positive numbers
fails:String#% supports hex formats using %X for negative numbers
fails:String#% behaves as if calling Kernel#Float for %e arguments, when the passed argument does not respond to #to_ary
fails:String#% behaves as if calling Kernel#Float for %e arguments, when the passed argument is hexadecimal string

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.jruby.truffle.core.format.format;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.format.FormatNode;
import org.jruby.truffle.core.format.LiteralFormatNode;
import org.jruby.truffle.core.format.convert.ToIntegerNode;
import org.jruby.truffle.core.format.convert.ToIntegerNodeGen;
import org.jruby.truffle.core.format.convert.ToStringNode;
import org.jruby.truffle.core.format.convert.ToStringNodeGen;
import org.jruby.truffle.core.format.exceptions.NoImplicitConversionException;
import org.jruby.truffle.core.format.write.bytes.WriteByteNodeGen;
import org.jruby.truffle.language.control.RaiseException;

import java.nio.charset.StandardCharsets;

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

private final boolean hasMinusFlag;

@Child private ToIntegerNode toIntegerNode;
@Child private ToStringNode toStringNode;

public FormatCharacterNode(RubyContext context, boolean hasMinusFlag) {
super(context);
this.hasMinusFlag = hasMinusFlag;
}

// @TruffleBoundary
@Specialization
protected byte[] format(VirtualFrame frame, int width, Object value) {
if (toStringNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
toStringNode = insert(ToStringNodeGen.create(getContext(),
false,
"to_str",
false,
null,
WriteByteNodeGen.create(getContext(), new LiteralFormatNode(getContext(), value))));
}
Object toStrResult;
try {
toStrResult = toStringNode.executeToString(frame, value);
} catch (NoImplicitConversionException e) {
toStrResult = null;
}

final String charString;
if (toStrResult == null || isNil(toStrResult)) {
if (toIntegerNode == null) {
CompilerDirectives.transferToInterpreterAndInvalidate();
toIntegerNode = insert(ToIntegerNodeGen.create(getContext(), null));
}
final int charValue = (int) toIntegerNode.executeToInteger(frame, value);
// TODO BJF check char length is > 0
charString = Character.toString((char) charValue);
} else {
final String resultString = new String((byte[]) toStrResult);
final int size = resultString.length();
if (size > 1) {
throw new RaiseException(getContext().getCoreExceptions().argumentError("%c requires a character", this));
}
charString = resultString;
}


final boolean leftJustified = hasMinusFlag || width < 0;
if (width < 0) {
width = -width;
}

final String result = String.format("%" + (leftJustified ? "-" : "") + width + "." + width + "s", charString);
return result.getBytes(StandardCharsets.US_ASCII);
}

}
Original file line number Diff line number Diff line change
@@ -15,42 +15,80 @@
import com.oracle.truffle.api.dsl.Specialization;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.format.FormatNode;
import org.jruby.truffle.core.format.printf.PrintfTreeBuilder;
import org.jruby.truffle.core.format.printf.PrintfSimpleTreeBuilder;

import java.nio.charset.StandardCharsets;

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

private final String infiniteFormatString;
private final String finiteFormatString;
private final char format;
private final boolean hasSpaceFlag;
private final boolean hasZeroFlag;
private final boolean hasPlusFlag;
private final boolean hasMinusFlag;

public FormatFloatNode(RubyContext context, int width, int precision, char format, boolean hasSpaceFlag, boolean hasZeroFlag) {
public FormatFloatNode(RubyContext context, char format, boolean hasSpaceFlag, boolean hasZeroFlag, boolean hasPlusFlag, boolean hasMinusFlag) {
super(context);
final StringBuilder inifiniteFormatBuilder = new StringBuilder();
inifiniteFormatBuilder.append("%");
this.format = format;
this.hasSpaceFlag = hasSpaceFlag;
this.hasZeroFlag = hasZeroFlag;
this.hasPlusFlag = hasPlusFlag;
this.hasMinusFlag = hasMinusFlag;
}

if (hasSpaceFlag) {
inifiniteFormatBuilder.append(" ");
inifiniteFormatBuilder.append(width + 5);
}
if (hasZeroFlag && width != 0) {
inifiniteFormatBuilder.append("0");
inifiniteFormatBuilder.append(width + 5);
}
if(!hasSpaceFlag && !hasZeroFlag){
inifiniteFormatBuilder.append(width + 5);
}

@TruffleBoundary
@Specialization(guards = "isInfinite(value)")
public byte[] formatInfinite(int width, int precision, double value) {
final String infinityString = String.format(getInfiniteFormatString(width), value);
return mapInifityResult(infinityString).getBytes(StandardCharsets.US_ASCII);
}

@TruffleBoundary
@Specialization(guards = "!isInfinite(value)")
public byte[] formatFinite(int width, int precision,double value) {
return mapFiniteResult(String.format(getFiniteFormatString(width, precision), value)).getBytes(StandardCharsets.US_ASCII);
}

inifiniteFormatBuilder.append(format);
protected boolean isInfinite(double value) {
return Double.isInfinite(value);
}

infiniteFormatString = inifiniteFormatBuilder.toString();
private static String mapFiniteResult(String input){
if(input.contains("NAN")){
return input.replaceFirst("NAN", "NaN");
}
return input;
}

private static String mapInifityResult(String input){
if(input.contains("-Infinity")) {
return input.replaceFirst("-Infinity", "-Inf");
} else if(input.contains("-INFINITY")){
return input.replaceFirst("-INFINITY", "-Inf");
}
else if(input.contains("INFINITY")){
return input.replaceFirst("INFINITY", "Inf");
} else if(input.contains("Infinity")) {
return input.replaceFirst("Infinity", "Inf");
}
return input;
}

private String getFiniteFormatString(final int width, final int precision){
final StringBuilder finiteFormatBuilder = new StringBuilder();
finiteFormatBuilder.append("%");
if(hasMinusFlag){
finiteFormatBuilder.append("-");
}
if(hasPlusFlag){
finiteFormatBuilder.append("+");
}

if (hasSpaceFlag) {
finiteFormatBuilder.append(" ");
@@ -71,32 +109,42 @@ public FormatFloatNode(RubyContext context, int width, int precision, char forma
finiteFormatBuilder.append(width);
}

if (precision != PrintfTreeBuilder.DEFAULT) {
if (precision != PrintfSimpleTreeBuilder.DEFAULT) {
finiteFormatBuilder.append(".");
finiteFormatBuilder.append(precision);
}

finiteFormatBuilder.append(format);

finiteFormatString = finiteFormatBuilder.toString();
return finiteFormatBuilder.toString();
}

@TruffleBoundary
@Specialization(guards = "isInfinite(value)")
public byte[] formatInfinite(double value) {
final String infinityString = String.format(infiniteFormatString, value);
final String shortenInfinityString = infinityString.substring(0, infinityString.length() - 5);
return shortenInfinityString.getBytes(StandardCharsets.US_ASCII);
}
private String getInfiniteFormatString(final int width){
final StringBuilder inifiniteFormatBuilder = new StringBuilder();
inifiniteFormatBuilder.append("%");
if(hasMinusFlag){
inifiniteFormatBuilder.append("-");
}
if(hasPlusFlag){
inifiniteFormatBuilder.append("+");
}

@TruffleBoundary
@Specialization(guards = "!isInfinite(value)")
public byte[] formatFinite(double value) {
return String.format(finiteFormatString, value).getBytes(StandardCharsets.US_ASCII);
}
if (hasSpaceFlag) {
inifiniteFormatBuilder.append(" ");
inifiniteFormatBuilder.append(width + 5);
}
if (hasZeroFlag && width != 0) {
inifiniteFormatBuilder.append("0");
inifiniteFormatBuilder.append(width + 5);
}
if(!hasSpaceFlag && !hasZeroFlag){
inifiniteFormatBuilder.append(width + 5);
}

protected boolean isInfinite(double value) {
return Double.isInfinite(value);

inifiniteFormatBuilder.append(format);

return inifiniteFormatBuilder.toString();
}

}
Original file line number Diff line number Diff line change
@@ -9,58 +9,56 @@
*/
package org.jruby.truffle.core.format.format;

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

import org.jruby.truffle.Layouts;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.format.FormatNode;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.object.DynamicObject;
import org.jruby.truffle.core.format.printf.PrintfTreeBuilder;
import org.jruby.truffle.Layouts;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.format.FormatNode;
import org.jruby.truffle.core.format.printf.PrintfSimpleTreeBuilder;

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

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

private final char format;
private final boolean hasPlusFlag;
private final boolean useAlternativeFormat;
private final boolean isLeftJustified;
private final boolean hasMinusFlag;
private final boolean hasSpaceFlag;
private final boolean hasZeroFlag;
private final int precision;

public FormatIntegerBinaryNode(RubyContext context, char format, int precision, boolean hasPlusFlag, boolean useAlternativeFormat,
boolean isLeftJustified, boolean hasSpaceFlag, boolean hasZeroFlag) {
public FormatIntegerBinaryNode(RubyContext context, char format, boolean hasPlusFlag, boolean useAlternativeFormat,
boolean hasMinusFlag, boolean hasSpaceFlag, boolean hasZeroFlag) {
super(context);
this.format = format;
this.hasPlusFlag = hasPlusFlag;
this.useAlternativeFormat = useAlternativeFormat;
this.isLeftJustified = isLeftJustified;
this.hasMinusFlag = hasMinusFlag;
this.hasSpaceFlag = hasSpaceFlag;
this.precision = precision;
this.hasZeroFlag = hasZeroFlag;
}

@Specialization
public byte[] format(int width, int value) {
public byte[] format(int width, int precision, int value) {
final boolean isNegative = value < 0;
final boolean negativeAndPadded = isNegative && (this.hasSpaceFlag || this.hasPlusFlag);
final String formatted = negativeAndPadded ? Integer.toBinaryString(-value) : Integer.toBinaryString(value);
return getFormattedString(formatted, width, this.precision, isNegative, this.hasSpaceFlag, this.hasPlusFlag,
this.hasZeroFlag, this.useAlternativeFormat, this.isLeftJustified, this.format);
return getFormattedString(formatted, width, precision, isNegative, this.hasSpaceFlag, this.hasPlusFlag,
this.hasZeroFlag, this.useAlternativeFormat, this.hasMinusFlag, this.format);
}

@TruffleBoundary
@Specialization(guards = "isRubyBignum(value)")
public byte[] format(int width, DynamicObject value) {
public byte[] format(int width, int precision, DynamicObject value) {
final BigInteger bigInteger = Layouts.BIGNUM.getValue(value);
final boolean isNegative = bigInteger.signum() == -1;
final boolean negativeAndPadded = isNegative && (this.hasSpaceFlag || this.hasPlusFlag);
@@ -78,18 +76,18 @@ public byte[] format(int width, DynamicObject value) {
}
formatted = builder.toString();
}
return getFormattedString(formatted, width, this.precision, isNegative, this.hasSpaceFlag, this.hasPlusFlag,
this.hasZeroFlag, this.useAlternativeFormat, this.isLeftJustified, this.format);
return getFormattedString(formatted, width, precision, isNegative, this.hasSpaceFlag, this.hasPlusFlag,
this.hasZeroFlag, this.useAlternativeFormat, this.hasMinusFlag, this.format);
}

@TruffleBoundary
private static byte[] getFormattedString(String formatted, int width, int precision, boolean isNegative,
boolean isSpacePadded, boolean hasPlusFlag, boolean hasZeroFlag,
boolean useAlternativeFormat, boolean isLeftJustified,
boolean useAlternativeFormat, boolean hasMinusFlag,
char format) {
if(width < 0 && width != PrintfTreeBuilder.DEFAULT){
if(width < 0 && width != PrintfSimpleTreeBuilder.DEFAULT){
width = -width;
isLeftJustified = true;
hasMinusFlag = true;
}

if (isNegative && !(isSpacePadded || hasPlusFlag)) {
@@ -106,9 +104,9 @@ private static byte[] getFormattedString(String formatted, int width, int precis
formatted = "..1";
}
} else {
if(hasZeroFlag || precision != PrintfTreeBuilder.DEFAULT) {
if(!isLeftJustified){
final int padZeros = precision != PrintfTreeBuilder.DEFAULT ? precision : width;
if(hasZeroFlag || precision != PrintfSimpleTreeBuilder.DEFAULT) {
if(!hasMinusFlag){
final int padZeros = precision != PrintfSimpleTreeBuilder.DEFAULT ? precision : width;
while (formatted.length() < padZeros) {
formatted = "0" + formatted;
}
@@ -117,7 +115,7 @@ private static byte[] getFormattedString(String formatted, int width, int precis
}

while (formatted.length() < width) {
if(!isLeftJustified){
if(!hasMinusFlag){
formatted = " " + formatted;
} else {
formatted = formatted + " ";
Original file line number Diff line number Diff line change
@@ -10,63 +10,200 @@
package org.jruby.truffle.core.format.format;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.dsl.Cached;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.object.DynamicObject;
import org.jruby.truffle.Layouts;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.format.FormatNode;
import org.jruby.truffle.core.format.printf.PrintfTreeBuilder;
import org.jruby.truffle.core.format.printf.PrintfSimpleTreeBuilder;
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 = "precision", type = FormatNode.class),
@NodeChild(value = "value", type = FormatNode.class),
})
public abstract class FormatIntegerNode extends FormatNode {

private final char format;
private final boolean hasSpaceFlag;
private final boolean hasZeroFlag;
private final int precision;
private final boolean hasPlusFlag;
private final boolean hasMinusFlag;
private final boolean hasFSharp;

public FormatIntegerNode(RubyContext context, char format, boolean hasSpaceFlag, boolean hasZeroFlag, int precision) {
public FormatIntegerNode(RubyContext context, char format, boolean hasSpaceFlag, boolean hasZeroFlag, boolean hasPlusFlag, boolean hasMinusFlag, boolean hasFSharp) {
super(context);
this.format = format;
this.hasSpaceFlag = hasSpaceFlag;
this.hasZeroFlag = hasZeroFlag;
this.precision = precision;
this.hasPlusFlag = hasPlusFlag;
this.hasMinusFlag = hasMinusFlag;
this.hasFSharp = hasFSharp;
}

@Specialization(
guards = {
"!isRubyBignum(value)",
"width == cachedWidth"
},
limit = "getLimit()"
)
public byte[] formatCached(int width,
Object value,
@Cached("width") int cachedWidth,
@Cached("makeFormatString(width)") String cachedFormatString) {
return doFormat(value, cachedFormatString);
@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" + new String(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);
}

@TruffleBoundary
@Specialization(guards = "!isRubyBignum(value)", contains = "formatCached")
public byte[] formatUncached(int width,
Object value) {
return doFormat(value, makeFormatString(width));
@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();
}
break;
case 'x':
formatted = Long.toString(value, 16);
if (hasFSharp && value > 0) {
formatted = "0x" + formatted;
}
if (value < 0) {
formatted = "..f" + Long.toString(-value, 16);
}
break;
case 'o':
formatted = Long.toString(value, 8);
if (hasFSharp && value > 0) {
formatted = "0" + formatted;
}
if (value < 0) {
formatted = "..7" + new String(elide(ConvertBytes.longToOctalBytes(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);
}

private static byte[] elide(byte[] bytes, char format, int precision) {
return bytes;
}


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;
}

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;
}
}

while (formatted.length() < padZeros) {
formatted = "0" + formatted;
}
}

if (addNegative) {
formatted = "-" + formatted;
}

while (formatted.length() < width) {
if (leftJustified) {
formatted = formatted + " ";
} else {
formatted = " " + formatted;
}
}

if (!isNegative) {
if (hasSpaceFlag || hasPlusFlag) {
if (!hasMinusFlag) {
if (hasPlusFlag) {
formatted = "+" + formatted;
} else {
formatted = " " + formatted;
}
}
}
}

return formatted.getBytes(StandardCharsets.US_ASCII);

}

@TruffleBoundary
@Specialization(guards = "isRubyBignum(value)")
public byte[] format(int width, DynamicObject value) {
public byte[] format(int width, int precision, DynamicObject value) {
final BigInteger bigInteger = Layouts.BIGNUM.getValue(value);

String formatted;
@@ -94,7 +231,7 @@ public byte[] format(int width, DynamicObject value) {
throw new UnsupportedOperationException();
}

while (formatted.length() < this.precision) {
while (formatted.length() < precision) {
formatted = "0" + formatted;
}

@@ -105,38 +242,4 @@ public byte[] format(int width, DynamicObject value) {
return formatted.getBytes(StandardCharsets.US_ASCII);
}

@TruffleBoundary
protected byte[] doFormat(Object value, String formatString) {
return String.format(formatString, value).getBytes(StandardCharsets.US_ASCII);
}

protected String makeFormatString(int width) {
final StringBuilder builder = new StringBuilder();

builder.append("%");

final int padZeros = precision != PrintfTreeBuilder.DEFAULT ? precision : width;

if (this.hasSpaceFlag) {
builder.append(" ");
builder.append(width);

if (this.hasZeroFlag || precision != PrintfTreeBuilder.DEFAULT) {
builder.append(".");
builder.append(padZeros);
}
} else if (this.hasZeroFlag || precision != PrintfTreeBuilder.DEFAULT) {
builder.append("0");
builder.append(padZeros);
}

builder.append(format);

return builder.toString();
}

protected int getLimit() {
return getContext().getOptions().PACK_CACHE;
}

}
Original file line number Diff line number Diff line change
@@ -11,15 +11,14 @@

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.Truffle;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.format.DescriptionTruncater;
import org.jruby.truffle.core.format.FormatEncoding;
import org.jruby.truffle.core.format.FormatErrorListener;
import org.jruby.truffle.core.format.FormatRootNode;
import org.jruby.truffle.language.RubyNode;

import java.util.List;

public class PrintfCompiler {

private final RubyContext context;
@@ -31,29 +30,13 @@ public PrintfCompiler(RubyContext context, RubyNode currentNode) {
}

public CallTarget compile(String formatString, byte[] format) {
final FormatErrorListener errorListener = new FormatErrorListener(context, currentNode);

final ANTLRInputStream input = new ANTLRInputStream(bytesToChars(format), format.length);

final PrintfLexer lexer = new PrintfLexer(input);
lexer.removeErrorListeners();
lexer.addErrorListener(errorListener);

final CommonTokenStream tokens = new CommonTokenStream(lexer);

final PrintfParser parser = new PrintfParser(tokens);

final PrintfTreeBuilder builder = new PrintfTreeBuilder(context, format);
parser.addParseListener(builder);

parser.removeErrorListeners();
parser.addErrorListener(errorListener);

parser.sequence();
final PrintfSimpleParser parser = new PrintfSimpleParser(bytesToChars(format));
final List<PrintfSimpleParser.SprintfConfig> configs = parser.parse();
final PrintfSimpleTreeBuilder builder = new PrintfSimpleTreeBuilder(context, configs);

return Truffle.getRuntime().createCallTarget(
new FormatRootNode(DescriptionTruncater.trunate(formatString),
FormatEncoding.DEFAULT, builder.getNode()));
new FormatRootNode(DescriptionTruncater.trunate(formatString),
FormatEncoding.DEFAULT, builder.getNode()));
}

private static char[] bytesToChars(byte[] bytes) {

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package org.jruby.truffle.core.format.printf;

import com.oracle.truffle.api.object.DynamicObject;
import org.jcodings.specific.USASCIIEncoding;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.format.FormatNode;
import org.jruby.truffle.core.format.LiteralFormatNode;
import org.jruby.truffle.core.format.control.SequenceNode;
import org.jruby.truffle.core.format.convert.ToDoubleWithCoercionNodeGen;
import org.jruby.truffle.core.format.convert.ToIntegerNodeGen;
import org.jruby.truffle.core.format.convert.ToStringNodeGen;
import org.jruby.truffle.core.format.format.FormatCharacterNodeGen;
import org.jruby.truffle.core.format.format.FormatFloatHumanReadableNodeGen;
import org.jruby.truffle.core.format.format.FormatFloatNodeGen;
import org.jruby.truffle.core.format.format.FormatIntegerBinaryNodeGen;
import org.jruby.truffle.core.format.format.FormatIntegerNodeGen;
import org.jruby.truffle.core.format.read.SourceNode;
import org.jruby.truffle.core.format.read.array.ReadArgumentIndexValueNodeGen;
import org.jruby.truffle.core.format.read.array.ReadHashValueNodeGen;
import org.jruby.truffle.core.format.read.array.ReadIntegerNodeGen;
import org.jruby.truffle.core.format.read.array.ReadStringNodeGen;
import org.jruby.truffle.core.format.read.array.ReadValueNodeGen;
import org.jruby.truffle.core.format.write.bytes.WriteBytesNodeGen;
import org.jruby.truffle.core.format.write.bytes.WritePaddedBytesNodeGen;
import org.jruby.truffle.core.rope.CodeRange;

import java.util.ArrayList;
import java.util.List;

public class PrintfSimpleTreeBuilder {


private final RubyContext context;
private final List<FormatNode> sequence = new ArrayList<>();
private final List<PrintfSimpleParser.SprintfConfig> configs;

public static int DEFAULT = -1;

private static final byte[] EMPTY_BYTES = new byte[]{};

public PrintfSimpleTreeBuilder(RubyContext context, List<PrintfSimpleParser.SprintfConfig> configs) {
this.context = context;
this.configs = configs;
}

private void buildTree() {
for (PrintfSimpleParser.SprintfConfig config : configs) {
final FormatNode node;
if (config.isLiteral()) {
node = WriteBytesNodeGen.create(context, new LiteralFormatNode(context, config.getLiteralBytes()));
} else {
final FormatNode valueNode;

if (config.getNamesBytes() != null) {
final DynamicObject key = context.getSymbolTable().getSymbol(context.getRopeTable().getRope(config.getNamesBytes(), USASCIIEncoding.INSTANCE, CodeRange.CR_7BIT));
valueNode = ReadHashValueNodeGen.create(context, key, new SourceNode());
} else if (config.getAbsoluteArgumentIndex() != null) {
valueNode = ReadArgumentIndexValueNodeGen.create(context, config.getAbsoluteArgumentIndex(), new SourceNode());
} else {
valueNode = ReadValueNodeGen.create(context, new SourceNode());
}

final FormatNode widthNode;
if (config.isWidthStar()) {
widthNode = ReadIntegerNodeGen.create(context, new SourceNode());
} else if (config.isArgWidth()){
widthNode = ReadArgumentIndexValueNodeGen.create(context, config.getWidth(), new SourceNode());
} else {
widthNode = new LiteralFormatNode(context, config.getWidth() == null ? -1 : config.getWidth());
}

final FormatNode precisionNode;
if(config.isPrecisionStar()){
precisionNode = ReadIntegerNodeGen.create(context, new SourceNode());
} else if(config.isPrecisionArg()){
precisionNode = ReadArgumentIndexValueNodeGen.create(context, config.getPrecision(), new SourceNode());
} else {
precisionNode = new LiteralFormatNode(context, config.getPrecision() == null ? -1 : config.getPrecision());
}


switch (config.getFormatType()){
case INTEGER:
final char format;
switch (config.getFormat()) {
case 'b':
case 'B':
format = config.getFormat();
break;
case 'd':
case 'i':
case 'u':
format = 'd';
break;
case 'o':
format = 'o';
break;
case 'x':
case 'X':
format = config.getFormat();
break;
default:
throw new UnsupportedOperationException();
}

if(config.getFormat() == 'b' || config.getFormat() == 'B'){
node = WriteBytesNodeGen.create(context,
FormatIntegerBinaryNodeGen.create(context, format,
config.isPlus(), config.isFsharp(),
config.isMinus(),
config.isHasSpace(),
config.isZero(),
widthNode,
precisionNode,
ToIntegerNodeGen.create(context, valueNode)));
} else {
node = WriteBytesNodeGen.create(context,
FormatIntegerNodeGen.create(context, format, config.isHasSpace(), config.isZero(), config.isPlus(), config.isMinus(), config.isFsharp(),
widthNode,
precisionNode,
ToIntegerNodeGen.create(context, valueNode)));
}
break;
case FLOAT:
switch (config.getFormat()){
case 'f':
case 'e':
case 'E':
node = WriteBytesNodeGen.create(context,
FormatFloatNodeGen.create(context,
config.getFormat(), config.isHasSpace(), config.isZero(), config.isPlus(), config.isMinus(),
widthNode,
precisionNode,
ToDoubleWithCoercionNodeGen.create(context,
valueNode)));
break;
case 'g':
case 'G':
node = WriteBytesNodeGen.create(context,
FormatFloatHumanReadableNodeGen.create(context,
ToDoubleWithCoercionNodeGen.create(context,
valueNode)));
break;
default:
throw new UnsupportedOperationException();
}
break;
case OTHER:
switch (config.getFormat()){
case 'c':
node = WriteBytesNodeGen.create(context,
FormatCharacterNodeGen.create(context, config.isMinus(), widthNode,
valueNode));
break;
case 's':
case 'p':
final String conversionMethodName = config.getFormat() == 's' ? "to_s" : "inspect";
final FormatNode conversionNode;

if(config.getAbsoluteArgumentIndex() == null && config.getNamesBytes() == null) {
conversionNode = ReadStringNodeGen.create(context, true, conversionMethodName, false, EMPTY_BYTES, new SourceNode());
} else {
conversionNode = ToStringNodeGen.create(context, true, conversionMethodName, false, EMPTY_BYTES, valueNode);
}

if (config.getWidth() != null || config.isWidthStar()) {
node = WritePaddedBytesNodeGen.create(context, config.isMinus(), widthNode, conversionNode);
} else {
node = WriteBytesNodeGen.create(context, conversionNode);
}
break;
default:
throw new UnsupportedOperationException();
}
break;
default:
throw new UnsupportedOperationException("unsupported type: " + config.getFormatType().toString());



}

}
sequence.add(node);
}


}



public FormatNode getNode() {
buildTree();
return new SequenceNode(context, sequence.toArray(new FormatNode[sequence.size()]));
}

}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -21,30 +21,29 @@
* Simply write bytes.
*/
@NodeChildren({
@NodeChild(value = "width", type = FormatNode.class),
@NodeChild(value = "value", type = FormatNode.class),
})
public abstract class WritePaddedBytesNode extends FormatNode {

private final ConditionProfile leftJustifiedProfile = ConditionProfile.createBinaryProfile();
private final int padding;
private final boolean leftJustified;

public WritePaddedBytesNode(RubyContext context, int padding, boolean leftJustified) {
public WritePaddedBytesNode(RubyContext context, boolean leftJustified) {
super(context);
this.padding = padding;
this.leftJustified = leftJustified;
}

@Specialization
public Object write(VirtualFrame frame, byte[] bytes) {
public Object write(VirtualFrame frame, int padding, byte[] bytes) {
if (leftJustifiedProfile.profile(leftJustified)) {
return writeLeftJustified(frame, bytes);
return writeLeftJustified(frame, padding, bytes);
} else {
return writeRightJustified(frame, bytes);
return writeRightJustified(frame, padding, bytes);
}
}

private Object writeLeftJustified(VirtualFrame frame, byte[] bytes) {
private Object writeLeftJustified(VirtualFrame frame, int padding, byte[] bytes) {
writeBytes(frame, bytes);

for (int n = 0; n < padding - bytes.length; n++) {
@@ -54,7 +53,7 @@ private Object writeLeftJustified(VirtualFrame frame, byte[] bytes) {
return null;
}

private Object writeRightJustified(VirtualFrame frame, byte[] bytes) {
private Object writeRightJustified(VirtualFrame frame, int padding, byte[] bytes) {
for (int n = 0; n < padding - bytes.length; n++) {
writeByte(frame, (byte) ' ');
}

0 comments on commit b01533b

Please sign in to comment.