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); |
| 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; |
| 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; |
| 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) { } |
| 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 |