diff options
Diffstat (limited to 'src/main/java/com')
14 files changed, 384 insertions, 340 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); } |