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 /** | 5 /** |
6 * This library contains the infrastructure to parse and integrate patch files. | 6 * This library contains the infrastructure to parse and integrate patch files. |
7 * | 7 * |
8 * Three types of elements can be patched: [LibraryElement], [ClassElement], | 8 * Three types of elements can be patched: [LibraryElement], [ClassElement], |
9 * [FunctionElement]. Patches are introduced in patch libraries which are loaded | 9 * [FunctionElement]. Patches are introduced in patch libraries which are loaded |
10 * together with the corresponding origin library. Which libraries that are | 10 * together with the corresponding origin library. Which libraries that are |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
54 * class PatchedClass { // An origin class. | 54 * class PatchedClass { // An origin class. |
55 * int regularField; // A regular field. | 55 * int regularField; // A regular field. |
56 * void regularMethod() {} // A regular method. | 56 * void regularMethod() {} // A regular method. |
57 * external void patchedMethod(); // An origin method. | 57 * external void patchedMethod(); // An origin method. |
58 * } | 58 * } |
59 * | 59 * |
60 * // In the patch library: | 60 * // In the patch library: |
61 * class _InjectedClass { // An injected class. | 61 * class _InjectedClass { // An injected class. |
62 * void _injectedMethod() {} // An injected method. | 62 * void _injectedMethod() {} // An injected method. |
63 * } | 63 * } |
64 * patch class PatchedClass { // A patch class. | 64 * @patch class PatchedClass { // A patch class. |
65 * int _injectedField; { // An injected field. | 65 * int _injectedField; { // An injected field. |
66 * patch void patchedMethod() {} // A patch method. | 66 * @patch void patchedMethod() {} // A patch method. |
67 * } | 67 * } |
68 * | 68 * |
69 * | 69 * |
70 * ## Declaration and implementation ## | 70 * ## Declaration and implementation ## |
71 * | 71 * |
72 * With patches we have two views on elements: as the 'declaration' which | 72 * With patches we have two views on elements: as the 'declaration' which |
73 * introduces the entity and defines its interface, and as the 'implementation' | 73 * introduces the entity and defines its interface, and as the 'implementation' |
74 * which defines the actual implementation of the entity. | 74 * which defines the actual implementation of the entity. |
75 * | 75 * |
76 * Every element has a 'declaration' and an 'implementation' element. For | 76 * Every element has a 'declaration' and an 'implementation' element. For |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
111 * - Work on function parameters is performed on the declaration of the function | 111 * - Work on function parameters is performed on the declaration of the function |
112 * element. | 112 * element. |
113 */ | 113 */ |
114 | 114 |
115 library patchparser; | 115 library patchparser; |
116 | 116 |
117 import 'dart:async'; | 117 import 'dart:async'; |
118 | 118 |
119 import "tree/tree.dart" as tree; | 119 import "tree/tree.dart" as tree; |
120 import "dart2jslib.dart" as leg; // CompilerTask, Compiler. | 120 import "dart2jslib.dart" as leg; // CompilerTask, Compiler. |
| 121 import "helpers/helpers.dart"; |
121 import "scanner/scannerlib.dart"; // Scanner, Parsers, Listeners | 122 import "scanner/scannerlib.dart"; // Scanner, Parsers, Listeners |
122 import "elements/elements.dart"; | 123 import "elements/elements.dart"; |
123 import "elements/modelx.dart" | 124 import "elements/modelx.dart" |
124 show LibraryElementX, | 125 show LibraryElementX, |
125 MetadataAnnotationX, | 126 MetadataAnnotationX, |
126 ClassElementX, | 127 ClassElementX, |
127 FunctionElementX; | 128 FunctionElementX; |
128 import 'util/util.dart'; | 129 import 'util/util.dart'; |
129 | 130 |
130 class PatchParserTask extends leg.CompilerTask { | 131 class PatchParserTask extends leg.CompilerTask { |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
162 } | 163 } |
163 | 164 |
164 void scanLibraryElements( | 165 void scanLibraryElements( |
165 CompilationUnitElement compilationUnit, | 166 CompilationUnitElement compilationUnit, |
166 LinkBuilder<tree.LibraryTag> imports) { | 167 LinkBuilder<tree.LibraryTag> imports) { |
167 measure(() { | 168 measure(() { |
168 // TODO(lrn): Possibly recursively handle 'part' directives in patch. | 169 // TODO(lrn): Possibly recursively handle 'part' directives in patch. |
169 leg.Script script = compilationUnit.script; | 170 leg.Script script = compilationUnit.script; |
170 Token tokens = new Scanner(script.file).tokenize(); | 171 Token tokens = new Scanner(script.file).tokenize(); |
171 Function idGenerator = compiler.getNextFreeClassId; | 172 Function idGenerator = compiler.getNextFreeClassId; |
172 PatchListener patchListener = | 173 Listener patchListener = new PatchElementListener(compiler, |
173 new PatchElementListener(compiler, | 174 compilationUnit, |
174 compilationUnit, | 175 idGenerator, |
175 idGenerator, | 176 imports); |
176 imports); | 177 new PartialParser(patchListener).parseUnit(tokens); |
177 new PatchParser(patchListener).parseUnit(tokens); | |
178 }); | 178 }); |
179 } | 179 } |
180 | 180 |
181 void parsePatchClassNode(PartialClassElement element) { | 181 void parsePatchClassNode(PartialClassElement element) { |
182 // Parse [PartialClassElement] using a "patch"-aware parser instead | 182 // Parse [PartialClassElement] using a "patch"-aware parser instead |
183 // of calling its [parseNode] method. | 183 // of calling its [parseNode] method. |
184 if (element.cachedNode != null) return; | 184 if (element.cachedNode != null) return; |
185 | 185 |
186 measure(() => compiler.withCurrentElement(element, () { | 186 measure(() => compiler.withCurrentElement(element, () { |
187 PatchMemberListener listener = new PatchMemberListener(compiler, element); | 187 MemberListener listener = new MemberListener(compiler, element); |
188 Parser parser = new PatchClassElementParser(listener); | 188 Parser parser = new PatchClassElementParser(listener); |
189 Token token = parser.parseTopLevelDeclaration(element.beginToken); | 189 Token token = parser.parseTopLevelDeclaration(element.beginToken); |
190 assert(identical(token, element.endToken.next)); | 190 assert(identical(token, element.endToken.next)); |
191 element.cachedNode = listener.popNode(); | 191 element.cachedNode = listener.popNode(); |
192 assert(listener.nodes.isEmpty); | 192 assert(listener.nodes.isEmpty); |
193 | 193 |
194 Link<Element> patches = element.localMembers; | 194 Link<Element> patches = element.localMembers; |
195 applyContainerPatch(element.origin, patches); | 195 applyContainerPatch(element.origin, patches); |
196 })); | 196 })); |
197 } | 197 } |
198 | 198 |
199 void applyContainerPatch(ClassElement originClass, | 199 void applyContainerPatch(ClassElement originClass, |
200 Link<Element> patches) { | 200 Link<Element> patches) { |
201 for (Element patch in patches) { | 201 for (Element patch in patches) { |
202 if (!isPatchElement(patch)) continue; | 202 if (!isPatchElement(compiler, patch)) continue; |
203 | 203 |
204 Element origin = originClass.localLookup(patch.name); | 204 Element origin = originClass.localLookup(patch.name); |
205 patchElement(compiler, origin, patch); | 205 patchElement(compiler, origin, patch); |
206 } | 206 } |
207 } | 207 } |
208 } | 208 } |
209 | 209 |
210 /** | 210 /** |
211 * Extension of the [Listener] interface to handle the extra "patch" pseudo- | |
212 * keyword in patch files. | |
213 * Patch files shouldn't have a type named "patch". | |
214 */ | |
215 abstract class PatchListener extends Listener { | |
216 void beginPatch(Token patch); | |
217 void endPatch(Token patch); | |
218 } | |
219 | |
220 /** | |
221 * Partial parser that extends the top-level and class grammars to allow the | |
222 * word "patch" in front of some declarations. | |
223 */ | |
224 class PatchParser extends PartialParser { | |
225 PatchParser(PatchListener listener) : super(listener); | |
226 | |
227 PatchListener get patchListener => listener; | |
228 | |
229 bool isPatch(Token token) => token.value == 'patch'; | |
230 | |
231 /** | |
232 * Parse top-level declarations, and allow "patch" in front of functions | |
233 * and classes. | |
234 */ | |
235 Token parseTopLevelDeclaration(Token token) { | |
236 if (!isPatch(token)) { | |
237 return super.parseTopLevelDeclaration(token); | |
238 } | |
239 Token patch = token; | |
240 token = token.next; | |
241 String value = token.stringValue; | |
242 if (identical(value, 'interface') | |
243 || identical(value, 'typedef') | |
244 || identical(value, '#') | |
245 || identical(value, 'abstract')) { | |
246 // At the top level, you can only patch functions and classes. | |
247 // Patch classes and functions can't be marked abstract. | |
248 return listener.unexpected(patch); | |
249 } | |
250 patchListener.beginPatch(patch); | |
251 token = super.parseTopLevelDeclaration(token); | |
252 patchListener.endPatch(patch); | |
253 return token; | |
254 } | |
255 | |
256 /** | |
257 * Parse a class member. | |
258 * If the member starts with "patch", it's a member override. | |
259 * Only methods can be overridden, including constructors, getters and | |
260 * setters, but not fields. If "patch" occurs in front of a field, the error | |
261 * is caught elsewhere. | |
262 */ | |
263 Token parseMember(Token token) { | |
264 if (!isPatch(token)) { | |
265 return super.parseMember(token); | |
266 } | |
267 Token patch = token; | |
268 patchListener.beginPatch(patch); | |
269 token = super.parseMember(token.next); | |
270 patchListener.endPatch(patch); | |
271 return token; | |
272 } | |
273 } | |
274 | |
275 /** | |
276 * Partial parser for patch files that also handles the members of class | 211 * Partial parser for patch files that also handles the members of class |
277 * declarations. | 212 * declarations. |
278 */ | 213 */ |
279 class PatchClassElementParser extends PatchParser { | 214 class PatchClassElementParser extends PartialParser { |
280 PatchClassElementParser(PatchListener listener) : super(listener); | 215 PatchClassElementParser(Listener listener) : super(listener); |
281 | 216 |
282 Token parseClassBody(Token token) => fullParseClassBody(token); | 217 Token parseClassBody(Token token) => fullParseClassBody(token); |
283 } | 218 } |
284 | 219 |
285 /** | 220 /** |
286 * Extension of [ElementListener] for parsing patch files. | 221 * Extension of [ElementListener] for parsing patch files. |
287 */ | 222 */ |
288 class PatchElementListener extends ElementListener implements PatchListener { | 223 class PatchElementListener extends ElementListener implements Listener { |
| 224 final leg.Compiler compiler; |
289 final LinkBuilder<tree.LibraryTag> imports; | 225 final LinkBuilder<tree.LibraryTag> imports; |
290 bool isMemberPatch = false; | |
291 bool isClassPatch = false; | |
292 | 226 |
293 PatchElementListener(leg.DiagnosticListener listener, | 227 PatchElementListener(leg.Compiler compiler, |
294 CompilationUnitElement patchElement, | 228 CompilationUnitElement patchElement, |
295 int idGenerator(), | 229 int idGenerator(), |
296 this.imports) | 230 this.imports) |
297 : super(listener, patchElement, idGenerator); | 231 : this.compiler = compiler, |
298 | 232 super(compiler, patchElement, idGenerator); |
299 MetadataAnnotation popMetadataHack() { | |
300 // TODO(ahe): Remove this method. | |
301 popNode(); // Discard null. | |
302 return new PatchMetadataAnnotation(); | |
303 } | |
304 | |
305 void beginPatch(Token token) { | |
306 if (identical(token.next.stringValue, "class")) { | |
307 isClassPatch = true; | |
308 } else { | |
309 isMemberPatch = true; | |
310 } | |
311 handleIdentifier(token); | |
312 } | |
313 | |
314 void endPatch(Token token) { | |
315 if (identical(token.next.stringValue, "class")) { | |
316 isClassPatch = false; | |
317 } else { | |
318 isMemberPatch = false; | |
319 } | |
320 } | |
321 | 233 |
322 /** | 234 /** |
323 * Allow script tags (import only, the parser rejects the rest for now) in | 235 * Allow script tags (import only, the parser rejects the rest for now) in |
324 * patch files. The import tags will be added to the library. | 236 * patch files. The import tags will be added to the library. |
325 */ | 237 */ |
326 bool allowLibraryTags() => true; | 238 bool allowLibraryTags() => true; |
327 | 239 |
328 void addLibraryTag(tree.LibraryTag tag) { | 240 void addLibraryTag(tree.LibraryTag tag) { |
329 super.addLibraryTag(tag); | 241 super.addLibraryTag(tag); |
330 imports.addLast(tag); | 242 imports.addLast(tag); |
331 } | 243 } |
332 | 244 |
333 void pushElement(Element patch) { | 245 void pushElement(Element patch) { |
334 if (isMemberPatch || (isClassPatch && patch is ClassElement)) { | 246 super.pushElement(patch); |
335 // Apply patch. | 247 if (isPatchElement(compiler, patch)) { |
336 patch.addMetadata(popMetadataHack()); | |
337 LibraryElement originLibrary = compilationUnitElement.library; | 248 LibraryElement originLibrary = compilationUnitElement.library; |
338 assert(originLibrary.isPatched); | 249 assert(originLibrary.isPatched); |
339 Element origin = originLibrary.localLookup(patch.name); | 250 Element origin = originLibrary.localLookup(patch.name); |
340 patchElement(listener, origin, patch); | 251 patchElement(listener, origin, patch); |
341 } | 252 } |
342 super.pushElement(patch); | |
343 } | 253 } |
344 } | 254 } |
345 | 255 |
346 /** | |
347 * Extension of [MemberListener] for parsing patch class bodies. | |
348 */ | |
349 class PatchMemberListener extends MemberListener implements PatchListener { | |
350 bool isMemberPatch = false; | |
351 bool isClassPatch = false; | |
352 PatchMemberListener(leg.DiagnosticListener listener, | |
353 Element enclosingElement) | |
354 : super(listener, enclosingElement); | |
355 | |
356 MetadataAnnotation popMetadataHack() { | |
357 // TODO(ahe): Remove this method. | |
358 popNode(); // Discard null. | |
359 return new PatchMetadataAnnotation(); | |
360 } | |
361 | |
362 void beginPatch(Token token) { | |
363 if (identical(token.next.stringValue, "class")) { | |
364 isClassPatch = true; | |
365 } else { | |
366 isMemberPatch = true; | |
367 } | |
368 handleIdentifier(token); | |
369 } | |
370 | |
371 void endPatch(Token token) { | |
372 if (identical(token.next.stringValue, "class")) { | |
373 isClassPatch = false; | |
374 } else { | |
375 isMemberPatch = false; | |
376 } | |
377 } | |
378 | |
379 void addMember(Element element) { | |
380 if (isMemberPatch || (isClassPatch && element is ClassElement)) { | |
381 element.addMetadata(popMetadataHack()); | |
382 } | |
383 super.addMember(element); | |
384 } | |
385 } | |
386 | |
387 // TODO(ahe): Get rid of this class. | |
388 class PatchMetadataAnnotation extends MetadataAnnotationX { | |
389 final leg.Constant value = null; | |
390 | |
391 PatchMetadataAnnotation() : super(STATE_DONE); | |
392 | |
393 tree.Node parseNode(leg.DiagnosticListener listener) => null; | |
394 | |
395 Token get beginToken => null; | |
396 Token get endToken => null; | |
397 } | |
398 | |
399 void patchElement(leg.DiagnosticListener listener, | 256 void patchElement(leg.DiagnosticListener listener, |
400 Element origin, | 257 Element origin, |
401 Element patch) { | 258 Element patch) { |
402 if (origin == null) { | 259 if (origin == null) { |
403 listener.reportError( | 260 listener.reportError( |
404 patch, leg.MessageKind.PATCH_NON_EXISTING, {'name': patch.name}); | 261 patch, leg.MessageKind.PATCH_NON_EXISTING, {'name': patch.name}); |
405 return; | 262 return; |
406 } | 263 } |
407 if (!(origin.isClass || | 264 if (!(origin.isClass || |
408 origin.isConstructor || | 265 origin.isConstructor || |
409 origin.isFunction || | 266 origin.isFunction || |
410 origin.isAbstractField)) { | 267 origin.isAbstractField)) { |
411 // TODO(ahe): Remove this error when the parser rejects all bad modifiers. | 268 // TODO(ahe): Remove this error when the parser rejects all bad modifiers. |
(...skipping 127 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
539 return; | 396 return; |
540 } | 397 } |
541 if (origin.isPatched) { | 398 if (origin.isPatched) { |
542 listener.internalError(origin, | 399 listener.internalError(origin, |
543 "Trying to patch a function more than once."); | 400 "Trying to patch a function more than once."); |
544 } | 401 } |
545 origin.applyPatch(patch); | 402 origin.applyPatch(patch); |
546 } | 403 } |
547 | 404 |
548 // TODO(johnniwinther): Add unittest when patch is (real) metadata. | 405 // TODO(johnniwinther): Add unittest when patch is (real) metadata. |
549 bool isPatchElement(Element element) { | 406 bool isPatchElement(leg.Compiler compiler, Element element) { |
550 // TODO(lrn): More checks needed if we introduce metadata for real. | 407 // TODO(lrn): More checks needed if we introduce metadata for real. |
551 // In that case, it must have the identifier "native" as metadata. | 408 // In that case, it must have the identifier "native" as metadata. |
552 for (Link link = element.metadata; !link.isEmpty; link = link.tail) { | 409 for (Link<MetadataAnnotation> link = element.metadata; |
553 if (link.head is PatchMetadataAnnotation) return true; | 410 !link.isEmpty; |
| 411 link = link.tail) { |
| 412 MetadataAnnotation annotation = link.head; |
| 413 if (annotation.beginToken != null && |
| 414 annotation.beginToken.next.value == 'patch') { |
| 415 // TODO(johnniwinther): Perform this check in |
| 416 // [Compiler.onLibrariesLoaded]. |
| 417 compiler.enqueuer.resolution.addDeferredAction(element, () { |
| 418 annotation.ensureResolved(compiler); |
| 419 if (annotation.value != compiler.patchConstant) { |
| 420 compiler.internalError(annotation, 'Invalid patch annotation.'); |
| 421 } |
| 422 }); |
| 423 return true; |
| 424 } |
554 } | 425 } |
555 return false; | 426 return false; |
556 } | 427 } |
OLD | NEW |