blob: 567e4421edacc8cc5fe78cbf3af8ae49095485c1 [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.
import 'dart:math' as math;
import 'chunk.dart';
import 'line_splitting/rule_set.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;
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.isBlock) addSpans(chunk.block.chunks);
}
}
addSpans(chunks);
var spans = spanSet.toList();
var rules = chunks.map((chunk) => chunk.rule).toSet();
var rows = <List<String>>[];
void addChunk(List<Chunk> chunks, String prefix, int index) {
var row = <String>[];
row.add('$prefix$index:');
var chunk = chunks[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 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}');
if (spans.length <= 20) {
var spanBars = '';
for (var span in spans) {
if (chunk.spans.contains(span)) {
if (index == 0 || !chunks[index - 1].spans.contains(span)) {
if (span.cost == 1) {
spanBars += '╓';
} else {
spanBars += span.cost.toString();
}
} else {
spanBars += '║';
}
} else {
if (index > 0 && chunks[index - 1].spans.contains(span)) {
spanBars += '╙';
} else {
spanBars += ' ';
}
}
}
row.add(spanBars);
}
if (chunk.text.length > 70) {
row.add(chunk.text.substring(0, 70));
} else {
row.add(chunk.text);
}
rows.add(row);
if (chunk.isBlock) {
for (var j = 0; j < chunk.block.chunks.length; j++) {
addChunk(chunk.block.chunks, '$prefix$index.', j);
}
}
}
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(' ');
buffer.write(chunk.text);
// Recurse into the block.
if (chunk.isBlock) writeChunksUnsplit(chunk.block.chunks);
}
}
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(' ');
}
buffer.write(chunk.text);
if (chunk.isBlock && !splits.shouldSplitAt(i)) {
writeChunksUnsplit(chunk.block.chunks);
}
}
log(buffer);
}
String _color(String ansiEscape) => useAnsiColors ? ansiEscape : '';