Skip to content

Commit

Permalink
Showing 15 changed files with 1,225 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
@@ -71,6 +71,10 @@ public DynamicObject argumentErrorNegativeArraySize(Node currentNode) {
return argumentError(coreStrings().NEGATIVE_ARRAY_SIZE.getRope(), currentNode, null);
}

public DynamicObject argumentErrorCharacterRequired(Node currentNode) {
return argumentError("%c requires a character", currentNode);
}

public DynamicObject argumentErrorCantOmitPrecision(Node currentNode) {
return argumentError("can't omit precision for a Float.", currentNode);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
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.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.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;
}

@Specialization(
guards = {
"width == cachedWidth"
},
limit = "getLimit()"
)
byte[] formatCached(VirtualFrame frame, int width, Object value,
@Cached("width") int cachedWidth,
@Cached("makeFormatString(width)") String cachedFormatString) {
final String charString = getCharString(frame, value);
return doFormat(charString, cachedFormatString);
}

@Specialization(contains = "formatCached")
protected byte[] format(VirtualFrame frame, int width, Object value) {
final String charString = getCharString(frame, value);
return doFormat(charString, makeFormatString(width));
}

protected String getCharString(VirtualFrame frame, 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().argumentErrorCharacterRequired(this));
}
charString = resultString;
}
return charString;
}

@TruffleBoundary
protected String makeFormatString(int width) {
final boolean leftJustified = hasMinusFlag || width < 0;
if (width < 0) {
width = -width;
}
return "%" + (leftJustified ? "-" : "") + width + "." + width + "s";
}

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

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

}
Original file line number Diff line number Diff line change
@@ -15,42 +15,82 @@
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 mapInfiniteResult(infinityString).getBytes(StandardCharsets.US_ASCII);
}

inifiniteFormatBuilder.append(format);
@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);
}

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

infiniteFormatString = inifiniteFormatBuilder.toString();
private static String mapFiniteResult(String input){
// Map java finite strings to the Ruby style NaN
if(input.contains("NAN")){
return input.replaceFirst("NAN", "NaN");
}
return input;
}

private static String mapInfiniteResult(String input){
// Map java infinite strings to the Ruby style Inf
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 +111,41 @@ 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 infiniteFormatBuilder = new StringBuilder();
infiniteFormatBuilder.append("%");
if(hasMinusFlag){
infiniteFormatBuilder.append("-");
}
if(hasPlusFlag){
infiniteFormatBuilder.append("+");
}

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

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

return infiniteFormatBuilder.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,67 +10,203 @@
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);
@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" + 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) {
// TODO BJF need to implement the skipping of bytes per format type when value is negative
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;

switch (format) {
case 'd':
case 'i':
@@ -94,7 +230,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 +241,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<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) {
Original file line number Diff line number Diff line change
@@ -0,0 +1,393 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.core.format.printf;

import org.jruby.truffle.core.format.exceptions.InvalidFormatException;

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

public class PrintfSimpleParser {

private final char[] source;

public PrintfSimpleParser(char[] source) {
this.source = source;
}

public List<SprintfConfig> parse() {
List<SprintfConfig> configs = new ArrayList<>();
ArgType argType = ArgType.NONE;

final int end = source.length;

for (int i = 0; i < end; ) {

// Add literal bytes up to the first %
int literalEnd = i;
for (; literalEnd < end && source[literalEnd] != '%'; literalEnd++) {
}
final int literalLength = literalEnd - i;
if (literalLength > 0) {
SprintfConfig config = new SprintfConfig();
config.setLiteral(true);
final char[] literalBytes = new char[literalLength];
System.arraycopy(source, i, literalBytes, 0, literalLength);
config.setLiteralBytes(charsToBytes(literalBytes));
configs.add(config);
}
if (literalEnd >= end) {
break; // format string ends with a literal
}

i = literalEnd + 1; // skip first %

SprintfConfig config = new SprintfConfig();
configs.add(config);

boolean finished = false;
boolean argTypeSet = false;

while (!finished) {
char p = i >= this.source.length ? '\0' : this.source[i];

switch (p) {
case ' ':
config.checkForFlags();
config.setHasSpace(true);
i++;
break;
case '#':
config.checkForFlags();
config.setFsharp(true);
i++;
break;
case '+':
config.checkForFlags();
config.setPlus(true);
i++;
break;
case '-':
config.checkForFlags();
config.setMinus(true);
i++;
break;
case '0':
config.checkForFlags();
config.setZero(true);
i++;
break;
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
LookAheadResult r = getNum(i, end);
i = r.getNextI();
if (valueAt(i) != null && valueAt(i) == '$') {
if (config.getAbsoluteArgumentIndex() != null) {
throw new InvalidFormatException("value given twice - " + r.getNumber() + "$");
}
checkPosArg(argType, r.getNumber());
argType = ArgType.NUMBERED;
argTypeSet = true;
config.setAbsoluteArgumentIndex(r.getNumber());
i++;
break;
}

config.checkForWidth();
config.setWidth(r.getNumber());
break;
case '<':
case '{':
char term = (p == '<') ? '>' : '}';

int j = i;
for (; j < end && this.source[j] != term; ) {
j++;
}
if (j >= end) {
throw new InvalidFormatException("malformed name - unmatched parenthesis");
}
final int nameLength = j - (i + 1);
final char[] nameBytes = new char[nameLength];
System.arraycopy(this.source, (i + 1), nameBytes, 0, nameLength);
config.setNamesBytes(charsToBytes(nameBytes));
i = j + 1;
checkNameArg(argType, nameBytes);
argType = ArgType.NAMED;
argTypeSet = true;
if (term == '}') {
config.setFormatType(SprintfConfig.FormatType.OTHER);
config.setFormat('s');
finished = true;
}
break;
case '*':
config.checkForWidth();

LookAheadResult numberDollarWidth = getNumberDollar(i + 1, end);
if (numberDollarWidth.getNumber() != null) {
config.setArgWidth(true);
config.setWidth(numberDollarWidth.getNumber());
checkPosArg(argType, numberDollarWidth.getNumber());
argType = ArgType.NUMBERED;
i = numberDollarWidth.getNextI();
} else {
checkNextArg(argType, 1); // TODO index next args
argType = ArgType.UNNUMBERED;
config.setWidthStar(true);
i++;
}
break;
case '.':
if (config.hasPrecision()) {
throw new InvalidFormatException("precision given twice");
}
config.setPrecisionVisited(true);
if (valueAt(i + 1) != null && valueAt(i + 1) == '*') {
LookAheadResult numberDollar = getNumberDollar(i + 2, end);
if (numberDollar.getNumber() != null) {
config.setPrecision(numberDollar.getNumber());
config.setPrecisionArg(true);
checkPosArg(argType, numberDollar.getNumber());
argType = ArgType.NUMBERED;
i = numberDollar.getNextI();
} else {
checkNextArg(argType, 1); // TODO idx
argType = ArgType.UNNUMBERED;
config.setPrecisionStar(true);
i += 2;
}
break;
}

LookAheadResult re = getNum(i + 1, end);
config.setPrecision(re.getNumber());
i = re.getNextI();
break;
case '\n':
case '\0':
i--;
case '%':
if (config.hasFlags()) {
throw new InvalidFormatException("invalid format character - %");
}
config.setLiteral(true);
byte[] literal = {(byte) '%'};
config.setLiteralBytes(literal);
i++;
finished = true;
break;
case 'c':
config.setFormatType(SprintfConfig.FormatType.OTHER);
config.setFormat(p);
i++;
if (!argTypeSet) {
checkNextArg(argType, 1);
argType = ArgType.UNNUMBERED;
}
finished = true;
break;
case 's':
case 'p':
config.setFormatType(SprintfConfig.FormatType.OTHER);
config.setFormat(p);
i++;
if (!argTypeSet) { // Speculative
checkNextArg(argType, 1);
argType = ArgType.UNNUMBERED;
}
finished = true;
break;
case 'd':
case 'i':
case 'o':
case 'x':
case 'X':
case 'b':
case 'B':
case 'u':
if (!argTypeSet) {
checkNextArg(argType, 1); // TODO idx correctly
argType = ArgType.UNNUMBERED;
}
config.setFormatType(SprintfConfig.FormatType.INTEGER);
config.setFormat(p);
finished = true;
i++;
break;
case 'g':
case 'G':
case 'e':
case 'E':
case 'a':
case 'A':
case 'f':
if (!argTypeSet) {
checkNextArg(argType, 1);
argType = ArgType.UNNUMBERED;
}
config.setFormatType(SprintfConfig.FormatType.FLOAT);
config.setFormat(p);
finished = true;
i++;
break;
default:
throw new InvalidFormatException("malformed format string - %" + p);
}
}
}
return configs;
}


private static void checkNextArg(ArgType argType, int nextArgumentIndex) {
switch (argType) {
case NUMBERED:
throw new InvalidFormatException("unnumbered(" + nextArgumentIndex + ") mixed with numbered");
case NAMED:
throw new InvalidFormatException("unnumbered(" + nextArgumentIndex + ") mixed with named");
}
}

private static void checkPosArg(ArgType posarg, int nextArgumentIndex) {
if (posarg == ArgType.UNNUMBERED) {
throw new InvalidFormatException("numbered(" + nextArgumentIndex + ") after unnumbered(" + posarg + ")");
}
if (posarg == ArgType.NAMED) {
throw new InvalidFormatException("numbered(" + nextArgumentIndex + ") after named");
}
if (nextArgumentIndex < 1) {
throw new InvalidFormatException("invalid index - " + nextArgumentIndex + "$");
}
}

private static void checkNameArg(ArgType argType, char[] name) {
if (argType == ArgType.UNNUMBERED) {
throw new InvalidFormatException("named" + new String(name) + " after unnumbered(%d)");
}
if (argType == ArgType.NUMBERED) {
throw new InvalidFormatException("named" + new String(name) + " after numbered");
}
}

private enum ArgType {
NONE,
NUMBERED,
UNNUMBERED,
NAMED
}

private void checkPosArg(int relativeArgumentIndex, int absoluteArgumentIndex) {
if (relativeArgumentIndex > 0) {
throw new InvalidFormatException("numbered(" + absoluteArgumentIndex + ") after unnumbered(" + relativeArgumentIndex + ")");
}
if (relativeArgumentIndex == -2) {
throw new InvalidFormatException("numbered(" + absoluteArgumentIndex + ") after named");
}
if (absoluteArgumentIndex < 1) {
throw new InvalidFormatException("invalid index - " + absoluteArgumentIndex + "$");
}
}

public LookAheadResult getNum(int startI, int end) {
StringBuilder sb = new StringBuilder();

int moreChars = 0;
for (int i = startI; i < end; i++) {
char nextChar = source[i];
if (!isDigit(nextChar)) {
break;
} else {
sb.append(nextChar);
moreChars += 1;
}
}

final int nextI = startI + moreChars;

if (nextI >= end) {
throw new InvalidFormatException("malformed format string - %%*[0-9]");
}

Integer result;
if (sb.length() > 0) {
result = Integer.parseInt(sb.toString());
} else {
result = null;
}
return new LookAheadResult(result, nextI);
}

public LookAheadResult getNumberDollar(int startI, int end) {
LookAheadResult lar = getNum(startI, end);
Integer result = null;
int newI = startI;
if (lar.getNumber() != null) {
final int nextI = lar.getNextI();
if (valueAt(nextI) != null && valueAt(nextI) == '$') {
result = lar.getNumber();
newI = nextI + 1;
if (result < 1) {
throw new InvalidFormatException("invalid index - " + result + "$");
}
}
}
return new LookAheadResult(result, newI);
}

public static class LookAheadResult {
private Integer number;
private int nextI;

public LookAheadResult(Integer number, int nextI) {
this.number = number;
this.nextI = nextI;
}

public Integer getNumber() {
return number;
}

public int getNextI() {
return nextI;
}
}

public static boolean isDigit(char c) {
return c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5' || c == '6' || c == '7' || c == '8' || c == '9';
}

public Character valueAt(int index) {
assert index >= 0;
if (index < this.source.length) {
return this.source[index];
} else {
return null;
}
}

private static byte[] charsToBytes(char[] chars) {
final byte[] bytes = new byte[chars.length];

for (int n = 0; n < chars.length; n++) {
bytes[n] = (byte) chars[n];
}

return bytes;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
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<SprintfConfig> configs;

public static int DEFAULT = -1;

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

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

private void buildTree() {
for (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
@@ -0,0 +1,220 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.core.format.printf;

import org.jruby.truffle.core.format.exceptions.InvalidFormatException;

public class SprintfConfig {

public enum FormatType {
INTEGER, FLOAT, OTHER
}

private boolean literal = false;
private byte[] literalBytes;

private byte[] namesBytes;
private boolean argWidth = false;

private Integer absoluteArgumentIndex;
private Integer precision;
private boolean precisionArg = false;
private boolean precisionVisited = false;
private Integer width;
private boolean hasSpace = false;
private boolean fsharp = false; // #
private boolean plus = false;
private boolean minus = false;
private boolean zero = false;
private boolean widthStar = false;
private boolean precisionStar = false;
private char format;
private FormatType formatType;


public void checkForFlags() {
if (hasWidth()) {
throw new InvalidFormatException("flag after width");
}
if (hasPrecision()) {
throw new InvalidFormatException("flag after precision");
}
}

public void checkForWidth() {
if (hasWidth()) {
throw new InvalidFormatException("width given twice");
}
if (hasPrecision()) {
throw new InvalidFormatException("width after precision");
}
}

public boolean isHasSpace() {
return hasSpace;
}

public void setHasSpace(boolean hasSpace) {
this.hasSpace = hasSpace;
}

public Integer getPrecision() {
return precision;
}

public void setPrecision(int precision) {
this.precision = precision;
}

public void setPrecision(Integer precision) {
this.precision = precision;
}

public Integer getWidth() {
return width;
}

public void setWidth(Integer width) {
this.width = width;
}

public boolean isFsharp() {
return fsharp;
}

public void setFsharp(boolean fsharp) {
this.fsharp = fsharp;
}

public boolean isPlus() {
return plus;
}

public void setPlus(boolean plus) {
this.plus = plus;
}

public boolean isMinus() {
return minus;
}

public void setMinus(boolean minus) {
this.minus = minus;
}

public boolean isZero() {
return zero;
}

public void setZero(boolean zero) {
this.zero = zero;
}

public FormatType getFormatType() {
return formatType;
}

public void setFormatType(FormatType formatType) {
this.formatType = formatType;
}

public char getFormat() {
return format;
}

public void setFormat(char format) {
this.format = format;
}

public boolean isWidthStar() {
return widthStar;
}

public void setWidthStar(boolean widthStar) {
this.widthStar = widthStar;
}

public boolean isPrecisionStar() {
return precisionStar;
}

public void setPrecisionStar(boolean precisionStar) {
this.precisionStar = precisionStar;
}

public boolean hasPrecision() {
return precision != null || precisionStar || precisionVisited;
}

public boolean hasWidth() {
return width != null || widthStar;
}

public boolean isLiteral() {
return literal;
}

public void setLiteral(boolean literal) {
this.literal = literal;
}

public byte[] getLiteralBytes() {
return literalBytes;
}

public void setLiteralBytes(byte[] literalBytes) {
this.literalBytes = literalBytes;
}

public Integer getAbsoluteArgumentIndex() {
return absoluteArgumentIndex;
}

public void setAbsoluteArgumentIndex(Integer absoluteArgumentIndex) {
this.absoluteArgumentIndex = absoluteArgumentIndex;
}

public boolean isArgWidth() {
return argWidth;
}

public void setArgWidth(boolean argWidth) {
this.argWidth = argWidth;
}


public boolean isPrecisionVisited() {
return precisionVisited;
}

public void setPrecisionVisited(boolean precisionVisited) {
this.precisionVisited = precisionVisited;
}

public byte[] getNamesBytes() {
return namesBytes;
}

public void setNamesBytes(byte[] namesBytes) {
this.namesBytes = namesBytes;
}

public boolean isPrecisionArg() {
return precisionArg;
}

public void setPrecisionArg(boolean precisionArg) {
this.precisionArg = precisionArg;
}

public boolean hasFlags() {
return literal || precision != null || precisionVisited || width != null || hasSpace || fsharp || plus || minus || zero || precisionStar || widthStar || formatType != null;
}
}
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 25768d9

Please sign in to comment.