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.rule.rule; | 5 library dart_style.src.rule.rule; |
6 | 6 |
7 import '../chunk.dart'; | 7 import '../chunk.dart'; |
8 import '../fast_hash.dart'; | 8 import '../fast_hash.dart'; |
9 | 9 |
10 /// A constraint that determines the different ways a related set of chunks may | 10 /// A constraint that determines the different ways a related set of chunks may |
11 /// be split. | 11 /// be split. |
12 abstract class Rule extends FastHash { | 12 class Rule extends FastHash { |
| 13 /// Rule value that splits no chunks. |
| 14 /// |
| 15 /// Every rule is required to treat this value as fully unsplit. |
| 16 static const unsplit = 0; |
| 17 |
| 18 /// Rule constraint value that means "any value as long as something splits". |
| 19 /// |
| 20 /// It disallows [unsplit] but allows any other value. |
| 21 static const mustSplit = -1; |
| 22 |
13 /// The number of different states this rule can be in. | 23 /// The number of different states this rule can be in. |
14 /// | 24 /// |
15 /// Each state determines which set of chunks using this rule are split and | 25 /// Each state determines which set of chunks using this rule are split and |
16 /// which aren't. Values range from zero to one minus this. Value zero | 26 /// which aren't. Values range from zero to one minus this. Value zero |
17 /// always means "no chunks are split" and increasing values by convention | 27 /// always means "no chunks are split" and increasing values by convention |
18 /// mean increasingly undesirable splits. | 28 /// mean increasingly undesirable splits. |
19 int get numValues; | 29 /// |
| 30 /// By default, a rule has two values: fully unsplit and fully split. |
| 31 int get numValues => 2; |
20 | 32 |
21 /// The rule value that forces this rule into its maximally split state. | 33 /// The rule value that forces this rule into its maximally split state. |
22 /// | 34 /// |
23 /// By convention, this is the highest of the range of allowed values. | 35 /// By convention, this is the highest of the range of allowed values. |
24 int get fullySplitValue => numValues - 1; | 36 int get fullySplitValue => numValues - 1; |
25 | 37 |
26 int get cost => Cost.normal; | 38 final int cost; |
27 | 39 |
28 /// During line splitting [LineSplitter] sets this to the index of this | 40 /// During line splitting [LineSplitter] sets this to the index of this |
29 /// rule in its list of rules. | 41 /// rule in its list of rules. |
30 int index; | 42 int index; |
31 | 43 |
| 44 /// If `true`, the rule has been "hardened" meaning it's been placed into a |
| 45 /// permanent "must fully split" state. |
| 46 bool get isHardened => _isHardened; |
| 47 bool _isHardened = false; |
| 48 |
32 /// The other [Rule]s that "surround" this one (and care about that fact). | 49 /// The other [Rule]s that "surround" this one (and care about that fact). |
33 /// | 50 /// |
34 /// In many cases, if a split occurs inside an expression, surrounding rules | 51 /// In many cases, if a split occurs inside an expression, surrounding rules |
35 /// also want to split too. For example, a split in the middle of an argument | 52 /// also want to split too. For example, a split in the middle of an argument |
36 /// forces the entire argument list to also split. | 53 /// forces the entire argument list to also split. |
37 /// | 54 /// |
38 /// This tracks those relationships. If this rule splits, (sets its value to | 55 /// This tracks those relationships. If this rule splits, (sets its value to |
39 /// [fullySplitValue]) then all of the outer rules will also be set to their | 56 /// [fullySplitValue]) then all of the outer rules will also be set to their |
40 /// fully split value. | 57 /// fully split value. |
41 /// | 58 /// |
42 /// This contains all direct as well as transitive relationships. If A | 59 /// This contains all direct as well as transitive relationships. If A |
43 /// contains B which contains C, C's outerRules contains both B and A. | 60 /// contains B which contains C, C's outerRules contains both B and A. |
44 Iterable<Rule> get outerRules => _outerRules; | |
45 final Set<Rule> _outerRules = new Set<Rule>(); | 61 final Set<Rule> _outerRules = new Set<Rule>(); |
46 | 62 |
47 /// Adds [inner] as an inner rule of this rule if it cares about inner rules. | 63 /// Adds [inner] as an inner rule of this rule if it cares about inner rules. |
48 /// | 64 /// |
49 /// When an inner rule splits, it forces any surrounding outer rules to also | 65 /// When an inner rule splits, it forces any surrounding outer rules to also |
50 /// split. | 66 /// split. |
51 void contain(Rule inner) { | 67 void contain(Rule inner) { |
52 if (!splitsOnInnerRules) return; | 68 if (!splitsOnInnerRules) return; |
53 inner._outerRules.add(this); | 69 inner._outerRules.add(this); |
54 } | 70 } |
55 | 71 |
56 /// Whether this rule cares about rules that it contains. | 72 /// Whether this rule cares about rules that it contains. |
57 /// | 73 /// |
58 /// If `true` then inner rules will constrain this one and force it to split | 74 /// If `true` then inner rules will constrain this one and force it to split |
59 /// when they split. Otherwise, it can split independently of any contained | 75 /// when they split. Otherwise, it can split independently of any contained |
60 /// rules. | 76 /// rules. |
61 bool get splitsOnInnerRules => true; | 77 bool get splitsOnInnerRules => true; |
62 | 78 |
63 bool isSplit(int value, Chunk chunk); | 79 Rule([int cost]) : cost = cost ?? Cost.normal; |
| 80 |
| 81 /// Creates a new rule that is already fully split. |
| 82 Rule.hard() : cost = 0 { |
| 83 // Set the cost to zero since it will always be applied, so there's no |
| 84 // point in penalizing it. |
| 85 // |
| 86 // Also, this avoids doubled counting in literal blocks where there is both |
| 87 // a split in the outer chunk containing the block and the inner hard split |
| 88 // between the elements or statements. |
| 89 harden(); |
| 90 } |
| 91 |
| 92 /// Fixes this rule into a "fully split" state. |
| 93 void harden() { |
| 94 _isHardened = true; |
| 95 } |
| 96 |
| 97 /// Returns `true` if [chunk] should split when this rule has [value]. |
| 98 bool isSplit(int value, Chunk chunk) { |
| 99 if (_isHardened) return true; |
| 100 |
| 101 if (value == Rule.unsplit) return false; |
| 102 |
| 103 // Let the subclass decide. |
| 104 return isSplitAtValue(value, chunk); |
| 105 } |
| 106 |
| 107 /// Subclasses can override this to determine which values split which chunks. |
| 108 /// |
| 109 /// By default, this assumes every chunk splits. |
| 110 bool isSplitAtValue(value, chunk) => true; |
64 | 111 |
65 /// Given that this rule has [value], determine if [other]'s value should be | 112 /// Given that this rule has [value], determine if [other]'s value should be |
66 /// constrained. | 113 /// constrained. |
67 /// | 114 /// |
68 /// Allows relationships between rules like "if I split, then this should | 115 /// Allows relationships between rules like "if I split, then this should |
69 /// split too". Returns a non-negative value to force [other] to take that | 116 /// split too". Returns a non-negative value to force [other] to take that |
70 /// value. Returns -1 to allow [other] to take any non-zero value. Returns | 117 /// value. Returns -1 to allow [other] to take any non-zero value. Returns |
71 /// null to not constrain other. | 118 /// null to not constrain other. |
72 int constrain(int value, Rule other) { | 119 int constrain(int value, Rule other) { |
73 // By default, any implied rule will be fully split if this one is fully | 120 // By default, any containing rule will be fully split if this one is split. |
74 // split. | 121 if (value == Rule.unsplit) return null; |
75 if (value == 0) return null; | |
76 if (_outerRules.contains(other)) return other.fullySplitValue; | 122 if (_outerRules.contains(other)) return other.fullySplitValue; |
77 | 123 |
78 return null; | 124 return null; |
79 } | 125 } |
80 | 126 |
| 127 /// A protected method for subclasses to add the rules that they constrain |
| 128 /// to [rules]. |
| 129 /// |
| 130 /// Called by [Rule] the first time [constrainedRules] is accessed. |
| 131 void addConstrainedRules(Set<Rule> rules) {} |
| 132 |
| 133 /// Discards constraints on any rule that doesn't have an index. |
| 134 /// |
| 135 /// This is called by [LineSplitter] after it has indexed all of the in-use |
| 136 /// rules. A rule may end up with a constraint on a rule that's no longer |
| 137 /// used by any chunk. This can happen if the rule gets hardened, or if it |
| 138 /// simply never got used by a chunk. For example, a rule for splitting an |
| 139 /// empty list of metadata annotations. |
| 140 /// |
| 141 /// This removes all of those. |
| 142 void forgetUnusedRules() { |
| 143 _outerRules.retainWhere((rule) => rule.index != null); |
| 144 |
| 145 // Clear the cached ones too. |
| 146 _constrainedRules = null; |
| 147 _allConstrainedRules = null; |
| 148 } |
| 149 |
| 150 /// The other [Rule]s that this rule places immediate constraints on. |
| 151 Set<Rule> get constrainedRules { |
| 152 // Lazy initialize this on first use. Note: Assumes this is only called |
| 153 // after the chunks have been written and any constraints have been wired |
| 154 // up. |
| 155 if (_constrainedRules == null) { |
| 156 _constrainedRules = _outerRules.toSet(); |
| 157 addConstrainedRules(_constrainedRules); |
| 158 } |
| 159 |
| 160 return _constrainedRules; |
| 161 } |
| 162 |
| 163 Set<Rule> _constrainedRules; |
| 164 |
| 165 /// The transitive closure of all of the rules this rule places constraints |
| 166 /// on, directly or indirectly, including itself. |
| 167 Set<Rule> get allConstrainedRules { |
| 168 if (_allConstrainedRules == null) { |
| 169 visit(Rule rule) { |
| 170 if (_allConstrainedRules.contains(rule)) return; |
| 171 |
| 172 _allConstrainedRules.add(rule); |
| 173 rule.constrainedRules.forEach(visit); |
| 174 } |
| 175 |
| 176 _allConstrainedRules = new Set(); |
| 177 visit(this); |
| 178 } |
| 179 |
| 180 return _allConstrainedRules; |
| 181 } |
| 182 |
| 183 Set<Rule> _allConstrainedRules; |
| 184 |
81 String toString() => "$id"; | 185 String toString() => "$id"; |
82 } | 186 } |
83 | |
84 /// A rule that always splits a chunk. | |
85 class HardSplitRule extends Rule { | |
86 int get numValues => 1; | |
87 | |
88 /// It's always going to be applied, so there's no point in penalizing it. | |
89 /// | |
90 /// Also, this avoids doubled counting in literal blocks where there is both | |
91 /// a split in the outer chunk containing the block and the inner hard split | |
92 /// between the elements or statements. | |
93 int get cost => 0; | |
94 | |
95 /// It's always split anyway. | |
96 bool get splitsOnInnerRules => false; | |
97 | |
98 bool isSplit(int value, Chunk chunk) => true; | |
99 | |
100 String toString() => "Hard"; | |
101 } | |
102 | |
103 /// A basic rule that has two states: unsplit or split. | |
104 class SimpleRule extends Rule { | |
105 /// Two values: 0 is unsplit, 1 is split. | |
106 int get numValues => 2; | |
107 | |
108 final int cost; | |
109 | |
110 final bool splitsOnInnerRules; | |
111 | |
112 SimpleRule({int cost, bool splitsOnInnerRules}) | |
113 : cost = cost != null ? cost : Cost.normal, | |
114 splitsOnInnerRules = | |
115 splitsOnInnerRules != null ? splitsOnInnerRules : true; | |
116 | |
117 bool isSplit(int value, Chunk chunk) => value == 1; | |
118 | |
119 String toString() => "Simple${super.toString()}"; | |
120 } | |
OLD | NEW |