OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 library dart_style.src.line_splitting.solve_state; | |
6 | |
7 import '../debug.dart' as debug; | |
8 import '../rule/rule.dart'; | |
9 import 'line_splitter.dart'; | |
10 import 'rule_set.dart'; | |
11 | |
12 /// A possibly incomplete solution in the line splitting search space. | |
13 /// | |
14 /// A single [SolveState] binds some subset of the rules to values while | |
15 /// leaving the rest unbound. If every rule is bound, the solve state describes | |
16 /// a complete solution to the line splitting problem. Even if rules are | |
17 /// unbound, a state can also usually be used as a solution by treating all | |
18 /// unbound rules as unsplit. (The usually is because a state that constrains | |
19 /// an unbound rule to split can't be used with that rule unsplit.) | |
20 /// | |
21 /// From a given solve state, we can explore the search tree to more refined | |
22 /// solve states by producing new ones that add more bound rules to the current | |
23 /// state. | |
24 class SolveState { | |
25 final LineSplitter _splitter; | |
26 final RuleSet _ruleValues; | |
27 | |
28 /// The unbound rules in this state that can be bound to produce new more | |
29 /// refined states. | |
30 /// | |
31 /// Keeping this set small is the key to make the entire line splitter | |
32 /// perform well. If we consider too make rules at each state, our | |
33 /// exploration of the solution space is too branchy and we waste time on | |
34 /// dead end solutions. | |
35 /// | |
36 /// Here is the key insight. The line splitter treats any unbound rule as | |
37 /// being unsplit. This means refining a solution always means taking a rule | |
38 /// that is unsplit and making it split. That monotonically increases the | |
39 /// cost, but may help fit the solution inside the page. | |
40 /// | |
41 /// We want to keep the cost low, so the only reason to consider making a | |
42 /// rule split is if it reduces an overflowing line. It's also the case that | |
43 /// splitting an earlier rule will often reshuffle the rest of the line. | |
44 /// | |
45 /// Taking that into account, the only rules we consider binding to extend a | |
46 /// solve state are *unbound rules inside the first line that is overflowing*. | |
47 /// Even if a line has dozens of rules, this generally keeps the branching | |
48 /// down to a few. It also means rules inside lines that already fit are | |
49 /// never touched. | |
50 /// | |
51 /// There is one other set of rules that go in here. Sometimes a bound rule | |
52 /// in the solve state constrains some other unbound rule to split. In that | |
53 /// case, we also consider that active so we know to not leave it at zero. | |
54 final _liveRules = new Set<Rule>(); | |
55 | |
56 /// The set of splits chosen for this state. | |
57 SplitSet get splits => _splits; | |
58 SplitSet _splits; | |
59 | |
60 /// The number of characters that do not fit inside the page with this set of | |
61 /// splits. | |
62 int get overflowChars => _overflowChars; | |
63 int _overflowChars; | |
64 | |
65 /// Whether we can treat this state as a complete solution by leaving its | |
66 /// unbound rules unsplit. | |
67 /// | |
68 /// This is generally true but will be false if the state contains any | |
69 /// unbound rules that are constrained to not be zero by other bound rules. | |
70 /// This avoids picking a solution that leaves those rules at zero when they | |
71 /// aren't allowed to be. | |
72 bool _isComplete = true; | |
73 | |
74 /// The constraints the bound rules in this state have on the remaining | |
75 /// unbound rules. | |
76 Map<Rule, int> _constraints; | |
77 | |
78 /// The bound rules that appear inside lines also containing unbound rules. | |
79 /// | |
80 /// By appearing in the same line, it means these bound rules may affect the | |
81 /// results of binding those unbound rules. This is used to tell if two | |
82 /// states may diverge by binding unbound rules or not. | |
83 Set<Rule> _boundRulesInUnboundLines; | |
84 | |
85 SolveState(this._splitter, this._ruleValues) { | |
86 _calculateSplits(); | |
87 _calculateCost(); | |
88 } | |
89 | |
90 /// Gets the value to use for [rule], either the bound value or `0` if it | |
91 /// isn't bound. | |
92 int getValue(Rule rule) { | |
93 if (rule is HardSplitRule) return 0; | |
94 | |
95 return _ruleValues.getValue(rule); | |
96 } | |
97 | |
98 /// Returns `true` if this state is a better solution to use as the final | |
99 /// result than [other]. | |
100 bool isBetterThan(SolveState other) { | |
101 // If this state contains an unbound rule that we know can't be left | |
102 // unsplit, we can't pick this as a solution. | |
103 if (!_isComplete) return false; | |
104 | |
105 // Anything is better than nothing. | |
106 if (other == null) return true; | |
107 | |
108 // Prefer the solution that fits the most in the page. | |
109 if (overflowChars != other.overflowChars) { | |
110 return overflowChars < other.overflowChars; | |
111 } | |
112 | |
113 // Otherwise, prefer the best cost. | |
114 return splits.cost < other.splits.cost; | |
115 } | |
116 | |
117 /// Determines if this state "overlaps" [other]. | |
118 /// | |
119 /// Two states overlap if they currently have the same score and we can tell | |
120 /// for certain that they won't diverge as their unbound rules are bound. If | |
121 /// that's the case, then whichever state is better now (based on their | |
122 /// currently bound rule values) is the one that will always win, regardless | |
123 /// of how they get expanded. | |
124 /// | |
125 /// In other words, their entire expanded solution trees also overlap. In | |
126 /// that case, there's no point in expanding both, so we can just pick the | |
127 /// winner now and discard the other. | |
128 /// | |
129 /// For this to be true, we need to prove that binding an unbound rule won't | |
130 /// affect one state differently from the other. We have to show that they | |
131 /// are parallel. | |
132 /// | |
133 /// Two things could cause this *not* to be the case. | |
134 /// | |
135 /// 1. If one state's bound rules place different constraints on the unbound | |
136 /// rules than the other. | |
137 /// | |
138 /// 2. If one state's different bound rules are in the same line as an | |
139 /// unbound rule. That affects the indentation and length of the line, | |
140 /// which affects the context where the unbound rule is being chosen. | |
141 /// | |
142 /// If neither of these is the case, the states overlap. Returns `<0` if this | |
143 /// state is better, or `>0` if [other] wins. If the states do not overlap, | |
144 /// returns `0`. | |
145 int compareOverlap(SolveState other) { | |
146 if (!_isOverlapping(other)) return 0; | |
147 | |
148 // They do overlap, so see which one wins. | |
149 for (var rule in _splitter.rules) { | |
150 var value = _ruleValues.getValue(rule); | |
151 var otherValue = other._ruleValues.getValue(rule); | |
152 | |
153 if (value != otherValue) return value.compareTo(otherValue); | |
154 } | |
155 | |
156 // The way SolveStates are expanded should guarantee that we never generate | |
157 // the exact same state twice. Getting here implies that that failed. | |
158 throw "unreachable"; | |
159 } | |
160 | |
161 /// Enqueues more solve states to consider based on this one. | |
162 /// | |
163 /// For each unbound rule in this state that occurred in the first long line, | |
164 /// enqueue solve states that bind that rule to each value it can have and | |
165 /// bind all previous rules to zero. (In other words, try all subsolutions | |
166 /// where that rule becomes the first new rule to split at.) | |
167 void expand() { | |
168 var unsplitRules = _ruleValues.clone(); | |
169 | |
170 // Walk down the rules looking for unbound ones to try. | |
171 var triedRules = 0; | |
172 for (var rule in _splitter.rules) { | |
173 if (_liveRules.contains(rule)) { | |
174 // We found one worth trying, so try all of its values. | |
175 for (var value = 1; value < rule.numValues; value++) { | |
176 var boundRules = unsplitRules.clone(); | |
177 | |
178 var mustSplitRules; | |
179 var valid = boundRules.tryBind(_splitter.rules, rule, value, (rule) { | |
180 if (mustSplitRules == null) mustSplitRules = []; | |
181 mustSplitRules.add(rule); | |
182 }); | |
183 | |
184 // Make sure we don't violate the constraints of the bound rules. | |
185 if (!valid) continue; | |
186 | |
187 var state = new SolveState(_splitter, boundRules); | |
188 | |
189 // If some unbound rules are constrained to split, remember that. | |
190 if (mustSplitRules != null) { | |
191 state._isComplete = false; | |
192 state._liveRules.addAll(mustSplitRules); | |
193 } | |
194 | |
195 _splitter.enqueue(state); | |
196 } | |
197 | |
198 // Stop once we've tried all of the ones we can. | |
199 if (++triedRules == _liveRules.length) break; | |
200 } | |
201 | |
202 // Fill in previous unbound rules with zero. | |
203 if (!_ruleValues.contains(rule)) { | |
204 // Pass a dummy callback because zero will never fail. (If it would | |
205 // have, that rule would already be bound to some other value.) | |
206 if (!unsplitRules.tryBind(_splitter.rules, rule, 0, (_) {})) { | |
207 break; | |
208 } | |
209 } | |
210 } | |
211 } | |
212 | |
213 /// Returns `true` if [other] overlaps this state. | |
214 bool _isOverlapping(SolveState other) { | |
215 _ensureOverlapFields(); | |
216 other._ensureOverlapFields(); | |
217 | |
218 // Lines that contain both bound and unbound rules must have the same | |
219 // bound values. | |
220 if (_boundRulesInUnboundLines.length != | |
221 other._boundRulesInUnboundLines.length) { | |
222 return false; | |
223 } | |
224 | |
225 for (var rule in _boundRulesInUnboundLines) { | |
226 if (!other._boundRulesInUnboundLines.contains(rule)) return false; | |
227 if (_ruleValues.getValue(rule) != other._ruleValues.getValue(rule)) { | |
228 return false; | |
229 } | |
230 } | |
231 | |
232 if (_constraints.length != other._constraints.length) return false; | |
233 | |
234 for (var rule in _constraints.keys) { | |
235 if (_constraints[rule] != other._constraints[rule]) return false; | |
236 } | |
237 | |
238 return true; | |
239 } | |
240 | |
241 /// Calculates the [SplitSet] for this solve state, assuming any unbound | |
242 /// rules are set to zero. | |
243 void _calculateSplits() { | |
244 // Figure out which expression nesting levels got split and need to be | |
245 // assigned columns. | |
246 var usedNestingLevels = new Set(); | |
247 for (var i = 0; i < _splitter.chunks.length - 1; i++) { | |
248 var chunk = _splitter.chunks[i]; | |
249 if (chunk.rule.isSplit(getValue(chunk.rule), chunk)) { | |
250 usedNestingLevels.add(chunk.nesting); | |
251 chunk.nesting.clearTotalUsedIndent(); | |
252 } | |
253 } | |
254 | |
255 for (var nesting in usedNestingLevels) { | |
256 nesting.refreshTotalUsedIndent(usedNestingLevels); | |
257 } | |
258 | |
259 _splits = new SplitSet(_splitter.chunks.length); | |
260 for (var i = 0; i < _splitter.chunks.length - 1; i++) { | |
261 var chunk = _splitter.chunks[i]; | |
262 if (chunk.rule.isSplit(getValue(chunk.rule), chunk)) { | |
263 var indent = 0; | |
264 if (!chunk.flushLeftAfter) { | |
265 // Add in the chunk's indent. | |
266 indent = _splitter.blockIndentation + chunk.indent; | |
267 | |
268 // And any expression nesting. | |
269 indent += chunk.nesting.totalUsedIndent; | |
270 } | |
271 | |
272 _splits.add(i, indent); | |
273 } | |
274 } | |
275 } | |
276 | |
277 /// Evaluates the cost (i.e. the relative "badness") of splitting the line | |
278 /// into [lines] physical lines based on the current set of rules. | |
279 void _calculateCost() { | |
280 assert(_splits != null); | |
281 | |
282 // Calculate the length of each line and apply the cost of any spans that | |
283 // get split. | |
284 var cost = 0; | |
285 _overflowChars = 0; | |
286 | |
287 var length = _splitter.firstLineIndent; | |
288 | |
289 // The unbound rules in use by the current line. This will be null after | |
290 // the first long line has completed. | |
291 var currentLineRules = []; | |
292 | |
293 endLine(int end) { | |
294 // Track lines that went over the length. It is only rules contained in | |
295 // long lines that we may want to split. | |
296 if (length > _splitter.writer.pageWidth) { | |
297 _overflowChars += length - _splitter.writer.pageWidth; | |
298 | |
299 // Only try rules that are in the first long line, since we know at | |
300 // least one of them *will* be split. | |
301 if (currentLineRules != null && currentLineRules.isNotEmpty) { | |
302 _liveRules.addAll(currentLineRules); | |
303 currentLineRules = null; | |
304 } | |
305 } else { | |
306 // The line fit, so don't keep track of its rules. | |
307 if (currentLineRules != null) { | |
308 currentLineRules.clear(); | |
309 } | |
310 } | |
311 } | |
312 | |
313 // The set of spans that contain chunks that ended up splitting. We store | |
314 // these in a set so a span's cost doesn't get double-counted if more than | |
315 // one split occurs in it. | |
316 var splitSpans = new Set(); | |
317 | |
318 for (var i = 0; i < _splitter.chunks.length; i++) { | |
319 var chunk = _splitter.chunks[i]; | |
320 | |
321 length += chunk.text.length; | |
322 | |
323 // Ignore the split after the last chunk. | |
324 if (i == _splitter.chunks.length - 1) break; | |
325 | |
326 if (_splits.shouldSplitAt(i)) { | |
327 endLine(i); | |
328 | |
329 splitSpans.addAll(chunk.spans); | |
330 | |
331 // Include the cost of the nested block. | |
332 if (chunk.blockChunks.isNotEmpty) { | |
333 cost += | |
334 _splitter.writer.formatBlock(chunk, _splits.getColumn(i)).cost; | |
335 } | |
336 | |
337 // Start the new line. | |
338 length = _splits.getColumn(i); | |
339 } else { | |
340 if (chunk.spaceWhenUnsplit) length++; | |
341 | |
342 // Include the nested block inline, if any. | |
343 length += chunk.unsplitBlockLength; | |
344 | |
345 // If we might be in the first overly long line, keep track of any | |
346 // unbound rules we encounter. These are ones that we'll want to try to | |
347 // bind to shorten the long line. | |
348 if (currentLineRules != null && | |
349 chunk.rule != null && | |
350 !chunk.isHardSplit && | |
351 !_ruleValues.contains(chunk.rule)) { | |
352 currentLineRules.add(chunk.rule); | |
353 } | |
354 } | |
355 } | |
356 | |
357 // Add the costs for the rules that split. | |
358 _ruleValues.forEach(_splitter.rules, (rule, value) { | |
359 // A rule may be bound to zero if another rule constrains it to not split. | |
360 if (value != 0) cost += rule.cost; | |
361 }); | |
362 | |
363 // Add the costs for the spans containing splits. | |
364 for (var span in splitSpans) cost += span.cost; | |
365 | |
366 // Finish the last line. | |
367 endLine(_splitter.chunks.length); | |
368 | |
369 _splits.setCost(cost); | |
370 } | |
371 | |
372 /// Lazily initializes the fields needed to compare two states for overlap. | |
373 /// | |
374 /// We do this lazily because the calculation is a bit slow, and is only | |
375 /// needed when we have two states with the same score. | |
376 void _ensureOverlapFields() { | |
377 if (_constraints != null) return; | |
378 | |
379 _calculateConstraints(); | |
380 _calculateBoundRulesInUnboundLines(); | |
381 } | |
382 | |
383 /// Initializes [_constraints] with any constraints the bound rules place on | |
384 /// the unbound rules. | |
385 void _calculateConstraints() { | |
386 _constraints = {}; | |
387 | |
388 var unboundRules = []; | |
389 var boundRules = []; | |
390 | |
391 for (var rule in _splitter.rules) { | |
392 if (_ruleValues.contains(rule)) { | |
393 boundRules.add(rule); | |
394 } else { | |
395 unboundRules.add(rule); | |
396 } | |
397 } | |
398 | |
399 for (var bound in boundRules) { | |
400 var value = _ruleValues.getValue(bound); | |
401 | |
402 for (var unbound in unboundRules) { | |
403 var constraint = bound.constrain(value, unbound); | |
404 if (constraint != null) { | |
405 _constraints[unbound] = constraint; | |
406 } | |
407 } | |
408 } | |
409 } | |
410 | |
411 void _calculateBoundRulesInUnboundLines() { | |
412 _boundRulesInUnboundLines = new Set(); | |
413 | |
414 var boundInLine = new Set(); | |
415 var hasUnbound = false; | |
416 | |
417 for (var i = 0; i < _splitter.chunks.length - 1; i++) { | |
418 if (splits.shouldSplitAt(i)) { | |
419 if (hasUnbound) _boundRulesInUnboundLines.addAll(boundInLine); | |
420 | |
421 boundInLine.clear(); | |
422 hasUnbound = false; | |
423 } | |
424 | |
425 var rule = _splitter.chunks[i].rule; | |
426 if (rule != null && rule is! HardSplitRule) { | |
427 if (_ruleValues.contains(rule)) { | |
428 boundInLine.add(rule); | |
429 } else { | |
430 hasUnbound = true; | |
431 } | |
432 } | |
433 } | |
434 | |
435 if (hasUnbound) _boundRulesInUnboundLines.addAll(boundInLine); | |
436 } | |
437 | |
438 String toString() { | |
439 var buffer = new StringBuffer(); | |
440 | |
441 buffer.writeAll(_splitter.rules.map((rule) { | |
442 var valueLength = "${rule.fullySplitValue}".length; | |
443 | |
444 var value = "?"; | |
445 if (_ruleValues.contains(rule)) { | |
446 value = "${_ruleValues.getValue(rule)}"; | |
447 } | |
448 | |
449 value = value.padLeft(valueLength); | |
450 if (_liveRules.contains(rule)) { | |
451 value = debug.bold(value); | |
452 } else { | |
453 value = debug.gray(value); | |
454 } | |
455 | |
456 return value; | |
457 }), " "); | |
458 | |
459 buffer.write(" \$${splits.cost}"); | |
460 | |
461 if (overflowChars > 0) buffer.write(" (${overflowChars} over)"); | |
462 if (!_isComplete) buffer.write(" (incomplete)"); | |
463 if (splits == null) buffer.write(" invalid"); | |
464 | |
465 return buffer.toString(); | |
466 } | |
467 } | |
OLD | NEW |