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 |