OLD | NEW |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 to generate code that can initialize the `StaticConfiguration` in | 5 /// Library to generate code that can initialize the `StaticConfiguration` in |
6 /// `package:smoke/static.dart`. | 6 /// `package:smoke/static.dart`. |
7 /// | 7 /// |
8 /// This library doesn't have any specific logic to extract information from | 8 /// This library doesn't have any specific logic to extract information from |
9 /// Dart source code. To extract code using the analyzer, take a look at the | 9 /// Dart source code. To extract code using the analyzer, take a look at the |
10 /// `smoke.codegen.recorder` library. | 10 /// `smoke.codegen.recorder` library. |
11 library smoke.codegen.generator; | 11 library smoke.codegen.generator; |
12 | 12 |
13 import 'dart:collection' show SplayTreeMap, SplayTreeSet; | 13 import 'dart:collection' show SplayTreeMap, SplayTreeSet; |
14 | 14 |
15 import 'package:smoke/src/common.dart' show compareLists, compareMaps; | 15 import 'package:smoke/src/common.dart' show compareLists, compareMaps; |
16 | 16 |
17 /// Collects the necessary information and generates code to initialize a | 17 /// Collects the necessary information and generates code to initialize a |
18 /// `StaticConfiguration`. After setting up the generator by calling | 18 /// `StaticConfiguration`. After setting up the generator by calling |
19 /// [addGetter], [addSetter], [addSymbol], and [addDeclaration], you can | 19 /// [addGetter], [addSetter], [addSymbol], and [addDeclaration], you can |
20 /// retrieve the generated code using the following three methods: | 20 /// retrieve the generated code using the following three methods: |
21 /// | 21 /// |
22 /// * [writeImports] writes a list of imports directives, | 22 /// * [writeImports] writes a list of imports directives, |
23 /// * [writeTopLevelDeclarations] writes additional declarations used to | 23 /// * [writeTopLevelDeclarations] writes additional declarations used to |
24 /// represent mixin classes by name in the generated code. | 24 /// represent mixin classes by name in the generated code. |
25 /// * [writeInitCall] writes the actual code that allocates the static | 25 /// * [writeStaticConfiguration] writes the actual code that allocates the |
26 /// configuration. | 26 /// static configuration. |
27 /// | 27 /// |
28 /// You'd need to include all three in your generated code, since the | 28 /// You'd need to include all three in your generated code, since the |
29 /// initialization code refers to symbols that are only available from the | 29 /// initialization code refers to symbols that are only available from the |
30 /// generated imports or the generated top-level declarations. | 30 /// generated imports or the generated top-level declarations. |
31 class SmokeCodeGenerator { | 31 class SmokeCodeGenerator { |
32 // Note: we use SplayTreeSet/Map here and below to keep generated code sorted. | 32 // Note: we use SplayTreeSet/Map here and below to keep generated code sorted. |
33 /// Names used as getters via smoke. | 33 /// Names used as getters via smoke. |
34 final Set<String> _getters = new SplayTreeSet(); | 34 final Set<String> _getters = new SplayTreeSet(); |
35 | 35 |
36 /// Names used as setters via smoke. | 36 /// Names used as setters via smoke. |
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
126 /// Register that we might try to read declarations of [type], even if no | 126 /// Register that we might try to read declarations of [type], even if no |
127 /// declaration exists. This informs the smoke system that querying for a | 127 /// declaration exists. This informs the smoke system that querying for a |
128 /// member in this class might be intentional and not an error. | 128 /// member in this class might be intentional and not an error. |
129 void addEmptyDeclaration(TypeIdentifier type) { | 129 void addEmptyDeclaration(TypeIdentifier type) { |
130 _addLibrary(type.importUrl); | 130 _addLibrary(type.importUrl); |
131 _declarations.putIfAbsent(type, | 131 _declarations.putIfAbsent(type, |
132 () => new SplayTreeMap<String, _DeclarationCode>()); | 132 () => new SplayTreeMap<String, _DeclarationCode>()); |
133 } | 133 } |
134 | 134 |
135 /// Writes to [buffer] a line for each import that is needed by the generated | 135 /// Writes to [buffer] a line for each import that is needed by the generated |
136 /// code. The code added by [writeInitCall] depends on these imports. | 136 /// code. The code added by [writeStaticConfiguration] depends on these |
| 137 /// imports. |
137 void writeImports(StringBuffer buffer) { | 138 void writeImports(StringBuffer buffer) { |
138 DEFAULT_IMPORTS.forEach((i) => buffer.writeln(i)); | 139 DEFAULT_IMPORTS.forEach((i) => buffer.writeln(i)); |
139 _libraryPrefix.forEach((url, prefix) { | 140 _libraryPrefix.forEach((url, prefix) { |
140 buffer.writeln("import '$url' as $prefix;"); | 141 buffer.writeln("import '$url' as $prefix;"); |
141 }); | 142 }); |
142 } | 143 } |
143 | 144 |
144 /// Writes to [buffer] top-level declarations that are used by the code | 145 /// Writes to [buffer] top-level declarations that are used by the code |
145 /// generated in [writeInitCall]. These are typically declarations of empty | 146 /// generated in [writeStaticConfiguration]. These are typically declarations |
146 /// classes that are then used as placeholders for mixin superclasses. | 147 /// of empty classes that are then used as placeholders for mixin |
| 148 /// superclasses. |
147 void writeTopLevelDeclarations(StringBuffer buffer) { | 149 void writeTopLevelDeclarations(StringBuffer buffer) { |
148 var types = new Set() | 150 var types = new Set() |
149 ..addAll(_parents.keys) | 151 ..addAll(_parents.keys) |
150 ..addAll(_parents.values) | 152 ..addAll(_parents.values) |
151 ..addAll(_declarations.keys) | 153 ..addAll(_declarations.keys) |
152 ..addAll(_declarations.values.expand( | 154 ..addAll(_declarations.values.expand( |
153 (m) => m.values.map((d) => d.type))) | 155 (m) => m.values.map((d) => d.type))) |
154 ..removeWhere((t) => t.importUrl != null); | 156 ..removeWhere((t) => t.importUrl != null); |
155 for (var type in types) { | 157 for (var type in types) { |
156 buffer.write('abstract class ${type.name} {}'); | 158 buffer.write('abstract class ${type.name} {}'); |
157 if (type.comment != null) buffer.write(' // ${type.comment}'); | 159 if (type.comment != null) buffer.write(' // ${type.comment}'); |
158 buffer.writeln(); | 160 buffer.writeln(); |
159 } | 161 } |
160 } | 162 } |
161 | 163 |
162 /// Appends to [buffer] code that will initialize smoke's static | 164 /// Appends to [buffer] code that will create smoke's static configuration. |
163 /// configuration. For example, the code might be of the form: | 165 /// For example, the code might be of the form: |
164 /// | 166 /// |
165 /// useGeneratedCode(new StaticConfiguration( | 167 /// new StaticConfiguration( |
166 /// getters: { | 168 /// getters: { |
167 /// #i: (o) => o.i, | 169 /// #i: (o) => o.i, |
168 /// ... | 170 /// ... |
169 /// names: { | 171 /// names: { |
170 /// #i: "i", | 172 /// #i: "i", |
171 /// })); | 173 /// }) |
| 174 /// |
| 175 /// Callers of this code can assign this expression to a variable, and should |
| 176 /// generate code that invokes `useGeneratedCode`. |
172 /// | 177 /// |
173 /// The optional [indent] argument is used for formatting purposes. All | 178 /// The optional [indent] argument is used for formatting purposes. All |
174 /// entries in each map (getters, setters, names, declarations, parents) are | 179 /// entries in each map (getters, setters, names, declarations, parents) are |
175 /// sorted alphabetically. | 180 /// sorted alphabetically. |
176 /// | 181 /// |
177 /// **Note**: this code assumes that imports from [writeImports] and top-level | 182 /// **Note**: this code assumes that imports from [writeImports] and top-level |
178 /// declarations from [writeTopLevelDeclarations] are included in the same | 183 /// declarations from [writeTopLevelDeclarations] are included in the same |
179 /// library where this code will live. | 184 /// library where this code will live. |
180 void writeInitCall(StringBuffer buffer, [int indent = 2]) { | 185 void writeStaticConfiguration(StringBuffer buffer, [int indent = 2]) { |
181 final spaces = new List.filled(indent, ' ').join(''); | 186 final spaces = ' ' * (indent + 4); |
182 var args = {}; | 187 var args = {}; |
183 | 188 |
184 if (_getters.isNotEmpty) { | 189 if (_getters.isNotEmpty) { |
185 args['getters'] = _getters.map((n) => '${_symbol(n)}: (o) => o.$n'); | 190 args['getters'] = _getters.map((n) => '${_symbol(n)}: (o) => o.$n'); |
186 } | 191 } |
187 if (_setters.isNotEmpty) { | 192 if (_setters.isNotEmpty) { |
188 args['setters'] = _setters.map( | 193 args['setters'] = _setters.map( |
189 (n) => '${_symbol(n)}: (o, v) { o.$n = v; }'); | 194 (n) => '${_symbol(n)}: (o, v) { o.$n = v; }'); |
190 } | 195 } |
191 | 196 |
192 if (_parents.isNotEmpty) { | 197 if (_parents.isNotEmpty) { |
193 var parentsMap = []; | 198 var parentsMap = []; |
194 _parents.forEach((child, parent) { | 199 _parents.forEach((child, parent) { |
195 var parent = _parents[child]; | 200 var parent = _parents[child]; |
196 parentsMap.add('${child.asCode(_libraryPrefix)}: ' | 201 parentsMap.add('${child.asCode(_libraryPrefix)}: ' |
197 '${parent.asCode(_libraryPrefix)}'); | 202 '${parent.asCode(_libraryPrefix)}'); |
198 }); | 203 }); |
199 args['parents'] = parentsMap; | 204 args['parents'] = parentsMap; |
200 } | 205 } |
201 | 206 |
202 if (_declarations.isNotEmpty) { | 207 if (_declarations.isNotEmpty) { |
203 var declarations = []; | 208 var declarations = []; |
204 _declarations.forEach((type, members) { | 209 _declarations.forEach((type, members) { |
205 final sb = new StringBuffer() | 210 final sb = new StringBuffer() |
206 ..write(type.asCode(_libraryPrefix)) | 211 ..write(type.asCode(_libraryPrefix)) |
207 ..write(': '); | 212 ..write(': '); |
208 if (members.isEmpty) { | 213 if (members.isEmpty) { |
209 sb.write('const {}'); | 214 sb.write('{}'); |
210 } else { | 215 } else { |
211 sb.write('{\n'); | 216 sb.write('{\n'); |
212 members.forEach((name, decl) { | 217 members.forEach((name, decl) { |
213 var decl = members[name].asCode(_libraryPrefix); | 218 var decl = members[name].asCode(_libraryPrefix); |
214 sb.write('$spaces ${_symbol(name)}: $decl,\n'); | 219 sb.write('${spaces} ${_symbol(name)}: $decl,\n'); |
215 }); | 220 }); |
216 sb.write('$spaces }'); | 221 sb.write('${spaces} }'); |
217 } | 222 } |
218 declarations.add(sb.toString()); | 223 declarations.add(sb.toString()); |
219 }); | 224 }); |
220 args['declarations'] = declarations; | 225 args['declarations'] = declarations; |
221 } | 226 } |
222 | 227 |
223 if (_staticMethods.isNotEmpty) { | 228 if (_staticMethods.isNotEmpty) { |
224 var methods = []; | 229 var methods = []; |
225 _staticMethods.forEach((type, members) { | 230 _staticMethods.forEach((type, members) { |
226 var className = type.asCode(_libraryPrefix); | 231 var className = type.asCode(_libraryPrefix); |
227 final sb = new StringBuffer() | 232 final sb = new StringBuffer() |
228 ..write(className) | 233 ..write(className) |
229 ..write(': '); | 234 ..write(': '); |
230 if (members.isEmpty) { | 235 if (members.isEmpty) { |
231 sb.write('const {}'); | 236 sb.write('{}'); |
232 } else { | 237 } else { |
233 sb.write('{\n'); | 238 sb.write('{\n'); |
234 for (var name in members) { | 239 for (var name in members) { |
235 sb.write('$spaces ${_symbol(name)}: $className.$name,\n'); | 240 sb.write('${spaces} ${_symbol(name)}: $className.$name,\n'); |
236 } | 241 } |
237 sb.write('$spaces }'); | 242 sb.write('${spaces} }'); |
238 } | 243 } |
239 methods.add(sb.toString()); | 244 methods.add(sb.toString()); |
240 }); | 245 }); |
241 args['staticMethods'] = methods; | 246 args['staticMethods'] = methods; |
242 } | 247 } |
243 | 248 |
244 if (_names.isNotEmpty) { | 249 if (_names.isNotEmpty) { |
245 args['names'] = _names.map((n) => "${_symbol(n)}: r'$n'"); | 250 args['names'] = _names.map((n) => "${_symbol(n)}: r'$n'"); |
246 } | 251 } |
247 | 252 |
248 buffer..write(spaces) | 253 buffer..writeln('new StaticConfiguration(') |
249 ..writeln('useGeneratedCode(new StaticConfiguration(') | 254 ..write('${spaces}checkedMode: false'); |
250 ..write('$spaces checkedMode: false'); | |
251 | 255 |
252 args.forEach((name, mapContents) { | 256 args.forEach((name, mapContents) { |
253 buffer.writeln(','); | 257 buffer.writeln(','); |
254 // TODO(sigmund): use const map when Type can be keys (dartbug.com/17123) | 258 // TODO(sigmund): use const map when Type can be keys (dartbug.com/17123) |
255 buffer.writeln('$spaces $name: {'); | 259 buffer.writeln('${spaces}$name: {'); |
256 for (var entry in mapContents) { | 260 for (var entry in mapContents) { |
257 buffer.writeln('$spaces $entry,'); | 261 buffer.writeln('${spaces} $entry,'); |
258 } | 262 } |
259 buffer.write('$spaces }'); | 263 buffer.write('${spaces}}'); |
260 }); | 264 }); |
261 buffer.writeln('));'); | 265 buffer.write(')'); |
262 } | 266 } |
263 | 267 |
264 /// Adds a library that needs to be imported. | 268 /// Adds a library that needs to be imported. |
265 void _addLibrary(String url) { | 269 void _addLibrary(String url) { |
266 if (url == null || url == 'dart:core') return; | 270 if (url == null || url == 'dart:core') return; |
267 _libraryPrefix.putIfAbsent(url, () => 'smoke_${_libraryPrefix.length}'); | 271 _libraryPrefix.putIfAbsent(url, () => 'smoke_${_libraryPrefix.length}'); |
268 } | 272 } |
269 } | 273 } |
270 | 274 |
271 /// Information used to generate code that allocates a `Declaration` object. | 275 /// Information used to generate code that allocates a `Declaration` object. |
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
486 '^(?:$_publicIdentifierRE(?:\$|[.](?!\$)))+?\$'); | 490 '^(?:$_publicIdentifierRE(?:\$|[.](?!\$)))+?\$'); |
487 | 491 |
488 /// Operator names allowed as symbols. The name of the oeprators is the same as | 492 /// Operator names allowed as symbols. The name of the oeprators is the same as |
489 /// the operator itself except for unary minus, where the name is "unary-". | 493 /// the operator itself except for unary minus, where the name is "unary-". |
490 const String _operatorRE = | 494 const String _operatorRE = |
491 r'(?:[\-+*/%&|^]|\[\]=?|==|~/?|<[<=]?|>[>=]?|unary-)'; | 495 r'(?:[\-+*/%&|^]|\[\]=?|==|~/?|<[<=]?|>[>=]?|unary-)'; |
492 | 496 |
493 /// Pattern that matches public symbols. | 497 /// Pattern that matches public symbols. |
494 final RegExp _publicSymbolPattern = new RegExp( | 498 final RegExp _publicSymbolPattern = new RegExp( |
495 '^(?:$_operatorRE\$|$_publicIdentifierRE(?:=?\$|[.](?!\$)))+?\$'); | 499 '^(?:$_operatorRE\$|$_publicIdentifierRE(?:=?\$|[.](?!\$)))+?\$'); |
OLD | NEW |