blob: 6ec208032be8e3f4bec71b45bf0699b8c4a72efc [file] [log] [blame]
Damien Martin-Guillerezf88f4d82015-09-25 13:56:55 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
Francois-Rene Rideauc3ad5412015-06-05 22:13:21 +00002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14package com.google.devtools.build.lib.syntax;
15
Klaus Aehlig26c86f82018-05-29 06:57:03 -070016import com.google.common.base.Strings;
Florian Weikert2591e192015-10-05 14:24:51 +000017import com.google.common.collect.ImmutableSet;
18import com.google.common.collect.Iterables;
vladmos46907932017-06-30 14:01:45 +020019import com.google.devtools.build.lib.events.Location;
tomlu67c84b12017-11-06 19:49:16 +010020import com.google.devtools.build.lib.skylarkinterface.SkylarkPrintable;
vladmos46907932017-06-30 14:01:45 +020021import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter;
John Field585d1a02015-12-16 16:03:52 +000022import com.google.devtools.build.lib.skylarkinterface.SkylarkValue;
Francois-Rene Rideau4e994102015-09-17 22:41:28 +000023import com.google.devtools.build.lib.syntax.SkylarkList.Tuple;
Francois-Rene Rideauc3ad5412015-06-05 22:13:21 +000024import java.io.IOException;
brandjonf107a532017-08-16 22:46:02 +020025import java.util.Arrays;
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +000026import java.util.Formattable;
27import java.util.Formatter;
brandjona9cafcb2018-04-04 12:24:33 -070028import java.util.IllegalFormatException;
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +000029import java.util.List;
30import java.util.Map;
31import java.util.MissingFormatWidthException;
brandjona9cafcb2018-04-04 12:24:33 -070032import java.util.UnknownFormatConversionException;
Florian Weikert2591e192015-10-05 14:24:51 +000033import java.util.regex.Matcher;
34import java.util.regex.Pattern;
vladmos46907932017-06-30 14:01:45 +020035import javax.annotation.Nullable;
Francois-Rene Rideauc3ad5412015-06-05 22:13:21 +000036
vladmos46907932017-06-30 14:01:45 +020037/** (Pretty) Printing of Skylark values */
38public class Printer {
Francois-Rene Rideauc3ad5412015-06-05 22:13:21 +000039
vladmos46907932017-06-30 14:01:45 +020040 public static final char SKYLARK_QUOTATION_MARK = '"';
Francois-Rene Rideau304e1952015-08-25 13:30:10 +000041
Florian Weikert2591e192015-10-05 14:24:51 +000042 /*
vladmos46907932017-06-30 14:01:45 +020043 * Suggested maximum number of list elements that should be printed via printAbbreviatedList().
Florian Weikert2591e192015-10-05 14:24:51 +000044 * By default, this setting is not considered and no limitation takes place.
45 */
vladmos46907932017-06-30 14:01:45 +020046 public static final int SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT = 4;
Florian Weikert2591e192015-10-05 14:24:51 +000047
48 /*
vladmos46907932017-06-30 14:01:45 +020049 * Suggested limit for printAbbreviatedList() to shorten the values of list elements when
50 * their combined string length reaches this value.
Florian Weikert2591e192015-10-05 14:24:51 +000051 * By default, this setting is not considered and no limitation takes place.
52 */
vladmos46907932017-06-30 14:01:45 +020053 public static final int SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH = 32;
Florian Weikert2591e192015-10-05 14:24:51 +000054
vladmos46907932017-06-30 14:01:45 +020055 /**
vladmos6ff634d2017-07-05 10:25:01 -040056 * Creates an instance of {@link BasePrinter} that wraps an existing buffer.
57 *
58 * @param buffer an {@link Appendable}
59 * @return new {@link BasePrinter}
vladmos46907932017-06-30 14:01:45 +020060 */
vladmos6ff634d2017-07-05 10:25:01 -040061 static BasePrinter getPrinter(Appendable buffer) {
vladmos46907932017-06-30 14:01:45 +020062 return new BasePrinter(buffer);
Francois-Rene Rideauc3ad5412015-06-05 22:13:21 +000063 }
64
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +000065 /**
vladmos6ff634d2017-07-05 10:25:01 -040066 * Creates an instance of {@link BasePrinter} with an empty buffer.
67 *
68 * @return new {@link BasePrinter}
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +000069 */
vladmos46907932017-06-30 14:01:45 +020070 public static BasePrinter getPrinter() {
tomlubeafd7e2018-04-05 15:03:19 -070071 return new BasePrinter(new StringBuilder());
72 }
73
74 /**
Klaus Aehlig26c86f82018-05-29 06:57:03 -070075 * Creates an instance of {@link PrettyPrinter} with an empty buffer.
76 *
77 * @return new {@link PrettyPrinter}
78 */
79 public static PrettyPrinter getPrettyPrinter() {
80 return new PrettyPrinter(new StringBuilder());
81 }
82
83 /**
brandjone2d1a552018-04-09 15:50:16 -070084 * Creates an instance of {@link BasePrinter} with an empty buffer and whose format strings allow
85 * only %s and %%.
tomlubeafd7e2018-04-05 15:03:19 -070086 */
brandjone2d1a552018-04-09 15:50:16 -070087 public static BasePrinter getSimplifiedPrinter() {
88 return new BasePrinter(new StringBuilder(), /*simplifiedFormatStrings=*/ true);
Francois-Rene Rideauc3ad5412015-06-05 22:13:21 +000089 }
90
vladmos46907932017-06-30 14:01:45 +020091 private Printer() {}
92
93 // These static methods proxy to the similar methods of BasePrinter
94
95 /**
cparsons7ec3f212018-02-16 14:21:10 -080096 * Format an object with Skylark's {@code debugPrint}.
97 */
98 public static String debugPrint(Object x) {
99 return getPrinter().debugPrint(x).toString();
100 }
101
102 /**
vladmos46907932017-06-30 14:01:45 +0200103 * Format an object with Skylark's {@code str}.
104 */
Florian Weikertf07e5442015-07-01 13:08:43 +0000105 public static String str(Object x) {
vladmos46907932017-06-30 14:01:45 +0200106 return getPrinter().str(x).toString();
Florian Weikertf07e5442015-07-01 13:08:43 +0000107 }
Francois-Rene Rideau304e1952015-08-25 13:30:10 +0000108
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000109 /**
vladmos46907932017-06-30 14:01:45 +0200110 * Format an object with Skylark's {@code repr}.
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000111 */
Florian Weikertf07e5442015-07-01 13:08:43 +0000112 public static String repr(Object x) {
vladmos46907932017-06-30 14:01:45 +0200113 return getPrinter().repr(x).toString();
Florian Weikert2591e192015-10-05 14:24:51 +0000114 }
115
116 /**
117 * Print a list of object representations.
118 *
vladmos46907932017-06-30 14:01:45 +0200119 * <p>The length of the output will be limited when both {@code maxItemsToPrint} and
120 * {@code criticalItemsStringLength} have values greater than zero.
Florian Weikert2591e192015-10-05 14:24:51 +0000121 *
vladmos46907932017-06-30 14:01:45 +0200122 * @param list the list of objects to repr (each as with repr)
Florian Weikert2591e192015-10-05 14:24:51 +0000123 * @param before a string to print before the list
124 * @param separator a separator to print between each object
125 * @param after a string to print after the list
vladmos46907932017-06-30 14:01:45 +0200126 * @param singletonTerminator null or a string to print after the list if it is a singleton The
127 * singleton case is notably relied upon in python syntax to distinguish a tuple of size one
128 * such as ("foo",) from a merely parenthesized object such as ("foo").
Florian Weikert2591e192015-10-05 14:24:51 +0000129 * @param maxItemsToPrint the maximum number of elements to be printed.
130 * @param criticalItemsStringLength a soft limit for the total string length of all arguments.
vladmos46907932017-06-30 14:01:45 +0200131 * 'Soft' means that this limit may be exceeded because of formatting.
132 * @return string representation.
Florian Weikert2591e192015-10-05 14:24:51 +0000133 */
vladmos46907932017-06-30 14:01:45 +0200134 public static String printAbbreviatedList(
135 Iterable<?> list,
136 String before,
137 String separator,
138 String after,
139 @Nullable String singletonTerminator,
140 int maxItemsToPrint,
Florian Weikert2591e192015-10-05 14:24:51 +0000141 int criticalItemsStringLength) {
vladmos6ff634d2017-07-05 10:25:01 -0400142 return new LengthLimitedPrinter()
vladmos46907932017-06-30 14:01:45 +0200143 .printAbbreviatedList(
144 list,
145 before,
146 separator,
147 after,
148 singletonTerminator,
149 maxItemsToPrint,
150 criticalItemsStringLength)
151 .toString();
Florian Weikert2591e192015-10-05 14:24:51 +0000152 }
153
154 /**
vladmos46907932017-06-30 14:01:45 +0200155 * Print a list of object representations.
156 *
157 * @param list the list of objects to repr (each as with repr)
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000158 * @param before a string to print before the list
159 * @param separator a separator to print between each object
160 * @param after a string to print after the list
vladmos46907932017-06-30 14:01:45 +0200161 * @param singletonTerminator null or a string to print after the list if it is a singleton The
162 * singleton case is notably relied upon in python syntax to distinguish a tuple of size one
163 * such as ("foo",) from a merely parenthesized object such as ("foo").
164 * @return string representation.
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000165 */
vladmos46907932017-06-30 14:01:45 +0200166 public static String printAbbreviatedList(
167 Iterable<?> list,
168 String before,
169 String separator,
170 String after,
171 @Nullable String singletonTerminator) {
172 return printAbbreviatedList(list, before, separator, after, singletonTerminator,
173 SUGGESTED_CRITICAL_LIST_ELEMENTS_COUNT, SUGGESTED_CRITICAL_LIST_ELEMENTS_STRING_LENGTH);
Florian Weikertf07e5442015-07-01 13:08:43 +0000174 }
175
vladmos46907932017-06-30 14:01:45 +0200176 /**
177 * Print a list of object representations.
178 *
179 * <p>The length of the output will be limited when both {@code maxItemsToPrint} and
180 * {@code criticalItemsStringLength} have values greater than zero.
181 *
182 * @param list the list of objects to repr (each as with repr)
183 * @param isTuple if true the list will be formatted with parentheses and with a trailing comma
184 * in case of one-element tuples.
185 * @param maxItemsToPrint the maximum number of elements to be printed.
186 * @param criticalItemsStringLength a soft limit for the total string length of all arguments.
187 * 'Soft' means that this limit may be exceeded because of formatting.
188 * @return string representation.
189 */
190 public static String printAbbreviatedList(
191 Iterable<?> list,
192 boolean isTuple,
193 int maxItemsToPrint,
194 int criticalItemsStringLength) {
vladmos6ff634d2017-07-05 10:25:01 -0400195 return new LengthLimitedPrinter()
vladmos46907932017-06-30 14:01:45 +0200196 .printAbbreviatedList(list, isTuple, maxItemsToPrint, criticalItemsStringLength)
197 .toString();
198 }
199
200 /**
201 * Perform Python-style string formatting, as per pattern % tuple Limitations: only %d %s %r %%
202 * are supported.
203 *
204 * @param pattern a format string.
205 * @param arguments an array containing positional arguments.
206 * @return the formatted string.
207 */
208 public static String format(String pattern, Object... arguments) {
209 return getPrinter().format(pattern, arguments).toString();
210 }
211
212 /**
213 * Perform Python-style string formatting, as per pattern % tuple Limitations: only %d %s %r %%
214 * are supported.
215 *
216 * @param pattern a format string.
217 * @param arguments a tuple containing positional arguments.
218 * @return the formatted string.
219 */
220 public static String formatWithList(String pattern, List<?> arguments) {
221 return getPrinter().formatWithList(pattern, arguments).toString();
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000222 }
223
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000224 /**
Francois-Rene Rideau304e1952015-08-25 13:30:10 +0000225 * Perform Python-style string formatting, lazily.
226 *
227 * @param pattern a format string.
228 * @param arguments positional arguments.
229 * @return the formatted string.
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000230 */
Laurent Le Brune51a4d22016-10-11 18:04:16 +0000231 public static Formattable formattable(final String pattern, Object... arguments) {
brandjonf107a532017-08-16 22:46:02 +0200232 final List<Object> args = Arrays.asList(arguments);
Francois-Rene Rideau304e1952015-08-25 13:30:10 +0000233 return new Formattable() {
vladmos46907932017-06-30 14:01:45 +0200234 @Override
235 public String toString() {
236 return formatWithList(pattern, args);
237 }
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000238
vladmos46907932017-06-30 14:01:45 +0200239 @Override
240 public void formatTo(Formatter formatter, int flags, int width, int precision) {
241 Printer.getPrinter(formatter.out()).formatWithList(pattern, args);
242 }
243 };
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000244 }
245
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000246 /**
vladmos46907932017-06-30 14:01:45 +0200247 * Append a char to a buffer. In case of {@link IOException} throw an {@link AssertionError}
248 * instead
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000249 *
vladmos46907932017-06-30 14:01:45 +0200250 * @return buffer
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000251 */
vladmos46907932017-06-30 14:01:45 +0200252 public static Appendable append(Appendable buffer, char c) {
253 try {
254 return buffer.append(c);
255 } catch (IOException e) {
256 throw new AssertionError(e);
Francois-Rene Rideau8d5cce32015-06-16 23:12:04 +0000257 }
vladmos46907932017-06-30 14:01:45 +0200258 }
259
260 /**
261 * Append a char sequence to a buffer. In case of {@link IOException} throw an
262 * {@link AssertionError} instead
263 *
264 * @return buffer
265 */
266 public static Appendable append(Appendable buffer, CharSequence s) {
267 try {
268 return buffer.append(s);
269 } catch (IOException e) {
270 throw new AssertionError(e);
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000271 }
vladmos46907932017-06-30 14:01:45 +0200272 }
273
274 /**
275 * Append a char sequence range to a buffer. In case of {@link IOException} throw an
276 * {@link AssertionError} instead
277 * @return buffer
278 */
279 private static Appendable append(Appendable buffer, CharSequence s, int start, int end) {
280 try {
281 return buffer.append(s, start, end);
282 } catch (IOException e) {
283 throw new AssertionError(e);
284 }
Francois-Rene Rideaud61f5312015-06-13 03:34:47 +0000285 }
Florian Weikert2591e192015-10-05 14:24:51 +0000286
vladmos46907932017-06-30 14:01:45 +0200287 /** Actual class that implements Printer API */
vladmos6ff634d2017-07-05 10:25:01 -0400288 public static class BasePrinter implements SkylarkPrinter {
vladmos46907932017-06-30 14:01:45 +0200289 // Methods of this class should not recurse through static methods of Printer
290
vladmos6ff634d2017-07-05 10:25:01 -0400291 protected final Appendable buffer;
vladmos46907932017-06-30 14:01:45 +0200292
293 /**
brandjona9cafcb2018-04-04 12:24:33 -0700294 * If true, the only percent sequences allowed in format strings are %s substitutions and %%
295 * escapes.
vladmos46907932017-06-30 14:01:45 +0200296 */
brandjona9cafcb2018-04-04 12:24:33 -0700297 protected final boolean simplifiedFormatStrings;
298
299 /**
300 * Creates a printer.
301 *
302 * @param buffer the {@link Appendable} that will be written to
303 * @param simplifiedFormatStrings if true, format strings will allow only %s and %%
304 */
305 protected BasePrinter(Appendable buffer, boolean simplifiedFormatStrings) {
vladmos46907932017-06-30 14:01:45 +0200306 this.buffer = buffer;
brandjona9cafcb2018-04-04 12:24:33 -0700307 this.simplifiedFormatStrings = simplifiedFormatStrings;
vladmos46907932017-06-30 14:01:45 +0200308 }
309
brandjona9cafcb2018-04-04 12:24:33 -0700310 /**
311 * Creates a printer that writes to the given buffer and that does not use simplified format
312 * strings.
313 */
314 protected BasePrinter(Appendable buffer) {
315 this(buffer, /*simplifiedFormatStrings=*/ false);
316 }
317
318 /**
319 * Creates a printer that uses a fresh buffer and that does not use simplified format strings.
320 */
vladmos6ff634d2017-07-05 10:25:01 -0400321 protected BasePrinter() {
brandjona9cafcb2018-04-04 12:24:33 -0700322 this(new StringBuilder());
vladmos6ff634d2017-07-05 10:25:01 -0400323 }
324
vladmos46907932017-06-30 14:01:45 +0200325 @Override
326 public String toString() {
327 return buffer.toString();
328 }
329
330 /**
cparsons7ec3f212018-02-16 14:21:10 -0800331 * Print an informal debug-only representation of object x.
332 *
333 * @param o the object
334 * @return the buffer, in fluent style
335 */
336 public BasePrinter debugPrint(Object o) {
337 if (o instanceof SkylarkValue) {
338 ((SkylarkValue) o).debugPrint(this);
339 return this;
340 }
341
342 return this.str(o);
343 }
344
345 /**
vladmos46907932017-06-30 14:01:45 +0200346 * Print an informal representation of object x. Currently only differs from repr in the
347 * behavior for strings and labels at top-level, that are returned as is rather than quoted.
348 *
349 * @param o the object
350 * @return the buffer, in fluent style
351 */
352 public BasePrinter str(Object o) {
vladmos6ff634d2017-07-05 10:25:01 -0400353 if (o instanceof SkylarkValue) {
354 ((SkylarkValue) o).str(this);
vladmos46907932017-06-30 14:01:45 +0200355 return this;
356 }
357
358 if (o instanceof String) {
359 return this.append((String) o);
360 }
361 return this.repr(o);
362 }
363
364 /**
365 * Print an official representation of object x. For regular data structures, the value should
366 * be parsable back into an equal data structure.
367 *
368 * @param o the string a representation of which to repr.
369 * @return BasePrinter.
370 */
371 @Override
372 public BasePrinter repr(Object o) {
373 if (o == null) {
brandjonf107a532017-08-16 22:46:02 +0200374 // Java null is not a valid Skylark value, but sometimes printers are used on non-Skylark
375 // values such as Locations or ASTs.
376 this.append("null");
vladmos46907932017-06-30 14:01:45 +0200377
tomlu67c84b12017-11-06 19:49:16 +0100378 } else if (o instanceof SkylarkPrintable) {
379 ((SkylarkPrintable) o).repr(this);
vladmos46907932017-06-30 14:01:45 +0200380
381 } else if (o instanceof String) {
382 writeString((String) o);
383
384 } else if (o instanceof Integer || o instanceof Double) {
385 this.append(o.toString());
386
vladmosf07773b2017-07-11 16:31:32 +0200387 } else if (Boolean.TRUE.equals(o)) {
vladmos46907932017-06-30 14:01:45 +0200388 this.append("True");
389
vladmosf07773b2017-07-11 16:31:32 +0200390 } else if (Boolean.FALSE.equals(o)) {
vladmos46907932017-06-30 14:01:45 +0200391 this.append("False");
392
393 } else if (o instanceof Map<?, ?>) {
394 Map<?, ?> dict = (Map<?, ?>) o;
395 this.printList(dict.entrySet(), "{", ", ", "}", null);
396
397 } else if (o instanceof List<?>) {
398 List<?> seq = (List<?>) o;
399 this.printList(seq, false);
400
401 } else if (o instanceof Map.Entry<?, ?>) {
402 Map.Entry<?, ?> entry = (Map.Entry<?, ?>) o;
403 this.repr(entry.getKey());
404 this.append(": ");
405 this.repr(entry.getValue());
vladmos46907932017-06-30 14:01:45 +0200406 } else if (o instanceof Class<?>) {
407 this.append(EvalUtils.getDataTypeNameFromClass((Class<?>) o));
408
Googler4ace4652019-09-16 07:47:08 -0700409 } else if (o instanceof Node || o instanceof Location) {
vladmos46907932017-06-30 14:01:45 +0200410 // AST node objects and locations are printed in tracebacks and error messages,
411 // it's safe to print their toString representations
412 this.append(o.toString());
413
414 } else {
vladmosd4cc4b62017-07-14 19:04:55 +0200415 // Other types of objects shouldn't be leaked to Skylark, but if happens, their
416 // .toString method shouldn't be used because their return values are likely to contain
417 // memory addresses or other nondeterministic information.
418 this.append("<unknown object " + o.getClass().getName() + ">");
vladmos46907932017-06-30 14:01:45 +0200419 }
420
421 return this;
422 }
423
424 /**
425 * Write a properly escaped Skylark representation of a string to a buffer.
426 *
427 * @param s the string a representation of which to repr.
vladmos6ff634d2017-07-05 10:25:01 -0400428 * @return this printer.
vladmos46907932017-06-30 14:01:45 +0200429 */
vladmos6ff634d2017-07-05 10:25:01 -0400430 protected BasePrinter writeString(String s) {
vladmos46907932017-06-30 14:01:45 +0200431 this.append(SKYLARK_QUOTATION_MARK);
432 int len = s.length();
433 for (int i = 0; i < len; i++) {
434 char c = s.charAt(i);
435 escapeCharacter(c);
436 }
437 return this.append(SKYLARK_QUOTATION_MARK);
438 }
439
440 private BasePrinter backslashChar(char c) {
441 return this.append('\\').append(c);
442 }
443
444 private BasePrinter escapeCharacter(char c) {
445 if (c == SKYLARK_QUOTATION_MARK) {
446 return backslashChar(c);
447 }
448 switch (c) {
449 case '\\':
450 return backslashChar('\\');
451 case '\r':
452 return backslashChar('r');
453 case '\n':
454 return backslashChar('n');
455 case '\t':
456 return backslashChar('t');
457 default:
458 if (c < 32) {
459 //TODO(bazel-team): support \x escapes
460 return this.append(String.format("\\x%02x", (int) c));
461 }
462 return this.append(c); // no need to support UTF-8
463 } // endswitch
464 }
465
466 /**
467 * Print a list of object representations
468 *
469 * @param list the list of objects to repr (each as with repr)
470 * @param before a string to print before the list items, e.g. an opening bracket
471 * @param separator a separator to print between items
472 * @param after a string to print after the list items, e.g. a closing bracket
473 * @param singletonTerminator null or a string to print after the list if it is a singleton The
474 * singleton case is notably relied upon in python syntax to distinguish a tuple of size one
475 * such as ("foo",) from a merely parenthesized object such as ("foo").
vladmos6ff634d2017-07-05 10:25:01 -0400476 * @return this printer.
vladmos46907932017-06-30 14:01:45 +0200477 */
478 @Override
479 public BasePrinter printList(
480 Iterable<?> list,
481 String before,
482 String separator,
483 String after,
484 @Nullable String singletonTerminator) {
vladmos46907932017-06-30 14:01:45 +0200485
vladmos46907932017-06-30 14:01:45 +0200486 this.append(before);
vladmos6ff634d2017-07-05 10:25:01 -0400487 int len = appendListElements(list, separator);
vladmos46907932017-06-30 14:01:45 +0200488 if (singletonTerminator != null && len == 1) {
489 this.append(singletonTerminator);
490 }
491 return this.append(after);
492 }
493
494 /**
495 * Appends the given elements to the specified {@link Appendable} and returns the number of
496 * elements.
497 */
498 private int appendListElements(Iterable<?> list, String separator) {
499 boolean printSeparator = false; // don't print the separator before the first element
500 int len = 0;
501 for (Object o : list) {
502 if (printSeparator) {
503 this.append(separator);
504 }
505 this.repr(o);
506 printSeparator = true;
507 len++;
508 }
509 return len;
510 }
511
512 /**
vladmos46907932017-06-30 14:01:45 +0200513 * Print a Skylark list or tuple of object representations
514 *
515 * @param list the contents of the list or tuple
516 * @param isTuple if true the list will be formatted with parentheses and with a trailing comma
vladmos6ff634d2017-07-05 10:25:01 -0400517 * in case of one-element tuples. 'Soft' means that this limit may be exceeded because of
518 * formatting.
519 * @return this printer.
vladmos46907932017-06-30 14:01:45 +0200520 */
vladmos46907932017-06-30 14:01:45 +0200521 @Override
522 public BasePrinter printList(Iterable<?> list, boolean isTuple) {
vladmos6ff634d2017-07-05 10:25:01 -0400523 if (isTuple) {
524 return this.printList(list, "(", ", ", ")", ",");
525 } else {
526 return this.printList(list, "[", ", ", "]", null);
527 }
vladmos46907932017-06-30 14:01:45 +0200528 }
529
530 /**
brandjona9cafcb2018-04-04 12:24:33 -0700531 * Perform Python-style string formatting, similar to the {@code pattern % tuple} syntax.
vladmos46907932017-06-30 14:01:45 +0200532 *
brandjona9cafcb2018-04-04 12:24:33 -0700533 * <p>The only supported placeholder patterns are
534 * <ul>
535 * <li>{@code %s} (convert as if by {@code str()})
536 * <li>{@code %r} (convert as if by {@code repr()})
537 * <li>{@code %d} (convert an integer to its decimal representation)
538 * </ul>
539 * To encode a literal percent character, escape it as {@code %%}. It is an error to have a
540 * non-escaped {@code %} at the end of the string or followed by any character not listed above.
541 *
542 * <p>If this printer has {@code simplifiedFormatStrings} set, only {@code %s} and {@code %%}
543 * are permitted.
544 *
545 * @param pattern a format string that may contain placeholders
546 * @param arguments an array containing arguments to substitute into the placeholders in order
547 * @return the formatted string
548 * @throws IllegalFormatException if {@code pattern} is not a valid format string, or if
549 * {@code arguments} mismatches the number or type of placeholders in {@code pattern}
vladmos46907932017-06-30 14:01:45 +0200550 */
551 @Override
552 public BasePrinter format(String pattern, Object... arguments) {
brandjonf107a532017-08-16 22:46:02 +0200553 return this.formatWithList(pattern, Arrays.asList(arguments));
vladmos46907932017-06-30 14:01:45 +0200554 }
555
556 /**
brandjona9cafcb2018-04-04 12:24:33 -0700557 * Perform Python-style string formatting, similar to the {@code pattern % tuple} syntax.
vladmos46907932017-06-30 14:01:45 +0200558 *
brandjona9cafcb2018-04-04 12:24:33 -0700559 * <p>Same as {@link #format(String, Object...)}, but with a list instead of variadic args.
vladmos46907932017-06-30 14:01:45 +0200560 */
561 @Override
562 public BasePrinter formatWithList(String pattern, List<?> arguments) {
563 // TODO(bazel-team): support formatting arguments, and more complex Python patterns.
564 // N.B. MissingFormatWidthException is the only kind of IllegalFormatException
565 // whose constructor can take and display arbitrary error message, hence its use below.
566
567 int length = pattern.length();
568 int argLength = arguments.size();
569 int i = 0; // index of next character in pattern
570 int a = 0; // index of next argument in arguments
571
572 while (i < length) {
573 int p = pattern.indexOf('%', i);
574 if (p == -1) {
575 Printer.append(buffer, pattern, i, length);
576 break;
577 }
578 if (p > i) {
579 Printer.append(buffer, pattern, i, p);
580 }
581 if (p == length - 1) {
582 throw new MissingFormatWidthException(
583 "incomplete format pattern ends with %: " + this.repr(pattern));
584 }
585 char directive = pattern.charAt(p + 1);
586 i = p + 2;
587 switch (directive) {
588 case '%':
589 this.append('%');
590 continue;
591 case 'd':
592 case 'r':
593 case 's':
brandjona9cafcb2018-04-04 12:24:33 -0700594 if (simplifiedFormatStrings && (directive != 's')) {
595 throw new UnknownFormatConversionException(
596 "cannot use %" + directive + " substitution placeholder when "
597 + "simplifiedFormatStrings is set");
598 }
vladmos46907932017-06-30 14:01:45 +0200599 if (a >= argLength) {
600 throw new MissingFormatWidthException(
601 "not enough arguments for format pattern "
vladmos6ff634d2017-07-05 10:25:01 -0400602 + Printer.repr(pattern)
vladmos46907932017-06-30 14:01:45 +0200603 + ": "
vladmos6ff634d2017-07-05 10:25:01 -0400604 + Printer.repr(Tuple.copyOf(arguments)));
vladmos46907932017-06-30 14:01:45 +0200605 }
606 Object argument = arguments.get(a++);
607 switch (directive) {
608 case 'd':
609 if (argument instanceof Integer) {
610 this.append(argument.toString());
611 continue;
612 } else {
613 throw new MissingFormatWidthException(
vladmos6ff634d2017-07-05 10:25:01 -0400614 "invalid argument " + Printer.repr(argument) + " for format pattern %d");
vladmos46907932017-06-30 14:01:45 +0200615 }
616 case 'r':
617 this.repr(argument);
618 continue;
619 case 's':
620 this.str(argument);
621 continue;
622 }
623 // fall through
624 default:
625 throw new MissingFormatWidthException(
626 // The call to Printer.repr doesn't cause an infinite recursion because it's
627 // only used to format a string properly
628 String.format("unsupported format character \"%s\" at index %s in %s",
629 String.valueOf(directive), p + 1, Printer.repr(pattern)));
630 }
631 }
632 if (a < argLength) {
633 throw new MissingFormatWidthException(
634 "not all arguments converted during string formatting");
635 }
636 return this;
637 }
638
639 @Override
640 public BasePrinter append(char c) {
641 Printer.append(buffer, c);
642 return this;
643 }
644
645 @Override
646 public BasePrinter append(CharSequence s) {
647 Printer.append(buffer, s);
648 return this;
649 }
vladmos6ff634d2017-07-05 10:25:01 -0400650
651 BasePrinter append(CharSequence sequence, int start, int end) {
652 return this.append(sequence.subSequence(start, end));
653 }
654 }
655
jingwen68c57f02018-11-21 16:17:17 -0800656 /** A printer that breaks lines between the entries of lists, with proper indenting. */
657 public static class PrettyPrinter extends BasePrinter {
Klaus Aehlig26c86f82018-05-29 06:57:03 -0700658 static final int BASE_INDENT = 4;
659 private int indent;
660
661 protected PrettyPrinter(Appendable buffer) {
662 super(buffer);
663 indent = 0;
664 }
665
666 @Override
667 public BasePrinter printList(
668 Iterable<?> list,
669 String before,
670 String untrimmedSeparator,
671 String after,
672 @Nullable String singletonTerminator) {
673
Klaus Aehlig36955912018-06-14 08:17:33 -0700674 // If the list is empty, do not split the presentation over
675 // several lines.
676 if (!list.iterator().hasNext()) {
677 this.append(before + after);
678 return this;
679 }
680
Klaus Aehlig26c86f82018-05-29 06:57:03 -0700681 String separator = untrimmedSeparator.trim();
682
683 this.append(before + "\n");
684 indent += BASE_INDENT;
685 boolean printSeparator = false; // don't print the separator before the first element
686 int len = 0;
687 for (Object o : list) {
688 if (printSeparator) {
689 this.append(separator + "\n");
690 }
691 this.append(Strings.repeat(" ", indent));
692 this.repr(o);
693 printSeparator = true;
694 len++;
695 }
696 if (singletonTerminator != null && len == 1) {
697 this.append(singletonTerminator);
698 }
699 this.append("\n");
700 indent -= BASE_INDENT;
701 this.append(Strings.repeat(" ", indent) + after);
702 return this;
703 }
704 }
705
vladmos6ff634d2017-07-05 10:25:01 -0400706 /** A version of {@code BasePrinter} that is able to print abbreviated lists. */
707 public static final class LengthLimitedPrinter extends BasePrinter {
708
709 private static final ImmutableSet<Character> SPECIAL_CHARS =
710 ImmutableSet.of(',', ' ', '"', '\'', ':', '(', ')', '[', ']', '{', '}');
711
712 private static final Pattern ARGS_PATTERN = Pattern.compile("<\\d+ more arguments>");
713
714 // Limits can be set several times recursively and then unset the same amount of times.
715 // But in fact they should be set only the first time and unset only the last time.
716 // To achieve that we need to keep track of the recursion depth.
717 private int recursionDepth;
718 // Current limit of symbols to print in the limited mode (`ignoreLimit = false`).
719 private int limit;
720 private boolean ignoreLimit = true;
721 private boolean previouslyShortened;
722
723 /**
724 * Print a list of object representations.
725 *
726 * <p>The length of the output will be limited when both {@code maxItemsToPrint} and {@code
727 * criticalItemsStringLength} have values greater than zero.
728 *
729 * @param list the list of objects to repr (each as with repr)
730 * @param before a string to print before the list
731 * @param separator a separator to print between each object
732 * @param after a string to print after the list
733 * @param singletonTerminator null or a string to print after the list if it is a singleton The
734 * singleton case is notably relied upon in python syntax to distinguish a tuple of size one
735 * such as ("foo",) from a merely parenthesized object such as ("foo").
736 * @param maxItemsToPrint the maximum number of elements to be printed.
737 * @param criticalItemsStringLength a soft limit for the total string length of all arguments.
738 * 'Soft' means that this limit may be exceeded because of formatting.
739 * @return the BasePrinter.
740 */
741 LengthLimitedPrinter printAbbreviatedList(
742 Iterable<?> list,
743 String before,
744 String separator,
745 String after,
746 @Nullable String singletonTerminator,
747 int maxItemsToPrint,
748 int criticalItemsStringLength) {
749 this.append(before);
750 int len = appendListElements(list, separator, maxItemsToPrint, criticalItemsStringLength);
751 if (singletonTerminator != null && len == 1) {
752 this.append(singletonTerminator);
753 }
754 return this.append(after);
755 }
756
757 /**
758 * Print a Skylark list or tuple of object representations
759 *
760 * @param list the contents of the list or tuple
761 * @param isTuple if true the list will be formatted with parentheses and with a trailing comma
762 * in case of one-element tuples.
763 * @param maxItemsToPrint the maximum number of elements to be printed.
764 * @param criticalItemsStringLength a soft limit for the total string length of all arguments.
765 * 'Soft' means that this limit may be exceeded because of formatting.
766 * @return this printer.
767 */
768 public LengthLimitedPrinter printAbbreviatedList(
769 Iterable<?> list, boolean isTuple, int maxItemsToPrint, int criticalItemsStringLength) {
770 if (isTuple) {
771 return this.printAbbreviatedList(
772 list, "(", ", ", ")", ",", maxItemsToPrint, criticalItemsStringLength);
773 } else {
774 return this.printAbbreviatedList(
775 list, "[", ", ", "]", null, maxItemsToPrint, criticalItemsStringLength);
776 }
777 }
778
779 /**
780 * Tries to append the given elements to the specified {@link Appendable} until specific limits
781 * are reached.
782 *
783 * @return the number of appended elements.
784 */
785 private int appendListElements(
786 Iterable<?> list, String separator, int maxItemsToPrint, int criticalItemsStringLength) {
787 boolean printSeparator = false; // don't print the separator before the first element
788 boolean skipArgs = false;
789 int items = Iterables.size(list);
790 int len = 0;
791 // We don't want to print "1 more arguments", hence we don't skip arguments if there is only
792 // one above the limit.
793 int itemsToPrint = (items - maxItemsToPrint == 1) ? items : maxItemsToPrint;
794 enforceLimit(criticalItemsStringLength);
795 for (Object o : list) {
796 // We don't want to print "1 more arguments", even if we hit the string limit.
797 if (len == itemsToPrint || (hasHitLimit() && len < items - 1)) {
798 skipArgs = true;
799 break;
800 }
801 if (printSeparator) {
802 this.append(separator);
803 }
804 this.repr(o);
805 printSeparator = true;
806 len++;
807 }
808 ignoreLimit();
809 if (skipArgs) {
810 this.append(separator);
811 this.append(String.format("<%d more arguments>", items - len));
812 }
813 return len;
814 }
815
816 @Override
817 public LengthLimitedPrinter append(CharSequence csq) {
818 if (ignoreLimit || hasOnlySpecialChars(csq)) {
819 // Don't update limit.
820 Printer.append(buffer, csq);
821 previouslyShortened = false;
822 } else {
823 int length = csq.length();
824 if (length <= limit) {
825 limit -= length;
826 Printer.append(buffer, csq);
827 } else {
828 Printer.append(buffer, csq, 0, limit);
829 // We don't want to append multiple ellipses.
830 if (!previouslyShortened) {
831 Printer.append(buffer, "...");
832 }
833 appendTrailingSpecialChars(csq, limit);
834 previouslyShortened = true;
835 limit = 0;
836 }
837 }
838 return this;
839 }
840
841 @Override
842 public LengthLimitedPrinter append(char c) {
843 // Use the local `append(sequence)` method so that limits can apply
844 return this.append(String.valueOf(c));
845 }
846
847 /**
848 * Appends any trailing "special characters" (e.g. brackets, quotation marks) in the given
849 * sequence to the output buffer, regardless of the limit.
850 *
851 * <p>For example, let's look at foo(['too long']). Without this method, the shortened result
852 * would be foo(['too...) instead of the prettier foo(['too...']).
853 *
854 * <p>If the input string was already shortened and contains "<x more arguments>", this part
855 * will also be appended.
856 */
857 // TODO(bazel-team): Given an input list
858 //
859 // [1, 2, 3, [10, 20, 30, 40, 50, 60], 4, 5, 6]
860 //
861 // the inner list gets doubly mangled as
862 //
863 // [1, 2, 3, [10, 20, 30, 40, <2 more argu...<2 more arguments>], <3 more arguments>]
864 private LengthLimitedPrinter appendTrailingSpecialChars(CharSequence csq, int limit) {
865 int length = csq.length();
866 Matcher matcher = ARGS_PATTERN.matcher(csq);
867 // We assume that everything following the "x more arguments" part has to be copied, too.
868 int start = matcher.find() ? matcher.start() : length;
869 // Find the left-most non-arg char that has to be copied.
870 for (int i = start - 1; i > limit; --i) {
871 if (isSpecialChar(csq.charAt(i))) {
872 start = i;
873 } else {
874 break;
875 }
876 }
877 if (start < length) {
878 Printer.append(buffer, csq, start, csq.length());
879 }
880 return this;
881 }
882
883 /**
884 * Returns whether the given sequence denotes characters that are not part of the value of an
885 * argument.
886 *
887 * <p>Examples are brackets, braces and quotation marks.
888 */
889 private boolean hasOnlySpecialChars(CharSequence csq) {
890 for (int i = 0; i < csq.length(); ++i) {
891 if (!isSpecialChar(csq.charAt(i))) {
892 return false;
893 }
894 }
895 return true;
896 }
897
898 private boolean isSpecialChar(char c) {
899 return SPECIAL_CHARS.contains(c);
900 }
901
902 boolean hasHitLimit() {
903 return limit <= 0;
904 }
905
906 private void enforceLimit(int limit) {
907 ignoreLimit = false;
908 if (recursionDepth == 0) {
909 this.limit = limit;
910 ++recursionDepth;
911 }
912 }
913
914 private void ignoreLimit() {
915 if (recursionDepth > 0) {
916 --recursionDepth;
917 }
918 if (recursionDepth == 0) {
919 ignoreLimit = true;
920 }
921 }
vladmos46907932017-06-30 14:01:45 +0200922 }
Francois-Rene Rideauc3ad5412015-06-05 22:13:21 +0000923}