package php.runtime.util; import php.runtime.Memory; import java.math.BigInteger; import java.util.ArrayList; import java.util.List; import java.util.Locale; public class PrintF { private static final BigInteger BIG_2_64 = BigInteger.ONE.shiftLeft(64); private static final BigInteger BIG_TEN = new BigInteger("10"); private final String format; private final Locale locale; protected final Memory[] args; public PrintF(Locale locale, String format, Memory[] args){ this.locale = locale; this.format = format; this.args = args; } private List<Segment> parse(){ List<Segment> segments = new ArrayList<Segment>(); int length = format.length(); int start = 0; int index = 0; StringBuilder sb = new StringBuilder(); StringBuilder flags = new StringBuilder(); for(int i = 0; i < length; i++) { char ch = format.charAt(i); if (i + 1 < length && ch == '%'){ sb.append(ch); boolean isLeft = false; boolean isAlt = false; boolean isShowSign = false; int argIndex = -1; int leftPadLength = 0; int width = 0; int padChar = -1; flags.setLength(0); int j = i + 1; loop: for(; j < length; j++){ ch = format.charAt(j); switch (ch){ case '-': isLeft = true; if (j + 1 < length && format.charAt(j + 1) == '0') { padChar = '0'; j++; } break; case '#': isAlt = true; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if (ch == '0' && padChar < 0) padChar = '0'; else { int value = ch - '0'; for (int k = j + 1; k < length; k++) { char digit = format.charAt(k); if (Character.isDigit(digit)) { value = value * 10 + digit - '0'; j++; } else break; } if (j + 1 < length && format.charAt(j + 1) == '$') { argIndex = value - 1; j++; } else { width = value; } } break; case '\'': padChar = format.charAt(j + 1); j += 1; break; case '+': isShowSign = true; break; case ' ': case ',': case '(': flags.append(ch); break; default: break loop; } } int head = j; if (argIndex < 0) argIndex = index; loop: for (; j < length; j++) { ch = format.charAt(j); switch (ch) { case '%': i = j; segments.add(new TextSegment(sb.toString())); sb.setLength(0); break loop; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '.': case '$': break; case 's': case 'S': sb.setLength(sb.length() - 1); if (width <= 0 && 0 < leftPadLength) width = leftPadLength; index++; segments.add(new StringSegment( sb, isLeft || isAlt, padChar, ch == 'S', width, format.substring(head, j), argIndex )); sb.setLength(0); i = j; break loop; case 'c': case 'C': sb.setLength(sb.length() - 1); if (width <= 0 && 0 < leftPadLength) width = leftPadLength; index++; segments.add(new CharSegment( sb, isLeft || isAlt, padChar, ch == 'C', width, format.substring(head, j), argIndex )); sb.setLength(0); i = j; break loop; case 'i': ch = 'd'; case 'd': case 'x': case 'o': case 'X': case 'b': case 'B': case 'u': sb.setLength(sb.length() - 1); if (sb.length() > 0) segments.add(new TextSegment(sb.toString())); sb.setLength(0); if (isAlt) sb.append('#'); if (isShowSign) sb.append('+'); sb.append(flags); if (width > 0) { if (isLeft) sb.append('-'); else if (padChar == '0') sb.append('0'); sb.append(width); } sb.append(format, head, j); sb.append(ch); index++; segments.add(LongSegment.valueOf(sb.toString(), argIndex)); sb.setLength(0); i = j; break loop; case 'e': case 'E': case 'f': case 'g': case 'G': case 'F': Locale _locale = locale; if (ch == 'F') ch = 'f'; else _locale = null; sb.setLength(sb.length() - 1); if (sb.length() > 0) segments.add(new TextSegment(sb.toString())); sb.setLength(0); if (isAlt) sb.append('#'); if (isShowSign) sb.append('+'); sb.append(flags); if (width > 0) { if (isLeft) sb.append('-'); else if (padChar == '0') sb.append('0'); sb.append(width); } sb.append(format, head, j); sb.append(ch); index++; segments.add(new DoubleSegment( sb.toString(), isLeft && padChar == '0', argIndex, _locale )); sb.setLength(0); i = j; break loop; default: if (isLeft) sb.append('-'); if (isAlt) sb.append('#'); sb.append(flags); sb.append(format, head, j); sb.append(ch); i = j; break loop; } } } else sb.append(ch); } if (sb.length() > 0) segments.add(new TextSegment(sb.toString())); return segments; } public String toString(){ StringBuilder builder = new StringBuilder(); for(Segment segment : parse()){ if (!segment.apply(locale, builder, args)) return null; } return builder.toString(); } abstract public static class Segment { protected final String format; public Segment(String format){ this.format = format; } static boolean hasIndex(String format) { return format.indexOf('$') >= 0; } static int getIndex(String format) { int value = 0; for (int i = 0; i < format.length(); i++) { char ch; if ('0' <= (ch = format.charAt(i)) && ch <= '9') value = 10 * value + ch - '0'; else break; } return value - 1; } static String getIndexFormat(String format) { int p = format.indexOf('$'); return '%' + format.substring(p + 1); } abstract public Memory.Type getType(); abstract protected boolean apply(Locale locale, StringBuilder sb, Memory[] args); } /** * %c */ static class CharSegment extends StringSegment { public CharSegment(StringBuilder prefix, boolean isLeft, int pad, boolean isUpper, int width, String format, int index) { super(prefix, isLeft, pad, isUpper, width, format, index); } @Override public Memory.Type getType() { return Memory.Type.INT; } @Override protected String toValue(Memory[] args) { return String.valueOf(args[_index].toChar()); } } public class TextSegment extends Segment { public TextSegment(String format) { super(format); } @Override public Memory.Type getType() { return Memory.Type.STRING; } @Override protected boolean apply(Locale locale, StringBuilder sb, Memory[] args) { sb.append(format); return true; } } /** * %s */ static class StringSegment extends Segment { protected final char []_prefix; protected final int _min; protected final int _max; protected final boolean _isLeft; protected final boolean _isUpper; protected final char _pad; protected final int _index; public StringSegment(StringBuilder prefix, boolean isLeft, int pad, boolean isUpper, int width, String format, int index) { super(format); _prefix = new char[prefix.length()]; _isLeft = isLeft; _isUpper = isUpper; if (pad >= 0) _pad = (char) pad; else _pad = ' '; prefix.getChars(0, _prefix.length, _prefix, 0); if (hasIndex(format)) { index = getIndex(format); format = getIndexFormat(format); } int i = 0; int len = format.length(); int max = Integer.MAX_VALUE; char ch; if (0 < len && format.charAt(0) == '.') { max = 0; for (i++; i < len && '0' <= (ch = format.charAt(i)) && ch <= '9'; i++) { max = 10 * max + ch - '0'; } } _min = width; _max = max; _index = index; } @Override public Memory.Type getType() { return Memory.Type.STRING; } protected String toValue(Memory[] args){ return args[_index].toString(); } @Override protected boolean apply(Locale locale, StringBuilder sb, Memory[] args) { sb.append(_prefix, 0, _prefix.length); if (_index >= args.length) return false; String value = toValue(args); int len = value.length(); if (_max < len) { value = value.substring(0, _max); len = _max; } if (_isUpper) value = value.toUpperCase(); if (!_isLeft) { for (int i = len; i < _min; i++) { sb.append(_pad); } } sb.append(value); if (_isLeft) { for (int i = len; i < _min; i++) { sb.append(_pad); } } return true; } } static class LongSegment extends Segment { protected int _index; LongSegment(String format, int _index) { super(format); this._index = _index; } static Segment valueOf(String format, int index) { if (hasIndex(format)) { index = getIndex(format); format = getIndexFormat(format); } else { format = '%' + format; } if (format.length() > 1 && format.charAt(1) == '.') { int i; for (i = 2; i < format.length(); i++) { char ch = format.charAt(i); if (! (Character.isDigit(ch))) break; } format = '%' + format.substring(i); } if (format.charAt(format.length() - 1) == 'x' || format.charAt(format.length() - 1) == 'X') { HexSegment hex = HexSegment.valueOf(format, index); if (hex != null) return hex; } if (format.charAt(format.length() - 1) == 'b' || format.charAt(format.length() - 1) == 'B') { BinarySegment bin = BinarySegment.valueOf(format, index); if (bin != null) return bin; } if (format.charAt(format.length() - 1) == 'u') { UnsignedSegment unsign = UnsignedSegment.valueOf(format, index); if (unsign != null) return unsign; } return new LongSegment(format, index); } @Override public Memory.Type getType() { return Memory.Type.INT; } @Override protected boolean apply(Locale locale, StringBuilder sb, Memory[] args) { long value; if (_index < args.length) value = args[_index].toLong(); else { return false; } sb.append(String.format(Locale.ENGLISH, format, value)); return true; } } static class HexSegment extends Segment { private final int _index; private final int _min; private final char _pad; private boolean _isUpper; HexSegment(String format, int index, int min, int pad, boolean isUpper) { super(format); _index = index; _min = min; if (pad >= 0) _pad = (char) pad; else _pad = ' '; _isUpper = isUpper; } static HexSegment valueOf(String format, int index) { int length = format.length(); int offset = 1; boolean isUpper = format.charAt(length - 1) == 'X'; char pad = ' '; if (format.charAt(offset) == ' ') { pad = ' '; offset++; } else if (format.charAt(offset) == '0') { pad = '0'; offset++; } int min = 0; for (; offset < length - 1; offset++) { char ch = format.charAt(offset); if ('0' <= ch && ch <= '9') min = 10 * min + ch - '0'; else return null; } return new HexSegment(format, index, min, pad, isUpper); } @Override public Memory.Type getType() { return Memory.Type.INT; } @Override public boolean apply(Locale locale, StringBuilder sb, Memory []args) { long value; if (_index >= 0 && _index < args.length) value = args[_index].toLong(); else return false; int digits = 0; long shift = value; for (int i = 0; i < 16; i++) { if (shift != 0) digits = i; shift = shift >>> 4; } for (int i = digits + 1; i < _min; i++) sb.append(_pad); for (; digits >= 0; digits--) { int digit = (int) (value >>> (4 * digits)) & 0xf; if (digit <= 9) sb.append((char) ('0' + digit)); else if (_isUpper) sb.append((char) ('A' + digit - 10)); else sb.append((char) ('a' + digit - 10)); } return true; } } static class BinarySegment extends Segment { private final int _index; private final int _min; private final char _pad; @Override public Memory.Type getType() { return Memory.Type.INT; } BinarySegment(String format, int index, int min, int pad) { super(format); _index = index; _min = min; if (pad >= 0) _pad = (char) pad; else _pad = ' '; } static BinarySegment valueOf(String format, int index) { int length = format.length(); int offset = 1; char pad = ' '; if (format.charAt(offset) == ' ') { pad = ' '; offset++; } else if (format.charAt(offset) == '0') { pad = '0'; offset++; } int min = 0; for (; offset < length - 1; offset++) { char ch = format.charAt(offset); if ('0' <= ch && ch <= '9') min = 10 * min + ch - '0'; else return null; } return new BinarySegment(format, index, min, pad); } @Override public boolean apply(Locale locale, StringBuilder sb, Memory[] args) { long value; if (_index >= 0 && _index < args.length) value = args[_index].toLong(); else return false; int digits = 0; long shift = value; for (int i = 0; i < 64; i++) { if (shift != 0) digits = i; shift = shift >>> 1; } for (int i = digits + 1; i < _min; i++) sb.append(_pad); for (; digits >= 0; digits--) { int digit = (int) (value >>> (digits)) & 0x1; sb.append((char) ('0' + digit)); } return true; } } static class UnsignedSegment extends Segment { private final int _index; private final int _min; private final char _pad; @Override public Memory.Type getType() { return Memory.Type.INT; } UnsignedSegment(String format, int index, int min, int pad) { super(format); _index = index; _min = min; if (pad >= 0) _pad = (char) pad; else _pad = ' '; } static UnsignedSegment valueOf(String format, int index) { int length = format.length(); int offset = 1; if (format.charAt(offset) == '+') offset++; char pad = ' '; if (format.charAt(offset) == ' ') { pad = ' '; offset++; } else if (format.charAt(offset) == '0') { pad = '0'; offset++; } int min = 0; for (; offset < length - 1; offset++) { char ch = format.charAt(offset); if ('0' <= ch && ch <= '9') min = 10 * min + ch - '0'; else return null; } return new UnsignedSegment(format, index, min, pad); } @Override public boolean apply(Locale locale, StringBuilder sb, Memory[] args) { long value; if (_index >= 0 && _index < args.length) value = args[_index].toLong(); else return false; char []buf = new char[32]; int digits = buf.length; if (value == 0) { buf[--digits] = '0'; } else if (value > 0) { while (value != 0) { int digit = (int) (value % 10); buf[--digits] = (char) ('0' + digit); value = value / 10; } } else { BigInteger bigInt = new BigInteger(String.valueOf(value)); bigInt = bigInt.add(BIG_2_64); while (bigInt.compareTo(BigInteger.ZERO) != 0) { int digit = bigInt.mod(BIG_TEN).intValue(); buf[--digits] = (char) ('0' + digit); bigInt = bigInt.divide(BIG_TEN); } } for (int i = buf.length - digits; i < _min; i++) sb.append(_pad); for (; digits < buf.length; digits++) { sb.append(buf[digits]); } return true; } } static class DoubleSegment extends Segment { private final String _format; private final boolean _isLeftZero; private final int _index; private final Locale _locale; @Override public Memory.Type getType() { return Memory.Type.DOUBLE; } DoubleSegment(String format, boolean isLeftZero, int index, Locale locale) { super(format); if (hasIndex(format)) { _index = getIndex(format); _format = getIndexFormat(format); } else { _format = '%' + format; _index = index; } _isLeftZero = isLeftZero; _locale = locale; } @Override public boolean apply(Locale locale, StringBuilder sb, Memory[] args) { double value; if (_index < args.length) value = args[_index].toDouble(); else return false; String s; if (_locale == null) s = String.format(Locale.ENGLISH, _format, value); else s = String.format(_locale, _format, value); if (_isLeftZero) { int len = s.length(); // php/1174 "-0" not allowed by java formatter for (int i = 0; i < len; i++) { char ch = s.charAt(i); if (ch == ' ') sb.append('0'); else sb.append(ch); } } else { sb.append(s); } return true; } } }