aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Francois-Rene Rideau <tunes@google.com>2015-06-16 23:12:04 +0000
committerGravatar John Field <jfield@google.com>2015-06-17 15:24:06 +0000
commit8d5cce317f7f9a4390788f27f5f3087794207678 (patch)
treee4ae8222c7f30d3c9f5cad9fe9cfc8ff22011e31
parentb9ec66aa5e37657e0952f5f6c284e75ea75560cf (diff)
Skylark: support %r format specifier
Refactor the implementation of format. Add %r. Improve some error messages. -- MOS_MIGRATED_REVID=96154542
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java15
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java4
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/FunctionSignature.java1
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/LValue.java6
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/Printer.java168
-rw-r--r--src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java2
-rw-r--r--src/test/java/com/google/devtools/build/lib/syntax/PrinterTest.java50
9 files changed, 164 insertions, 90 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java b/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java
index f67b0feec9..794e0392e6 100644
--- a/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java
+++ b/src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java
@@ -472,8 +472,7 @@ public class MethodLibrary {
Location loc) throws EvalException, ConversionException {
int res = stringFind(false, self, sub, start, end, "'end' argument to rindex");
if (res < 0) {
- throw new EvalException(loc, String.format("substring %s not found in %s",
- Printer.repr(sub), Printer.repr(self)));
+ throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self));
}
return res;
}
@@ -498,8 +497,7 @@ public class MethodLibrary {
Location loc) throws EvalException, ConversionException {
int res = stringFind(true, self, sub, start, end, "'end' argument to index");
if (res < 0) {
- throw new EvalException(loc, String.format("substring %s not found in %s",
- Printer.repr(sub), Printer.repr(self)));
+ throw new EvalException(loc, Printer.format("substring %r not found in %r", sub, self));
}
return res;
}
@@ -718,8 +716,7 @@ public class MethodLibrary {
} else if (self instanceof Map<?, ?>) {
Map<?, ?> dictionary = (Map<?, ?>) self;
if (!dictionary.containsKey(key)) {
- throw new EvalException(loc, String.format("Key %s not found in dictionary",
- Printer.repr(key)));
+ throw new EvalException(loc, Printer.format("Key %r not found in dictionary", key));
}
return dictionary.get(key);
@@ -922,7 +919,7 @@ public class MethodLibrary {
}
} else {
throw new EvalException(loc,
- String.format("%s is not of type string or int or bool", Printer.repr(x)));
+ Printer.format("%r is not of type string or int or bool", x));
}
}
};
@@ -1121,8 +1118,8 @@ public class MethodLibrary {
if (defaultValue != Environment.NONE) {
return defaultValue;
} else {
- throw new EvalException(loc, String.format("Object of type '%s' has no field %s",
- EvalUtils.getDataTypeName(obj), Printer.repr(name)));
+ throw new EvalException(loc, Printer.format("Object of type '%s' has no field %r",
+ EvalUtils.getDataTypeName(obj), name));
}
}
return result;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
index b7937ad98c..65ea8df75d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java
@@ -190,18 +190,18 @@ public final class BinaryOperatorExpression extends Expression {
if (rval instanceof List<?>) {
List<?> rlist = (List<?>) rval;
if (EvalUtils.isTuple(rlist)) {
- return Printer.format(pattern, rlist);
+ return Printer.formatString(pattern, rlist);
}
/* string % list: fall thru */
}
if (rval instanceof SkylarkList) {
SkylarkList rlist = (SkylarkList) rval;
if (rlist.isTuple()) {
- return Printer.format(pattern, rlist.toList());
+ return Printer.formatString(pattern, rlist.toList());
}
}
- return Printer.format(pattern, Collections.singletonList(rval));
+ return Printer.formatString(pattern, Collections.singletonList(rval));
} catch (IllegalFormatException e) {
throw new EvalException(getLocation(), e.getMessage());
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
index bcdd4e8ce3..e5b06a971d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java
@@ -59,8 +59,8 @@ public final class DotExpression extends Expression {
throw new EvalException(getLocation(), customErrorMessage);
}
}
- throw new EvalException(getLocation(), String.format("Object of type '%s' has no field %s",
- EvalUtils.getDataTypeName(objValue), Printer.repr(name)));
+ throw new EvalException(getLocation(), Printer.format("Object of type '%s' has no field %r",
+ EvalUtils.getDataTypeName(objValue), name));
}
return result;
}
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FunctionSignature.java b/src/main/java/com/google/devtools/build/lib/syntax/FunctionSignature.java
index 272fc5e7d3..e1eda63b6a 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/FunctionSignature.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/FunctionSignature.java
@@ -19,7 +19,6 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Interner;
import com.google.common.collect.Interners;
import com.google.common.collect.Lists;
-
import com.google.devtools.build.lib.util.StringCanonicalizer;
import java.io.Serializable;
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/LValue.java b/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
index 42b5b097df..9de834f6ab 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/LValue.java
@@ -95,9 +95,9 @@ public class LValue implements Serializable {
if (variableType != null && !variableType.equals(resultType)
&& !resultType.equals(Environment.NoneType.class)
&& !variableType.equals(Environment.NoneType.class)) {
- throw new EvalException(loc, String.format("Incompatible variable types, "
- + "trying to assign %s (type of %s) to variable %s which is already %s",
- Printer.repr(result),
+ throw new EvalException(loc, Printer.format("Incompatible variable types, "
+ + "trying to assign %r (type of %s) to variable %s which is already %s",
+ result,
EvalUtils.getDataTypeName(result),
ident.getName(),
EvalUtils.getDataTypeNameFromClass(variableType)));
diff --git a/src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java b/src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java
index b3d68178cc..2e905b774d 100644
--- a/src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java
+++ b/src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java
@@ -120,7 +120,7 @@ public final class ListComprehension extends Expression {
@Override
public String toString() {
- return String.format("for %s in %s", variables.toString(), Printer.repr(list));
+ return Printer.format("for %s in %r", variables.toString(), list);
}
}
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 8739be8c65..56f129e6b8 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 @@ public final class Printer {
}
}
+ 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 @@ public final class Printer {
/**
* 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 @@ public final class Printer {
}
}
- 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 @@ public final class Printer {
*/
/**
- * 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()) {
- throw new MissingFormatWidthException(
- "not all arguments converted during string formatting");
- }
-
- List<Object> args = new ArrayList<>();
-
- for (Object o : tuple) {
- args.add(strFormattable(o));
- }
+ public static String formatString(String pattern, List<?> arguments)
+ throws IllegalFormatException {
+ return format(new StringBuilder(), pattern, arguments).toString();
+ }
- try {
- return String.format(pattern, args.toArray(EMPTY));
- } catch (IllegalFormatException e) {
- throw new MissingFormatWidthException(
- "invalid arguments for format string");
- }
+ /**
+ * 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));
}
- private static int countPlaceholders(String pattern) {
+ /**
+ * 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();
- boolean afterPercent = false;
- int i = 0;
- int count = 0;
+ 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) {
- switch (pattern.charAt(i)) {
- case 's':
+ 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':
- if (afterPercent) {
- count++;
- afterPercent = false;
+ case 'r':
+ case 's':
+ if (a >= argLength) {
+ throw new MissingFormatWidthException("not enough arguments for format pattern "
+ + repr(pattern) + ": " + repr(SkylarkList.tuple(arguments)));
}
- break;
-
- case '%':
- afterPercent = !afterPercent;
- break;
-
- default:
- if (afterPercent) {
- throw new MissingFormatWidthException("invalid arguments for format string");
+ 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;
}
- afterPercent = false;
- break;
+ default:
+ throw new MissingFormatWidthException(
+ "unsupported format character " + repr(String.valueOf(directive))
+ + " at index " + (p + 1) + " in " + repr(pattern));
}
- i++;
}
-
- return count;
+ if (a < argLength) {
+ throw new MissingFormatWidthException(
+ "not all arguments converted during string formatting");
+ }
+ return buffer;
}
}
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
index 7177c7f1d0..b764413ce4 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/EvaluationTest.java
@@ -607,7 +607,7 @@ public class EvaluationTest extends EvaluationTestCase {
@Test
public void testPercOnObjectInvalidFormat() throws Exception {
update("obj", createObjWithStr());
- checkEvalError("invalid arguments for format string", "'%d' % obj");
+ checkEvalError("invalid argument str marker for format pattern %d", "'%d' % obj");
}
@SuppressWarnings("unchecked")
diff --git a/src/test/java/com/google/devtools/build/lib/syntax/PrinterTest.java b/src/test/java/com/google/devtools/build/lib/syntax/PrinterTest.java
index 8578ecd9a0..d9637414dd 100644
--- a/src/test/java/com/google/devtools/build/lib/syntax/PrinterTest.java
+++ b/src/test/java/com/google/devtools/build/lib/syntax/PrinterTest.java
@@ -98,9 +98,9 @@ public class PrinterTest {
Printer.repr(makeFilesetEntry()));
}
- private void checkFormatPositionalFails(String format, List<?> tuple, String errorMessage) {
+ private void checkFormatPositionalFails(String errorMessage, String format, Object... arguments) {
try {
- Printer.format(format, tuple);
+ Printer.format(format, arguments);
fail();
} catch (IllegalFormatException e) {
assertThat(e).hasMessage(errorMessage);
@@ -109,28 +109,36 @@ public class PrinterTest {
@Test
public void testFormatPositional() throws Exception {
- assertEquals("foo 3", Printer.format("%s %d", makeTuple("foo", 3)));
+ assertEquals("foo 3", Printer.formatString("%s %d", makeTuple("foo", 3)));
+ assertEquals("foo 3", Printer.format("%s %d", "foo", 3));
// Note: formatString doesn't perform scalar x -> (x) conversion;
// The %-operator is responsible for that.
- assertThat(Printer.format("", makeTuple())).isEmpty();
- assertEquals("foo", Printer.format("%s", makeTuple("foo")));
- assertEquals("3.14159", Printer.format("%s", makeTuple(3.14159)));
- checkFormatPositionalFails("%s", makeTuple(1, 2, 3),
- "not all arguments converted during string formatting");
- assertEquals("%foo", Printer.format("%%%s", makeTuple("foo")));
- checkFormatPositionalFails("%%s", makeTuple("foo"),
- "not all arguments converted during string formatting");
- checkFormatPositionalFails("% %s", makeTuple("foo"),
- "invalid arguments for format string");
- assertEquals("[1, 2, 3]", Printer.format("%s", makeTuple(makeList(1, 2, 3))));
- assertEquals("(1, 2, 3)", Printer.format("%s", makeTuple(makeTuple(1, 2, 3))));
- assertEquals("[]", Printer.format("%s", makeTuple(makeList())));
- assertEquals("()", Printer.format("%s", makeTuple(makeTuple())));
-
- checkFormatPositionalFails("%.3g", makeTuple(), "invalid arguments for format string");
- checkFormatPositionalFails("%.3g", makeTuple(1, 2), "invalid arguments for format string");
- checkFormatPositionalFails("%.s", makeTuple(), "invalid arguments for format string");
+ assertThat(Printer.formatString("", makeTuple())).isEmpty();
+ assertEquals("foo", Printer.format("%s", "foo"));
+ assertEquals("3.14159", Printer.format("%s", 3.14159));
+ checkFormatPositionalFails("not all arguments converted during string formatting",
+ "%s", 1, 2, 3);
+ assertEquals("%foo", Printer.format("%%%s", "foo"));
+ checkFormatPositionalFails("not all arguments converted during string formatting",
+ "%%s", "foo");
+ checkFormatPositionalFails("unsupported format character \" \" at index 1 in \"% %s\"",
+ "% %s", "foo");
+ assertEquals("[1, 2, 3]", Printer.format("%s", makeList(1, 2, 3)));
+ assertEquals("(1, 2, 3)", Printer.format("%s", makeTuple(1, 2, 3)));
+ assertEquals("[]", Printer.format("%s", makeList()));
+ assertEquals("()", Printer.format("%s", makeTuple()));
+ assertEquals("% 1 \"2\" 3", Printer.format("%% %d %r %s", 1, "2", "3"));
+
+ checkFormatPositionalFails(
+ "invalid argument \"1\" for format pattern %d",
+ "%d", "1");
+ checkFormatPositionalFails("unsupported format character \".\" at index 1 in \"%.3g\"",
+ "%.3g");
+ checkFormatPositionalFails("unsupported format character \".\" at index 1 in \"%.3g\"",
+ "%.3g", 1, 2);
+ checkFormatPositionalFails("unsupported format character \".\" at index 1 in \"%.s\"",
+ "%.s");
}
private String createExpectedFilesetEntryString(FilesetEntry.SymlinkBehavior symlinkBehavior) {