Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(757)

Side by Side Diff: lib/src/nesting.dart

Issue 1255643002: New, simpler and faster line splitter. (Closed) Base URL: https://github.com/dart-lang/dart_style.git@master
Patch Set: Optimize nesting. Reformat. Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/src/line_writer.dart ('k') | lib/src/nesting_builder.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2014, 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.nesting;
6
7 /// A single level of expression nesting.
8 ///
9 /// When a line is split in the middle of an expression, this tracks the
10 /// context of where in the expression that split occurs. It ensures that the
11 /// [LineSplitter] obeys the expression nesting when deciding what column to
12 /// start lines at when split inside an expression.
13 ///
14 /// Each instance of this represents a single level of expression nesting. If we
15 /// split at to chunks with different levels of nesting, the splitter ensures
16 /// they each get assigned to different columns.
17 ///
18 /// In addition, each level has an indent. This is the number of spaces it is
19 /// indented relative to the outer expression. It's almost always
20 /// [Indent.expression], but cascades are special magic snowflakes and use
21 /// [Indent.cascade].
22 class NestingLevel {
23 /// The nesting level surrounding this one, or `null` if this is represents
24 /// top level code in a block.
25 NestingLevel get parent => _parent;
26 NestingLevel _parent;
27
28 /// The number of characters that this nesting level is indented relative to
29 /// the containing level.
30 ///
31 /// Normally, this is [Indent.expression], but cascades use [Indent.cascade].
32 final int indent;
33
34 /// The number of nesting levels surrounding this one.
35 int get depth {
36 var result = 0;
37 var nesting = this;
38 while (nesting != null) {
39 result++;
40 nesting = nesting.parent;
41 }
42
43 return result - 1;
44 }
45
46 NestingLevel() : indent = 0;
47
48 NestingLevel._(this._parent, this.indent);
49
50 /// Creates a new deeper level of nesting indented [spaces] more characters
51 /// that the outer level.
52 NestingLevel nest(int spaces) => new NestingLevel._(this, spaces);
53
54 /// Gets the relative indentation of the nesting level at [depth].
55 int indentAtDepth(int depth) {
56 // How many levels do we need to walk up to reach [depth]?
57 var levels = this.depth - depth;
58 assert(levels >= 0);
59
60 var nesting = this;
61 for (var i = 0; i < levels; i++) {
62 nesting = nesting._parent;
63 }
64
65 return nesting.indent;
66 }
67
68 /// Discards this level's parent if it is not in [used] (or is not the top
69 /// level nesting).
70 void removeUnused(Set<NestingLevel> used) {
71 // Always keep the top level zero nesting.
72 if (_parent == null) return;
73 if (_parent._parent == null) return;
74
75 // Unlink the unused parent from the chain.
76 if (!used.contains(_parent)) _parent = _parent._parent;
77
78 // Walk up the whole chain.
79 _parent.removeUnused(used);
80 }
81
82 String toString() => depth.toString();
83 }
84
85 /// Maintains a stack of nested expressions that have currently been split.
86 ///
87 /// A single statement may have multiple different levels of indentation based
88 /// on the expression nesting level at the point where the line is broken. For
89 /// example:
90 ///
91 /// someFunction(argument, argument,
92 /// innerFunction(argument,
93 /// innermost), argument);
94 ///
95 /// This means that when splitting a line, we need to keep track of the nesting
96 /// level of the previous line(s) to determine how far the next line must be
97 /// indented.
98 ///
99 /// This class is a persistent collection. Each instance is immutable and
100 /// methods to modify it return a new collection.
101 class NestingSplitter {
102 final NestingSplitter _parent;
103
104 /// The number of characters of indentation for the current nesting.
105 int get indent => _indent;
106 final int _indent;
107
108 /// The number of surrounding expression nesting levels.
109 int get depth => _depth;
110 final int _depth;
111
112 NestingSplitter() : this._(null, 0, 0);
113
114 NestingSplitter._(this._parent, this._depth, this._indent);
115
116 /// LinePrefixes implement their own value equality to ensure that two
117 /// prefixes with the same nesting stack are considered equal even if the
118 /// nesting occurred from different splits.
119 ///
120 /// For example, consider these two prefixes with `^` marking where splits
121 /// have been applied:
122 ///
123 /// fn( first, second, ...
124 /// ^
125 /// fn( first, second, ...
126 /// ^
127 ///
128 /// These are equivalent from the view of the suffix because they have the
129 /// same nesting stack, even though the nesting came from different tokens.
130 /// This lets us reuse memoized suffixes more frequently when solving.
131 bool operator ==(other) {
132 if (other is! NestingSplitter) return false;
133
134 var self = this;
135 while (self != null) {
136 if (self._indent != other._indent) return false;
137 if (self._depth != other._depth) return false;
138 self = self._parent;
139 other = other._parent;
140
141 // They should be the same length.
142 if ((self == null) != (other == null)) return false;
143 }
144
145 return true;
146 }
147
148 int get hashCode {
149 // TODO(rnystrom): Is it worth iterating through the stack?
150 return _indent.hashCode ^ _depth.hashCode;
151 }
152
153 /// Takes this nesting stack and produces all of the new nesting stacks that
154 /// are possible when followed by [nesting].
155 ///
156 /// This may produce multiple solutions because a non-incremental jump in
157 /// nesting depth can be sliced up multiple ways. Let's say the prefix is:
158 ///
159 /// first(second(third(...
160 ///
161 /// The current nesting stack is empty (since we're on the first line). How
162 /// do we modify it by taking into account the split after `third(`? The
163 /// simple answer is to just increase the indentation by one level:
164 ///
165 /// first(second(third(
166 /// argumentToThird)));
167 ///
168 /// This is correct in most cases, but not all. Consider:
169 ///
170 /// first(second(third(
171 /// argumentToThird),
172 /// argumentToSecond));
173 ///
174 /// Oops! There's no place for `argumentToSecond` to go. To handle that, the
175 /// second line needs to be indented one more level to make room for the later
176 /// line:
177 ///
178 /// first(second(third(
179 /// argumentToThird),
180 /// argumentToSecond));
181 ///
182 /// It's even possible we may need to do:
183 ///
184 /// first(second(third(
185 /// argumentToThird),
186 /// argumentToSecond),
187 /// argumentToFirst);
188 ///
189 /// To accommodate those, this returns the list of all possible ways the
190 /// nesting stack can be modified.
191 List<NestingSplitter> update(NestingLevel nesting) {
192 if (nesting.depth == _depth) return [this];
193
194 // If the new split is less nested than we currently are, pop and discard
195 // the previous nesting levels.
196 if (nesting.depth < _depth) {
197 // Pop items off the stack until we find the level we are now at.
198 var stack = this;
199 while (stack != null) {
200 if (stack._depth == nesting.depth) return [stack];
201 stack = stack._parent;
202 }
203
204 // If we got here, the level wasn't found. That means there is no correct
205 // stack level to pop to, since the stack skips past our indentation
206 // level.
207 return [];
208 }
209
210 // Going deeper, so try every indentation for every subset of expression
211 // nesting levels between the old and new one.
212 return _intermediateDepths(_depth, nesting.depth).map((depths) {
213 var result = this;
214
215 for (var depth in depths) {
216 result = new NestingSplitter._(
217 result, depth, result._indent + nesting.indentAtDepth(depth));
218 }
219
220 return new NestingSplitter._(
221 result, nesting.depth, result._indent + nesting.indent);
222 }).toList();
223 }
224
225 /// Given [min] and [max], generates all of the subsets of numbers in that
226 /// range (exclusive), including the empty set.
227 ///
228 /// This is used to determine what sets of intermediate nesting levels to
229 /// consider when jumping from a shallow nesting level to a much deeper one.
230 /// Subsets are generated in order of increasing length. For example, `(2, 6)`
231 /// yields:
232 ///
233 /// []
234 /// [3] [4] [5]
235 /// [3, 4] [3, 5] [4, 5]
236 /// [3, 4, 5]
237 ///
238 /// This ensures the splitter prefers solutions that use the least
239 /// indentation.
240 List<List<int>> _intermediateDepths(int min, int max) {
241 assert(min < max);
242
243 var subsets = [[]];
244
245 var lastLengthStart = 0;
246 var lastLengthEnd = subsets.length;
247
248 // Generate subsets in order of increasing length.
249 for (var length = 1; length <= max - min + 1; length++) {
250 // Start with each subset containing one fewer element.
251 for (var i = lastLengthStart; i < lastLengthEnd; i++) {
252 var previousSubset = subsets[i];
253
254 var start =
255 previousSubset.isNotEmpty ? previousSubset.last + 1 : min + 1;
256
257 // Then for each value in the remainer, make a new subset that is the
258 // union of the shorter subset and that value.
259 for (var j = start; j < max; j++) {
260 var subset = previousSubset.toList()..add(j);
261 subsets.add(subset);
262 }
263 }
264
265 // Move on to the next length range.
266 lastLengthStart = lastLengthEnd;
267 lastLengthEnd = subsets.length;
268 }
269
270 return subsets;
271 }
272
273 /// Shows each indentation level and the nesting depth associated with it.
274 ///
275 /// For example:
276 ///
277 /// |1|3
278 ///
279 /// Means that the first level of indentation is associated with nesting
280 /// level one, and the second level of indentation is associated with nesting
281 /// level three.
282 String toString() {
283 var result = "";
284
285 for (var nesting = this; nesting._depth != 0; nesting = nesting._parent) {
286 result = "|${nesting._depth}$result";
287 }
288
289 return result;
290 }
291 }
OLDNEW
« no previous file with comments | « lib/src/line_writer.dart ('k') | lib/src/nesting_builder.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698