aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com
diff options
context:
space:
mode:
authorGravatar Francois-Rene Rideau <tunes@google.com>2015-06-13 03:34:47 +0000
committerGravatar Han-Wen Nienhuys <hanwen@google.com>2015-06-15 10:51:53 +0000
commitd61f531427e7448d7900eba03a45674ed5dc000b (patch)
tree5e34f32565fdad804641eb37877bede73e7bf7ac /src/main/java/com
parent53594cbfbe74a5568503397a29b600e7c9b40a6b (diff)
Implement Skylark function repr
Move printing code from EvalUtils to Printer. Rename functions in Printer: printValue becomes str or print, prettyPrintValue becomes repr or write, formatString becomes format, makeFormattable becomes strFormattable, prettyPrintValues becomes listString. write being self-sufficient is made the reference, and print is the one that is a wrapper around write, rather than the other way around, avoiding mutual recursion. -- MOS_MIGRATED_REVID=95897834
Diffstat (limited to 'src/main/java/com')
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/MethodLibrary.java33
-rw-r--r--src/main/java/com/google/devtools/build/lib/packages/Type.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/query2/output/OutputFormatter.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/BinaryOperatorExpression.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/DotExpression.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/EvalUtils.java285
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/FuncallExpression.java10
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/FunctionSignature.java8
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/LValue.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/ListComprehension.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/Printer.java350
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkList.java7
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkNestedSet.java2
-rw-r--r--src/main/java/com/google/devtools/build/lib/syntax/SkylarkSignatureProcessor.java2
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);
}