Index: frog/leg/native_emitter.dart |
=================================================================== |
--- frog/leg/native_emitter.dart (revision 5925) |
+++ frog/leg/native_emitter.dart (working copy) |
@@ -1,356 +0,0 @@ |
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-class NativeEmitter { |
- |
- Compiler compiler; |
- StringBuffer buffer; |
- |
- // Classes that participate in dynamic dispatch. These are the |
- // classes that contain used members. |
- Set<ClassElement> classesWithDynamicDispatch; |
- |
- // Native classes found in the application. |
- Set<ClassElement> nativeClasses; |
- |
- // Caches the direct native subtypes of a native class. |
- Map<ClassElement, List<ClassElement>> subtypes; |
- |
- // Caches the native methods that are overridden by a native class. |
- // Note that the method that overrides does not have to be native: |
- // it's the overridden method that must make sure it will dispatch |
- // to its subclass if it sees an instance whose class is a subclass. |
- Set<FunctionElement> overriddenMethods; |
- |
- NativeEmitter(this.compiler) |
- : classesWithDynamicDispatch = new Set<ClassElement>(), |
- nativeClasses = new Set<ClassElement>(), |
- subtypes = new Map<ClassElement, List<ClassElement>>(), |
- overriddenMethods = new Set<FunctionElement>(), |
- buffer = new StringBuffer(); |
- |
- String get dynamicName() { |
- Element element = compiler.findHelper( |
- const SourceString('dynamicFunction')); |
- return compiler.namer.isolateAccess(element); |
- } |
- |
- String get dynamicSetMetadataName() { |
- Element element = compiler.findHelper( |
- const SourceString('dynamicSetMetadata')); |
- return compiler.namer.isolateAccess(element); |
- } |
- |
- String get typeNameOfName() { |
- Element element = compiler.findHelper( |
- const SourceString('getTypeNameOf')); |
- return compiler.namer.isolateAccess(element); |
- } |
- |
- String get defPropName() { |
- Element element = compiler.findHelper( |
- const SourceString('defineProperty')); |
- return compiler.namer.isolateAccess(element); |
- } |
- |
- String get toStringHelperName() { |
- Element element = compiler.findHelper( |
- const SourceString('toStringForNativeObject')); |
- return compiler.namer.isolateAccess(element); |
- } |
- |
- void generateNativeLiteral(ClassElement classElement) { |
- String quotedNative = classElement.nativeName.slowToString(); |
- String nativeCode = quotedNative.substring(2, quotedNative.length - 1); |
- String className = compiler.namer.getName(classElement); |
- buffer.add(className); |
- buffer.add(' = '); |
- buffer.add(nativeCode); |
- buffer.add(';\n'); |
- |
- String attachTo(name) => "$className.$name"; |
- |
- for (Element member in classElement.members) { |
- if (member.isInstanceMember()) { |
- compiler.emitter.addInstanceMember( |
- member, attachTo, buffer, isNative: true); |
- } |
- } |
- } |
- |
- bool isNativeLiteral(String quotedName) { |
- return quotedName[1] === '='; |
- } |
- |
- bool isNativeGlobal(String quotedName) { |
- return quotedName[1] === '@'; |
- } |
- |
- String toNativeName(ClassElement cls) { |
- String quotedName = cls.nativeName.slowToString(); |
- if (isNativeGlobal(quotedName)) { |
- // Global object, just be like the other types for now. |
- return quotedName.substring(3, quotedName.length - 1); |
- } else { |
- return quotedName.substring(2, quotedName.length - 1); |
- } |
- } |
- |
- void generateNativeClass(ClassElement classElement) { |
- nativeClasses.add(classElement); |
- |
- assert(classElement.backendMembers.isEmpty()); |
- String quotedName = classElement.nativeName.slowToString(); |
- if (isNativeLiteral(quotedName)) { |
- generateNativeLiteral(classElement); |
- // The native literal kind needs to be dealt with specially when |
- // generating code for it. |
- return; |
- } |
- |
- String nativeName = toNativeName(classElement); |
- bool hasUsedSelectors = false; |
- |
- String attachTo(String name) { |
- hasUsedSelectors = true; |
- return "$dynamicName('$name').$nativeName"; |
- } |
- |
- for (Element member in classElement.members) { |
- if (member.isInstanceMember()) { |
- compiler.emitter.addInstanceMember( |
- member, attachTo, buffer, isNative: true); |
- } |
- } |
- |
- compiler.emitter.generateTypeTests(classElement, (Element other) { |
- assert(requiresNativeIsCheck(other)); |
- buffer.add('${attachTo(compiler.namer.operatorIs(other))} = '); |
- buffer.add('function() { return true; };\n'); |
- }); |
- |
- if (hasUsedSelectors) classesWithDynamicDispatch.add(classElement); |
- } |
- |
- List<ClassElement> getDirectSubclasses(ClassElement cls) { |
- List<ClassElement> result = subtypes[cls]; |
- if (result === null) result = const<ClassElement>[]; |
- return result; |
- } |
- |
- void emitParameterStub(Element member, |
- String invocationName, |
- String stubParameters, |
- List<String> argumentsBuffer, |
- int indexOfLastOptionalArgumentInParameters) { |
- // The target JS function may check arguments.length so we need to |
- // make sure not to pass any unspecified optional arguments to it. |
- // For example, for the following Dart method: |
- // foo([x, y, z]); |
- // The call: |
- // foo(y: 1) |
- // must be turned into a JS call to: |
- // foo(null, y). |
- |
- List<String> nativeArgumentsBuffer = argumentsBuffer.getRange( |
- 0, indexOfLastOptionalArgumentInParameters + 1); |
- |
- ClassElement classElement = member.enclosingElement; |
- String nativeName = classElement.nativeName.slowToString(); |
- String nativeArguments = Strings.join(nativeArgumentsBuffer, ","); |
- |
- if (isNativeLiteral(nativeName) || !overriddenMethods.contains(member)) { |
- // Call the method directly. |
- buffer.add(' return this.${member.name.slowToString()}'); |
- buffer.add('($nativeArguments)'); |
- return; |
- } |
- |
- // If the method is overridden, we must check if the prototype of |
- // 'this' has the method available. Otherwise, we may end up |
- // calling the method from the super class. If the method is not |
- // available, we make a direct call to |
- // Object.prototype.$invocationName. This method will patch the |
- // prototype of 'this' to the real method. |
- |
- buffer.add(' if (Object.getPrototypeOf(this).hasOwnProperty('); |
- buffer.add("'$invocationName')) {\n"); |
- buffer.add(' return this.${member.name.slowToString()}'); |
- buffer.add('($nativeArguments)'); |
- buffer.add('\n }\n'); |
- buffer.add(' return Object.prototype.$invocationName.call(this'); |
- buffer.add(stubParameters == '' ? '' : ', $stubParameters'); |
- buffer.add(');'); |
- } |
- |
- void emitDynamicDispatchMetadata() { |
- if (classesWithDynamicDispatch.isEmpty()) return; |
- buffer.add('// ${classesWithDynamicDispatch.length} dynamic classes.\n'); |
- |
- // Build a pre-order traversal over all the classes and their subclasses. |
- Set<ClassElement> seen = new Set<ClassElement>(); |
- List<ClassElement> classes = <ClassElement>[]; |
- void visit(ClassElement cls) { |
- if (seen.contains(cls)) return; |
- seen.add(cls); |
- for (final ClassElement subclass in getDirectSubclasses(cls)) { |
- visit(subclass); |
- } |
- classes.add(cls); |
- } |
- for (final ClassElement cls in classesWithDynamicDispatch) { |
- visit(cls); |
- } |
- |
- Collection<ClassElement> dispatchClasses = classes.filter( |
- (cls) => !getDirectSubclasses(cls).isEmpty() && |
- classesWithDynamicDispatch.contains(cls)); |
- |
- buffer.add('// ${classes.length} classes\n'); |
- Collection<ClassElement> classesThatHaveSubclasses = classes.filter( |
- (ClassElement t) => !getDirectSubclasses(t).isEmpty()); |
- buffer.add('// ${classesThatHaveSubclasses.length} !leaf\n'); |
- |
- // Generate code that builds the map from cls tags used in dynamic dispatch |
- // to the set of cls tags of classes that extend (TODO: or implement) those |
- // classes. The set is represented as a string of tags joined with '|'. |
- // This is easily split into an array of tags, or converted into a regexp. |
- // |
- // To reduce the size of the sets, subsets are CSE-ed out into variables. |
- // The sets could be much smaller if we could make assumptions about the |
- // cls tags of other classes (which are constructor names or part of the |
- // result of Object.protocls.toString). For example, if objects that are |
- // Dart objects could be easily excluded, then we might be able to simplify |
- // the test, replacing dozens of HTMLxxxElement classes with the regexp |
- // /HTML.*Element/. |
- |
- // Temporary variables for common substrings. |
- List<String> varNames = <String>[]; |
- // var -> expression |
- Map<String, String> varDefns = <String>{}; |
- // tag -> expression (a string or a variable) |
- Map<ClassElement, String> tagDefns = new Map<ClassElement, String>(); |
- |
- String makeExpression(ClassElement cls) { |
- // Expression fragments for this set of cls keys. |
- List<String> expressions = <String>[]; |
- // TODO: Remove if cls is abstract. |
- List<String> subtags = [toNativeName(cls)]; |
- void walk(ClassElement cls) { |
- for (final ClassElement subclass in getDirectSubclasses(cls)) { |
- ClassElement tag = subclass; |
- String existing = tagDefns[tag]; |
- if (existing == null) { |
- subtags.add(toNativeName(tag)); |
- walk(subclass); |
- } else { |
- if (varDefns.containsKey(existing)) { |
- expressions.add(existing); |
- } else { |
- String varName = 'v${varNames.length}/*${tag}*/'; |
- varNames.add(varName); |
- varDefns[varName] = existing; |
- tagDefns[tag] = varName; |
- expressions.add(varName); |
- } |
- } |
- } |
- } |
- walk(cls); |
- String constantPart = "'${Strings.join(subtags, '|')}'"; |
- if (constantPart != "''") expressions.add(constantPart); |
- String expression; |
- if (expressions.length == 1) { |
- expression = expressions[0]; |
- } else { |
- expression = "[${Strings.join(expressions, ',')}].join('|')"; |
- } |
- return expression; |
- } |
- |
- for (final ClassElement cls in dispatchClasses) { |
- tagDefns[cls] = makeExpression(cls); |
- } |
- |
- // Write out a thunk that builds the metadata. |
- |
- if (!tagDefns.isEmpty()) { |
- buffer.add('(function(){\n'); |
- |
- for (final String varName in varNames) { |
- buffer.add(' var ${varName} = ${varDefns[varName]};\n'); |
- } |
- |
- buffer.add(' var table = [\n'); |
- buffer.add( |
- ' // [dynamic-dispatch-tag, ' |
- 'tags of classes implementing dynamic-dispatch-tag]'); |
- bool needsComma = false; |
- List<String> entries = <String>[]; |
- for (final ClassElement cls in dispatchClasses) { |
- String clsName = toNativeName(cls); |
- entries.add("\n ['$clsName', ${tagDefns[cls]}]"); |
- } |
- buffer.add(Strings.join(entries, ',')); |
- buffer.add('];\n'); |
- buffer.add('$dynamicSetMetadataName(table);\n'); |
- |
- buffer.add('})();\n'); |
- } |
- } |
- |
- bool isSupertypeOfNativeClass(Element element) { |
- if (element.isTypeVariable()) { |
- compiler.cancel("Is check for type variable", element: work.element); |
- return false; |
- } |
- if (element.computeType(compiler) is FunctionType) return false; |
- |
- if (!element.isClass()) { |
- compiler.cancel("Is check does not handle element", element: element); |
- return false; |
- } |
- |
- return subtypes[element] !== null; |
- } |
- |
- bool requiresNativeIsCheck(Element element) { |
- if (!element.isClass()) return false; |
- ClassElement cls = element; |
- if (cls.isNative()) return true; |
- return isSupertypeOfNativeClass(element); |
- } |
- |
- void emitIsChecks(StringBuffer buffer) { |
- for (Element type in compiler.universe.isChecks) { |
- if (!requiresNativeIsCheck(type)) continue; |
- String name = compiler.namer.operatorIs(type); |
- buffer.add("$defPropName(Object.prototype, '$name', "); |
- buffer.add('function() { return false; });\n'); |
- } |
- } |
- |
- void assembleCode(StringBuffer other) { |
- if (nativeClasses.isEmpty()) return; |
- |
- // Because of native classes, we have to generate some is checks |
- // by calling a method, instead of accessing a property. So we |
- // attach to the JS Object prototype these methods that return |
- // false, and will be overridden by subclasses when they have to |
- // return true. |
- StringBuffer objectProperties = new StringBuffer(); |
- emitIsChecks(objectProperties); |
- |
- // In order to have the toString method on every native class, |
- // we must patch the JS Object prototype with a helper method. |
- String toStringName = compiler.namer.instanceMethodName( |
- null, const SourceString('toString'), 0); |
- objectProperties.add("$defPropName(Object.prototype, '$toStringName', "); |
- objectProperties.add( |
- 'function() { return $toStringHelperName(this); });\n'); |
- |
- // Finally, emit the code in the main buffer. |
- other.add('(function() {\n$objectProperties$buffer\n})();\n'); |
- } |
-} |