diff options
21 files changed, 641 insertions, 536 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 f2d1b4fe87..f67b0feec9 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 @@ -32,6 +32,7 @@ import com.google.devtools.build.lib.syntax.Environment; import com.google.devtools.build.lib.syntax.EvalException; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.FuncallExpression; +import com.google.devtools.build.lib.syntax.Printer; import com.google.devtools.build.lib.syntax.SelectorList; import com.google.devtools.build.lib.syntax.SelectorValue; import com.google.devtools.build.lib.syntax.SkylarkEnvironment; @@ -472,7 +473,7 @@ public class MethodLibrary { 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", - EvalUtils.prettyPrintValue(sub), EvalUtils.prettyPrintValue(self))); + Printer.repr(sub), Printer.repr(self))); } return res; } @@ -498,7 +499,7 @@ public class MethodLibrary { 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", - EvalUtils.prettyPrintValue(sub), EvalUtils.prettyPrintValue(self))); + Printer.repr(sub), Printer.repr(self))); } return res; } @@ -578,7 +579,7 @@ public class MethodLibrary { if (!kwargs.containsKey(word)) { throw new EvalException(loc, "No replacement found for '" + word + "'"); } - matcher.appendReplacement(result, EvalUtils.printValue(kwargs.get(word))); + matcher.appendReplacement(result, Printer.str(kwargs.get(word))); } matcher.appendTail(result); return result.toString(); @@ -718,7 +719,7 @@ public class MethodLibrary { Map<?, ?> dictionary = (Map<?, ?>) self; if (!dictionary.containsKey(key)) { throw new EvalException(loc, String.format("Key %s not found in dictionary", - EvalUtils.prettyPrintValue(key))); + Printer.repr(key))); } return dictionary.get(key); @@ -872,8 +873,17 @@ public class MethodLibrary { "Converts any object to string. This is useful for debugging.", mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")}) private static BuiltinFunction str = new BuiltinFunction("str") { - public String invoke(Object x) throws EvalException { - return EvalUtils.printValue(x); + public String invoke(Object x) { + return Printer.str(x); + } + }; + + @SkylarkSignature(name = "repr", returnType = String.class, doc = + "Converts any object to a string representation. This is useful for debugging.", + mandatoryPositionals = {@Param(name = "x", doc = "The object to convert.")}) + private static BuiltinFunction repr = new BuiltinFunction("repr") { + public String invoke(Object x) { + return Printer.repr(x); } }; @@ -908,12 +918,11 @@ public class MethodLibrary { return Integer.parseInt((String) x); } catch (NumberFormatException e) { throw new EvalException(loc, - "invalid literal for int(): " + EvalUtils.prettyPrintValue(x)); + "invalid literal for int(): " + Printer.repr(x)); } } else { throw new EvalException(loc, - String.format("argument must be string, int, or bool, not '%s'", - EvalUtils.prettyPrintValue(x))); + String.format("%s is not of type string or int or bool", Printer.repr(x))); } } }; @@ -1113,7 +1122,7 @@ public class MethodLibrary { return defaultValue; } else { throw new EvalException(loc, String.format("Object of type '%s' has no field %s", - EvalUtils.getDataTypeName(obj), EvalUtils.prettyPrintValue(name))); + EvalUtils.getDataTypeName(obj), Printer.repr(name))); } } return result; @@ -1189,7 +1198,7 @@ public class MethodLibrary { new com.google.common.base.Function<Object, String>() { @Override public String apply(Object input) { - return EvalUtils.printValue(input); + return Printer.str(input); }})); env.handleEvent(Event.warn(loc, msg)); return Environment.NONE; @@ -1298,7 +1307,7 @@ public class MethodLibrary { items, get, keys, values); private static final List<BaseFunction> pureGlobalFunctions = ImmutableList.<BaseFunction>of( - bool, int_, len, minus, select, sorted, str); + bool, int_, len, minus, repr, select, sorted, str); private static final List<BaseFunction> skylarkGlobalFunctions = ImmutableList.<BaseFunction>builder() diff --git a/src/main/java/com/google/devtools/build/lib/packages/Type.java b/src/main/java/com/google/devtools/build/lib/packages/Type.java index 593838bf63..7998d93cfe 100644 --- a/src/main/java/com/google/devtools/build/lib/packages/Type.java +++ b/src/main/java/com/google/devtools/build/lib/packages/Type.java @@ -28,6 +28,7 @@ import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.FilesetEntry; import com.google.devtools.build.lib.syntax.GlobList; import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.Printer; import com.google.devtools.build.lib.syntax.SelectorValue; import com.google.devtools.build.lib.syntax.SkylarkList; import com.google.devtools.build.lib.util.LoggingUtil; @@ -367,9 +368,9 @@ public abstract class Type<T> { if (what != null) { builder.append(" for ").append(what); } - builder.append(", but got '"); - EvalUtils.printValue(value, builder); - builder.append("' (").append(EvalUtils.getDataTypeName(value)).append(")"); + builder.append(", but got "); + Printer.write(builder, value); + builder.append(" (").append(EvalUtils.getDataTypeName(value)).append(")"); return builder.toString(); } diff --git a/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java index e47fcb70da..26432465d4 100644 --- a/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java +++ b/src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java @@ -27,6 +27,7 @@ import com.google.devtools.build.lib.packages.Rule; import com.google.devtools.build.lib.packages.Target; import com.google.devtools.build.lib.syntax.EvalUtils; import com.google.devtools.build.lib.syntax.Label; +import com.google.devtools.build.lib.syntax.Printer; import com.google.devtools.build.lib.util.BinaryPredicate; import com.google.devtools.build.lib.util.Pair; import com.google.devtools.common.options.EnumConverter; @@ -309,7 +310,7 @@ public abstract class OutputFormatter implements Serializable { // Display it as a list (and not as a tuple). Attributes can never be tuples. value = new ArrayList<>((List<?>) value); } - EvalUtils.prettyPrintValue(value, out); + Printer.write(out, value); out.println(","); } out.printf(")\n%n"); @@ -345,7 +346,7 @@ public abstract class OutputFormatter implements Serializable { * shows the lowest rank for a given node, i.e. the length of the shortest * path from a zero-rank node to it. * - * If the result came from a <code>deps(x)</code> query, then the MINRANKs + * <p>If the result came from a <code>deps(x)</code> query, then the MINRANKs * correspond to the shortest path from x to each of its prerequisites. */ private static class MinrankOutputFormatter extends OutputFormatter { @@ -396,7 +397,7 @@ public abstract class OutputFormatter implements Serializable { * highest rank for a given node, i.e. the length of the longest non-cyclic * path from a zero-rank node to it. * - * If the result came from a <code>deps(x)</code> query, then the MAXRANKs + * <p>If the result came from a <code>deps(x)</code> query, then the MAXRANKs * correspond to the longest path from x to each of its prerequisites. */ private static class MaxrankOutputFormatter extends OutputFormatter { 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 23e3e16b19..b7937ad98c 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,19 +190,18 @@ public final class BinaryOperatorExpression extends Expression { if (rval instanceof List<?>) { List<?> rlist = (List<?>) rval; if (EvalUtils.isTuple(rlist)) { - return EvalUtils.formatString(pattern, rlist); + return Printer.format(pattern, rlist); } /* string % list: fall thru */ } if (rval instanceof SkylarkList) { SkylarkList rlist = (SkylarkList) rval; if (rlist.isTuple()) { - return EvalUtils.formatString(pattern, rlist.toList()); + return Printer.format(pattern, rlist.toList()); } } - return EvalUtils.formatString(pattern, - Collections.singletonList(rval)); + return Printer.format(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 0b3271ef49..bcdd4e8ce3 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 @@ -60,7 +60,7 @@ public final class DotExpression extends Expression { } } throw new EvalException(getLocation(), String.format("Object of type '%s' has no field %s", - EvalUtils.getDataTypeName(objValue), EvalUtils.prettyPrintValue(name))); + EvalUtils.getDataTypeName(objValue), Printer.repr(name))); } return result; } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java index 74206b4e43..8db7648f44 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java @@ -14,32 +14,20 @@ package com.google.devtools.build.lib.syntax; import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Function; -import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.google.devtools.build.lib.collect.nestedset.NestedSet; -import com.google.devtools.build.lib.collect.nestedset.Order; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.events.Location; -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.Comparator; -import java.util.Formattable; -import java.util.Formatter; -import java.util.IllegalFormatException; import java.util.List; import java.util.Map; -import java.util.MissingFormatWidthException; import java.util.Set; /** @@ -316,187 +304,6 @@ public abstract class EvalUtils { return isTuple ? ImmutableList.copyOf(seq) : seq; } - /** - * Print build-language value 'o' in display format into the specified buffer. - */ - public static void printValue(Object o, Appendable buffer) { - // Exception-swallowing wrapper due to annoying Appendable interface. - try { - printValueX(o, buffer); - } catch (IOException e) { - throw new AssertionError(e); // can't happen - } - } - - private static void printValueX(Object o, Appendable buffer) - throws IOException { - if (o == null) { - throw new NullPointerException(); // Java null is not a build language value. - } else if (o instanceof String || o instanceof Integer || o instanceof Double) { - buffer.append(o.toString()); - - } else if (o == Environment.NONE) { - buffer.append("None"); - - } else if (o == Boolean.TRUE) { - buffer.append("True"); - - } else if (o == Boolean.FALSE) { - buffer.append("False"); - - } else if (o instanceof List<?>) { - List<?> seq = (List<?>) o; - printList(seq, isImmutable(seq), buffer); - - } else if (o instanceof SkylarkList) { - SkylarkList list = (SkylarkList) o; - printList(list.toList(), list.isTuple(), buffer); - - } else if (o instanceof Map<?, ?>) { - Map<?, ?> dict = (Map<?, ?>) o; - printList(dict.entrySet(), "{", ", ", "}", null, buffer); - - } else if (o instanceof Map.Entry<?, ?>) { - Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; - prettyPrintValue(entry.getKey(), buffer); - buffer.append(": "); - prettyPrintValue(entry.getValue(), buffer); - - } else if (o instanceof SkylarkNestedSet) { - SkylarkNestedSet set = (SkylarkNestedSet) o; - buffer.append("set("); - printList(set, "[", ", ", "]", null, buffer); - Order order = set.getOrder(); - if (order != Order.STABLE_ORDER) { - buffer.append(", order = \"" + order.getName() + "\""); - } - buffer.append(")"); - - } else if (o instanceof BaseFunction) { - BaseFunction func = (BaseFunction) o; - buffer.append("<function " + func.getName() + ">"); - - } else if (o instanceof FilesetEntry) { - FilesetEntry entry = (FilesetEntry) o; - buffer.append("FilesetEntry(srcdir = "); - prettyPrintValue(entry.getSrcLabel().toString(), buffer); - buffer.append(", files = "); - prettyPrintValue(makeStringList(entry.getFiles()), buffer); - buffer.append(", excludes = "); - prettyPrintValue(makeList(entry.getExcludes()), buffer); - buffer.append(", destdir = "); - prettyPrintValue(entry.getDestDir().getPathString(), buffer); - buffer.append(", strip_prefix = "); - prettyPrintValue(entry.getStripPrefix(), buffer); - buffer.append(", symlinks = \""); - buffer.append(entry.getSymlinkBehavior().toString()); - buffer.append("\")"); - } else if (o instanceof PathFragment) { - buffer.append(((PathFragment) o).getPathString()); - } else { - buffer.append(o.toString()); - } - } - - public static void printList(Iterable<?> list, - String before, String separator, String after, String singletonTerminator, Appendable buffer) - throws IOException { - boolean printSeparator = false; // don't print the separator before the first element - int len = 0; - buffer.append(before); - for (Object o : list) { - if (printSeparator) { - buffer.append(separator); - } - prettyPrintValue(o, buffer); - printSeparator = true; - len++; - } - if (singletonTerminator != null && len == 1) { - buffer.append(singletonTerminator); - } - buffer.append(after); - } - - public static void printList(Iterable<?> list, boolean isTuple, Appendable buffer) - throws IOException { - if (isTuple) { - printList(list, "(", ", ", ")", ",", buffer); - } else { - printList(list, "[", ", ", "]", null, buffer); - } - } - - private static List<?> makeList(Collection<?> list) { - return list == null ? Lists.newArrayList() : Lists.newArrayList(list); - } - - private static List<String> makeStringList(List<Label> labels) { - if (labels == null) { return Collections.emptyList(); } - List<String> strings = Lists.newArrayListWithCapacity(labels.size()); - for (Label label : labels) { - strings.add(label.toString()); - } - return strings; - } - - /** - * Print build-language value 'o' in parseable format into the specified - * buffer. (Only differs from printValueX in treatment of strings at toplevel, - * i.e. not within a sequence or dict) - */ - public static void prettyPrintValue(Object o, Appendable buffer) { - // Exception-swallowing wrapper due to annoying Appendable interface. - try { - prettyPrintValueX(o, buffer); - } catch (IOException e) { - throw new AssertionError(e); // can't happen - } - } - - private static void prettyPrintValueX(Object o, Appendable buffer) - throws IOException { - if (o instanceof Label) { - o = o.toString(); // Pretty-print a label like a string - } - if (o instanceof String) { - Printer.writeString(buffer, (String) o); - } else { - printValueX(o, buffer); - } - } - - /** - * Pretty-print value 'o' to a string. Convenience overloading of - * prettyPrintValue(Object, Appendable). - */ - public static String prettyPrintValue(Object o) { - StringBuilder buffer = new StringBuilder(); - prettyPrintValue(o, buffer); - return buffer.toString(); - } - - /** - * Pretty-print values of 'o' separated by the separator. - */ - public static String prettyPrintValues(String separator, Iterable<Object> o) { - return Joiner.on(separator).join(Iterables.transform(o, new Function<Object, String>() { - @Override - public String apply(Object input) { - return prettyPrintValue(input); - } - })); - } - - /** - * Print value 'o' to a string. Convenience overloading of printValue(Object, Appendable). - */ - public static String printValue(Object o) { - StringBuilder buffer = new StringBuilder(); - printValue(o, buffer); - return buffer.toString(); - } - public static Object checkNotNull(Expression expr, Object obj) throws EvalException { if (obj == null) { throw new EvalException(expr.getLocation(), @@ -507,98 +314,6 @@ public abstract class EvalUtils { } /** - * 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. - */ - private static Object makeFormattable(final Object o) { - if (o instanceof Integer || o instanceof Double || o instanceof String) { - return o; - } else { - return new Formattable() { - @Override - public String toString() { - return "Formattable[" + o + "]"; - } - - @Override - public void formatTo(Formatter formatter, int flags, int width, - int precision) { - printValue(o, formatter.out()); - } - }; - } - } - - private static final Object[] EMPTY = new Object[0]; - - /* - * N.B. MissingFormatWidthException is the only kind of IllegalFormatException - * whose constructor can take and display arbitrary error message, hence its use below. - */ - - /** - * 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. - * - * @param pattern a format string. - * @param tuple a tuple containing positional arguments - */ - public static String formatString(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(makeFormattable(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 the truth value of an object, according to Python rules. * http://docs.python.org/2/library/stdtypes.html#truth-value-testing */ diff --git a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java index 5f2c512252..21df356d12 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java @@ -26,7 +26,6 @@ import com.google.devtools.build.lib.events.Location; import com.google.devtools.build.lib.syntax.EvalException.EvalExceptionWithJavaCause; import com.google.devtools.build.lib.util.StringUtilities; -import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; @@ -270,12 +269,7 @@ public final class FuncallExpression extends Expression { if (obj != null) { sb.append(obj).append("."); } - sb.append(func); - try { - EvalUtils.printList(args, "(", ", ", ")", null, sb); - } catch (IOException x) { - throw new RuntimeException("Error while printing", x); - } + Printer.printList(sb.append(func), args, "(", ", ", ")", null); return sb.toString(); } @@ -331,7 +325,7 @@ public final class FuncallExpression extends Expression { } else { throw new EvalException(loc, "Method invocation returned None, please contact Skylark developers: " + methodName - + "(" + EvalUtils.prettyPrintValues(", ", ImmutableList.copyOf(args)) + ")"); + + Printer.listString(ImmutableList.copyOf(args), "(", ", ", ")", null)); } } result = SkylarkType.convertToSkylark(result, method); 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 3792e21fbc..272fc5e7d3 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 @@ -389,8 +389,12 @@ public abstract class FunctionSignature implements Serializable { } public void optional(int i) { mandatory(i); - sb.append(" = ").append((defaultValues == null) - ? "?" : EvalUtils.prettyPrintValue(defaultValues.get(j++))); + sb.append(" = "); + if (defaultValues == null) { + sb.append("?"); + } else { + Printer.write(sb, defaultValues.get(j++)); + } } }; Show show = new Show(); 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 fcaf3a8342..42b5b097df 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 @@ -97,7 +97,7 @@ public class LValue implements Serializable { && !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", - EvalUtils.prettyPrintValue(result), + Printer.repr(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 bd83e21821..b3d68178cc 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(), EvalUtils.prettyPrintValue(list)); + return String.format("for %s in %s", variables.toString(), Printer.repr(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 259b34bb9d..8739be8c65 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,7 +13,21 @@ // limitations under the License. package com.google.devtools.build.lib.syntax; +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; +import java.util.Formatter; +import java.util.IllegalFormatException; +import java.util.List; +import java.util.Map; +import java.util.MissingFormatWidthException; /** * (Pretty) Printing of Skylark values @@ -23,12 +37,157 @@ public final class Printer { private Printer() { } - private static Appendable backslashChar(Appendable buffer, char c) throws IOException { - return buffer.append('\\').append(c); + /** + * Get an informal representation of object x. + * Currently only differs from repr in the behavior for strings and labels at top-level, + * that are returned as is rather than quoted. + * @return the representation. + */ + public static String str(Object x) { + return print(new StringBuilder(), x).toString(); + } + + /** + * Get an official representation of object x. + * For regular data structures, the value should be parsable back into an equal data structure. + * @return the representation. + */ + public static String repr(Object x) { + return write(new StringBuilder(), x).toString(); + } + + // In absence of a Python naming tradition, the write() vs print() function names + // follow the Lisp tradition: print() displays the informal representation (as in Python str) + // whereas write() displays a readable representation (as in Python repr). + /** + * Print an informal representation of object x. + * Currently only differs from repr in the behavior for strings and labels at top-level, + * that are returned as is rather than quoted. + * @param buffer the Appendable to which to print the representation + * @param o the object + * @return the buffer, in fluent style + */ + public static Appendable print(Appendable buffer, Object o) { + if (o instanceof Label) { + return append(buffer, o.toString()); // Pretty-print a label like a string + } + if (o instanceof String) { + return append(buffer, (String) o); + } + return write(buffer, o); + } + + /** + * Print an official representation of object x. + * For regular data structures, the value should be parsable back into an equal data structure. + * @param buffer the Appendable to write to. + * @param o the string a representation of which to write. + * @return the Appendable, in fluent style. + */ + public static Appendable write(Appendable buffer, Object o) { + if (o == null) { + throw new NullPointerException(); // Java null is not a build language value. + + } else if (o instanceof String) { + writeString(buffer, (String) o); + + } else if (o instanceof Integer || o instanceof Double) { + append(buffer, o.toString()); + + } else if (o == Environment.NONE) { + append(buffer, "None"); + + } else if (o == Boolean.TRUE) { + append(buffer, "True"); + + } else if (o == Boolean.FALSE) { + append(buffer, "False"); + + } else if (o instanceof List<?>) { + List<?> seq = (List<?>) o; + printList(buffer, seq, EvalUtils.isImmutable(seq)); + + } else if (o instanceof SkylarkList) { + SkylarkList list = (SkylarkList) o; + printList(buffer, list.toList(), list.isTuple()); + + } else if (o instanceof Map<?, ?>) { + Map<?, ?> dict = (Map<?, ?>) o; + printList(buffer, dict.entrySet(), "{", ", ", "}", null); + + } else if (o instanceof Map.Entry<?, ?>) { + Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o; + write(buffer, entry.getKey()); + append(buffer, ": "); + write(buffer, entry.getValue()); + + } else if (o instanceof SkylarkNestedSet) { + SkylarkNestedSet set = (SkylarkNestedSet) o; + append(buffer, "set("); + printList(buffer, set, "[", ", ", "]", null); + Order order = set.getOrder(); + if (order != Order.STABLE_ORDER) { + append(buffer, ", order = \"" + order.getName() + "\""); + } + append(buffer, ")"); + + } else if (o instanceof BaseFunction) { + BaseFunction func = (BaseFunction) o; + append(buffer, "<function " + func.getName() + ">"); + + } else if (o instanceof Label) { + write(buffer, o.toString()); + + } else if (o instanceof FilesetEntry) { + FilesetEntry entry = (FilesetEntry) o; + append(buffer, "FilesetEntry(srcdir = "); + write(buffer, entry.getSrcLabel().toString()); + append(buffer, ", files = "); + write(buffer, makeStringList(entry.getFiles())); + append(buffer, ", excludes = "); + write(buffer, makeList(entry.getExcludes())); + append(buffer, ", destdir = "); + write(buffer, entry.getDestDir().getPathString()); + append(buffer, ", strip_prefix = "); + write(buffer, entry.getStripPrefix()); + append(buffer, ", symlinks = \""); + append(buffer, entry.getSymlinkBehavior().toString()); + append(buffer, "\")"); + + } else if (o instanceof PathFragment) { + append(buffer, ((PathFragment) o).getPathString()); + + } else { + append(buffer, o.toString()); + } + + return buffer; + } + + // Throughout this file, we transform IOException into AssertionError. + // During normal operations, we only use in-memory Appendable-s that + // cannot cause an IOException. + private static Appendable append(Appendable buffer, char c) { + try { + return buffer.append(c); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private static Appendable append(Appendable buffer, CharSequence s) { + try { + return buffer.append(s); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + private static Appendable backslashChar(Appendable buffer, char c) { + return append(append(buffer, '\\'), c); } - private static Appendable escapeCharacter(Appendable buffer, char c, char quote) - throws IOException { + private static Appendable escapeCharacter(Appendable buffer, char c, char quote) { if (c == quote) { return backslashChar(buffer, c); } @@ -43,29 +202,28 @@ public final class Printer { return backslashChar(buffer, 't'); default: if (c < 32) { - return buffer.append(String.format("\\x%02x", (int) c)); + return append(buffer, String.format("\\x%02x", (int) c)); } - return buffer.append(c); // no need to support UTF-8 + return append(buffer, c); // no need to support UTF-8 } // endswitch } /** * Write a properly escaped Skylark representation of a string to a buffer. * - * @param buffer the Appendable we're writing to. + * @param buffer the Appendable to write to. * @param s the string a representation of which to write. * @param quote the quote character to use, '"' or '\''. * @return the Appendable, in fluent style. */ - public static Appendable writeString(Appendable buffer, String s, char quote) - throws IOException { - buffer.append(quote); + public static Appendable writeString(Appendable buffer, String s, char quote) { + append(buffer, quote); int len = s.length(); for (int i = 0; i < len; i++) { char c = s.charAt(i); escapeCharacter(buffer, c, quote); } - return buffer.append(quote); + return append(buffer, quote); } /** @@ -77,7 +235,175 @@ public final class Printer { * @param s the string a representation of which to write. * @return the buffer, in fluent style. */ - public static Appendable writeString(Appendable buffer, String s) throws IOException { + public static Appendable writeString(Appendable buffer, String s) { return writeString(buffer, s, '"'); } + + /** + * Print a list of object representations + * @param buffer an appendable buffer onto which to write the list. + * @param list the list of objects to write (each as with repr) + * @param before a string to print before the list + * @param separator a separator to print between each object + * @param after a string to print after the list + * @param singletonTerminator null or a string to print after the list if it is a singleton + * The singleton case is notably relied upon in python syntax to distinguish + * a tuple of size one such as ("foo",) from a merely parenthesized object such as ("foo"). + * @return the Appendable, in fluent style. + */ + public static Appendable printList(Appendable buffer, Iterable<?> list, + String before, String separator, String after, String singletonTerminator) { + boolean printSeparator = false; // don't print the separator before the first element + int len = 0; + append(buffer, before); + for (Object o : list) { + if (printSeparator) { + append(buffer, separator); + } + write(buffer, o); + printSeparator = true; + len++; + } + if (singletonTerminator != null && len == 1) { + append(buffer, singletonTerminator); + } + return append(buffer, after); + } + + /** + * Print a Skylark list or tuple of object representations + * @param buffer an appendable buffer onto which to write the list. + * @param list the contents of the list or tuple + * @param isTuple is it a tuple or a list? + * @return the Appendable, in fluent style. + */ + public static Appendable printList(Appendable buffer, Iterable<?> list, boolean isTuple) { + if (isTuple) { + return printList(buffer, list, "(", ", ", ")", ","); + } else { + return printList(buffer, list, "[", ", ", "]", null); + } + } + + /** + * Print a list of object representations + * @param list the list of objects to write (each as with repr) + * @param before a string to print before the list + * @param separator a separator to print between each object + * @param after a string to print after the list + * @param singletonTerminator null or a string to print after the list if it is a singleton + * The singleton case is notably relied upon in python syntax to distinguish + * a tuple of size one such as ("foo",) from a merely parenthesized object such as ("foo"). + * @return a String, the representation. + */ + public static String listString(Iterable<?> list, + String before, String separator, String after, String singletonTerminator) { + return printList(new StringBuilder(), list, before, separator, after, singletonTerminator) + .toString(); + } + + private static List<?> makeList(Collection<?> list) { + return list == null ? Lists.newArrayList() : Lists.newArrayList(list); + } + + private static List<String> makeStringList(List<Label> labels) { + if (labels == null) { + return Collections.emptyList(); + } + List<String> strings = Lists.newArrayListWithCapacity(labels.size()); + for (Label label : labels) { + strings.add(label.toString()); + } + return strings; + } + + /** + * 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. + */ + private static Object strFormattable(final Object o) { + if (o instanceof Integer || o instanceof Double || o instanceof String) { + return o; + } else { + return new Formattable() { + @Override + public String toString() { + return str(o); + } + + @Override + public void formatTo(Formatter formatter, int flags, int width, int precision) { + print(formatter.out(), o); + } + }; + } + } + + private static final Object[] EMPTY = new Object[0]; + + /* + * N.B. MissingFormatWidthException is the only kind of IllegalFormatException + * whose constructor can take and display arbitrary error message, hence its use below. + */ + + /** + * 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. + * + * @param pattern a format string. + * @param tuple a tuple containing positional arguments + */ + 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)); + } + + 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; + } } diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java index 2d6f39722e..cbcf21a95c 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java @@ -80,7 +80,7 @@ public abstract class SkylarkList implements Iterable<Object> { @Override public String toString() { - return EvalUtils.prettyPrintValue(this); + return Printer.repr(this); } @Override @@ -197,11 +197,6 @@ public abstract class SkylarkList implements Iterable<Object> { public List<Object> toList() { return isTuple() ? list : Lists.newArrayList(list); } - - @Override - public String toString() { - return EvalUtils.prettyPrintValue(this); - } } /** diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java index 8637597c29..55daeba25c 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java @@ -212,7 +212,7 @@ public final class SkylarkNestedSet implements Iterable<Object> { @Override public String toString() { - return EvalUtils.prettyPrintValue(this); + return Printer.repr(this); } public Order getOrder() { diff --git a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java index 42946eefb1..d114dc7992 100644 --- a/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java +++ b/src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java @@ -193,7 +193,7 @@ public class SkylarkSignatureProcessor { } else if (defaultValue != null && enforcedType != null) { Preconditions.checkArgument(enforcedType.contains(defaultValue), "In function '%s', parameter '%s' has default value %s that isn't of enforced type %s", - name, param.name(), EvalUtils.prettyPrintValue(defaultValue), enforcedType); + name, param.name(), Printer.repr(defaultValue), enforcedType); } return new Parameter.Optional<>(param.name(), officialType, defaultValue); } diff --git a/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java b/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java index 7ecf86bb7b..0eee09b298 100644 --- a/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java +++ b/src/test/java/com/google/devtools/build/lib/packages/RuleClassTest.java @@ -279,7 +279,7 @@ public class RuleClassTest extends PackageLoadingTestCase { // TODO(blaze-team): (2009) refactor to use assertContainsEvent Iterator<String> expectedMessages = Arrays.asList( "expected value of type 'list(label)' for attribute 'my-labellist-attr' " - + "in 'ruleA' rule, but got 'foobar' (string)", + + "in 'ruleA' rule, but got \"foobar\" (string)", "no such attribute 'bogus-attr' in 'ruleA' rule", "missing value for mandatory " + "attribute 'my-string-attr' in 'ruleA' rule", diff --git a/src/test/java/com/google/devtools/build/lib/packages/TypeTest.java b/src/test/java/com/google/devtools/build/lib/packages/TypeTest.java index 39c5dbeefa..6604e6286f 100644 --- a/src/test/java/com/google/devtools/build/lib/packages/TypeTest.java +++ b/src/test/java/com/google/devtools/build/lib/packages/TypeTest.java @@ -71,7 +71,7 @@ public class TypeTest { // This does not use assertMessageContainsWordsWithQuotes because at least // one test should test exact wording (but they all shouldn't to make // changing/improving the messages easy). - assertThat(e).hasMessage("expected value of type 'int', but got 'foo' (string)"); + assertThat(e).hasMessage("expected value of type 'int', but got \"foo\" (string)"); } } @@ -83,7 +83,7 @@ public class TypeTest { fail(); } catch (Type.ConversionException e) { assertThat(e).hasMessage("expected value of type 'list(string)' for myexpr, " - + "but got '[(1,2), 3, 4]' (string)"); + + "but got \"[(1,2), 3, 4]\" (string)"); } } @@ -100,7 +100,7 @@ public class TypeTest { Type.STRING.convert(3, null); fail(); } catch (Type.ConversionException e) { - MoreAsserts.assertContainsWordsWithQuotes(e.getMessage(), "3", "string"); + assertThat(e).hasMessage("expected value of type 'string', but got 3 (int)"); } } @@ -123,7 +123,8 @@ public class TypeTest { Type.BOOLEAN.convert("unexpected", null); fail(); } catch (Type.ConversionException e) { - MoreAsserts.assertContainsWordsWithQuotes(e.getMessage(), "unexpected"); + assertThat(e).hasMessage( + "expected value of type 'int', but got \"unexpected\" (string)"); } // Integers other than [0, 1] should fail. try { @@ -264,7 +265,7 @@ public class TypeTest { Type.LABEL.convert(3, null); fail(); } catch (Type.ConversionException e) { - MoreAsserts.assertContainsWordsWithQuotes(e.getMessage(), "3", "string"); + assertThat(e).hasMessage("expected value of type 'string', but got 3 (int)"); } } @@ -296,18 +297,18 @@ public class TypeTest { Type.STRING_DICT.convert(input, null); fail(); } catch (Type.ConversionException e) { - assertThat(e).hasMessage("expected value of type 'string' for dict value element, but got " - + "'[\"bar\", \"baz\"]' (List)"); + assertThat(e).hasMessage("expected value of type 'string' for dict value element, " + + "but got [\"bar\", \"baz\"] (List)"); } } @Test public void testNonStringList() throws Exception { try { - Type.STRING_LIST.convert(3, null); + Type.STRING_LIST.convert(3, "blah"); fail(); } catch (Type.ConversionException e) { - MoreAsserts.assertContainsWordsWithQuotes(e.getMessage(), "3", "list(string)"); + assertThat(e).hasMessage("expected value of type 'list(string)' for blah, but got 3 (int)"); } } @@ -315,10 +316,11 @@ public class TypeTest { public void testStringListBadElements() throws Exception { Object input = Arrays.<Object>asList("foo", "bar", 1); try { - Type.STRING_LIST.convert(input, null); + Type.STRING_LIST.convert(input, "argument quux"); fail(); } catch (Type.ConversionException e) { - MoreAsserts.assertContainsWordsWithQuotes(e.getMessage(), "1", "string"); + assertThat(e).hasMessage( + "expected value of type 'string' for element 2 of argument quux, but got 1 (int)"); } } @@ -338,10 +340,10 @@ public class TypeTest { @Test public void testNonLabelList() throws Exception { try { - Type.LABEL_LIST.convert(3, null, currentRule); + Type.LABEL_LIST.convert(3, "foo", currentRule); fail(); } catch (Type.ConversionException e) { - MoreAsserts.assertContainsWordsWithQuotes(e.getMessage(), "3", "list(label)"); + assertThat(e).hasMessage("expected value of type 'list(label)' for foo, but got 3 (int)"); } } @@ -352,7 +354,8 @@ public class TypeTest { Type.LABEL_LIST.convert(list, null, currentRule); fail(); } catch (Type.ConversionException e) { - MoreAsserts.assertContainsWordsWithQuotes(e.getMessage(), "2", "string"); + assertThat(e).hasMessage( + "expected value of type 'string' for element 1 of null, but got 2 (int)"); } } @@ -392,8 +395,8 @@ public class TypeTest { Type.LABEL_LIST_DICT.convert(input, null, currentRule); fail(); } catch (Type.ConversionException e) { - assertThat(e).hasMessage("expected value of type 'string' for dict key element," - + " but got '2' (int)"); + assertThat(e).hasMessage( + "expected value of type 'string' for dict key element, but got 2 (int)"); } } @@ -405,7 +408,9 @@ public class TypeTest { Type.LABEL_LIST_DICT.convert(input, null, currentRule); fail(); } catch (Type.ConversionException e) { - MoreAsserts.assertContainsWordsWithQuotes(e.getMessage(), "//foo:bar", "list(label)"); + assertThat(e).hasMessage( + "expected value of type 'list(label)' for dict value element, " + + "but got \"//foo:bar\" (string)"); } } @@ -419,7 +424,7 @@ public class TypeTest { fail(); } catch (Type.ConversionException e) { assertThat(e).hasMessage("expected value of type 'list(label)' for dict value element, " - + "but got 'bar' (string)"); + + "but got \"bar\" (string)"); } } @@ -460,7 +465,7 @@ public class TypeTest { fail(); } catch (Type.ConversionException e) { assertThat(e).hasMessage( - "expected value of type 'string' for dict key element, but got '2' (int)"); + "expected value of type 'string' for dict key element, but got 2 (int)"); } } @@ -472,7 +477,9 @@ public class TypeTest { Type.STRING_LIST_DICT.convert(input, null, currentRule); fail(); } catch (Type.ConversionException e) { - MoreAsserts.assertContainsWordsWithQuotes(e.getMessage(), "bar", "list(string)"); + assertThat(e).hasMessage( + "expected value of type 'list(string)' for dict value element, " + + "but got \"bar\" (string)"); } } @@ -485,7 +492,7 @@ public class TypeTest { fail(); } catch (Type.ConversionException e) { assertThat(e).hasMessage("expected value of type 'string' for dict key element, but got " - + "'[\"foo\"]' (List)"); + + "[\"foo\"] (List)"); } } @@ -512,7 +519,7 @@ public class TypeTest { fail(); } catch (Type.ConversionException e) { assertThat(e).hasMessage("expected value of type 'string' for dict key element, but got " - + "'2' (int)"); + + "2 (int)"); } } @@ -525,7 +532,7 @@ public class TypeTest { fail(); } catch (Type.ConversionException e) { assertThat(e).hasMessage("expected value of type 'string' for dict value element, but got " - + "'[\"bang\"]' (List)"); + + "[\"bang\"] (List)"); } } @@ -539,7 +546,7 @@ public class TypeTest { fail(); } catch (Type.ConversionException e) { assertThat(e).hasMessage("expected value of type 'string' for dict key element, but got " - + "'[\"foo\", \"bar\"]' (List)"); + + "[\"foo\", \"bar\"] (List)"); } } diff --git a/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java b/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java index acd9fb03e4..da709a002b 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/BaseFunctionTest.java @@ -142,15 +142,15 @@ public class BaseFunctionTest extends EvaluationTestCase { + "b1 = bar(name='foo', type='jpg', version=42)\n" + "b2 = bar()\n"); - assertThat(EvalUtils.prettyPrintValue(lookup("v1"))) + assertThat(Printer.repr(lookup("v1"))) .isEqualTo("(1, 2, 3, 4, 5, 6, 7, 8, (), {})"); - assertThat(EvalUtils.prettyPrintValue(lookup("v2"))) + assertThat(Printer.repr(lookup("v2"))) .isEqualTo("(1, \"x\", \"y\", \"z\", 5, 6, 7, 9, (\"t\",), {\"i\": 0})"); // NB: the conversion to a TreeMap below ensures the keys are sorted. - assertThat(EvalUtils.prettyPrintValue( + assertThat(Printer.repr( new TreeMap<String, Object>((Map<String, Object>) lookup("b1")))) .isEqualTo("{\"name\": \"foo\", \"type\": \"jpg\", \"version\": 42}"); - assertThat(EvalUtils.prettyPrintValue(lookup("b2"))).isEqualTo("{}"); + assertThat(Printer.repr(lookup("b2"))).isEqualTo("{}"); } } diff --git a/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java b/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java index 13c0987364..fa98c70e3d 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/EvalUtilsTest.java @@ -14,11 +14,9 @@ package com.google.devtools.build.lib.syntax; -import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; import com.google.common.collect.Lists; @@ -27,7 +25,6 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; import java.util.Arrays; -import java.util.IllegalFormatException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -39,15 +36,18 @@ import java.util.Map; @RunWith(JUnit4.class) public class EvalUtilsTest { - private static List<?> makeList(Object ...args) { + private static List<?> makeList(Object... args) { return EvalUtils.makeSequence(Arrays.<Object>asList(args), false); } - private static List<?> makeTuple(Object ...args) { + + private static List<?> makeTuple(Object... args) { return EvalUtils.makeSequence(Arrays.<Object>asList(args), true); } + private static Map<Object, Object> makeDict() { return new LinkedHashMap<>(); } + private static FilesetEntry makeFilesetEntry() { try { return new FilesetEntry(Label.parseAbsolute("//foo:bar"), @@ -78,164 +78,4 @@ public class EvalUtilsTest { assertFalse(EvalUtils.isImmutable(makeDict())); assertFalse(EvalUtils.isImmutable(makeFilesetEntry())); } - - @Test - public void testPrintValue() throws Exception { - // Note that prettyPrintValue and printValue only differ on behaviour of - // labels and strings at toplevel. - assertEquals("foo\nbar", EvalUtils.printValue("foo\nbar")); - assertEquals("\"foo\\nbar\"", EvalUtils.prettyPrintValue("foo\nbar")); - assertEquals("foo\nbar", EvalUtils.printValue("foo\nbar")); - assertEquals("'", EvalUtils.printValue("'")); - assertEquals("\"'\"", EvalUtils.prettyPrintValue("'")); - assertEquals("\"", EvalUtils.printValue("\"")); - assertEquals("\"\\\"\"", EvalUtils.prettyPrintValue("\"")); - assertEquals("a\\b", EvalUtils.printValue("a\\b")); - assertEquals("\"a\\\\b\"", EvalUtils.prettyPrintValue("a\\b")); - assertEquals("3", EvalUtils.printValue(3)); - assertEquals("3", EvalUtils.prettyPrintValue(3)); - assertEquals("None", EvalUtils.printValue(Environment.NONE)); - assertEquals("None", EvalUtils.prettyPrintValue(Environment.NONE)); - - assertEquals("//x:x", EvalUtils.printValue(Label.parseAbsolute("//x"))); - assertEquals("\"//x:x\"", EvalUtils.prettyPrintValue(Label.parseAbsolute("//x"))); - - List<?> list = makeList("foo", "bar"); - List<?> tuple = makeTuple("foo", "bar"); - - assertEquals("(1, [\"foo\", \"bar\"], 3)", - EvalUtils.printValue(makeTuple(1, list, 3))); - assertEquals("(1, [\"foo\", \"bar\"], 3)", - EvalUtils.prettyPrintValue(makeTuple(1, list, 3))); - assertEquals("[1, (\"foo\", \"bar\"), 3]", - EvalUtils.printValue(makeList(1, tuple, 3))); - assertEquals("[1, (\"foo\", \"bar\"), 3]", - EvalUtils.prettyPrintValue(makeList(1, tuple, 3))); - - Map<Object, Object> dict = makeDict(); - dict.put(1, tuple); - dict.put(2, list); - dict.put("foo", makeList()); - assertEquals("{1: (\"foo\", \"bar\"), 2: [\"foo\", \"bar\"], \"foo\": []}", - EvalUtils.printValue(dict)); - assertEquals("{1: (\"foo\", \"bar\"), 2: [\"foo\", \"bar\"], \"foo\": []}", - EvalUtils.prettyPrintValue(dict)); - assertEquals("FilesetEntry(srcdir = \"//foo:bar\", files = [], " - + "excludes = [\"xyz\"], destdir = \"\", " - + "strip_prefix = \".\", symlinks = \"copy\")", - EvalUtils.prettyPrintValue(makeFilesetEntry())); - } - - private void checkFormatPositionalFails(String format, List<?> tuple, - String errorMessage) { - try { - EvalUtils.formatString(format, tuple); - fail(); - } catch (IllegalFormatException e) { - assertThat(e).hasMessage(errorMessage); - } - } - - @Test - public void testFormatPositional() throws Exception { - assertEquals("foo 3", EvalUtils.formatString("%s %d", makeTuple("foo", 3))); - - // Note: formatString doesn't perform scalar x -> (x) conversion; - // The %-operator is responsible for that. - assertThat(EvalUtils.formatString("", makeTuple())).isEmpty(); - assertEquals("foo", EvalUtils.formatString("%s", makeTuple("foo"))); - assertEquals("3.14159", EvalUtils.formatString("%s", makeTuple(3.14159))); - checkFormatPositionalFails("%s", makeTuple(1, 2, 3), - "not all arguments converted during string formatting"); - assertEquals("%foo", EvalUtils.formatString("%%%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]", EvalUtils.formatString("%s", makeTuple(makeList(1, 2, 3)))); - assertEquals("(1, 2, 3)", EvalUtils.formatString("%s", makeTuple(makeTuple(1, 2, 3)))); - assertEquals("[]", EvalUtils.formatString("%s", makeTuple(makeList()))); - assertEquals("()", EvalUtils.formatString("%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"); - } - - private String createExpectedFilesetEntryString(FilesetEntry.SymlinkBehavior symlinkBehavior) { - return "FilesetEntry(srcdir = \"//x:x\"," - + " files = [\"//x:x\"]," - + " excludes = []," - + " destdir = \"\"," - + " strip_prefix = \".\"," - + " symlinks = \"" + symlinkBehavior.toString().toLowerCase() + "\")"; - } - - private FilesetEntry createTestFilesetEntry(FilesetEntry.SymlinkBehavior symlinkBehavior) - throws Exception { - Label label = Label.parseAbsolute("//x"); - return new FilesetEntry(label, - Arrays.asList(label), - Arrays.<String>asList(), - "", - symlinkBehavior, - "."); - } - - @Test - public void testFilesetEntrySymlinkAttr() throws Exception { - FilesetEntry entryDereference = - createTestFilesetEntry(FilesetEntry.SymlinkBehavior.DEREFERENCE); - - assertEquals(createExpectedFilesetEntryString(FilesetEntry.SymlinkBehavior.DEREFERENCE), - EvalUtils.prettyPrintValue(entryDereference)); - } - - private FilesetEntry createStripPrefixFilesetEntry(String stripPrefix) throws Exception { - Label label = Label.parseAbsolute("//x"); - return new FilesetEntry( - label, - Arrays.asList(label), - Arrays.<String>asList(), - "", - FilesetEntry.SymlinkBehavior.DEREFERENCE, - stripPrefix); - } - - @Test - public void testFilesetEntryStripPrefixAttr() throws Exception { - FilesetEntry withoutStripPrefix = createStripPrefixFilesetEntry("."); - FilesetEntry withStripPrefix = createStripPrefixFilesetEntry("orange"); - - String prettyWithout = EvalUtils.prettyPrintValue(withoutStripPrefix); - String prettyWith = EvalUtils.prettyPrintValue(withStripPrefix); - - assertThat(prettyWithout).contains("strip_prefix = \".\""); - assertThat(prettyWith).contains("strip_prefix = \"orange\""); - } - - @Test - public void testRegressionCrashInPrettyPrintValue() throws Exception { - // Would cause crash in code such as this: - // Fileset(name='x', entries=[], out=[FilesetEntry(files=['a'])]) - // While formatting the "expected x, got y" message for the 'out' - // attribute, prettyPrintValue(FilesetEntry) would be recursively called - // with a List<Label> even though this isn't a valid datatype in the - // interpreter. - // Fileset isn't part of bazel, even though FilesetEntry is. - Label label = Label.parseAbsolute("//x"); - assertEquals("FilesetEntry(srcdir = \"//x:x\"," - + " files = [\"//x:x\"]," - + " excludes = []," - + " destdir = \"\"," - + " strip_prefix = \".\"," - + " symlinks = \"copy\")", - EvalUtils.prettyPrintValue( - new FilesetEntry(label, - Arrays.asList(label), - Arrays.<String>asList(), - "", - FilesetEntry.SymlinkBehavior.COPY, - "."))); - } } 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 b3e5bcff48..7177c7f1d0 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 @@ -542,7 +542,7 @@ public class EvaluationTest extends EvaluationTestCase { @Test public void testListComprehensionOnDictionaryCompositeExpression() throws Exception { eval("d = {1:'a',2:'b'}\n" + "l = [d[x] for x in d]"); - assertEquals("[\"a\", \"b\"]", EvalUtils.prettyPrintValue(lookup("l"))); + assertEquals("[\"a\", \"b\"]", Printer.repr(lookup("l"))); } @Test 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 new file mode 100644 index 0000000000..d69c76f681 --- /dev/null +++ b/src/test/java/com/google/devtools/build/lib/syntax/PrinterTest.java @@ -0,0 +1,214 @@ +// Copyright 2006-2015 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.devtools.build.lib.syntax; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Arrays; +import java.util.IllegalFormatException; +import java.util.List; +import java.util.Map; + +/** + * Test properties of the evaluator's datatypes and utility functions + * without actually creating any parse trees. + */ +@RunWith(JUnit4.class) +public class PrinterTest { + + private static List<?> makeList(Object... args) { + return EvalUtils.makeSequence(Arrays.<Object>asList(args), false); + } + + private static List<?> makeTuple(Object... args) { + return EvalUtils.makeSequence(Arrays.<Object>asList(args), true); + } + + private static FilesetEntry makeFilesetEntry() { + try { + return new FilesetEntry(Label.parseAbsolute("//foo:bar"), + Lists.<Label>newArrayList(), Lists.newArrayList("xyz"), "", + FilesetEntry.SymlinkBehavior.COPY, "."); + } catch (Label.SyntaxException e) { + throw new RuntimeException("Bad label: ", e); + } + } + + @Test + public void testPrinter() throws Exception { + // Note that prettyPrintValue and printValue only differ on behaviour of + // labels and strings at toplevel. + assertEquals("foo\nbar", Printer.str("foo\nbar")); + assertEquals("\"foo\\nbar\"", Printer.repr("foo\nbar")); + assertEquals("'", Printer.str("'")); + assertEquals("\"'\"", Printer.repr("'")); + assertEquals("\"", Printer.str("\"")); + assertEquals("\"\\\"\"", Printer.repr("\"")); + assertEquals("3", Printer.str(3)); + assertEquals("3", Printer.repr(3)); + assertEquals("None", Printer.repr(Environment.NONE)); + + assertEquals("//x:x", Printer.str(Label.parseAbsolute("//x"))); + assertEquals("\"//x:x\"", Printer.repr(Label.parseAbsolute("//x"))); + + List<?> list = makeList("foo", "bar"); + List<?> tuple = makeTuple("foo", "bar"); + + assertEquals("(1, [\"foo\", \"bar\"], 3)", + Printer.str(makeTuple(1, list, 3))); + assertEquals("(1, [\"foo\", \"bar\"], 3)", + Printer.repr(makeTuple(1, list, 3))); + assertEquals("[1, (\"foo\", \"bar\"), 3]", + Printer.str(makeList(1, tuple, 3))); + assertEquals("[1, (\"foo\", \"bar\"), 3]", + Printer.repr(makeList(1, tuple, 3))); + + Map<Object, Object> dict = ImmutableMap.of( + 1, tuple, + 2, list, + "foo", makeList()); + assertEquals("{1: (\"foo\", \"bar\"), 2: [\"foo\", \"bar\"], \"foo\": []}", + Printer.str(dict)); + assertEquals("{1: (\"foo\", \"bar\"), 2: [\"foo\", \"bar\"], \"foo\": []}", + Printer.repr(dict)); + assertEquals("FilesetEntry(srcdir = \"//foo:bar\", files = [], " + + "excludes = [\"xyz\"], destdir = \"\", " + + "strip_prefix = \".\", symlinks = \"copy\")", + Printer.repr(makeFilesetEntry())); + } + + private void checkFormatPositionalFails(String format, List<?> tuple, String errorMessage) { + try { + Printer.format(format, tuple); + fail(); + } catch (IllegalFormatException e) { + assertThat(e).hasMessage(errorMessage); + } + } + + @Test + public void testFormatPositional() throws Exception { + assertEquals("foo 3", Printer.format("%s %d", makeTuple("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"); + } + + private String createExpectedFilesetEntryString(FilesetEntry.SymlinkBehavior symlinkBehavior) { + return "FilesetEntry(srcdir = \"//x:x\"," + + " files = [\"//x:x\"]," + + " excludes = []," + + " destdir = \"\"," + + " strip_prefix = \".\"," + + " symlinks = \"" + symlinkBehavior.toString().toLowerCase() + "\")"; + } + + private FilesetEntry createTestFilesetEntry(FilesetEntry.SymlinkBehavior symlinkBehavior) + throws Exception { + Label label = Label.parseAbsolute("//x"); + return new FilesetEntry( + label, + Arrays.asList(label), + Arrays.<String>asList(), + "", + symlinkBehavior, + "."); + } + + @Test + public void testFilesetEntrySymlinkAttr() throws Exception { + FilesetEntry entryDereference = + createTestFilesetEntry(FilesetEntry.SymlinkBehavior.DEREFERENCE); + + assertEquals(createExpectedFilesetEntryString(FilesetEntry.SymlinkBehavior.DEREFERENCE), + Printer.repr(entryDereference)); + } + + private FilesetEntry createStripPrefixFilesetEntry(String stripPrefix) throws Exception { + Label label = Label.parseAbsolute("//x"); + return new FilesetEntry( + label, + Arrays.asList(label), + Arrays.<String>asList(), + "", + FilesetEntry.SymlinkBehavior.DEREFERENCE, + stripPrefix); + } + + @Test + public void testFilesetEntryStripPrefixAttr() throws Exception { + FilesetEntry withoutStripPrefix = createStripPrefixFilesetEntry("."); + FilesetEntry withStripPrefix = createStripPrefixFilesetEntry("orange"); + + String prettyWithout = Printer.repr(withoutStripPrefix); + String prettyWith = Printer.repr(withStripPrefix); + + assertThat(prettyWithout).contains("strip_prefix = \".\""); + assertThat(prettyWith).contains("strip_prefix = \"orange\""); + } + + @Test + public void testRegressionCrashInPrettyPrintValue() throws Exception { + // Would cause crash in code such as this: + // Fileset(name='x', entries=[], out=[FilesetEntry(files=['a'])]) + // While formatting the "expected x, got y" message for the 'out' + // attribute, prettyPrintValue(FilesetEntry) would be recursively called + // with a List<Label> even though this isn't a valid datatype in the + // interpreter. + // Fileset isn't part of bazel, even though FilesetEntry is. + Label label = Label.parseAbsolute("//x"); + assertEquals("FilesetEntry(srcdir = \"//x:x\"," + + " files = [\"//x:x\"]," + + " excludes = []," + + " destdir = \"\"," + + " strip_prefix = \".\"," + + " symlinks = \"copy\")", + Printer.repr( + new FilesetEntry( + label, + Arrays.asList(label), + Arrays.<String>asList(), + "", + FilesetEntry.SymlinkBehavior.COPY, + "."))); + } +} diff --git a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkShell.java b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkShell.java index f00d8f5ab7..c4bb7d84a9 100644 --- a/src/test/java/com/google/devtools/build/lib/syntax/SkylarkShell.java +++ b/src/test/java/com/google/devtools/build/lib/syntax/SkylarkShell.java @@ -72,7 +72,7 @@ class SkylarkShell { try { Object result = ev.eval(input); if (result != null) { - System.out.println(EvalUtils.prettyPrintValue(result)); + System.out.println(Printer.repr(result)); } } catch (Exception e) { e.printStackTrace(); |