| 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 |