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 |