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 |