Chromium Code Reviews| 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 'line_splitter.dart'; | 9 import 'line_splitter.dart'; |
| 10 import 'rule_set.dart'; | 10 import 'rule_set.dart'; |
| 11 | 11 |
| 12 /// A possibly incomplete solution in the line splitting search space. | 12 /// A possibly incomplete solution in the line splitting search space. |
| 13 /// | 13 /// |
| 14 /// A single [SolveState] binds some subset of the rules to values while | 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 | 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 | 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 | 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 | 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.) | 19 /// an unbound rule to split can't be used with that rule unsplit.) |
| 20 /// | 20 /// |
| 21 /// From a given solve state, we can explore the search tree to more refined | 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 | 22 /// solve states by producing new ones that add more bound rules to the current |
| 23 /// state. | 23 /// state. |
| 24 class SolveState { | 24 class SolveState { |
| 25 final LineSplitter _splitter; | 25 final LineSplitter _splitter; |
| 26 final RuleSet _ruleValues; | 26 final RuleSet _ruleValues; |
| 27 | 27 |
| 28 /// The set of [Rule]s that are bound by [_ruleValues]. | |
| 29 /// | |
| 30 /// Cached by [_ensureConstraints] for use by [_ensureUnboundConstraints]. | |
| 31 Set<Rule> _boundRules; | |
| 32 | |
| 33 /// The set of [Rule]s that are not bound by [_ruleValues]. | |
| 34 /// | |
| 35 /// Cached by [_ensureConstraints] for use by [_ensureUnboundConstraints]. | |
| 36 Set<Rule> _unboundRules; | |
| 37 | |
| 28 /// The unbound rules in this state that can be bound to produce new more | 38 /// The unbound rules in this state that can be bound to produce new more |
| 29 /// refined states. | 39 /// refined states. |
| 30 /// | 40 /// |
| 31 /// Keeping this set small is the key to make the entire line splitter | 41 /// Keeping this set small is the key to make the entire line splitter |
| 32 /// perform well. If we consider too many rules at each state, our | 42 /// 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 | 43 /// exploration of the solution space is too branchy and we waste time on |
| 34 /// dead end solutions. | 44 /// dead end solutions. |
| 35 /// | 45 /// |
| 36 /// Here is the key insight. The line splitter treats any unbound rule as | 46 /// 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 | 47 /// being unsplit. This means refining a solution always means taking a rule |
| (...skipping 204 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 242 } | 252 } |
| 243 | 253 |
| 244 _ensureConstraints(); | 254 _ensureConstraints(); |
| 245 other._ensureConstraints(); | 255 other._ensureConstraints(); |
| 246 if (_constraints.length != other._constraints.length) return false; | 256 if (_constraints.length != other._constraints.length) return false; |
| 247 | 257 |
| 248 for (var rule in _constraints.keys) { | 258 for (var rule in _constraints.keys) { |
| 249 if (_constraints[rule] != other._constraints[rule]) return false; | 259 if (_constraints[rule] != other._constraints[rule]) return false; |
| 250 } | 260 } |
| 251 | 261 |
| 262 _ensureUnboundConstraints(); | |
| 263 other._ensureUnboundConstraints(); | |
| 252 if (_unboundConstraints.length != other._unboundConstraints.length) { | 264 if (_unboundConstraints.length != other._unboundConstraints.length) { |
| 253 return false; | 265 return false; |
| 254 } | 266 } |
| 255 | 267 |
| 256 for (var rule in _unboundConstraints.keys) { | 268 for (var rule in _unboundConstraints.keys) { |
| 257 var disallowed = _unboundConstraints[rule]; | 269 var disallowed = _unboundConstraints[rule]; |
| 258 var otherDisallowed = other._unboundConstraints[rule]; | 270 var otherDisallowed = other._unboundConstraints[rule]; |
| 259 | 271 |
| 260 if (disallowed.length != otherDisallowed.length) return false; | 272 if (disallowed.length != otherDisallowed.length) return false; |
| 261 for (var value in disallowed) { | 273 for (var value in disallowed) { |
| (...skipping 158 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 420 /// Adds [rule] and all of the rules it constrains to the set of [_liveRules]. | 432 /// Adds [rule] and all of the rules it constrains to the set of [_liveRules]. |
| 421 /// | 433 /// |
| 422 /// Only does this if [rule] is a valid soft rule. Returns `true` if any new | 434 /// Only does this if [rule] is a valid soft rule. Returns `true` if any new |
| 423 /// live rules were added. | 435 /// live rules were added. |
| 424 bool _addLiveRules(Rule rule) { | 436 bool _addLiveRules(Rule rule) { |
| 425 if (rule == null) return false; | 437 if (rule == null) return false; |
| 426 if (rule is HardSplitRule) return false; | 438 if (rule is HardSplitRule) return false; |
| 427 | 439 |
| 428 var added = false; | 440 var added = false; |
| 429 for (var constrained in rule.allConstrainedRules) { | 441 for (var constrained in rule.allConstrainedRules) { |
| 430 // The rule may constrain some other rule that was already hardened and | |
| 431 // discarded. In that case, we can ignore it. | |
| 432 if (constrained.index == null) continue; | |
| 433 | |
| 434 if (_ruleValues.contains(constrained)) continue; | 442 if (_ruleValues.contains(constrained)) continue; |
| 435 | 443 |
| 436 _liveRules.add(constrained); | 444 _liveRules.add(constrained); |
| 437 added = true; | 445 added = true; |
| 438 } | 446 } |
| 439 | 447 |
| 440 return added; | 448 return added; |
| 441 } | 449 } |
| 442 | 450 |
| 443 /// Lazily initializes the [_boundInUnboundLines], which is needed to compare | 451 /// Lazily initializes the [_boundInUnboundLines], which is needed to compare |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 475 } | 483 } |
| 476 | 484 |
| 477 /// Lazily initializes the [_constraints], which is needed to compare two | 485 /// Lazily initializes the [_constraints], which is needed to compare two |
| 478 /// states for overlap. | 486 /// states for overlap. |
| 479 /// | 487 /// |
| 480 /// We do this lazily because the calculation is a bit slow, and is only | 488 /// We do this lazily because the calculation is a bit slow, and is only |
| 481 /// needed when we have two states with the same score. | 489 /// needed when we have two states with the same score. |
| 482 void _ensureConstraints() { | 490 void _ensureConstraints() { |
| 483 if (_constraints != null) return; | 491 if (_constraints != null) return; |
| 484 | 492 |
| 485 var unboundRules = []; | 493 _unboundRules = new Set(); |
| 486 var boundRules = []; | 494 _boundRules = new Set(); |
| 487 | 495 |
| 488 for (var rule in _splitter.rules) { | 496 for (var rule in _splitter.rules) { |
| 489 if (_ruleValues.contains(rule)) { | 497 if (_ruleValues.contains(rule)) { |
| 490 boundRules.add(rule); | 498 _boundRules.add(rule); |
| 491 } else { | 499 } else { |
| 492 unboundRules.add(rule); | 500 _unboundRules.add(rule); |
| 493 } | 501 } |
| 494 } | 502 } |
| 495 | 503 |
| 496 _constraints = {}; | 504 _constraints = {}; |
|
kevmoo
2015/11/09 23:40:46
QQ: does order matter here? Would using HashMap fr
Bob Nystrom
2015/11/10 20:08:24
Nope. No measurable improvement.
| |
| 497 | 505 |
| 498 for (var unbound in unboundRules) { | 506 for (var bound in _boundRules) { |
| 499 for (var bound in boundRules) { | 507 for (var unbound in bound.constrainedRules) { |
| 508 if (!_unboundRules.contains(unbound)) continue; | |
| 509 | |
| 500 var value = _ruleValues.getValue(bound); | 510 var value = _ruleValues.getValue(bound); |
| 501 var constraint = bound.constrain(value, unbound); | 511 var constraint = bound.constrain(value, unbound); |
| 502 if (constraint != null) { | 512 if (constraint != null) { |
| 503 _constraints[unbound] = constraint; | 513 _constraints[unbound] = constraint; |
| 504 } | 514 } |
| 505 } | 515 } |
| 506 } | 516 } |
| 517 } | |
| 518 | |
| 519 /// Lazily initializes the [_unboundConstraints], which is needed to compare | |
| 520 /// two states for overlap. | |
| 521 /// | |
| 522 /// We do this lazily because the calculation is a bit slow, and is only | |
| 523 /// needed when we have two states with the same score. | |
| 524 void _ensureUnboundConstraints() { | |
| 525 if (_unboundConstraints != null) return; | |
| 526 | |
| 527 // _ensureConstraints should be called first which initializes these. | |
| 528 assert(_boundRules != null); | |
| 529 assert(_unboundRules != null); | |
| 507 | 530 |
| 508 _unboundConstraints = {}; | 531 _unboundConstraints = {}; |
|
kevmoo
2015/11/09 23:40:46
ditto RE HashMap
Bob Nystrom
2015/11/10 20:08:24
Acknowledged.
| |
| 509 | 532 |
| 510 for (var unbound in unboundRules) { | 533 for (var unbound in _unboundRules) { |
| 511 var disallowedValues; | 534 var disallowedValues; |
| 512 | 535 |
| 513 for (var value = 0; value < unbound.numValues; value++) { | 536 for (var bound in unbound.constrainedRules) { |
| 514 for (var j = 0; j < boundRules.length; j++) { | 537 if (!_boundRules.contains(bound)) continue; |
| 515 var bound = boundRules[j]; | 538 |
| 539 var boundValue = _ruleValues.getValue(bound); | |
| 540 | |
| 541 for (var value = 0; value < unbound.numValues; value++) { | |
| 516 var constraint = unbound.constrain(value, bound); | 542 var constraint = unbound.constrain(value, bound); |
| 517 | 543 |
| 518 // If the unbound rule doesn't place any constraint on this bound | 544 // If the unbound rule doesn't place any constraint on this bound |
| 519 // rule, we're fine. | 545 // rule, we're fine. |
| 520 if (constraint == null) continue; | 546 if (constraint == null) continue; |
| 521 | 547 |
| 522 // If the bound rule's value already meets the constraint it applies, | 548 // If the bound rule's value already meets the constraint it applies, |
| 523 // we don't need to track it. This way, two states that have the | 549 // we don't need to track it. This way, two states that have the |
| 524 // same bound value, one of which has a satisfied constraint, are | 550 // same bound value, one of which has a satisfied constraint, are |
| 525 // still allowed to overlap. | 551 // still allowed to overlap. |
| 526 var boundValue = _ruleValues.getValue(bound); | |
| 527 if (constraint == boundValue) continue; | 552 if (constraint == boundValue) continue; |
| 528 if (constraint == -1 && boundValue != 0) continue; | 553 if (constraint == -1 && boundValue != 0) continue; |
| 529 | 554 |
| 530 if (disallowedValues == null) { | 555 if (disallowedValues == null) { |
| 531 disallowedValues = new Set(); | 556 disallowedValues = new Set(); |
| 532 _unboundConstraints[unbound] = disallowedValues; | 557 _unboundConstraints[unbound] = disallowedValues; |
| 533 } | 558 } |
| 534 | 559 |
| 535 disallowedValues.add(value); | 560 disallowedValues.add(value); |
| 536 } | 561 } |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 561 | 586 |
| 562 buffer.write(" \$${splits.cost}"); | 587 buffer.write(" \$${splits.cost}"); |
| 563 | 588 |
| 564 if (overflowChars > 0) buffer.write(" (${overflowChars} over)"); | 589 if (overflowChars > 0) buffer.write(" (${overflowChars} over)"); |
| 565 if (!_isComplete) buffer.write(" (incomplete)"); | 590 if (!_isComplete) buffer.write(" (incomplete)"); |
| 566 if (splits == null) buffer.write(" invalid"); | 591 if (splits == null) buffer.write(" invalid"); |
| 567 | 592 |
| 568 return buffer.toString(); | 593 return buffer.toString(); |
| 569 } | 594 } |
| 570 } | 595 } |
| OLD | NEW |