OLD | NEW |
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 Loading... |
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 } |
OLD | NEW |