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

Side by Side Diff: source_span/lib/src/file.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 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 | « source_span/lib/src/colors.dart ('k') | source_span/lib/src/location.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 source_span.file;
6
7 import 'dart:math' as math;
8 import 'dart:typed_data';
9
10 import 'location.dart';
11 import 'location_mixin.dart';
12 import 'span.dart';
13 import 'span_mixin.dart';
14 import 'span_with_context.dart';
15
16 // Constants to determine end-of-lines.
17 const int _LF = 10;
18 const int _CR = 13;
19
20 /// A class representing a source file.
21 ///
22 /// This doesn't necessarily have to correspond to a file on disk, just a chunk
23 /// of text usually with a URL associated with it.
24 class SourceFile {
25 /// The URL where the source file is located.
26 ///
27 /// This may be null, indicating that the URL is unknown or unavailable.
28 final Uri url;
29
30 /// An array of offsets for each line beginning in the file.
31 ///
32 /// Each offset refers to the first character *after* the newline. If the
33 /// source file has a trailing newline, the final offset won't actually be in
34 /// the file.
35 final _lineStarts = <int>[0];
36
37 /// The code points of the characters in the file.
38 final Uint32List _decodedChars;
39
40 /// The length of the file in characters.
41 int get length => _decodedChars.length;
42
43 /// The number of lines in the file.
44 int get lines => _lineStarts.length;
45
46 /// The line that the offset fell on the last time [getLine] was called.
47 ///
48 /// In many cases, sequential calls to getLine() are for nearby, usually
49 /// increasing offsets. In that case, we can find the line for an offset
50 /// quickly by first checking to see if the offset is on the same line as the
51 /// previous result.
52 int _cachedLine;
53
54 /// Creates a new source file from [text].
55 ///
56 /// [url] may be either a [String], a [Uri], or `null`.
57 SourceFile(String text, {url})
58 : this.decoded(text.runes, url: url);
59
60 /// Creates a new source file from a list of decoded characters.
61 ///
62 /// [url] may be either a [String], a [Uri], or `null`.
63 SourceFile.decoded(Iterable<int> decodedChars, {url})
64 : url = url is String ? Uri.parse(url) : url,
65 _decodedChars = new Uint32List.fromList(decodedChars.toList()) {
66 for (var i = 0; i < _decodedChars.length; i++) {
67 var c = _decodedChars[i];
68 if (c == _CR) {
69 // Return not followed by newline is treated as a newline
70 var j = i + 1;
71 if (j >= _decodedChars.length || _decodedChars[j] != _LF) c = _LF;
72 }
73 if (c == _LF) _lineStarts.add(i + 1);
74 }
75 }
76
77 /// Returns a span in [this] from [start] to [end] (exclusive).
78 ///
79 /// If [end] isn't passed, it defaults to the end of the file.
80 FileSpan span(int start, [int end]) {
81 if (end == null) end = length - 1;
82 return new _FileSpan(this, start, end);
83 }
84
85 /// Returns a location in [this] at [offset].
86 FileLocation location(int offset) => new FileLocation._(this, offset);
87
88 /// Gets the 0-based line corresponding to [offset].
89 int getLine(int offset) {
90 if (offset < 0) {
91 throw new RangeError("Offset may not be negative, was $offset.");
92 } else if (offset > length) {
93 throw new RangeError("Offset $offset must not be greater than the number "
94 "of characters in the file, $length.");
95 }
96
97 if (offset < _lineStarts.first) return -1;
98 if (offset >= _lineStarts.last) return _lineStarts.length - 1;
99
100 if (_isNearCachedLine(offset)) return _cachedLine;
101
102 _cachedLine = _binarySearch(offset) - 1;
103 return _cachedLine;
104 }
105
106 /// Returns `true` if [offset] is near [_cachedLine].
107 ///
108 /// Checks on [_cachedLine] and the next line. If it's on the next line, it
109 /// updates [_cachedLine] to point to that.
110 bool _isNearCachedLine(int offset) {
111 if (_cachedLine == null) return false;
112
113 // See if it's before the cached line.
114 if (offset < _lineStarts[_cachedLine]) return false;
115
116 // See if it's on the cached line.
117 if (_cachedLine >= _lineStarts.length - 1 ||
118 offset < _lineStarts[_cachedLine + 1]) {
119 return true;
120 }
121
122 // See if it's on the next line.
123 if (_cachedLine >= _lineStarts.length - 2 ||
124 offset < _lineStarts[_cachedLine + 2]) {
125 _cachedLine++;
126 return true;
127 }
128
129 return false;
130 }
131
132 /// Binary search through [_lineStarts] to find the line containing [offset].
133 ///
134 /// Returns the index of the line in [_lineStarts].
135 int _binarySearch(int offset) {
136 int min = 0;
137 int max = _lineStarts.length - 1;
138 while (min < max) {
139 var half = min + ((max - min) ~/ 2);
140 if (_lineStarts[half] > offset) {
141 max = half;
142 } else {
143 min = half + 1;
144 }
145 }
146
147 return max;
148 }
149
150 /// Gets the 0-based column corresponding to [offset].
151 ///
152 /// If [line] is passed, it's assumed to be the line containing [offset] and
153 /// is used to more efficiently compute the column.
154 int getColumn(int offset, {int line}) {
155 if (offset < 0) {
156 throw new RangeError("Offset may not be negative, was $offset.");
157 } else if (offset > length) {
158 throw new RangeError("Offset $offset must be not be greater than the "
159 "number of characters in the file, $length.");
160 }
161
162 if (line == null) {
163 line = getLine(offset);
164 } else if (line < 0) {
165 throw new RangeError("Line may not be negative, was $line.");
166 } else if (line >= lines) {
167 throw new RangeError("Line $line must be less than the number of "
168 "lines in the file, $lines.");
169 }
170
171 var lineStart = _lineStarts[line];
172 if (lineStart > offset) {
173 throw new RangeError("Line $line comes after offset $offset.");
174 }
175
176 return offset - lineStart;
177 }
178
179 /// Gets the offset for a [line] and [column].
180 ///
181 /// [column] defaults to 0.
182 int getOffset(int line, [int column]) {
183 if (column == null) column = 0;
184
185 if (line < 0) {
186 throw new RangeError("Line may not be negative, was $line.");
187 } else if (line >= lines) {
188 throw new RangeError("Line $line must be less than the number of "
189 "lines in the file, $lines.");
190 } else if (column < 0) {
191 throw new RangeError("Column may not be negative, was $column.");
192 }
193
194 var result = _lineStarts[line] + column;
195 if (result > length ||
196 (line + 1 < lines && result >= _lineStarts[line + 1])) {
197 throw new RangeError("Line $line doesn't have $column columns.");
198 }
199
200 return result;
201 }
202
203 /// Returns the text of the file from [start] to [end] (exclusive).
204 ///
205 /// If [end] isn't passed, it defaults to the end of the file.
206 String getText(int start, [int end]) =>
207 new String.fromCharCodes(_decodedChars.sublist(start, end));
208 }
209
210 /// A [SourceLocation] within a [SourceFile].
211 ///
212 /// Unlike the base [SourceLocation], [FileLocation] lazily computes its line
213 /// and column values based on its offset and the contents of [file].
214 ///
215 /// A [FileLocation] can be created using [SourceFile.location].
216 class FileLocation extends SourceLocationMixin implements SourceLocation {
217 /// The [file] that [this] belongs to.
218 final SourceFile file;
219
220 final int offset;
221 Uri get sourceUrl => file.url;
222 int get line => file.getLine(offset);
223 int get column => file.getColumn(offset);
224
225 FileLocation._(this.file, this.offset) {
226 if (offset < 0) {
227 throw new RangeError("Offset may not be negative, was $offset.");
228 } else if (offset > file.length) {
229 throw new RangeError("Offset $offset must not be greater than the number "
230 "of characters in the file, ${file.length}.");
231 }
232 }
233
234 FileSpan pointSpan() => new _FileSpan(file, offset, offset);
235 }
236
237 /// A [SourceSpan] within a [SourceFile].
238 ///
239 /// Unlike the base [SourceSpan], [FileSpan] lazily computes its line and column
240 /// values based on its offset and the contents of [file]. [FileSpan.message] is
241 /// also able to provide more context then [SourceSpan.message], and
242 /// [FileSpan.union] will return a [FileSpan] if possible.
243 ///
244 /// A [FileSpan] can be created using [SourceFile.span].
245 abstract class FileSpan implements SourceSpanWithContext {
246 /// The [file] that [this] belongs to.
247 SourceFile get file;
248
249 /// Returns a new span that covers both [this] and [other].
250 ///
251 /// Unlike [union], [other] may be disjoint from [this]. If it is, the text
252 /// between the two will be covered by the returned span.
253 FileSpan expand(FileSpan other);
254 }
255
256 /// The implementation of [FileSpan].
257 ///
258 /// This is split into a separate class so that `is _FileSpan` checks can be run
259 /// to make certain operations more efficient. If we used `is FileSpan`, that
260 /// would break if external classes implemented the interface.
261 class _FileSpan extends SourceSpanMixin implements FileSpan {
262 final SourceFile file;
263
264 /// The offset of the beginning of the span.
265 ///
266 /// [start] is lazily generated from this to avoid allocating unnecessary
267 /// objects.
268 final int _start;
269
270 /// The offset of the end of the span.
271 ///
272 /// [end] is lazily generated from this to avoid allocating unnecessary
273 /// objects.
274 final int _end;
275
276 Uri get sourceUrl => file.url;
277 int get length => _end - _start;
278 FileLocation get start => new FileLocation._(file, _start);
279 FileLocation get end => new FileLocation._(file, _end);
280 String get text => file.getText(_start, _end);
281 String get context => file.getText(file.getOffset(start.line),
282 end.line == file.lines - 1 ? null : file.getOffset(end.line + 1));
283
284 _FileSpan(this.file, this._start, this._end) {
285 if (_end < _start) {
286 throw new ArgumentError('End $_end must come after start $_start.');
287 } else if (_end > file.length) {
288 throw new RangeError("End $_end must not be greater than the number "
289 "of characters in the file, ${file.length}.");
290 } else if (_start < 0) {
291 throw new RangeError("Start may not be negative, was $_start.");
292 }
293 }
294
295 int compareTo(SourceSpan other) {
296 if (other is! _FileSpan) return super.compareTo(other);
297
298 _FileSpan otherFile = other;
299 var result = _start.compareTo(otherFile._start);
300 return result == 0 ? _end.compareTo(otherFile._end) : result;
301 }
302
303 SourceSpan union(SourceSpan other) {
304 if (other is! FileSpan) return super.union(other);
305
306
307 _FileSpan span = expand(other);
308
309 if (other is _FileSpan) {
310 if (this._start > other._end || other._start > this._end) {
311 throw new ArgumentError("Spans $this and $other are disjoint.");
312 }
313 } else {
314 if (this._start > other.end.offset || other.start.offset > this._end) {
315 throw new ArgumentError("Spans $this and $other are disjoint.");
316 }
317 }
318
319 return span;
320 }
321
322 bool operator ==(other) {
323 if (other is! FileSpan) return super == other;
324 if (other is! _FileSpan) {
325 return super == other && sourceUrl == other.sourceUrl;
326 }
327
328 return _start == other._start && _end == other._end &&
329 sourceUrl == other.sourceUrl;
330 }
331
332 // Eliminates dart2js warning about overriding `==`, but not `hashCode`
333 int get hashCode => super.hashCode;
334
335 /// Returns a new span that covers both [this] and [other].
336 ///
337 /// Unlike [union], [other] may be disjoint from [this]. If it is, the text
338 /// between the two will be covered by the returned span.
339 FileSpan expand(FileSpan other) {
340 if (sourceUrl != other.sourceUrl) {
341 throw new ArgumentError("Source URLs \"${sourceUrl}\" and "
342 " \"${other.sourceUrl}\" don't match.");
343 }
344
345 if (other is _FileSpan) {
346 var start = math.min(this._start, other._start);
347 var end = math.max(this._end, other._end);
348 return new _FileSpan(file, start, end);
349 } else {
350 var start = math.min(this._start, other.start.offset);
351 var end = math.max(this._end, other.end.offset);
352 return new _FileSpan(file, start, end);
353 }
354 }
355 }
OLDNEW
« no previous file with comments | « source_span/lib/src/colors.dart ('k') | source_span/lib/src/location.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698