Skylark: support %r format specifier
Refactor the implementation of format.
Add %r. Improve some error messages.
--
MOS_MIGRATED_REVID=96154542
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/Printer.java b/src/main/java/com/google/devtools/build/lib/syntax/Printer.java
index 8739be8..56f129e 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/Printer.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/Printer.java
@@ -13,13 +13,12 @@
// limitations under the License.
package com.google.devtools.build.lib.syntax;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
-
import com.google.devtools.build.lib.collect.nestedset.Order;
import com.google.devtools.build.lib.vfs.PathFragment;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Formattable;
@@ -183,6 +182,14 @@
}
}
+ private static Appendable append(Appendable buffer, CharSequence s, int start, int end) {
+ try {
+ return buffer.append(s, start, end);
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+
private static Appendable backslashChar(Appendable buffer, char c) {
return append(append(buffer, '\\'), c);
}
@@ -320,8 +327,10 @@
/**
* Convert BUILD language objects to Formattable so JDK can render them correctly.
* Don't do this for numeric or string types because we want %d, %x, %s to work.
+ * This function is intended for use in assertions such as Precondition.checkArgument
+ * so that you only pay the cost of computing the string when the assertion passes.
*/
- private static Object strFormattable(final Object o) {
+ public static Object strFormattable(final Object o) {
if (o instanceof Integer || o instanceof Double || o instanceof String) {
return o;
} else {
@@ -339,7 +348,29 @@
}
}
- private static final Object[] EMPTY = new Object[0];
+ /**
+ * Convert BUILD language objects to Formattable so JDK can render them correctly.
+ * Don't do this for numeric or string types because we want %d, %x, %s to work.
+ * This function is intended for use in assertions such as Precondition.checkArgument
+ * so that you only pay the cost of computing the string when the assertion passes.
+ */
+ public static Object reprFormattable(final Object o) {
+ if (o instanceof Integer || o instanceof Double) {
+ return o;
+ } else {
+ return new Formattable() {
+ @Override
+ public String toString() {
+ return repr(o);
+ }
+
+ @Override
+ public void formatTo(Formatter formatter, int flags, int width, int precision) {
+ write(formatter.out(), o);
+ }
+ };
+ }
+ }
/*
* N.B. MissingFormatWidthException is the only kind of IllegalFormatException
@@ -347,63 +378,102 @@
*/
/**
- * Perform Python-style string formatting. Implemented by delegation to Java's
- * own string formatting routine to avoid reinventing the wheel. In more
- * obscure cases, semantics follow JDK (not Python) rules.
+ * Perform Python-style string formatting.
*
* @param pattern a format string.
- * @param tuple a tuple containing positional arguments
+ * @param arguments a tuple containing positional arguments.
+ * @return the formatted string.
*/
- public static String format(String pattern, List<?> tuple) throws IllegalFormatException {
- int count = countPlaceholders(pattern);
- if (count != tuple.size()) {
+ public static String formatString(String pattern, List<?> arguments)
+ throws IllegalFormatException {
+ return format(new StringBuilder(), pattern, arguments).toString();
+ }
+
+ /**
+ * Perform Python-style string formatting.
+ *
+ * @param pattern a format string.
+ * @param arguments positional arguments.
+ * @return the formatted string.
+ */
+ public static String format(String pattern, Object... arguments)
+ throws IllegalFormatException {
+ return formatString(pattern, ImmutableList.copyOf(arguments));
+ }
+
+ /**
+ * Perform Python-style string formatting, as per pattern % tuple
+ * Limitations: only %d %s %r %% are supported.
+ *
+ * @param buffer an Appendable to output to.
+ * @param pattern a format string.
+ * @param arguments a list containing positional arguments.
+ * @return the buffer, in fluent style.
+ */
+ // TODO(bazel-team): support formatting arguments, and more complex Python patterns.
+ public static Appendable format(Appendable buffer, String pattern, List<?> arguments)
+ throws IllegalFormatException {
+ // N.B. MissingFormatWidthException is the only kind of IllegalFormatException
+ // whose constructor can take and display arbitrary error message, hence its use below.
+
+ int length = pattern.length();
+ int argLength = arguments.size();
+ int i = 0; // index of next character in pattern
+ int a = 0; // index of next argument in arguments
+
+ while (i < length) {
+ int p = pattern.indexOf('%', i);
+ if (p == -1) {
+ append(buffer, pattern, i, length);
+ break;
+ }
+ if (p > i) {
+ append(buffer, pattern, i, p);
+ }
+ if (p == length - 1) {
+ throw new MissingFormatWidthException(
+ "incomplete format pattern ends with %: " + repr(pattern));
+ }
+ char directive = pattern.charAt(p + 1);
+ i = p + 2;
+ switch (directive) {
+ case '%':
+ append(buffer, '%');
+ continue;
+ case 'd':
+ case 'r':
+ case 's':
+ if (a >= argLength) {
+ throw new MissingFormatWidthException("not enough arguments for format pattern "
+ + repr(pattern) + ": " + repr(SkylarkList.tuple(arguments)));
+ }
+ Object argument = arguments.get(a++);
+ switch (directive) {
+ case 'd':
+ if (argument instanceof Integer) {
+ append(buffer, argument.toString());
+ continue;
+ } else {
+ throw new MissingFormatWidthException(
+ "invalid argument " + repr(argument) + " for format pattern %d");
+ }
+ case 'r':
+ write(buffer, argument);
+ continue;
+ case 's':
+ print(buffer, argument);
+ continue;
+ }
+ default:
+ throw new MissingFormatWidthException(
+ "unsupported format character " + repr(String.valueOf(directive))
+ + " at index " + (p + 1) + " in " + repr(pattern));
+ }
+ }
+ if (a < argLength) {
throw new MissingFormatWidthException(
"not all arguments converted during string formatting");
}
-
- List<Object> args = new ArrayList<>();
-
- for (Object o : tuple) {
- args.add(strFormattable(o));
- }
-
- try {
- return String.format(pattern, args.toArray(EMPTY));
- } catch (IllegalFormatException e) {
- throw new MissingFormatWidthException(
- "invalid arguments for format string");
- }
- }
-
- private static int countPlaceholders(String pattern) {
- int length = pattern.length();
- boolean afterPercent = false;
- int i = 0;
- int count = 0;
- while (i < length) {
- switch (pattern.charAt(i)) {
- case 's':
- case 'd':
- if (afterPercent) {
- count++;
- afterPercent = false;
- }
- break;
-
- case '%':
- afterPercent = !afterPercent;
- break;
-
- default:
- if (afterPercent) {
- throw new MissingFormatWidthException("invalid arguments for format string");
- }
- afterPercent = false;
- break;
- }
- i++;
- }
-
- return count;
+ return buffer;
}
}