OLD | NEW |
| (Empty) |
1 // Copyright (c) 2013, 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 /// Contains a code printer that generates code by recording the source maps. | |
6 library source_maps.printer; | |
7 | |
8 import 'package:source_span/source_span.dart'; | |
9 | |
10 import 'builder.dart'; | |
11 import 'src/source_map_span.dart'; | |
12 | |
13 const int _LF = 10; | |
14 const int _CR = 13; | |
15 | |
16 /// A simple printer that keeps track of offset locations and records source | |
17 /// maps locations. | |
18 class Printer { | |
19 final String filename; | |
20 final StringBuffer _buff = new StringBuffer(); | |
21 final SourceMapBuilder _maps = new SourceMapBuilder(); | |
22 String get text => _buff.toString(); | |
23 String get map => _maps.toJson(filename); | |
24 | |
25 /// Current source location mapping. | |
26 SourceLocation _loc; | |
27 | |
28 /// Current line in the buffer; | |
29 int _line = 0; | |
30 | |
31 /// Current column in the buffer. | |
32 int _column = 0; | |
33 | |
34 Printer(this.filename); | |
35 | |
36 /// Add [str] contents to the output, tracking new lines to track correct | |
37 /// positions for span locations. When [projectMarks] is true, this method | |
38 /// adds a source map location on each new line, projecting that every new | |
39 /// line in the target file (printed here) corresponds to a new line in the | |
40 /// source file. | |
41 void add(String str, {projectMarks: false}) { | |
42 var chars = str.runes.toList(); | |
43 var length = chars.length; | |
44 for (int i = 0; i < length; i++) { | |
45 var c = chars[i]; | |
46 if (c == _LF || (c == _CR && (i + 1 == length || chars[i + 1] != _LF))) { | |
47 // Return not followed by line-feed is treated as a new line. | |
48 _line++; | |
49 _column = 0; | |
50 if (projectMarks && _loc != null) { | |
51 if (_loc is FileLocation) { | |
52 var file = (_loc as FileLocation).file; | |
53 mark(file.location(file.getOffset(_loc.line + 1))); | |
54 } else { | |
55 mark(new SourceLocation(0, | |
56 sourceUrl: _loc.sourceUrl, line: _loc.line + 1, column: 0)); | |
57 } | |
58 } | |
59 } else { | |
60 _column++; | |
61 } | |
62 } | |
63 _buff.write(str); | |
64 } | |
65 | |
66 | |
67 /// Append a [total] number of spaces in the target file. Typically used for | |
68 /// formatting indentation. | |
69 void addSpaces(int total) { | |
70 for (int i = 0; i < total; i++) _buff.write(' '); | |
71 _column += total; | |
72 } | |
73 | |
74 /// Marks that the current point in the target file corresponds to the [mark] | |
75 /// in the source file, which can be either a [SourceLocation] or a | |
76 /// [SourceSpan]. When the mark is a [SourceMapSpan] with `isIdentifier` set, | |
77 /// this also records the name of the identifier in the source map | |
78 /// information. | |
79 void mark(mark) { | |
80 var loc; | |
81 var identifier = null; | |
82 if (mark is SourceLocation) { | |
83 loc = mark; | |
84 } else if (mark is SourceSpan) { | |
85 loc = mark.start; | |
86 if (mark is SourceMapSpan && mark.isIdentifier) identifier = mark.text; | |
87 } | |
88 _maps.addLocation( | |
89 loc, | |
90 new SourceLocation(_buff.length, line: _line, column: _column), | |
91 identifier); | |
92 _loc = loc; | |
93 } | |
94 } | |
95 | |
96 /// A more advanced printer that keeps track of offset locations to record | |
97 /// source maps, but additionally allows nesting of different kind of items, | |
98 /// including [NestedPrinter]s, and it let's you automatically indent text. | |
99 /// | |
100 /// This class is especially useful when doing code generation, where different | |
101 /// peices of the code are generated independently on separate printers, and are | |
102 /// finally put together in the end. | |
103 class NestedPrinter implements NestedItem { | |
104 | |
105 /// Items recoded by this printer, which can be [String] literals, | |
106 /// [NestedItem]s, and source map information like [SourceLocation] and | |
107 /// [SourceSpan]. | |
108 List _items = []; | |
109 | |
110 /// Internal buffer to merge consecutive strings added to this printer. | |
111 StringBuffer _buff; | |
112 | |
113 /// Current indentation, which can be updated from outside this class. | |
114 int indent; | |
115 | |
116 /// Item used to indicate that the following item is copied from the original | |
117 /// source code, and hence we should preserve source-maps on every new line. | |
118 static final _ORIGINAL = new Object(); | |
119 | |
120 NestedPrinter([this.indent = 0]); | |
121 | |
122 /// Adds [object] to this printer. [object] can be a [String], | |
123 /// [NestedPrinter], or anything implementing [NestedItem]. If [object] is a | |
124 /// [String], the value is appended directly, without doing any formatting | |
125 /// changes. If you wish to add a line of code with automatic indentation, use | |
126 /// [addLine] instead. [NestedPrinter]s and [NestedItem]s are not processed | |
127 /// until [build] gets called later on. We ensure that [build] emits every | |
128 /// object in the order that they were added to this printer. | |
129 /// | |
130 /// The [location] and [span] parameters indicate the corresponding source map | |
131 /// location of [object] in the original input. Only one, [location] or | |
132 /// [span], should be provided at a time. | |
133 /// | |
134 /// Indicate [isOriginal] when [object] is copied directly from the user code. | |
135 /// Setting [isOriginal] will make this printer propagate source map locations | |
136 /// on every line-break. | |
137 void add(object, {SourceLocation location, SourceSpan span, | |
138 bool isOriginal: false}) { | |
139 if (object is! String || location != null || span != null || isOriginal) { | |
140 _flush(); | |
141 assert(location == null || span == null); | |
142 if (location != null) _items.add(location); | |
143 if (span != null) _items.add(span); | |
144 if (isOriginal) _items.add(_ORIGINAL); | |
145 } | |
146 | |
147 if (object is String) { | |
148 _appendString(object); | |
149 } else { | |
150 _items.add(object); | |
151 } | |
152 } | |
153 | |
154 /// Append `2 * indent` spaces to this printer. | |
155 void insertIndent() => _indent(indent); | |
156 | |
157 /// Add a [line], autoindenting to the current value of [indent]. Note, | |
158 /// indentation is not inferred from the contents added to this printer. If a | |
159 /// line starts or ends an indentation block, you need to also update [indent] | |
160 /// accordingly. Also, indentation is not adapted for nested printers. If | |
161 /// you add a [NestedPrinter] to this printer, its indentation is set | |
162 /// separately and will not include any the indentation set here. | |
163 /// | |
164 /// The [location] and [span] parameters indicate the corresponding source map | |
165 /// location of [object] in the original input. Only one, [location] or | |
166 /// [span], should be provided at a time. | |
167 void addLine(String line, {SourceLocation location, SourceSpan span}) { | |
168 if (location != null || span != null) { | |
169 _flush(); | |
170 assert(location == null || span == null); | |
171 if (location != null) _items.add(location); | |
172 if (span != null) _items.add(span); | |
173 } | |
174 if (line == null) return; | |
175 if (line != '') { | |
176 // We don't indent empty lines. | |
177 _indent(indent); | |
178 _appendString(line); | |
179 } | |
180 _appendString('\n'); | |
181 } | |
182 | |
183 /// Appends a string merging it with any previous strings, if possible. | |
184 void _appendString(String s) { | |
185 if (_buff == null) _buff = new StringBuffer(); | |
186 _buff.write(s); | |
187 } | |
188 | |
189 /// Adds all of the current [_buff] contents as a string item. | |
190 void _flush() { | |
191 if (_buff != null) { | |
192 _items.add(_buff.toString()); | |
193 _buff = null; | |
194 } | |
195 } | |
196 | |
197 void _indent(int indent) { | |
198 for (int i = 0; i < indent; i++) _appendString(' '); | |
199 } | |
200 | |
201 /// Returns a string representation of all the contents appended to this | |
202 /// printer, including source map location tokens. | |
203 String toString() { | |
204 _flush(); | |
205 return (new StringBuffer()..writeAll(_items)).toString(); | |
206 } | |
207 | |
208 /// [Printer] used during the last call to [build], if any. | |
209 Printer printer; | |
210 | |
211 /// Returns the text produced after calling [build]. | |
212 String get text => printer.text; | |
213 | |
214 /// Returns the source-map information produced after calling [build]. | |
215 String get map => printer.map; | |
216 | |
217 /// Builds the output of this printer and source map information. After | |
218 /// calling this function, you can use [text] and [map] to retrieve the | |
219 /// geenrated code and source map information, respectively. | |
220 void build(String filename) { | |
221 writeTo(printer = new Printer(filename)); | |
222 } | |
223 | |
224 /// Implements the [NestedItem] interface. | |
225 void writeTo(Printer printer) { | |
226 _flush(); | |
227 bool propagate = false; | |
228 for (var item in _items) { | |
229 if (item is NestedItem) { | |
230 item.writeTo(printer); | |
231 } else if (item is String) { | |
232 printer.add(item, projectMarks: propagate); | |
233 propagate = false; | |
234 } else if (item is SourceLocation || item is SourceSpan) { | |
235 printer.mark(item); | |
236 } else if (item == _ORIGINAL) { | |
237 // we insert booleans when we are about to quote text that was copied | |
238 // from the original source. In such case, we will propagate marks on | |
239 // every new-line. | |
240 propagate = true; | |
241 } else { | |
242 throw new UnsupportedError('Unknown item type: $item'); | |
243 } | |
244 } | |
245 } | |
246 } | |
247 | |
248 /// An item added to a [NestedPrinter]. | |
249 abstract class NestedItem { | |
250 /// Write the contents of this item into [printer]. | |
251 void writeTo(Printer printer); | |
252 } | |
OLD | NEW |