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