StringFormatter.java
/*
* Copyright (c) 2013, 2014 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.runtime.core;
import com.oracle.truffle.api.CompilerDirectives;
import org.jruby.truffle.runtime.DebugOperations;
import org.jruby.truffle.runtime.RubyContext;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.List;
public class StringFormatter {
@CompilerDirectives.TruffleBoundary
public static String format(RubyContext context, String format, List<Object> values) {
final ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
final PrintStream printStream = new PrintStream(byteArray);
format(context, printStream, format, values);
return byteArray.toString();
}
@CompilerDirectives.TruffleBoundary
public static void format(RubyContext context, PrintStream stream, String format, List<Object> values) {
/*
* See http://www.ruby-doc.org/core-1.9.3/Kernel.html#method-i-sprintf.
*
* At the moment we just do the basics that we need. We will need a proper lexer later on.
* Or better than that we could compile to Truffle nodes if the format string is constant! I
* don't think we can easily translate to Java's format syntax, otherwise JRuby would do
* that and they don't.
*/
// I'm not using a for loop, because Checkstyle won't let me modify the control variable
int n = 0;
int v = 0;
while (n < format.length()) {
final char c = format.charAt(n);
n++;
if (c == '%') {
// %[flags][width][.precision]type
final String flagChars = "0";
boolean zeroPad = false;
while (n < format.length() && flagChars.indexOf(format.charAt(n)) != -1) {
switch (format.charAt(n)) {
case '0':
zeroPad = true;
break;
}
n++;
}
int width;
if (n < format.length() && Character.isDigit(format.charAt(n))) {
final int widthStart = n;
while (Character.isDigit(format.charAt(n))) {
n++;
}
width = Integer.parseInt(format.substring(widthStart, n));
} else {
width = 0;
}
int precision;
if (format.charAt(n) == '.') {
n++;
final int precisionStart = n;
while (Character.isDigit(format.charAt(n))) {
n++;
}
precision = Integer.parseInt(format.substring(precisionStart, n));
} else {
precision = 5;
}
final char type = format.charAt(n);
n++;
final StringBuilder formatBuilder = new StringBuilder();
formatBuilder.append("%");
if (width > 0) {
if (zeroPad) {
formatBuilder.append("0");
}
formatBuilder.append(width);
}
switch (type) {
case '%': {
stream.print("%");
break;
}
case 's': {
formatBuilder.append("s");
assert formatBuilder.toString().equals("%s");
stream.print(DebugOperations.send(context, values.get(v), "to_s", null));
break;
}
case 'd': {
formatBuilder.append("d");
final Object value = values.get(v);
final long longValue;
if (value instanceof Integer) {
longValue = (int) value;
} else if (value instanceof Long) {
longValue = (long) value;
} else {
throw new UnsupportedOperationException();
}
stream.printf(formatBuilder.toString(), longValue);
break;
}
case 'f': {
formatBuilder.append(".");
formatBuilder.append(precision);
formatBuilder.append("f");
final double value = CoreLibrary.toDouble(values.get(v));
stream.printf(formatBuilder.toString(), value);
break;
}
default:
throw new RuntimeException("Kernel#sprintf error " + type);
}
v++;
} else {
stream.print(c);
}
}
}
}