Skip to content

Commit

Permalink
Better debug diagnostics in the solver. (#1624)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
munificent authored Dec 16, 2024
1 parent e9bd213 commit 67ba681
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 5 deletions.
3 changes: 3 additions & 0 deletions example/format.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ void main(List<String> args) {
debug.useAnsiColors = true;
debug.tracePieceBuilder = true;
debug.traceSolver = true;
debug.traceSolverEnqueing = true;
debug.traceSolverDequeing = true;
debug.traceSolverShowCode = true;

_formatStmt('''
1 + 2;
Expand Down
53 changes: 51 additions & 2 deletions lib/src/back_end/code.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import '../source_code.dart';
/// as fast simply appending a single [GroupCode] to the parent solution's
/// [GroupCode].
sealed class Code {
/// Traverse the [Code] tree and generate a string for debugging purposes.
String toDebugString() {
/// Traverse the [Code] tree and generate a string showing the [Code] tree's
/// structure for debugging purposes.
String toCodeTree() {
var buffer = StringBuffer();
var prefix = '';

Expand Down Expand Up @@ -55,6 +56,14 @@ sealed class Code {

return buffer.toString();
}

/// Write the [Code] to a string of output code, ignoring selection and
/// format on/off markers.
String toDebugString() {
var builder = _DebugStringBuilder();
builder.traverse(this);
return builder.finish();
}
}

/// A [Code] object which can be written to and contain other child [Code]
Expand Down Expand Up @@ -376,3 +385,43 @@ final class _StringBuilder {
selectionLength: selectionLength);
}
}

/// Traverses a [Code] tree and produces a string of output code, ignoring
/// selection and format on/off markers.
///
/// This is a simpler version of [_StringBuilder] that doesn't require having
/// access to the original [SourceCode] and line ending.
final class _DebugStringBuilder {
final StringBuffer _buffer = StringBuffer();

/// How many spaces of indentation should be written before the next text.
int _indent = 0;

void traverse(Code code) {
switch (code) {
case _NewlineCode():
_buffer.writeln();
if (code._blank) _buffer.writeln();
_indent = code._indent;

case _TextCode():
// Write any pending indentation.
_buffer.write(' ' * _indent);
_indent = 0;
_buffer.write(code._text);

case GroupCode():
_indent = code._indent;
for (var i = 0; i < code._children.length; i++) {
traverse(code._children[i]);
}

case _MarkerCode():
case _EnableFormattingCode():
// The debug output doesn't support disabled formatting or selections.
break;
}
}

String finish() => _buffer.toString();
}
24 changes: 21 additions & 3 deletions lib/src/back_end/solver.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,14 @@ final class Solver {
_queue.add(solution);
Profile.end('Solver enqueue');

if (debug.traceSolverEnqueing) {
debug.log(debug.bold('Enqueue initial $solution'));
if (debug.traceSolverShowCode) {
debug.log(solution.code.toDebugString());
debug.log('');
}
}

// The lowest cost solution found so far that does overflow.
var best = solution;

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

attempts++;

if (debug.traceSolver) {
if (debug.traceSolverDequeing) {
debug.log(debug.bold('Try #$attempts $solution'));
debug.log(solution.code.toDebugString());
debug.log('');
if (debug.traceSolverShowCode) {
debug.log(solution.code.toDebugString());
debug.log('');
}
}

if (solution.isValid) {
Expand All @@ -127,6 +137,14 @@ final class Solver {
pageWidth: _pageWidth, leadingIndent: _leadingIndent)) {
Profile.count('Solver enqueue');
_queue.add(expanded);

if (debug.traceSolverEnqueing) {
debug.log(debug.bold('Enqueue $expanded'));
if (debug.traceSolverShowCode) {
debug.log(expanded.code.toDebugString());
debug.log('');
}
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions lib/src/debug.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ bool tracePieceBuilder = false;
/// Set this to `true` to turn on diagnostic output while solving pieces.
bool traceSolver = false;

/// Set this to `true` to turn on diagnostic output when the solver enqueues a
/// potential solution.
bool traceSolverEnqueing = false;

/// Set this to `true` to turn on diagnostic output when the solver dequeues a
/// potential solution.
bool traceSolverDequeing = false;

/// Set this to `true` to show the formatted code for a given solution when the
/// solver it printing diagnostic output.
bool traceSolverShowCode = false;

bool useAnsiColors = false;

const unicodeSection = '\u00a7';
Expand Down

0 comments on commit 67ba681

Please sign in to comment.