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