OLD | NEW |
(Empty) | |
| 1 // Copyright (c) 2011, 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 // TODO(jimhug): This should be an interface to work better with tools. |
| 6 /** |
| 7 * Represents a file of source code. |
| 8 */ |
| 9 class SourceFile implements Comparable { |
| 10 // TODO(terry): This filename for in memory buffer. May need to rework if |
| 11 // filename is used for more than informational. |
| 12 static String IN_MEMORY_FILE = '<buffer>'; |
| 13 |
| 14 /** The name of the file. */ |
| 15 final String filename; |
| 16 |
| 17 /** The text content of the file. */ |
| 18 String _text; |
| 19 |
| 20 /** |
| 21 * The order of the source file in a given library. This is used while we're |
| 22 * writing code for a library. A single source file can be used |
| 23 */ |
| 24 // TODO(jmesserly): I don't like having properties that are only valid |
| 25 // sometimes. An alternative would be to store it in a Map that's used by |
| 26 // WorldGenerator while it's emitting code. This seems simpler. |
| 27 int orderInLibrary; |
| 28 |
| 29 List<int> _lineStarts; |
| 30 |
| 31 SourceFile(this.filename, this._text); |
| 32 |
| 33 String get text() => _text; |
| 34 |
| 35 set text(String newText) { |
| 36 if (newText != _text) { |
| 37 _text = newText; |
| 38 _lineStarts = null; |
| 39 orderInLibrary = null; |
| 40 } |
| 41 } |
| 42 |
| 43 List<int> get lineStarts() { |
| 44 if (_lineStarts == null) { |
| 45 var starts = [0]; |
| 46 var index = 0; |
| 47 while (index < text.length) { |
| 48 index = text.indexOf('\n', index) + 1; |
| 49 if (index <= 0) break; |
| 50 starts.add(index); |
| 51 } |
| 52 starts.add(text.length + 1); |
| 53 _lineStarts = starts; |
| 54 } |
| 55 return _lineStarts; |
| 56 } |
| 57 |
| 58 int getLine(int position) { |
| 59 // TODO(jimhug): Implement as binary search. |
| 60 var starts = lineStarts; |
| 61 for (int i=0; i < starts.length; i++) { |
| 62 if (starts[i] > position) return i-1; |
| 63 } |
| 64 world.internalError('bad position'); |
| 65 } |
| 66 |
| 67 int getColumn(int line, int position) { |
| 68 return position - lineStarts[line]; |
| 69 } |
| 70 |
| 71 /** |
| 72 * Create a pretty string representation from a character position |
| 73 * in the file. |
| 74 */ |
| 75 String getLocationMessage(String message, int start, |
| 76 [int end, bool includeText=false]) { |
| 77 var line = getLine(start); |
| 78 var column = getColumn(line, start); |
| 79 |
| 80 var buf = new StringBuffer( |
| 81 '${filename}:${line + 1}:${column + 1}: $message'); |
| 82 if (includeText) { |
| 83 buf.add('\n'); |
| 84 var textLine; |
| 85 // +1 for 0-indexing, +1 again to avoid the last line of the file |
| 86 if ((line + 2) < _lineStarts.length) { |
| 87 textLine = text.substring(_lineStarts[line], _lineStarts[line+1]); |
| 88 } else { |
| 89 textLine = text.substring(_lineStarts[line]) + '\n'; |
| 90 } |
| 91 |
| 92 int toColumn = Math.min(column + (end-start), textLine.length); |
| 93 if (options.useColors) { |
| 94 buf.add(textLine.substring(0, column)); |
| 95 buf.add(_RED_COLOR); |
| 96 buf.add(textLine.substring(column, toColumn)); |
| 97 buf.add(_NO_COLOR); |
| 98 buf.add(textLine.substring(toColumn)); |
| 99 } else { |
| 100 buf.add(textLine); |
| 101 } |
| 102 |
| 103 int i = 0; |
| 104 for (; i < column; i++) { |
| 105 buf.add(' '); |
| 106 } |
| 107 |
| 108 if (options.useColors) buf.add(_RED_COLOR); |
| 109 for (; i < toColumn; i++) { |
| 110 buf.add('^'); |
| 111 } |
| 112 if (options.useColors) buf.add(_NO_COLOR); |
| 113 } |
| 114 |
| 115 return buf.toString(); |
| 116 } |
| 117 |
| 118 /** Compares two source files. */ |
| 119 int compareTo(SourceFile other) { |
| 120 if (orderInLibrary != null && other.orderInLibrary != null) { |
| 121 return orderInLibrary - other.orderInLibrary; |
| 122 } else { |
| 123 return filename.compareTo(other.filename); |
| 124 } |
| 125 } |
| 126 } |
| 127 |
| 128 |
| 129 /** |
| 130 * A range of characters in a [SourceFile]. Used to represent the source |
| 131 * positions of [Token]s and [Node]s for error reporting or other tooling |
| 132 * work. |
| 133 */ |
| 134 // TODO(jmesserly): Rename to Span - but first write cool refactoring tool |
| 135 class SourceSpan implements Comparable { |
| 136 /** The [SourceFile] that contains this span. */ |
| 137 final SourceFile file; |
| 138 |
| 139 /** The character position of the start of this span. */ |
| 140 final int start; |
| 141 |
| 142 /** The character position of the end of this span. */ |
| 143 final int end; |
| 144 |
| 145 SourceSpan(this.file, this.start, this.end); |
| 146 |
| 147 /** Returns the source text corresponding to this [Span]. */ |
| 148 String get text() { |
| 149 return file.text.substring(start, end); |
| 150 } |
| 151 |
| 152 toMessageString(String message) { |
| 153 return file.getLocationMessage(message, start, end: end, includeText: true); |
| 154 } |
| 155 |
| 156 int get line() { |
| 157 return file.getLine(start); |
| 158 } |
| 159 |
| 160 int get column() { |
| 161 return file.getColumn(line, start); |
| 162 } |
| 163 |
| 164 int get endLine() { |
| 165 return file.getLine(end); |
| 166 } |
| 167 |
| 168 int get endColumn() { |
| 169 return file.getColumn(endLine, end); |
| 170 } |
| 171 |
| 172 String get locationText() { |
| 173 var line = file.getLine(start); |
| 174 var column = file.getColumn(line, start); |
| 175 return '${file.filename}:${line + 1}:${column + 1}'; |
| 176 } |
| 177 |
| 178 /** Compares two source spans by file and position. Handles nulls. */ |
| 179 int compareTo(SourceSpan other) { |
| 180 if (file == other.file) { |
| 181 int d = start - other.start; |
| 182 return d == 0 ? (end - other.end) : d; |
| 183 } |
| 184 return file.compareTo(other.file); |
| 185 } |
| 186 } |
OLD | NEW |