Index: dart_style/lib/src/line_splitting/line_splitter.dart |
diff --git a/dart_style/lib/src/line_splitting/line_splitter.dart b/dart_style/lib/src/line_splitting/line_splitter.dart |
deleted file mode 100644 |
index f562ca40dc699dc45f96e404f1b4ae7c2b66d207..0000000000000000000000000000000000000000 |
--- a/dart_style/lib/src/line_splitting/line_splitter.dart |
+++ /dev/null |
@@ -1,199 +0,0 @@ |
-// Copyright (c) 2015, 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. |
- |
-library dart_style.src.line_splitting.line_splitter; |
- |
-import '../chunk.dart'; |
-import '../debug.dart' as debug; |
-import '../line_writer.dart'; |
-import '../rule/rule.dart'; |
-import 'rule_set.dart'; |
-import 'solve_state.dart'; |
-import 'solve_state_queue.dart'; |
- |
-/// To ensure the solver doesn't go totally pathological on giant code, we cap |
-/// it at a fixed number of attempts. |
-/// |
-/// If the optimal solution isn't found after this many tries, it just uses the |
-/// best it found so far. |
-const _maxAttempts = 5000; |
- |
-/// Takes a set of chunks and determines the best values for its rules in order |
-/// to fit it inside the page boundary. |
-/// |
-/// This problem is exponential in the number of rules and a single expression |
-/// in Dart can be quite large, so it isn't feasible to brute force this. For |
-/// example: |
-/// |
-/// outer( |
-/// fn(1 + 2, 3 + 4, 5 + 6, 7 + 8), |
-/// fn(1 + 2, 3 + 4, 5 + 6, 7 + 8), |
-/// fn(1 + 2, 3 + 4, 5 + 6, 7 + 8), |
-/// fn(1 + 2, 3 + 4, 5 + 6, 7 + 8)); |
-/// |
-/// There are 509,607,936 ways this can be split. |
-/// |
-/// The problem is even harder because we may not be able to easily tell if a |
-/// given solution is the best one. It's possible that there is *no* solution |
-/// that fits in the page (due to long strings or identifiers) so the winning |
-/// solution may still have overflow characters. This makes it hard to know |
-/// when we are done and can stop looking. |
-/// |
-/// There are a couple of pieces of domain knowledge we use to cope with this: |
-/// |
-/// - Changing a rule from unsplit to split will never lower its cost. A |
-/// solution with all rules unsplit will always be the one with the lowest |
-/// cost (zero). Conversely, setting all of its rules to the maximum split |
-/// value will always have the highest cost. |
-/// |
-/// (You might think there is a converse rule about overflow characters. The |
-/// solution with the fewest splits will have the most overflow, and the |
-/// solution with the most splits will have the least overflow. Alas, because |
-/// of indentation, that isn't always the case. Adding a split may *increase* |
-/// overflow in some cases.) |
-/// |
-/// - If all of the chunks for a rule are inside lines that already fit in the |
-/// page, then splitting that rule will never improve the solution. |
-/// |
-/// - If two partial solutions have the same cost and the bound rules don't |
-/// affect any of the remaining unbound rules, then whichever partial |
-/// solution is currently better will always be the winner regardless of what |
-/// the remaining unbound rules are bound to. |
-/// |
-/// We start off with a [SolveState] where all rules are unbound (which |
-/// implicitly treats them as unsplit). For a given solve state, we can produce |
-/// a set of expanded states that takes some of the rules in the first long |
-/// line and bind them to split values. This always produces new solve states |
-/// with higher cost (but often fewer overflow characters) than the parent |
-/// state. |
-/// |
-/// We take these expanded states and add them to a work list sorted by cost. |
-/// Since unsplit rules always have lower cost solutions, we know that no state |
-/// we enqueue later will ever have a lower cost than the ones we already have |
-/// enqueued. |
-/// |
-/// Then we keep pulling states off the work list and expanding them and adding |
-/// the results back into the list. We do this until we hit a solution where |
-/// all characters fit in the page. The first one we find will have the lowest |
-/// cost and we're done. |
-/// |
-/// We also keep running track of the best solution we've found so far that |
-/// has the fewest overflow characters and the lowest cost. If no solution fits, |
-/// we'll use this one. |
-/// |
-/// When enqueing a solution, we can sometimes collapse it and a previously |
-/// queued one by preferring one or the other. If two solutions have the same |
-/// cost and we can prove that they won't diverge later as unbound rules are |
-/// set, we can pick the winner now and discard the other. This lets us avoid |
-/// redundantly exploring entire subtrees of the solution space. |
-/// |
-/// As a final escape hatch for pathologically nasty code, after trying some |
-/// fixed maximum number of solve states, we just bail and return the best |
-/// solution found so far. |
-/// |
-/// Even with the above algorithmic optimizations, complex code may still |
-/// require a lot of exploring to find an optimal solution. To make that fast, |
-/// this code is carefully profiled and optimized. If you modify this, make |
-/// sure to test against the benchmark to ensure you don't regress performance. |
-class LineSplitter { |
- final LineWriter writer; |
- |
- /// The list of chunks being split. |
- final List<Chunk> chunks; |
- |
- /// The set of soft rules whose values are being selected. |
- final List<Rule> rules; |
- |
- /// The number of characters of additional indentation to apply to each line. |
- /// |
- /// This is used when formatting blocks to get the output into the right |
- /// column based on where the block appears. |
- final int blockIndentation; |
- |
- /// The starting column of the first line. |
- final int firstLineIndent; |
- |
- /// The queue of solve states to explore further. |
- /// |
- /// This is sorted lowest-cost first. This ensures that as soon as we find a |
- /// solution that fits in the page, we know it will be the lowest cost one |
- /// and can stop looking. |
- final _queue = new SolveStateQueue(); |
- |
- /// The lowest cost solution found so far. |
- SolveState _bestSolution; |
- |
- /// Creates a new splitter for [_writer] that tries to fit [chunks] into the |
- /// page width. |
- LineSplitter(this.writer, List<Chunk> chunks, int blockIndentation, |
- int firstLineIndent, |
- {bool flushLeft: false}) |
- : chunks = chunks, |
- // Collect the set of soft rules that we need to select values for. |
- rules = chunks |
- .map((chunk) => chunk.rule) |
- .where((rule) => rule != null && rule is! HardSplitRule) |
- .toSet() |
- .toList(growable: false), |
- blockIndentation = blockIndentation, |
- firstLineIndent = flushLeft ? 0 : firstLineIndent + blockIndentation { |
- _queue.bindSplitter(this); |
- |
- // Store the rule's index in the rule so we can get from a chunk to a rule |
- // index quickly. |
- for (var i = 0; i < rules.length; i++) { |
- rules[i].index = i; |
- } |
- } |
- |
- /// Determine the best way to split the chunks into lines that fit in the |
- /// page, if possible. |
- /// |
- /// Returns a [SplitSet] that defines where each split occurs and the |
- /// indentation of each line. |
- /// |
- /// [firstLineIndent] is the number of characters of whitespace to prefix the |
- /// first line of output with. |
- SplitSet apply() { |
- // Start with a completely unbound, unsplit solution. |
- _queue.add(new SolveState(this, new RuleSet(rules.length))); |
- |
- var attempts = 0; |
- while (_queue.isNotEmpty) { |
- var state = _queue.removeFirst(); |
- |
- if (state.isBetterThan(_bestSolution)) { |
- _bestSolution = state; |
- |
- // Since we sort solutions by cost the first solution we find that |
- // fits is the winner. |
- if (_bestSolution.overflowChars == 0) break; |
- } |
- |
- if (debug.traceSplitter) { |
- var best = state == _bestSolution ? " (best)" : ""; |
- debug.log("$state$best"); |
- debug.dumpLines(chunks, firstLineIndent, state.splits); |
- debug.log(); |
- } |
- |
- if (attempts++ > _maxAttempts) break; |
- |
- // Try bumping the rule values for rules whose chunks are on long lines. |
- state.expand(); |
- } |
- |
- if (debug.traceSplitter) { |
- debug.log("$_bestSolution (winner)"); |
- debug.dumpLines(chunks, firstLineIndent, _bestSolution.splits); |
- debug.log(); |
- } |
- |
- return _bestSolution.splits; |
- } |
- |
- void enqueue(SolveState state) { |
- _queue.add(state); |
- } |
-} |