blob: 7fcfaf2b66b075281520dc8676d808c0cd8c8d24 [file] [log] [blame]
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Internal debugging utilities.
library;
import 'dart:math' as math;
import 'chunk.dart';
import 'line_splitting/rule_set.dart';
import 'piece/piece.dart';
/// Set this to `true` to turn on diagnostic output while building chunks.
bool traceChunkBuilder = false;
/// Set this to `true` to turn on diagnostic output while writing lines.
bool traceLineWriter = false;
/// Set this to `true` to turn on diagnostic output while line splitting.
bool traceSplitter = false;
/// Set this to `true` to turn on diagnostic output while building pieces.
bool tracePieceBuilder = false;
/// Set this to `true` to turn on diagnostic output while solving pieces.
bool traceSolver = false;
bool useAnsiColors = false;
const unicodeSection = '\u00a7';
const unicodeMidDot = '\u00b7';
/// The whitespace prefixing each line of output.
String _indent = '';
void indent() {
_indent = ' $_indent';
}
void unindent() {
_indent = _indent.substring(2);
}
/// Constants for ANSI color escape codes.
final _gray = _color('\u001b[1;30m');
final _green = _color('\u001b[32m');
final _none = _color('\u001b[0m');
final _bold = _color('\u001b[1m');
/// Prints [message] to stdout with each line correctly indented.
void log([message]) {
if (message == null) {
print('');
return;
}
print(_indent + message.toString().replaceAll('\n', '\n$_indent'));
}
/// Wraps [message] in gray ANSI escape codes if enabled.
String gray(message) => '$_gray$message$_none';
/// Wraps [message] in green ANSI escape codes if enabled.
String green(message) => '$_green$message$_none';
/// Wraps [message] in bold ANSI escape codes if enabled.
String bold(message) => '$_bold$message$_none';
/// Prints [chunks] to stdout, one chunk per line, with detailed information
/// about each chunk.
void dumpChunks(int start, List<Chunk> chunks) {
if (chunks.skip(start).isEmpty) return;
// Show the spans as vertical bands over their range (unless there are too
// many).
var spanSet = <Span>{};
void addSpans(List<Chunk> chunks) {
for (var chunk in chunks) {
spanSet.addAll(chunk.spans);
if (chunk is BlockChunk) addSpans(chunk.children);
}
}
addSpans(chunks);
var rows = <List<String>>[];
void addChunk(List<Chunk> chunks, String prefix, int index) {
var chunk = chunks[index];
if (chunk is BlockChunk) {
for (var j = 0; j < chunk.children.length; j++) {
addChunk(chunk.children, '$prefix$index.', j);
}
}
var row = <String>[];
row.add('$prefix$index:');
void writeIf(predicate, String Function() callback) {
if (predicate) {
row.add(callback());
} else {
row.add('');
}
}
var rule = chunk.rule;
writeIf(rule.cost != 0, () => '\$${rule.cost}');
var ruleString = rule.toString();
if (rule.isHardened) ruleString += '!';
row.add(ruleString);
var rules = chunks.map((chunk) => chunk.rule).toSet();
var constrainedRules = rule.constrainedRules.toSet().intersection(rules);
writeIf(
constrainedRules.isNotEmpty, () => "-> ${constrainedRules.join(" ")}");
var properties = [
if (chunk.flushLeft) 'fl',
if (chunk.isDouble) '2x',
if (chunk.spaceWhenUnsplit) 'sp',
if (chunk.canDivide) 'dv',
].join(' ');
row.add(properties);
writeIf(chunk.indent != 0, () => 'indent ${chunk.indent}');
writeIf(chunk.nesting.indent != 0, () => 'nest ${chunk.nesting}');
var spans = spanSet.toList();
if (spans.length <= 20) {
var spanBars = '';
for (var span in spans) {
if (chunk.spans.contains(span)) {
if (index == chunks.length - 1 ||
!chunks[index + 1].spans.contains(span)) {
// This is the last chunk with the span.
spanBars += '╙';
} else {
spanBars += '║';
}
} else {
// If the next chunk has this span, then show it bridging this chunk
// and the next because a split between them breaks the span.
if (index < chunks.length - 1 &&
chunks[index + 1].spans.contains(span)) {
if (span.cost == 1) {
spanBars += '╓';
} else {
spanBars += span.cost.toString();
}
}
}
}
row.add(spanBars);
}
row.add(chunk.spans.map((span) => span.id).join(' '));
if (chunk.text.length > 70) {
row.add(chunk.text.substring(0, 70));
} else {
row.add(chunk.text);
}
rows.add(row);
}
for (var i = start; i < chunks.length; i++) {
addChunk(chunks, '', i);
}
var rowWidths = List.filled(rows.first.length, 0);
for (var row in rows) {
for (var i = 0; i < row.length; i++) {
rowWidths[i] = math.max(rowWidths[i], row[i].length);
}
}
var buffer = StringBuffer();
for (var row in rows) {
for (var i = 0; i < row.length; i++) {
if (rowWidths[i] == 0) continue;
var cell = row[i].padRight(rowWidths[i]);
if (i != row.length - 1) cell = gray(cell);
buffer.write(cell);
buffer.write(' ');
}
buffer.writeln();
}
print(buffer.toString());
}
/// Shows all of the constraints between the rules used by [chunks].
void dumpConstraints(List<Chunk> chunks) {
var rules = chunks.map((chunk) => chunk.rule).toSet();
for (var rule in rules) {
var constrainedValues = <String>[];
for (var value = 0; value < rule.numValues; value++) {
var constraints = <String>[];
for (var other in rules) {
if (rule == other) continue;
var constraint = rule.constrain(value, other);
if (constraint != null) {
constraints.add('$other->$constraint');
}
}
if (constraints.isNotEmpty) {
constrainedValues.add("$value:(${constraints.join(' ')})");
}
}
log("$rule ${constrainedValues.join(' ')}");
}
}
/// Convert the line to a [String] representation.
///
/// It will determine how best to split it into multiple lines of output and
/// return a single string that may contain one or more newline characters.
void dumpLines(List<Chunk> chunks, SplitSet splits) {
var buffer = StringBuffer();
void writeChunksUnsplit(List<Chunk> chunks) {
for (var chunk in chunks) {
if (chunk.spaceWhenUnsplit) buffer.write(' ');
// Recurse into the block.
if (chunk is BlockChunk) writeChunksUnsplit(chunk.children);
buffer.write(chunk.text);
}
}
for (var i = 0; i < chunks.length; i++) {
var chunk = chunks[i];
if (splits.shouldSplitAt(i)) {
for (var j = 0; j < (chunk.isDouble ? 2 : 1); j++) {
buffer.writeln();
buffer.write(gray('| ' * (splits.getColumn(i) ~/ 2)));
}
} else if (chunk.spaceWhenUnsplit) {
buffer.write(' ');
}
if (chunk is BlockChunk && !splits.shouldSplitAt(i)) {
writeChunksUnsplit(chunk.children);
}
buffer.write(chunk.text);
}
log(buffer);
}
/// Build a string representation of the [piece] tree.
String pieceTree(Piece piece) {
var buffer = StringBuffer();
_PieceDebugTree(piece).write(buffer, 0);
return buffer.toString();
}
/// A stringified representation of a tree of pieces for debug output.
class _PieceDebugTree {
final String label;
final List<_PieceDebugTree> children = [];
_PieceDebugTree(Piece piece) : label = piece.toString() {
piece.forEachChild((child) {
children.add(_PieceDebugTree(child));
});
}
/// The approximate number of characters of output needed to print this tree
/// on a single line.
///
/// Used to determine when to show a tree's children inline or split. Note
/// that this is O(n^2), but we don't really care since it's only used for
/// debug output.
int get width {
var result = label.length;
for (var child in children) {
result += child.width;
}
return result;
}
void write(StringBuffer buffer, int indent) {
buffer.write(label);
if (children.isEmpty) return;
buffer.write('(');
// Split the tree if it is too long.
var isSplit = indent * 2 + width > 80;
if (isSplit) {
indent++;
buffer.writeln();
buffer.write(' ' * indent);
}
var first = true;
for (var child in children) {
if (!first) {
if (isSplit) {
buffer.writeln();
buffer.write(' ' * indent);
} else {
buffer.write(' ');
}
}
child.write(buffer, indent);
first = false;
}
if (isSplit) {
indent--;
buffer.writeln();
buffer.write(' ' * indent);
}
buffer.write(')');
}
}
String _color(String ansiEscape) => useAnsiColors ? ansiEscape : '';