| 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 |