| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library source_file; | |
| 6 | |
| 7 import 'dart:math'; | |
| 8 import 'dart:convert' show UTF8; | |
| 9 | |
| 10 /** | |
| 11 * Represents a file of source code. The content can be either a [String] or | |
| 12 * a UTF-8 encoded [List<int>] of bytes. | |
| 13 */ | |
| 14 abstract class SourceFile { | |
| 15 | |
| 16 /** The name of the file. */ | |
| 17 String get filename; | |
| 18 | |
| 19 /** The text content of the file represented as a String. */ | |
| 20 String slowText(); | |
| 21 | |
| 22 /** The content of the file represented as a UTF-8 encoded [List<int>]. */ | |
| 23 List<int> slowUtf8Bytes(); | |
| 24 | |
| 25 /** | |
| 26 * The length of the string representation of this source file, i.e., | |
| 27 * equivalent to [:slowText().length:], but faster. | |
| 28 */ | |
| 29 int get length; | |
| 30 | |
| 31 /** | |
| 32 * Sets the string length of this source file. For source files based on UTF-8 | |
| 33 * byte arrays, the string length is computed and assigned by the scanner. | |
| 34 */ | |
| 35 set length(int v); | |
| 36 | |
| 37 /** | |
| 38 * A map from line numbers to offsets in the string text representation of | |
| 39 * this source file. | |
| 40 */ | |
| 41 List<int> get lineStarts { | |
| 42 if (lineStartsCache == null) { | |
| 43 // When reporting errors during scanning, the line numbers are not yet | |
| 44 // available and need to be computed using this slow path. | |
| 45 lineStartsCache = lineStartsFromString(slowText()); | |
| 46 } | |
| 47 return lineStartsCache; | |
| 48 } | |
| 49 | |
| 50 /** | |
| 51 * Sets the line numbers map for this source file. This map is computed and | |
| 52 * assigned by the scanner, avoiding a separate traversal of the source file. | |
| 53 * | |
| 54 * The map contains one additional entry at the end of the file, as if the | |
| 55 * source file had one more empty line at the end. This simplifies the binary | |
| 56 * search in [getLine]. | |
| 57 */ | |
| 58 set lineStarts(List<int> v) => lineStartsCache = v; | |
| 59 | |
| 60 List<int> lineStartsCache; | |
| 61 | |
| 62 List<int> lineStartsFromString(String text) { | |
| 63 var starts = [0]; | |
| 64 var index = 0; | |
| 65 while (index < text.length) { | |
| 66 index = text.indexOf('\n', index) + 1; | |
| 67 if (index <= 0) break; | |
| 68 starts.add(index); | |
| 69 } | |
| 70 starts.add(text.length + 1); // One additional line start at the end. | |
| 71 return starts; | |
| 72 } | |
| 73 | |
| 74 /** | |
| 75 * Returns the line number for the offset [position] in the string | |
| 76 * representation of this source file. | |
| 77 */ | |
| 78 int getLine(int position) { | |
| 79 List<int> starts = lineStarts; | |
| 80 if (position < 0 || starts.last <= position) { | |
| 81 throw 'bad position #$position in file $filename with ' | |
| 82 'length ${length}.'; | |
| 83 } | |
| 84 int first = 0; | |
| 85 int count = starts.length; | |
| 86 while (count > 1) { | |
| 87 int step = count ~/ 2; | |
| 88 int middle = first + step; | |
| 89 int lineStart = starts[middle]; | |
| 90 if (position < lineStart) { | |
| 91 count = step; | |
| 92 } else { | |
| 93 first = middle; | |
| 94 count -= step; | |
| 95 } | |
| 96 } | |
| 97 return first; | |
| 98 } | |
| 99 | |
| 100 /** | |
| 101 * Returns the column number for the offset [position] in the string | |
| 102 * representation of this source file. | |
| 103 */ | |
| 104 int getColumn(int line, int position) { | |
| 105 return position - lineStarts[line]; | |
| 106 } | |
| 107 | |
| 108 String slowSubstring(int start, int end); | |
| 109 | |
| 110 /** | |
| 111 * Create a pretty string representation for [message] from a character | |
| 112 * range `[start, end]` in this file. | |
| 113 * | |
| 114 * If [includeSourceLine] is `true` the first source line code line that | |
| 115 * contains the range will be included as well as marker characters ('^') | |
| 116 * underlining the range. | |
| 117 * | |
| 118 * Use [colorize] to wrap source code text and marker characters in color | |
| 119 * escape codes. | |
| 120 */ | |
| 121 String getLocationMessage(String message, int start, int end, | |
| 122 {bool includeSourceLine: true, | |
| 123 String colorize(String text)}) { | |
| 124 if (colorize == null) { | |
| 125 colorize = (text) => text; | |
| 126 } | |
| 127 var line = getLine(start); | |
| 128 var column = getColumn(line, start); | |
| 129 | |
| 130 var buf = new StringBuffer('${filename}:'); | |
| 131 if (start != end || start != 0) { | |
| 132 // Line/column info is relevant. | |
| 133 buf.write('${line + 1}:${column + 1}:'); | |
| 134 } | |
| 135 buf.write('\n$message\n'); | |
| 136 | |
| 137 if (start != end && includeSourceLine) { | |
| 138 String textLine; | |
| 139 // +1 for 0-indexing, +1 again to avoid the last line of the file | |
| 140 if ((line + 2) < lineStarts.length) { | |
| 141 textLine = slowSubstring(lineStarts[line], lineStarts[line+1]); | |
| 142 } else { | |
| 143 textLine = '${slowSubstring(lineStarts[line], length)}\n'; | |
| 144 } | |
| 145 | |
| 146 int toColumn = min(column + (end-start), textLine.length); | |
| 147 buf.write(textLine.substring(0, column)); | |
| 148 buf.write(colorize(textLine.substring(column, toColumn))); | |
| 149 buf.write(textLine.substring(toColumn)); | |
| 150 | |
| 151 int i = 0; | |
| 152 for (; i < column; i++) { | |
| 153 buf.write(' '); | |
| 154 } | |
| 155 | |
| 156 for (; i < toColumn; i++) { | |
| 157 buf.write(colorize('^')); | |
| 158 } | |
| 159 } | |
| 160 | |
| 161 return buf.toString(); | |
| 162 } | |
| 163 } | |
| 164 | |
| 165 class Utf8BytesSourceFile extends SourceFile { | |
| 166 final String filename; | |
| 167 | |
| 168 /** The UTF-8 encoded content of the source file. */ | |
| 169 final List<int> content; | |
| 170 | |
| 171 Utf8BytesSourceFile(this.filename, this.content); | |
| 172 | |
| 173 String slowText() => UTF8.decode(content); | |
| 174 | |
| 175 List<int> slowUtf8Bytes() => content; | |
| 176 | |
| 177 String slowSubstring(int start, int end) { | |
| 178 // TODO(lry): to make this faster, the scanner could record the UTF-8 slack | |
| 179 // for all positions of the source text. We could use [:content.sublist:]. | |
| 180 return slowText().substring(start, end); | |
| 181 } | |
| 182 | |
| 183 int get length { | |
| 184 if (lengthCache == -1) { | |
| 185 // During scanning the length is not yet assigned, so we use a slow path. | |
| 186 length = slowText().length; | |
| 187 } | |
| 188 return lengthCache; | |
| 189 } | |
| 190 set length(int v) => lengthCache = v; | |
| 191 int lengthCache = -1; | |
| 192 } | |
| 193 | |
| 194 class CachingUtf8BytesSourceFile extends Utf8BytesSourceFile { | |
| 195 String cachedText; | |
| 196 | |
| 197 CachingUtf8BytesSourceFile(String filename, List<int> content) | |
| 198 : super(filename, content); | |
| 199 | |
| 200 String slowText() { | |
| 201 if (cachedText == null) { | |
| 202 cachedText = super.slowText(); | |
| 203 } | |
| 204 return cachedText; | |
| 205 } | |
| 206 } | |
| 207 | |
| 208 class StringSourceFile extends SourceFile { | |
| 209 final String filename; | |
| 210 final String text; | |
| 211 | |
| 212 StringSourceFile(this.filename, this.text); | |
| 213 | |
| 214 int get length => text.length; | |
| 215 set length(int v) { } | |
| 216 | |
| 217 String slowText() => text; | |
| 218 | |
| 219 List<int> slowUtf8Bytes() => UTF8.encode(text); | |
| 220 | |
| 221 String slowSubstring(int start, int end) => text.substring(start, end); | |
| 222 } | |
| OLD | NEW |