Skip to content

Commit 67ba681

Browse files
authored
Better debug diagnostics in the solver. (#1624)
The formatter supports a bunch of debug flags you can enable to see what the solver is doing as it works its magic. That output can be pretty verbose and chatty. When I added the Code intermediate representation, it got worse. Even more verbose, but less helpful because the Code tree doesn't really show you as much as just seeing the formatted output. This improves the debug output a few ways: - Instead of showing the Code tree (which is almost never useful), show the formatted result of that. This is a little tricky because the normal way we lower a Code tree to an output string requires access to the original SourceCode being formatted (in order to support `// dart format off`). The Solver doesn't have that. So instead, there's a new little debug-only visitor to lower Code to a string that doesn't handle format on/off markers and selections. In practice, 99% of my time debugging the solver doesn't have to do with format on/off or selections, so that's fine. - Add support for showing when a potential solution is enqueued versus dequeued. Often, when trying to figure out why a given solution doesn't win, it's useful to make sure it actually ended up in the queue at all, so showing enqueued solutions does that. - Add support for showing solutions without the code. Showing the code for each solution can be pretty verbose. Often I just want to see the solution itself (the states it chose for each piece) and don't care about the code. So this lets you toggle that independently. There are no user-facing changes in this PR.
1 parent e9bd213 commit 67ba681

File tree

4 files changed

+87
-5
lines changed

4 files changed

+87
-5
lines changed

example/format.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ void main(List<String> args) {
1515
debug.useAnsiColors = true;
1616
debug.tracePieceBuilder = true;
1717
debug.traceSolver = true;
18+
debug.traceSolverEnqueing = true;
19+
debug.traceSolverDequeing = true;
20+
debug.traceSolverShowCode = true;
1821

1922
_formatStmt('''
2023
1 + 2;

lib/src/back_end/code.dart

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import '../source_code.dart';
1616
/// as fast simply appending a single [GroupCode] to the parent solution's
1717
/// [GroupCode].
1818
sealed class Code {
19-
/// Traverse the [Code] tree and generate a string for debugging purposes.
20-
String toDebugString() {
19+
/// Traverse the [Code] tree and generate a string showing the [Code] tree's
20+
/// structure for debugging purposes.
21+
String toCodeTree() {
2122
var buffer = StringBuffer();
2223
var prefix = '';
2324

@@ -55,6 +56,14 @@ sealed class Code {
5556

5657
return buffer.toString();
5758
}
59+
60+
/// Write the [Code] to a string of output code, ignoring selection and
61+
/// format on/off markers.
62+
String toDebugString() {
63+
var builder = _DebugStringBuilder();
64+
builder.traverse(this);
65+
return builder.finish();
66+
}
5867
}
5968

6069
/// A [Code] object which can be written to and contain other child [Code]
@@ -376,3 +385,43 @@ final class _StringBuilder {
376385
selectionLength: selectionLength);
377386
}
378387
}
388+
389+
/// Traverses a [Code] tree and produces a string of output code, ignoring
390+
/// selection and format on/off markers.
391+
///
392+
/// This is a simpler version of [_StringBuilder] that doesn't require having
393+
/// access to the original [SourceCode] and line ending.
394+
final class _DebugStringBuilder {
395+
final StringBuffer _buffer = StringBuffer();
396+
397+
/// How many spaces of indentation should be written before the next text.
398+
int _indent = 0;
399+
400+
void traverse(Code code) {
401+
switch (code) {
402+
case _NewlineCode():
403+
_buffer.writeln();
404+
if (code._blank) _buffer.writeln();
405+
_indent = code._indent;
406+
407+
case _TextCode():
408+
// Write any pending indentation.
409+
_buffer.write(' ' * _indent);
410+
_indent = 0;
411+
_buffer.write(code._text);
412+
413+
case GroupCode():
414+
_indent = code._indent;
415+
for (var i = 0; i < code._children.length; i++) {
416+
traverse(code._children[i]);
417+
}
418+
419+
case _MarkerCode():
420+
case _EnableFormattingCode():
421+
// The debug output doesn't support disabled formatting or selections.
422+
break;
423+
}
424+
}
425+
426+
String finish() => _buffer.toString();
427+
}

lib/src/back_end/solver.dart

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ final class Solver {
9090
_queue.add(solution);
9191
Profile.end('Solver enqueue');
9292

93+
if (debug.traceSolverEnqueing) {
94+
debug.log(debug.bold('Enqueue initial $solution'));
95+
if (debug.traceSolverShowCode) {
96+
debug.log(solution.code.toDebugString());
97+
debug.log('');
98+
}
99+
}
100+
93101
// The lowest cost solution found so far that does overflow.
94102
var best = solution;
95103

@@ -101,10 +109,12 @@ final class Solver {
101109

102110
attempts++;
103111

104-
if (debug.traceSolver) {
112+
if (debug.traceSolverDequeing) {
105113
debug.log(debug.bold('Try #$attempts $solution'));
106-
debug.log(solution.code.toDebugString());
107-
debug.log('');
114+
if (debug.traceSolverShowCode) {
115+
debug.log(solution.code.toDebugString());
116+
debug.log('');
117+
}
108118
}
109119

110120
if (solution.isValid) {
@@ -127,6 +137,14 @@ final class Solver {
127137
pageWidth: _pageWidth, leadingIndent: _leadingIndent)) {
128138
Profile.count('Solver enqueue');
129139
_queue.add(expanded);
140+
141+
if (debug.traceSolverEnqueing) {
142+
debug.log(debug.bold('Enqueue $expanded'));
143+
if (debug.traceSolverShowCode) {
144+
debug.log(expanded.code.toDebugString());
145+
debug.log('');
146+
}
147+
}
130148
}
131149
}
132150

lib/src/debug.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ bool tracePieceBuilder = false;
2626
/// Set this to `true` to turn on diagnostic output while solving pieces.
2727
bool traceSolver = false;
2828

29+
/// Set this to `true` to turn on diagnostic output when the solver enqueues a
30+
/// potential solution.
31+
bool traceSolverEnqueing = false;
32+
33+
/// Set this to `true` to turn on diagnostic output when the solver dequeues a
34+
/// potential solution.
35+
bool traceSolverDequeing = false;
36+
37+
/// Set this to `true` to show the formatted code for a given solution when the
38+
/// solver it printing diagnostic output.
39+
bool traceSolverShowCode = false;
40+
2941
bool useAnsiColors = false;
3042

3143
const unicodeSection = '\u00a7';

0 commit comments

Comments
 (0)