OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library dart_style.src.line_splitting.solve_state; | 5 library dart_style.src.line_splitting.solve_state; |
6 | 6 |
7 import '../debug.dart' as debug; | 7 import '../debug.dart' as debug; |
8 import '../rule/rule.dart'; | 8 import '../rule/rule.dart'; |
| 9 import '../whitespace.dart'; |
9 import 'line_splitter.dart'; | 10 import 'line_splitter.dart'; |
10 import 'rule_set.dart'; | 11 import 'rule_set.dart'; |
11 | 12 |
12 /// A possibly incomplete solution in the line splitting search space. | 13 /// A possibly incomplete solution in the line splitting search space. |
13 /// | 14 /// |
14 /// A single [SolveState] binds some subset of the rules to values while | 15 /// 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 /// 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 /// 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, 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 /// 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 /// an unbound rule to split can't be used with that rule unsplit.) |
20 /// | 21 /// |
21 /// From a given solve state, we can explore the search tree to more refined | 22 /// 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 /// solve states by producing new ones that add more bound rules to the current |
23 /// state. | 24 /// state. |
24 class SolveState { | 25 class SolveState { |
25 final LineSplitter _splitter; | 26 final LineSplitter _splitter; |
26 final RuleSet _ruleValues; | 27 final RuleSet _ruleValues; |
27 | 28 |
| 29 /// The set of [Rule]s that are bound by [_ruleValues]. |
| 30 /// |
| 31 /// Cached by [_ensureConstraints] for use by [_ensureUnboundConstraints]. |
| 32 Set<Rule> _boundRules; |
| 33 |
| 34 /// The set of [Rule]s that are not bound by [_ruleValues]. |
| 35 /// |
| 36 /// Cached by [_ensureConstraints] for use by [_ensureUnboundConstraints]. |
| 37 Set<Rule> _unboundRules; |
| 38 |
28 /// The unbound rules in this state that can be bound to produce new more | 39 /// The unbound rules in this state that can be bound to produce new more |
29 /// refined states. | 40 /// refined states. |
30 /// | 41 /// |
31 /// Keeping this set small is the key to make the entire line splitter | 42 /// 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 | 43 /// perform well. If we consider too many rules at each state, our |
33 /// exploration of the solution space is too branchy and we waste time on | 44 /// exploration of the solution space is too branchy and we waste time on |
34 /// dead end solutions. | 45 /// dead end solutions. |
35 /// | 46 /// |
36 /// Here is the key insight. The line splitter treats any unbound rule as | 47 /// 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 | 48 /// being unsplit. This means refining a solution always means taking a rule |
38 /// that is unsplit and making it split. That monotonically increases the | 49 /// that is unsplit and making it split. That monotonically increases the |
39 /// cost, but may help fit the solution inside the page. | 50 /// cost, but may help fit the solution inside the page. |
40 /// | 51 /// |
41 /// We want to keep the cost low, so the only reason to consider making a | 52 /// 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 | 53 /// rule split is if it reduces an overflowing line. It's also the case that |
(...skipping 25 matching lines...) Expand all Loading... |
68 /// This is generally true but will be false if the state contains any | 79 /// 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. | 80 /// 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 | 81 /// This avoids picking a solution that leaves those rules at zero when they |
71 /// aren't allowed to be. | 82 /// aren't allowed to be. |
72 bool _isComplete = true; | 83 bool _isComplete = true; |
73 | 84 |
74 /// The constraints the bound rules in this state have on the remaining | 85 /// The constraints the bound rules in this state have on the remaining |
75 /// unbound rules. | 86 /// unbound rules. |
76 Map<Rule, int> _constraints; | 87 Map<Rule, int> _constraints; |
77 | 88 |
| 89 /// The unbound rule values that are disallowed because they would place |
| 90 /// invalid constraints on the currently bound values. |
| 91 /// |
| 92 /// For example, say rule A is bound to 1 and B is unbound. If B has value |
| 93 /// 1, it constrains A to be 1. Likewise, if B is 2, it constrains A to be |
| 94 /// 2 as well. Since we already know A is 1, that means we know B cannot be |
| 95 /// bound to value 2. This means B is more limited in this state than it |
| 96 /// might be in another state that binds A to a different value. |
| 97 /// |
| 98 /// It's important to track this, because we can't allow to states to overlap |
| 99 /// if one permits more values for some unbound rule than the other does. |
| 100 Map<Rule, Set<int>> _unboundConstraints; |
| 101 |
78 /// The bound rules that appear inside lines also containing unbound rules. | 102 /// The bound rules that appear inside lines also containing unbound rules. |
79 /// | 103 /// |
80 /// By appearing in the same line, it means these bound rules may affect the | 104 /// 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 | 105 /// results of binding those unbound rules. This is used to tell if two |
82 /// states may diverge by binding unbound rules or not. | 106 /// states may diverge by binding unbound rules or not. |
83 Set<Rule> _boundRulesInUnboundLines; | 107 Set<Rule> _boundRulesInUnboundLines; |
84 | 108 |
85 SolveState(this._splitter, this._ruleValues) { | 109 SolveState(this._splitter, this._ruleValues) { |
86 _calculateSplits(); | 110 _calculateSplits(); |
87 _calculateCost(); | 111 _calculateCost(); |
88 } | 112 } |
89 | 113 |
90 /// Gets the value to use for [rule], either the bound value or `0` if it | 114 /// Gets the value to use for [rule], either the bound value or |
91 /// isn't bound. | 115 /// [Rule.unsplit] if it isn't bound. |
92 int getValue(Rule rule) { | 116 int getValue(Rule rule) => _ruleValues.getValue(rule); |
93 if (rule is HardSplitRule) return 0; | |
94 | |
95 return _ruleValues.getValue(rule); | |
96 } | |
97 | 117 |
98 /// Returns `true` if this state is a better solution to use as the final | 118 /// Returns `true` if this state is a better solution to use as the final |
99 /// result than [other]. | 119 /// result than [other]. |
100 bool isBetterThan(SolveState other) { | 120 bool isBetterThan(SolveState other) { |
101 // If this state contains an unbound rule that we know can't be left | 121 // If this state contains an unbound rule that we know can't be left |
102 // unsplit, we can't pick this as a solution. | 122 // unsplit, we can't pick this as a solution. |
103 if (!_isComplete) return false; | 123 if (!_isComplete) return false; |
104 | 124 |
105 // Anything is better than nothing. | 125 // Anything is better than nothing. |
106 if (other == null) return true; | 126 if (other == null) return true; |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
205 // have, that rule would already be bound to some other value.) | 225 // have, that rule would already be bound to some other value.) |
206 if (!unsplitRules.tryBind(_splitter.rules, rule, 0, (_) {})) { | 226 if (!unsplitRules.tryBind(_splitter.rules, rule, 0, (_) {})) { |
207 break; | 227 break; |
208 } | 228 } |
209 } | 229 } |
210 } | 230 } |
211 } | 231 } |
212 | 232 |
213 /// Returns `true` if [other] overlaps this state. | 233 /// Returns `true` if [other] overlaps this state. |
214 bool _isOverlapping(SolveState other) { | 234 bool _isOverlapping(SolveState other) { |
215 _ensureOverlapFields(); | |
216 other._ensureOverlapFields(); | |
217 | |
218 // Lines that contain both bound and unbound rules must have the same | 235 // Lines that contain both bound and unbound rules must have the same |
219 // bound values. | 236 // bound values. |
| 237 _ensureBoundRulesInUnboundLines(); |
| 238 other._ensureBoundRulesInUnboundLines(); |
220 if (_boundRulesInUnboundLines.length != | 239 if (_boundRulesInUnboundLines.length != |
221 other._boundRulesInUnboundLines.length) { | 240 other._boundRulesInUnboundLines.length) { |
222 return false; | 241 return false; |
223 } | 242 } |
224 | 243 |
225 for (var rule in _boundRulesInUnboundLines) { | 244 for (var rule in _boundRulesInUnboundLines) { |
226 if (!other._boundRulesInUnboundLines.contains(rule)) return false; | 245 if (!other._boundRulesInUnboundLines.contains(rule)) return false; |
227 if (_ruleValues.getValue(rule) != other._ruleValues.getValue(rule)) { | 246 if (_ruleValues.getValue(rule) != other._ruleValues.getValue(rule)) { |
228 return false; | 247 return false; |
229 } | 248 } |
230 } | 249 } |
231 | 250 |
| 251 _ensureConstraints(); |
| 252 other._ensureConstraints(); |
232 if (_constraints.length != other._constraints.length) return false; | 253 if (_constraints.length != other._constraints.length) return false; |
233 | 254 |
234 for (var rule in _constraints.keys) { | 255 for (var rule in _constraints.keys) { |
235 if (_constraints[rule] != other._constraints[rule]) return false; | 256 if (_constraints[rule] != other._constraints[rule]) return false; |
236 } | 257 } |
237 | 258 |
| 259 _ensureUnboundConstraints(); |
| 260 other._ensureUnboundConstraints(); |
| 261 if (_unboundConstraints.length != other._unboundConstraints.length) { |
| 262 return false; |
| 263 } |
| 264 |
| 265 for (var rule in _unboundConstraints.keys) { |
| 266 var disallowed = _unboundConstraints[rule]; |
| 267 var otherDisallowed = other._unboundConstraints[rule]; |
| 268 |
| 269 if (disallowed.length != otherDisallowed.length) return false; |
| 270 for (var value in disallowed) { |
| 271 if (!otherDisallowed.contains(value)) return false; |
| 272 } |
| 273 } |
| 274 |
238 return true; | 275 return true; |
239 } | 276 } |
240 | 277 |
241 /// Calculates the [SplitSet] for this solve state, assuming any unbound | 278 /// Calculates the [SplitSet] for this solve state, assuming any unbound |
242 /// rules are set to zero. | 279 /// rules are set to zero. |
243 void _calculateSplits() { | 280 void _calculateSplits() { |
244 // Figure out which expression nesting levels got split and need to be | 281 // Figure out which expression nesting levels got split and need to be |
245 // assigned columns. | 282 // assigned columns. |
246 var usedNestingLevels = new Set(); | 283 var usedNestingLevels = new Set(); |
247 for (var i = 0; i < _splitter.chunks.length - 1; i++) { | 284 for (var i = 0; i < _splitter.chunks.length - 1; i++) { |
(...skipping 12 matching lines...) Expand all Loading... |
260 for (var i = 0; i < _splitter.chunks.length - 1; i++) { | 297 for (var i = 0; i < _splitter.chunks.length - 1; i++) { |
261 var chunk = _splitter.chunks[i]; | 298 var chunk = _splitter.chunks[i]; |
262 if (chunk.rule.isSplit(getValue(chunk.rule), chunk)) { | 299 if (chunk.rule.isSplit(getValue(chunk.rule), chunk)) { |
263 var indent = 0; | 300 var indent = 0; |
264 if (!chunk.flushLeftAfter) { | 301 if (!chunk.flushLeftAfter) { |
265 // Add in the chunk's indent. | 302 // Add in the chunk's indent. |
266 indent = _splitter.blockIndentation + chunk.indent; | 303 indent = _splitter.blockIndentation + chunk.indent; |
267 | 304 |
268 // And any expression nesting. | 305 // And any expression nesting. |
269 indent += chunk.nesting.totalUsedIndent; | 306 indent += chunk.nesting.totalUsedIndent; |
| 307 |
| 308 if (chunk.indentBlock(getValue)) indent += Indent.expression; |
270 } | 309 } |
271 | 310 |
272 _splits.add(i, indent); | 311 _splits.add(i, indent); |
273 } | 312 } |
274 } | 313 } |
275 } | 314 } |
276 | 315 |
277 /// Evaluates the cost (i.e. the relative "badness") of splitting the line | 316 /// Evaluates the cost (i.e. the relative "badness") of splitting the line |
278 /// into [lines] physical lines based on the current set of rules. | 317 /// into [lines] physical lines based on the current set of rules. |
279 void _calculateCost() { | 318 void _calculateCost() { |
280 assert(_splits != null); | 319 assert(_splits != null); |
281 | 320 |
282 // Calculate the length of each line and apply the cost of any spans that | 321 // Calculate the length of each line and apply the cost of any spans that |
283 // get split. | 322 // get split. |
284 var cost = 0; | 323 var cost = 0; |
285 _overflowChars = 0; | 324 _overflowChars = 0; |
286 | 325 |
287 var length = _splitter.firstLineIndent; | 326 var length = _splitter.firstLineIndent; |
288 | 327 |
289 // The unbound rules in use by the current line. This will be null after | 328 // The unbound rules in use by the current line. This will be null after |
290 // the first long line has completed. | 329 // the first long line has completed. |
291 var currentLineRules = []; | 330 var foundOverflowRules = false; |
| 331 var start = 0; |
292 | 332 |
293 endLine(int end) { | 333 endLine(int end) { |
294 // Track lines that went over the length. It is only rules contained in | 334 // Track lines that went over the length. It is only rules contained in |
295 // long lines that we may want to split. | 335 // long lines that we may want to split. |
296 if (length > _splitter.writer.pageWidth) { | 336 if (length > _splitter.writer.pageWidth) { |
297 _overflowChars += length - _splitter.writer.pageWidth; | 337 _overflowChars += length - _splitter.writer.pageWidth; |
298 | 338 |
299 // Only try rules that are in the first long line, since we know at | 339 // Only try rules that are in the first long line, since we know at |
300 // least one of them *will* be split. | 340 // least one of them *will* be split. |
301 if (currentLineRules != null && currentLineRules.isNotEmpty) { | 341 if (!foundOverflowRules) { |
302 _liveRules.addAll(currentLineRules); | 342 for (var i = start; i < end; i++) { |
303 currentLineRules = null; | 343 if (_addLiveRules(_splitter.chunks[i].rule)) { |
304 } | 344 foundOverflowRules = true; |
305 } else { | 345 } |
306 // The line fit, so don't keep track of its rules. | 346 } |
307 if (currentLineRules != null) { | |
308 currentLineRules.clear(); | |
309 } | 347 } |
310 } | 348 } |
| 349 |
| 350 start = end; |
311 } | 351 } |
312 | 352 |
313 // The set of spans that contain chunks that ended up splitting. We store | 353 // 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 | 354 // these in a set so a span's cost doesn't get double-counted if more than |
315 // one split occurs in it. | 355 // one split occurs in it. |
316 var splitSpans = new Set(); | 356 var splitSpans = new Set(); |
317 | 357 |
| 358 // The nesting level of the chunk that ended the previous line. |
| 359 var previousNesting; |
| 360 |
318 for (var i = 0; i < _splitter.chunks.length; i++) { | 361 for (var i = 0; i < _splitter.chunks.length; i++) { |
319 var chunk = _splitter.chunks[i]; | 362 var chunk = _splitter.chunks[i]; |
320 | 363 |
321 length += chunk.text.length; | 364 length += chunk.text.length; |
322 | 365 |
323 // Ignore the split after the last chunk. | 366 // Ignore the split after the last chunk. |
324 if (i == _splitter.chunks.length - 1) break; | 367 if (i == _splitter.chunks.length - 1) break; |
325 | 368 |
326 if (_splits.shouldSplitAt(i)) { | 369 if (_splits.shouldSplitAt(i)) { |
327 endLine(i); | 370 endLine(i); |
328 | 371 |
329 splitSpans.addAll(chunk.spans); | 372 splitSpans.addAll(chunk.spans); |
330 | 373 |
331 // Include the cost of the nested block. | 374 // Include the cost of the nested block. |
332 if (chunk.blockChunks.isNotEmpty) { | 375 if (chunk.isBlock) { |
333 cost += | 376 cost += |
334 _splitter.writer.formatBlock(chunk, _splits.getColumn(i)).cost; | 377 _splitter.writer.formatBlock(chunk, _splits.getColumn(i)).cost; |
335 } | 378 } |
336 | 379 |
| 380 // Do not allow sequential lines to have the same indentation but for |
| 381 // different reasons. In other words, don't allow different expressions |
| 382 // to claim the same nesting level on subsequent lines. |
| 383 // |
| 384 // A contrived example would be: |
| 385 // |
| 386 // function(inner( |
| 387 // argument), second( |
| 388 // another); |
| 389 // |
| 390 // For the most part, we prevent this by the constraints on splits. |
| 391 // For example, the above can't happen because the split before |
| 392 // "argument", forces the split before "second". |
| 393 // |
| 394 // But there are a couple of squirrely cases where it's hard to prevent |
| 395 // by construction. Instead, this outlaws it by penalizing it very |
| 396 // heavily if it happens to get this far. |
| 397 if (previousNesting != null && |
| 398 chunk.nesting.totalUsedIndent != 0 && |
| 399 chunk.nesting.totalUsedIndent == previousNesting.totalUsedIndent && |
| 400 !identical(chunk.nesting, previousNesting)) { |
| 401 _overflowChars += 10000; |
| 402 } |
| 403 |
| 404 previousNesting = chunk.nesting; |
| 405 |
337 // Start the new line. | 406 // Start the new line. |
338 length = _splits.getColumn(i); | 407 length = _splits.getColumn(i); |
339 } else { | 408 } else { |
340 if (chunk.spaceWhenUnsplit) length++; | 409 if (chunk.spaceWhenUnsplit) length++; |
341 | 410 |
342 // Include the nested block inline, if any. | 411 // Include the nested block inline, if any. |
343 length += chunk.unsplitBlockLength; | 412 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 } | 413 } |
355 } | 414 } |
356 | 415 |
357 // Add the costs for the rules that split. | 416 // Add the costs for the rules that have any splits. |
358 _ruleValues.forEach(_splitter.rules, (rule, value) { | 417 _ruleValues.forEach(_splitter.rules, (rule, value) { |
359 // A rule may be bound to zero if another rule constrains it to not split. | 418 if (value != Rule.unsplit) cost += rule.cost; |
360 if (value != 0) cost += rule.cost; | |
361 }); | 419 }); |
362 | 420 |
363 // Add the costs for the spans containing splits. | 421 // Add the costs for the spans containing splits. |
364 for (var span in splitSpans) cost += span.cost; | 422 for (var span in splitSpans) cost += span.cost; |
365 | 423 |
366 // Finish the last line. | 424 // Finish the last line. |
367 endLine(_splitter.chunks.length); | 425 endLine(_splitter.chunks.length); |
368 | 426 |
369 _splits.setCost(cost); | 427 _splits.setCost(cost); |
370 } | 428 } |
371 | 429 |
372 /// Lazily initializes the fields needed to compare two states for overlap. | 430 /// Adds [rule] and all of the rules it constrains to the set of [_liveRules]. |
| 431 /// |
| 432 /// Only does this if [rule] is a valid soft rule. Returns `true` if any new |
| 433 /// live rules were added. |
| 434 bool _addLiveRules(Rule rule) { |
| 435 if (rule == null) return false; |
| 436 |
| 437 var added = false; |
| 438 for (var constrained in rule.allConstrainedRules) { |
| 439 if (_ruleValues.contains(constrained)) continue; |
| 440 |
| 441 _liveRules.add(constrained); |
| 442 added = true; |
| 443 } |
| 444 |
| 445 return added; |
| 446 } |
| 447 |
| 448 /// Lazily initializes the [_boundInUnboundLines], which is needed to compare |
| 449 /// two states for overlap. |
373 /// | 450 /// |
374 /// We do this lazily because the calculation is a bit slow, and is only | 451 /// 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. | 452 /// needed when we have two states with the same score. |
376 void _ensureOverlapFields() { | 453 void _ensureBoundRulesInUnboundLines() { |
377 if (_constraints != null) return; | 454 if (_boundRulesInUnboundLines != null) return; |
378 | 455 |
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(); | 456 _boundRulesInUnboundLines = new Set(); |
413 | 457 |
414 var boundInLine = new Set(); | 458 var boundInLine = new Set(); |
415 var hasUnbound = false; | 459 var hasUnbound = false; |
416 | 460 |
417 for (var i = 0; i < _splitter.chunks.length - 1; i++) { | 461 for (var i = 0; i < _splitter.chunks.length - 1; i++) { |
418 if (splits.shouldSplitAt(i)) { | 462 if (splits.shouldSplitAt(i)) { |
419 if (hasUnbound) _boundRulesInUnboundLines.addAll(boundInLine); | 463 if (hasUnbound) _boundRulesInUnboundLines.addAll(boundInLine); |
420 | 464 |
421 boundInLine.clear(); | 465 boundInLine.clear(); |
422 hasUnbound = false; | 466 hasUnbound = false; |
423 } | 467 } |
424 | 468 |
425 var rule = _splitter.chunks[i].rule; | 469 var rule = _splitter.chunks[i].rule; |
426 if (rule != null && rule is! HardSplitRule) { | 470 if (rule != null) { |
427 if (_ruleValues.contains(rule)) { | 471 if (_ruleValues.contains(rule)) { |
428 boundInLine.add(rule); | 472 boundInLine.add(rule); |
429 } else { | 473 } else { |
430 hasUnbound = true; | 474 hasUnbound = true; |
431 } | 475 } |
432 } | 476 } |
433 } | 477 } |
434 | 478 |
435 if (hasUnbound) _boundRulesInUnboundLines.addAll(boundInLine); | 479 if (hasUnbound) _boundRulesInUnboundLines.addAll(boundInLine); |
436 } | 480 } |
437 | 481 |
| 482 /// Lazily initializes the [_constraints], which is needed to compare two |
| 483 /// states for overlap. |
| 484 /// |
| 485 /// We do this lazily because the calculation is a bit slow, and is only |
| 486 /// needed when we have two states with the same score. |
| 487 void _ensureConstraints() { |
| 488 if (_constraints != null) return; |
| 489 |
| 490 _unboundRules = new Set(); |
| 491 _boundRules = new Set(); |
| 492 |
| 493 for (var rule in _splitter.rules) { |
| 494 if (_ruleValues.contains(rule)) { |
| 495 _boundRules.add(rule); |
| 496 } else { |
| 497 _unboundRules.add(rule); |
| 498 } |
| 499 } |
| 500 |
| 501 _constraints = {}; |
| 502 |
| 503 for (var bound in _boundRules) { |
| 504 for (var unbound in bound.constrainedRules) { |
| 505 if (!_unboundRules.contains(unbound)) continue; |
| 506 |
| 507 var value = _ruleValues.getValue(bound); |
| 508 var constraint = bound.constrain(value, unbound); |
| 509 if (constraint != null) { |
| 510 _constraints[unbound] = constraint; |
| 511 } |
| 512 } |
| 513 } |
| 514 } |
| 515 |
| 516 /// Lazily initializes the [_unboundConstraints], which is needed to compare |
| 517 /// two states for overlap. |
| 518 /// |
| 519 /// We do this lazily because the calculation is a bit slow, and is only |
| 520 /// needed when we have two states with the same score. |
| 521 void _ensureUnboundConstraints() { |
| 522 if (_unboundConstraints != null) return; |
| 523 |
| 524 // _ensureConstraints should be called first which initializes these. |
| 525 assert(_boundRules != null); |
| 526 assert(_unboundRules != null); |
| 527 |
| 528 _unboundConstraints = {}; |
| 529 |
| 530 for (var unbound in _unboundRules) { |
| 531 var disallowedValues; |
| 532 |
| 533 for (var bound in unbound.constrainedRules) { |
| 534 if (!_boundRules.contains(bound)) continue; |
| 535 |
| 536 var boundValue = _ruleValues.getValue(bound); |
| 537 |
| 538 for (var value = 0; value < unbound.numValues; value++) { |
| 539 var constraint = unbound.constrain(value, bound); |
| 540 |
| 541 // If the unbound rule doesn't place any constraint on this bound |
| 542 // rule, we're fine. |
| 543 if (constraint == null) continue; |
| 544 |
| 545 // If the bound rule's value already meets the constraint it applies, |
| 546 // we don't need to track it. This way, two states that have the |
| 547 // same bound value, one of which has a satisfied constraint, are |
| 548 // still allowed to overlap. |
| 549 if (constraint == boundValue) continue; |
| 550 if (constraint == Rule.mustSplit && boundValue != Rule.unsplit) { |
| 551 continue; |
| 552 } |
| 553 |
| 554 if (disallowedValues == null) { |
| 555 disallowedValues = new Set(); |
| 556 _unboundConstraints[unbound] = disallowedValues; |
| 557 } |
| 558 |
| 559 disallowedValues.add(value); |
| 560 } |
| 561 } |
| 562 } |
| 563 } |
| 564 |
438 String toString() { | 565 String toString() { |
439 var buffer = new StringBuffer(); | 566 var buffer = new StringBuffer(); |
440 | 567 |
441 buffer.writeAll(_splitter.rules.map((rule) { | 568 buffer.writeAll(_splitter.rules.map((rule) { |
442 var valueLength = "${rule.fullySplitValue}".length; | 569 var valueLength = "${rule.fullySplitValue}".length; |
443 | 570 |
444 var value = "?"; | 571 var value = "?"; |
445 if (_ruleValues.contains(rule)) { | 572 if (_ruleValues.contains(rule)) { |
446 value = "${_ruleValues.getValue(rule)}"; | 573 value = "${_ruleValues.getValue(rule)}"; |
447 } | 574 } |
(...skipping 10 matching lines...) Expand all Loading... |
458 | 585 |
459 buffer.write(" \$${splits.cost}"); | 586 buffer.write(" \$${splits.cost}"); |
460 | 587 |
461 if (overflowChars > 0) buffer.write(" (${overflowChars} over)"); | 588 if (overflowChars > 0) buffer.write(" (${overflowChars} over)"); |
462 if (!_isComplete) buffer.write(" (incomplete)"); | 589 if (!_isComplete) buffer.write(" (incomplete)"); |
463 if (splits == null) buffer.write(" invalid"); | 590 if (splits == null) buffer.write(" invalid"); |
464 | 591 |
465 return buffer.toString(); | 592 return buffer.toString(); |
466 } | 593 } |
467 } | 594 } |
OLD | NEW |