| OLD | NEW | 
 | (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 } |  | 
| OLD | NEW |