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 code_printer; | |
6 | |
7 import 'dart:utf' show stringToCodepoints; | |
8 import 'package:source_maps/source_maps.dart'; | |
9 | |
10 /** | |
11 * Helper class to format generated code and keep track of source map | |
12 * information. | |
13 */ | |
14 class CodePrinter { | |
15 | |
16 /** | |
17 * Items recoded by this printer, which can be [String] literals, | |
18 * other printing helpers such as [Declarations] and [CodePrinter], | |
19 * and source map information like [Location] and [Span]. | |
20 */ | |
21 List _items = []; | |
22 | |
23 /** Internal buffer to merge consecutive strings added to this printer. */ | |
24 StringBuffer _buff; | |
25 | |
26 /** Current indentation, which can be updated from outside this class. */ | |
27 int indent = 0; | |
28 | |
29 /** | |
30 * Item used to indicate that the following item is copied from the original | |
31 * source code, and hence we should preserve source-maps on every new line. | |
32 */ | |
33 static final _ORIGINAL = new Object(); | |
34 | |
35 CodePrinter(this.indent); | |
36 | |
37 /** | |
38 * Adds [object] to this printer. [object] can be a [String], [Declarations], | |
39 * or a [CodePrinter]. If [object] is a [String], the value is appended | |
40 * directly, without doing any formatting changes. If you wish to add a line | |
41 * of code with automatic indentation, use [addLine] instead. [Declarations] | |
42 * and [CodePrinter] are not processed until [build] gets called later on. | |
43 * We ensure that [build] emits every object in the order that they were added | |
44 * to this printer. | |
45 * | |
46 * The [location] and [span] parameters indicate the corresponding source map | |
47 * location of [object] in the original input. Only one, [location] or | |
48 * [span], should be provided at a time. | |
49 * | |
50 * Indicate [isOriginal] when [object] is copied directly from the user code. | |
51 * Setting [isOriginal] will make this printer propagate source map locations | |
52 * on every line-break. | |
53 */ | |
54 void add(object, {Location location, Span span, bool isOriginal: false}) { | |
55 if (object is! String || location != null || span != null || isOriginal) { | |
56 _flush(); | |
57 assert(location == null || span == null); | |
58 if (location != null) _items.add(location); | |
59 if (span != null) _items.add(span); | |
60 if (isOriginal) _items.add(_ORIGINAL); | |
61 } | |
62 | |
63 if (object is String) { | |
64 _appendString(object); | |
65 } else { | |
66 _items.add(object); | |
67 } | |
68 } | |
69 | |
70 /** Append `2 * indent` spaces to this printer. */ | |
71 void insertIndent() => _indent(indent); | |
72 | |
73 /** | |
74 * Add a [line], autoindenting to the current value of [indent]. Note, | |
75 * indentation is not inferred, so if a line opens or closes an indentation | |
76 * block, you need to also update [indent] accordingly. Also, indentation is | |
77 * not adapted for nested code printers. If you add a [CodePrinter] to this | |
78 * printer, its indentation is set separately and will not include any | |
79 * the indentation set here. | |
80 * | |
81 * The [location] and [span] parameters indicate the corresponding source map | |
82 * location of [object] in the original input. Only one, [location] or | |
83 * [span], should be provided at a time. | |
84 */ | |
85 void addLine(String line, {Location location, Span span}) { | |
86 if (location != null || span != null) { | |
87 _flush(); | |
88 assert(location == null || span == null); | |
89 if (location != null) _items.add(location); | |
90 if (span != null) _items.add(span); | |
91 } | |
92 if (line == null) return; | |
93 if (line != '') { | |
94 // We don't indent empty lines. | |
95 _indent(indent); | |
96 _appendString(line); | |
97 } | |
98 _appendString('\n'); | |
99 } | |
100 | |
101 /** Appends a string merging it with any previous strings, if possible. */ | |
102 void _appendString(String s) { | |
103 if (_buff == null) _buff = new StringBuffer(); | |
104 _buff.write(s); | |
105 } | |
106 | |
107 /** Adds all of the current [_buff] contents as a string item. */ | |
108 void _flush() { | |
109 if (_buff != null) { | |
110 _items.add(_buff.toString()); | |
111 _buff = null; | |
112 } | |
113 } | |
114 | |
115 void _indent(int indent) { | |
116 for (int i = 0; i < indent; i++) _appendString(' '); | |
117 } | |
118 | |
119 /** | |
120 * Returns a string representation of all the contents appended to this | |
121 * printer, including source map location tokens. | |
122 */ | |
123 String toString() { | |
124 _flush(); | |
125 return (new StringBuffer()..writeAll(_items)).toString(); | |
126 } | |
127 | |
128 /** [Printer] used during the last call to [build], if any. */ | |
129 Printer _printer; | |
130 | |
131 /** Returns the text produced after calling [build]. */ | |
132 String get text => _printer.text; | |
133 | |
134 /** Returns the source-map information produced after calling [build]. */ | |
135 String get map => _printer.map; | |
136 | |
137 /** | |
138 * Builds the output of this printer and source map information. After calling | |
139 * this function, you can use [text] and [map] to retrieve the geenrated code | |
140 * and source map information, respectively. | |
141 */ | |
142 void build(String filename) { | |
143 _build(_printer = new Printer(filename)); | |
144 } | |
145 | |
146 void _build(Printer printer) { | |
147 _flush(); | |
148 bool propagate = false; | |
149 for (var item in _items) { | |
150 if (item is Declarations) { | |
151 item._build(printer); | |
152 } else if (item is CodePrinter) { | |
153 item._build(printer); | |
154 } else if (item is String) { | |
155 printer.add(item, projectMarks: propagate); | |
156 propagate = false; | |
157 } else if (item is Location || item is Span) { | |
158 printer.mark(item); | |
159 } else if (item == _ORIGINAL) { | |
160 // we insert booleans when we are about to quote text that was copied | |
161 // from the original source. In such case, we will propagate marks on | |
162 // every new-line. | |
163 propagate = true; | |
164 } else { | |
165 throw new UnsupportedError('Unknown item type: $item'); | |
166 } | |
167 } | |
168 } | |
169 } | |
170 | |
171 /** A declaration of a field or local variable. */ | |
172 class Declaration implements Comparable { | |
173 final String type; | |
174 final String name; | |
175 final Span sourceSpan; | |
176 final String initializer; | |
177 | |
178 Declaration(this.type, this.name, this.sourceSpan, [this.initializer]); | |
179 | |
180 /** | |
181 * Sort declarations by type, so they can be merged together in a declaration | |
182 * group. | |
183 */ | |
184 int compareTo(Declaration other) { | |
185 if (type != other.type) return type.compareTo(other.type); | |
186 return name.compareTo(other.name); | |
187 } | |
188 } | |
189 | |
190 /** A set of declarations grouped together. */ | |
191 class Declarations { | |
192 | |
193 /** All declarations in this group. */ | |
194 final List<Declaration> declarations = <Declaration>[]; | |
195 | |
196 /** Indentation associated with this declaration group. */ | |
197 final int indent; | |
198 | |
199 /** Whether these declarations are local variables or fields in a class. */ | |
200 final bool isLocal; | |
201 | |
202 /** Whether types should be prefixed with the "static" keyword. */ | |
203 final bool staticKeyword; | |
204 | |
205 Declarations(this.indent, {this.isLocal: false, this.staticKeyword: false}); | |
206 | |
207 /** Add a declaration to this group. */ | |
208 void add(String type, String identifier, Span sourceSpan, [String init]) { | |
209 declarations.add( | |
210 new Declaration(isLocal ? 'var' : type, identifier, sourceSpan, init)); | |
211 } | |
212 | |
213 String toString() { | |
214 var printer = new Printer(null); | |
215 _build(printer); | |
216 return printer.text; | |
217 } | |
218 | |
219 void _build(Printer printer) { | |
220 if (declarations.length == 0) return; | |
221 declarations.sort(); | |
222 var lastType = null; | |
223 printer.addSpaces(2 * indent); | |
224 for (var d in declarations) { | |
225 if (d.type != lastType) { | |
226 if (lastType != null) { | |
227 printer.add(';\n'); | |
228 printer.addSpaces(2 * indent); | |
229 } | |
230 if (staticKeyword) printer.add('static '); | |
231 printer.add(d.type); | |
232 lastType = d.type; | |
233 } else { | |
234 printer.add(','); | |
235 } | |
236 printer.add(' '); | |
237 if (d.sourceSpan != null) printer.mark(d.sourceSpan); | |
238 printer.add(d.name); | |
239 if (d.initializer != null) { | |
240 printer..add(' = ')..add(d.initializer); | |
241 } | |
242 } | |
243 printer.add(';\n'); | |
244 } | |
245 } | |
OLD | NEW |