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

Side by Side Diff: pkg/compiler/lib/src/io/source_file.dart

Issue 2788373002: Add Source.getTextLine and use it to display source snippets in error messages. (Closed)
Patch Set: dartfmt Created 3 years, 8 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
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, 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 dart2js.io.source_file; 5 library dart2js.io.source_file;
6 6
7 import 'dart:convert' show UTF8; 7 import 'dart:convert' show UTF8;
8 import 'dart:math'; 8 import 'dart:math';
9 import 'dart:typed_data' show Uint8List; 9 import 'dart:typed_data' show Uint8List;
10 10
11 import 'line_column_provider.dart'; 11 import 'package:kernel/ast.dart' as kernel show Location, Source;
12 12
13 /** 13 import 'location_provider.dart' show LocationProvider;
14 * Represents a file of source code. The content can be either a [String] or 14
15 * a UTF-8 encoded [List<int>] of bytes. 15 /// Represents a file of source code. The content can be either a [String] or
16 */ 16 /// a UTF-8 encoded [List<int>] of bytes.
17 abstract class SourceFile implements LineColumnProvider { 17 abstract class SourceFile implements LocationProvider {
18 /// The absolute URI of the source file. 18 /// The absolute URI of the source file.
19 Uri get uri; 19 Uri get uri;
20 20
21 kernel.Source cachedKernelSource;
22
23 kernel.Source get kernelSource {
24 return cachedKernelSource ??=
25 new kernel.Source(lineStarts, slowUtf8ZeroTerminatedBytes())
26 ..cachedText = slowText();
27 }
28
21 /// The name of the file. 29 /// The name of the file.
22 /// 30 ///
23 /// This is [uri], maybe relativized to a more human-readable form. 31 /// This is [uri], maybe relativized to a more human-readable form.
24 String get filename => uri.toString(); 32 String get filename => uri.toString();
25 33
26 /** The text content of the file represented as a String. */ 34 /// The text content of the file represented as a String
27 String slowText(); 35 String slowText();
28 36
29 /** 37 /// The content of the file represented as a UTF-8 encoded [List<int>],
30 * The content of the file represented as a UTF-8 encoded [List<int>], 38 /// terminated with a trailing 0 byte.
31 * terminated with a trailing 0 byte.
32 */
33 List<int> slowUtf8ZeroTerminatedBytes(); 39 List<int> slowUtf8ZeroTerminatedBytes();
34 40
35 /** 41 /// The length of the string representation of this source file, i.e.,
36 * The length of the string representation of this source file, i.e., 42 /// equivalent to [:slowText().length:], but faster.
37 * equivalent to [:slowText().length:], but faster.
38 */
39 int get length; 43 int get length;
40 44
41 /** 45 /// Sets the string length of this source file. For source files based on
42 * Sets the string length of this source file. For source files based on UTF-8 46 /// UTF-8 byte arrays, the string length is computed and assigned by the
43 * byte arrays, the string length is computed and assigned by the scanner. 47 /// scanner.
44 */
45 set length(int v); 48 set length(int v);
46 49
47 /** 50 /// A map from line numbers to offsets in the string text representation of
48 * A map from line numbers to offsets in the string text representation of 51 /// this source file.
49 * this source file.
50 */
51 List<int> get lineStarts { 52 List<int> get lineStarts {
52 if (lineStartsCache == null) { 53 if (lineStartsCache == null) {
53 // When reporting errors during scanning, the line numbers are not yet 54 // When reporting errors during scanning, the line numbers are not yet
54 // available and need to be computed using this slow path. 55 // available and need to be computed using this slow path.
55 lineStartsCache = lineStartsFromString(slowText()); 56 lineStartsCache = lineStartsFromString(slowText());
56 } 57 }
57 return lineStartsCache; 58 return lineStartsCache;
58 } 59 }
59 60
60 /** 61 /// Sets the line numbers map for this source file. This map is computed and
61 * Sets the line numbers map for this source file. This map is computed and 62 /// assigned by the scanner, avoiding a separate traversal of the source file.
62 * assigned by the scanner, avoiding a separate traversal of the source file. 63 ///
63 * 64 /// The map contains one additional entry at the end of the file, as if the
64 * The map contains one additional entry at the end of the file, as if the 65 /// source file had one more empty line at the end. This simplifies the binary
65 * source file had one more empty line at the end. This simplifies the binary 66 /// search in [getLocation].
66 * search in [getLine].
67 */
68 set lineStarts(List<int> v) => lineStartsCache = v; 67 set lineStarts(List<int> v) => lineStartsCache = v;
69 68
70 List<int> lineStartsCache; 69 List<int> lineStartsCache;
71 70
72 List<int> lineStartsFromString(String text) { 71 List<int> lineStartsFromString(String text) {
73 var starts = [0]; 72 var starts = [0];
74 var index = 0; 73 var index = 0;
75 while (index < text.length) { 74 while (index < text.length) {
76 index = text.indexOf('\n', index) + 1; 75 index = text.indexOf('\n', index) + 1;
77 if (index <= 0) break; 76 if (index <= 0) break;
78 starts.add(index); 77 starts.add(index);
79 } 78 }
80 starts.add(text.length + 1); // One additional line start at the end. 79 starts.add(text.length + 1); // One additional line start at the end.
81 return starts; 80 return starts;
82 } 81 }
83 82
84 /** 83 kernel.Location getLocation(int offset) {
85 * Returns the line number for the offset [position] in the string 84 return kernelSource.getLocation(null, offset);
86 * representation of this source file.
87 */
88 int getLine(int position) {
89 List<int> starts = lineStarts;
90 if (position < 0 || starts.last <= position) {
91 throw 'bad position #$position in file $filename with '
92 'length ${length}.';
93 }
94 int first = 0;
95 int count = starts.length;
96 while (count > 1) {
97 int step = count ~/ 2;
98 int middle = first + step;
99 int lineStart = starts[middle];
100 if (position < lineStart) {
101 count = step;
102 } else {
103 first = middle;
104 count -= step;
105 }
106 }
107 return first;
108 } 85 }
109 86
110 /**
111 * Returns the column number for the offset [position] in the string
112 * representation of this source file.
113 */
114 int getColumn(int line, int position) {
115 return position - lineStarts[line];
116 }
117
118 /// Returns the offset for 0-based [line] and [column] numbers.
119 int getOffset(int line, int column) => lineStarts[line] + column;
120
121 String slowSubstring(int start, int end); 87 String slowSubstring(int start, int end);
122 88
123 /** 89 /// Create a pretty string representation for [message] from a character
124 * Create a pretty string representation for [message] from a character 90 /// range `[start, end]` in this file.
125 * range `[start, end]` in this file. 91 ///
126 * 92 /// If [includeSourceLine] is `true` the first source line code line that
127 * If [includeSourceLine] is `true` the first source line code line that 93 /// contains the range will be included as well as marker characters ('^')
128 * contains the range will be included as well as marker characters ('^') 94 /// underlining the range.
129 * underlining the range. 95 ///
130 * 96 /// Use [colorize] to wrap source code text and marker characters in color
131 * Use [colorize] to wrap source code text and marker characters in color 97 /// escape codes.
132 * escape codes.
133 */
134 String getLocationMessage(String message, int start, int end, 98 String getLocationMessage(String message, int start, int end,
135 {bool includeSourceLine: true, String colorize(String text)}) { 99 {bool includeSourceLine: true, String colorize(String text)}) {
136 if (colorize == null) { 100 if (colorize == null) {
137 colorize = (text) => text; 101 colorize = (text) => text;
138 } 102 }
139 if (end > length) { 103 if (end > length) {
140 start = length - 1; 104 start = length - 1;
141 end = length; 105 end = length;
142 } 106 }
143 107
144 int lineStart = getLine(start); 108 kernel.Location startLocation = kernelSource.getLocation(null, start);
145 int columnStart = getColumn(lineStart, start); 109 kernel.Location endLocation = kernelSource.getLocation(null, end);
146 int lineEnd = getLine(end); 110 int lineStart = startLocation.line - 1;
147 int columnEnd = getColumn(lineEnd, end); 111 int columnStart = startLocation.column - 1;
112 int lineEnd = endLocation.line - 1;
113 int columnEnd = endLocation.column - 1;
148 114
149 StringBuffer buf = new StringBuffer('${filename}:'); 115 StringBuffer buf = new StringBuffer('${filename}:');
150 if (start != end || start != 0) { 116 if (start != end || start != 0) {
151 // Line/column info is relevant. 117 // Line/column info is relevant.
152 buf.write('${lineStart + 1}:${columnStart + 1}:'); 118 buf.write('${lineStart + 1}:${columnStart + 1}:');
153 } 119 }
154 buf.write('\n$message\n'); 120 buf.write('\n$message\n');
155 121
156 if (start != end && includeSourceLine) { 122 if (start != end && includeSourceLine) {
157 if (lineStart == lineEnd) { 123 if (lineStart == lineEnd) {
158 String textLine = getLineText(lineStart); 124 String textLine = kernelSource.getTextLine(startLocation.line);
159 125
160 int toColumn = min(columnStart + (end - start), textLine.length); 126 int toColumn = min(columnStart + (end - start), textLine.length);
161 buf.write(textLine.substring(0, columnStart)); 127 buf.write(textLine.substring(0, columnStart));
162 buf.write(colorize(textLine.substring(columnStart, toColumn))); 128 buf.write(colorize(textLine.substring(columnStart, toColumn)));
163 buf.write(textLine.substring(toColumn)); 129 buf.writeln(textLine.substring(toColumn));
164 130
165 int i = 0; 131 int i = 0;
166 for (; i < columnStart; i++) { 132 for (; i < columnStart; i++) {
167 buf.write(' '); 133 buf.write(' ');
168 } 134 }
169 135
170 for (; i < toColumn; i++) { 136 for (; i < toColumn; i++) {
171 buf.write(colorize('^')); 137 buf.write(colorize('^'));
172 } 138 }
173 } else { 139 } else {
174 for (int line = lineStart; line <= lineEnd; line++) { 140 for (int line = lineStart; line <= lineEnd; line++) {
175 String textLine = getLineText(line); 141 String textLine = kernelSource.getTextLine(line + 1);
176 if (line == lineStart) { 142 if (line == lineStart) {
177 buf.write(textLine.substring(0, columnStart)); 143 buf.write(textLine.substring(0, columnStart));
178 buf.write(colorize(textLine.substring(columnStart))); 144 buf.writeln(colorize(textLine.substring(columnStart)));
179 } else if (line == lineEnd) { 145 } else if (line == lineEnd) {
180 buf.write(colorize(textLine.substring(0, columnEnd))); 146 buf.write(colorize(textLine.substring(0, columnEnd)));
181 buf.write(textLine.substring(columnEnd)); 147 buf.writeln(textLine.substring(columnEnd));
182 } else { 148 } else {
183 buf.write(colorize(textLine)); 149 buf.writeln(colorize(textLine));
184 } 150 }
185 } 151 }
186 } 152 }
187 } 153 }
188 154
189 return buf.toString(); 155 return buf.toString();
190 } 156 }
191 157
192 int get lines => lineStarts.length - 1; 158 int get lines => lineStarts.length - 1;
193
194 /// Returns the text of line at the 0-based [index] within this source file.
195 String getLineText(int index) {
196 // +1 for 0-indexing, +1 again to avoid the last line of the file
197 if ((index + 2) < lineStarts.length) {
198 return slowSubstring(lineStarts[index], lineStarts[index + 1]);
199 } else if ((index + 1) < lineStarts.length) {
200 return '${slowSubstring(lineStarts[index], length)}\n';
201 } else {
202 throw new ArgumentError("Line index $index is out of bounds.");
203 }
204 }
205 } 159 }
206 160
207 List<int> _zeroTerminateIfNecessary(List<int> bytes) { 161 List<int> _zeroTerminateIfNecessary(List<int> bytes) {
208 if (bytes.length > 0 && bytes.last == 0) return bytes; 162 if (bytes.length > 0 && bytes.last == 0) return bytes;
209 List<int> result = new Uint8List(bytes.length + 1); 163 List<int> result = new Uint8List(bytes.length + 1);
210 result.setRange(0, bytes.length, bytes); 164 result.setRange(0, bytes.length, bytes);
211 result[result.length - 1] = 0; 165 result[result.length - 1] = 0;
212 return result; 166 return result;
213 } 167 }
214 168
215 class Utf8BytesSourceFile extends SourceFile { 169 class Utf8BytesSourceFile extends SourceFile {
216 final Uri uri; 170 final Uri uri;
217 171
218 /** The UTF-8 encoded content of the source file. */ 172 /// The UTF-8 encoded content of the source file.
219 final List<int> zeroTerminatedContent; 173 final List<int> zeroTerminatedContent;
220 174
221 /** 175 /// Creates a Utf8BytesSourceFile.
222 * Creates a Utf8BytesSourceFile. 176 ///
223 * 177 /// If possible, the given [content] should be zero-terminated. If it isn't,
224 * If possible, the given [content] should be zero-terminated. If it isn't, 178 /// the constructor clones the content and adds a trailing 0.
225 * the constructor clones the content and adds a trailing 0.
226 */
227 Utf8BytesSourceFile(this.uri, List<int> content) 179 Utf8BytesSourceFile(this.uri, List<int> content)
228 : this.zeroTerminatedContent = _zeroTerminateIfNecessary(content); 180 : this.zeroTerminatedContent = _zeroTerminateIfNecessary(content);
229 181
230 String slowText() { 182 String slowText() {
231 // Don't convert the trailing zero byte. 183 // Don't convert the trailing zero byte.
232 return UTF8.decoder 184 return UTF8.decoder
233 .convert(zeroTerminatedContent, 0, zeroTerminatedContent.length - 1); 185 .convert(zeroTerminatedContent, 0, zeroTerminatedContent.length - 1);
234 } 186 }
235 187
236 List<int> slowUtf8ZeroTerminatedBytes() => zeroTerminatedContent; 188 List<int> slowUtf8ZeroTerminatedBytes() => zeroTerminatedContent;
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
285 set length(int v) {} 237 set length(int v) {}
286 238
287 String slowText() => text; 239 String slowText() => text;
288 240
289 List<int> slowUtf8ZeroTerminatedBytes() { 241 List<int> slowUtf8ZeroTerminatedBytes() {
290 return _zeroTerminateIfNecessary(UTF8.encode(text)); 242 return _zeroTerminateIfNecessary(UTF8.encode(text));
291 } 243 }
292 244
293 String slowSubstring(int start, int end) => text.substring(start, end); 245 String slowSubstring(int start, int end) => text.substring(start, end);
294 } 246 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698