Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(94)

Side by Side Diff: frog/leg/emitter.dart

Issue 9418045: Support for native in leg, and start moving native tests to a specific test suite. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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 * Generates the code for all used classes in the program. Static fields (even 6 * Generates the code for all used classes in the program. Static fields (even
7 * in classes) are ignored, since they can be treated as non-class elements. 7 * in classes) are ignored, since they can be treated as non-class elements.
8 * 8 *
9 * The code for the containing (used) methods must exist in the [:universe:]. 9 * The code for the containing (used) methods must exist in the [:universe:].
10 */ 10 */
11 class CodeEmitterTask extends CompilerTask { 11 class CodeEmitterTask extends CompilerTask {
12 static final String INHERIT_FUNCTION = ''' 12 static final String INHERIT_FUNCTION = '''
13 function(child, parent) { 13 function(child, parent) {
14 if (child.prototype.__proto__) { 14 if (child.prototype.__proto__) {
15 child.prototype.__proto__ = parent.prototype; 15 child.prototype.__proto__ = parent.prototype;
16 } else { 16 } else {
17 function tmp() {}; 17 function tmp() {};
18 tmp.prototype = parent.prototype; 18 tmp.prototype = parent.prototype;
19 child.prototype = new tmp(); 19 child.prototype = new tmp();
20 child.prototype.constructor = child; 20 child.prototype.constructor = child;
21 } 21 }
22 }'''; 22 }''';
23 23
24 /**
25 * Code for finding the type name of a JavaScript object.
26 */
27 static final String TYPE_NAME_OF_FUNCTION = '''
28 function(obj) {
29 var constructor = obj.constructor;
30 if (typeof(constructor) == 'function') {
31 // The constructor isn't null or undefined at this point. Try
32 // to grab hold of its name.
33 var name = constructor.name;
34 // If the name is a non-empty string, we use that as the type
35 // name of this object. On Firefox, we often get 'Object' as
36 // the constructor name even for more specialized objects so
37 // we have to fall through to the toString() based implementation
38 // below in that case.
39 if (name && typeof(name) == 'string' && name != 'Object') return name;
40 }
41 var string = Object.prototype.toString.call(obj);
42 var name = string.substring(8, string.length - 1);
43 if (name == 'Window') {
44 name = 'DOMWindow';
45 } else if (name == 'Document') {
46 name = 'HTMLDocument';
47 }
48 return name;
49 }
50 ''';
51
52 /**
53 * Code for defining a property in a JavaScript object that will not
54 * be visible through for-in (aka enumerable is false).
55 */
56 static final String DEF_PROP_FUNCTION = '''
57 function(obj, prop, value) {
58 Object.defineProperty(obj, prop,
59 {value: value, enumerable: false, writable: true, configurable: true});
60 }''';
61
62 /**
63 * Code for doing the dynamic dispatch on JavaScript prototypes that are not
64 * available at compile-time. Each property of a native Dart class
65 * is registered through this function, which is called with the
66 * following pattern:
67 *
68 * $dynamic('propertyName').prototypeName = // JS code
69 *
70 * What this function does is:
71 * - Creates a map of { prototypeName: JS code }.
72 * - Attaches 'propertyName' to the JS Object prototype that will
73 * intercept at runtime all calls to propertyName.
74 * - Sets the value of 'propertyName' to a function that queries the
75 * map with the prototype of 'this', patches the prototype of
76 * 'this' with the found JS code, and invokes the JS code.
77 *
78 */
79 String buildDynamicFunctionCode() => '''
80 function(name) {
81 var f = Object.prototype[name];
82 if (f && f.methods) return f.methods;
83
84 var methods = {};
85 if (f) methods.Object = f;
86 function dynamicBind() {
87 // Find the target method
88 var obj = this;
89 var tag = $typeNameOfName(obj);
90 var method = methods[tag];
91 if (!method) {
92 var table = $dynamicMetadataName;
93 for (var i = 0; i < table.length; i++) {
94 var entry = table[i];
95 if (entry.map.hasOwnProperty(tag)) {
96 method = methods[entry.tag];
97 if (method) break;
98 }
99 }
100 }
101 method = method || methods.Object;
102 var proto = Object.getPrototypeOf(obj);
103 if (!proto.hasOwnProperty(name)) {
104 $defPropName(proto, name, method);
105 }
106
107 return method.apply(this, Array.prototype.slice.call(arguments));
108 };
109 dynamicBind.methods = methods;
110 $defPropName(Object.prototype, name, dynamicBind);
111 return methods;
112 }
113 ''';
114
115 String buildDynamicMetadataCode() => '''
116 if (typeof $dynamicMetadataName == 'undefined') $dynamicMetadataName = [];
117 ''';
118
24 bool addedInheritFunction = false; 119 bool addedInheritFunction = false;
120 bool addedDynamicFunction = false;
25 final Namer namer; 121 final Namer namer;
26 122
27 CodeEmitterTask(Compiler compiler) : namer = compiler.namer, super(compiler); 123 CodeEmitterTask(Compiler compiler) : namer = compiler.namer, super(compiler);
28 String get name() => 'CodeEmitter'; 124 String get name() => 'CodeEmitter';
29 125
30 String get inheritsName() => '${compiler.namer.ISOLATE}.\$inherits'; 126 String get inheritsName() => '${compiler.namer.ISOLATE}.\$inherits';
127 String get dynamicName() => '${compiler.namer.ISOLATE}.\$dynamic';
128 String get defPropName() => '${compiler.namer.ISOLATE}.\$defProp';
129 String get typeNameOfName() => '${compiler.namer.ISOLATE}.\$typeNameOf';
130 String get dynamicMetadataName() =>
131 '${compiler.namer.ISOLATE}.\$dynamicMetatada';
31 132
32 void addInheritFunctionIfNecessary(StringBuffer buffer) { 133 void addInheritFunctionIfNecessary(StringBuffer buffer) {
33 if (addedInheritFunction) return; 134 if (addedInheritFunction) return;
34 addedInheritFunction = true; 135 addedInheritFunction = true;
35 buffer.add('$inheritsName = '); 136 buffer.add('$inheritsName = ');
36 buffer.add(INHERIT_FUNCTION); 137 buffer.add(INHERIT_FUNCTION);
37 buffer.add(';\n'); 138 buffer.add(';\n');
38 } 139 }
39 140
141 void addDynamicFunctionIfNecessary(StringBuffer buffer) {
142 if (addedDynamicFunction) return;
143 addedDynamicFunction = true;
144 buffer.add('$defPropName = ');
145 buffer.add(DEF_PROP_FUNCTION);
146 buffer.add('\n');
147 buffer.add('$typeNameOfName = ');
148 buffer.add(TYPE_NAME_OF_FUNCTION);
149 buffer.add('\n');
150 buffer.add('$dynamicName = ');
151 buffer.add(buildDynamicFunctionCode());
152 buffer.add('\n');
153 buffer.add(buildDynamicMetadataCode());
154 buffer.add('\n');
155 }
156
40 void addParameterStub(FunctionElement member, 157 void addParameterStub(FunctionElement member,
41 String prototype, 158 String attachTo(String invocationName),
42 StringBuffer buffer, 159 StringBuffer buffer,
43 Selector selector) { 160 Selector selector) {
44 FunctionParameters parameters = member.computeParameters(compiler); 161 FunctionParameters parameters = member.computeParameters(compiler);
45 int positionalArgumentCount = selector.positionalArgumentCount; 162 int positionalArgumentCount = selector.positionalArgumentCount;
46 if (positionalArgumentCount == parameters.parameterCount) { 163 if (positionalArgumentCount == parameters.parameterCount) {
47 assert(selector.namedArgumentCount == 0); 164 assert(selector.namedArgumentCount == 0);
48 return; 165 return;
49 } 166 }
50 CompileTimeConstantHandler constants = compiler.compileTimeConstantHandler; 167 CompileTimeConstantHandler constants = compiler.compileTimeConstantHandler;
51 List<SourceString> names = selector.getOrderedNamedArguments(); 168 List<SourceString> names = selector.getOrderedNamedArguments();
52 169
53 String invocationName = 170 String invocationName =
54 namer.instanceMethodInvocationName(member.name, selector); 171 namer.instanceMethodInvocationName(member.name, selector);
55 buffer.add('$prototype.$invocationName = function('); 172 buffer.add('${attachTo(invocationName)} = function(');
56 173
57 // The parameters that this stub takes. 174 // The parameters that this stub takes.
58 List<String> parametersBuffer = new List<String>(selector.argumentCount); 175 List<String> parametersBuffer = new List<String>(selector.argumentCount);
59 // The arguments that will be passed to the real method. 176 // The arguments that will be passed to the real method.
60 List<String> argumentsBuffer = new List<String>(parameters.parameterCount); 177 List<String> argumentsBuffer = new List<String>(parameters.parameterCount);
61 178
62 // We fill the lists depending on the selector. For example, 179 // We fill the lists depending on the selector. For example,
63 // take method foo: 180 // take method foo:
64 // foo(a, b, [c, d]); 181 // foo(a, b, [c, d]);
65 // 182 //
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after
114 } 231 }
115 count++; 232 count++;
116 }); 233 });
117 234
118 buffer.add('${Strings.join(parametersBuffer, ",")}) {\n'); 235 buffer.add('${Strings.join(parametersBuffer, ",")}) {\n');
119 buffer.add(' return this.${namer.getName(member)}'); 236 buffer.add(' return this.${namer.getName(member)}');
120 buffer.add('(${Strings.join(argumentsBuffer, ",")})\n}\n'); 237 buffer.add('(${Strings.join(argumentsBuffer, ",")})\n}\n');
121 } 238 }
122 239
123 void addParameterStubs(FunctionElement member, 240 void addParameterStubs(FunctionElement member,
124 String prototype, 241 String attachTo(String invocationName),
125 StringBuffer buffer) { 242 StringBuffer buffer) {
126 Set<Selector> selectors = compiler.universe.invokedNames[member.name]; 243 Set<Selector> selectors = compiler.universe.invokedNames[member.name];
127 if (selectors == null) return; 244 if (selectors == null) return;
128 for (Selector selector in selectors) { 245 for (Selector selector in selectors) {
129 if (!selector.applies(compiler, member)) continue; 246 if (!selector.applies(compiler, member)) continue;
130 addParameterStub(member, prototype, buffer, selector); 247 addParameterStub(member, attachTo, buffer, selector);
131 } 248 }
132 } 249 }
133 250
134 void addInstanceMember(Element member, 251 void addInstanceMember(Element member,
135 String prototype, 252 String prototype,
136 StringBuffer buffer) { 253 StringBuffer buffer) {
137 assert(member.isInstanceMember()); 254 assert(member.isInstanceMember());
138 if (member.kind === ElementKind.FUNCTION 255 if (member.kind === ElementKind.FUNCTION
139 || member.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY 256 || member.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY
140 || member.kind === ElementKind.GETTER 257 || member.kind === ElementKind.GETTER
141 || member.kind === ElementKind.SETTER) { 258 || member.kind === ElementKind.SETTER) {
142 String codeBlock = compiler.universe.generatedCode[member]; 259 String codeBlock = compiler.universe.generatedCode[member];
143 if (codeBlock !== null) { 260 if (codeBlock == null) return;
144 buffer.add('$prototype.${namer.getName(member)} = $codeBlock;\n'); 261 buffer.add('$prototype.${namer.getName(member)} = $codeBlock;\n');
145 }
146 codeBlock = compiler.universe.generatedBailoutCode[member]; 262 codeBlock = compiler.universe.generatedBailoutCode[member];
147 if (codeBlock !== null) { 263 if (codeBlock !== null) {
148 String name = namer.getBailoutName(member); 264 String name = namer.getBailoutName(member);
149 buffer.add('$prototype.$name = $codeBlock;\n'); 265 buffer.add('$prototype.$name = $codeBlock;\n');
150 } 266 }
151 FunctionElement function = member; 267 FunctionElement function = member;
152 if (!function.computeParameters(compiler).optionalParameters.isEmpty()) { 268 if (!function.computeParameters(compiler).optionalParameters.isEmpty()) {
153 addParameterStubs(member, prototype, buffer); 269 addParameterStubs(member, (name) => '$prototype.$name', buffer);
154 } 270 }
155 } else if (member.kind === ElementKind.FIELD) { 271 } else if (member.kind === ElementKind.FIELD) {
156 // TODO(ngeoffray): Have another class generate the code for the 272 // TODO(ngeoffray): Have another class generate the code for the
157 // fields. 273 // fields.
158 if (compiler.universe.invokedSetters.contains(member.name)) { 274 if (compiler.universe.invokedSetters.contains(member.name)) {
159 String setterName = namer.setterName(member.name); 275 String setterName = namer.setterName(member.name);
160 buffer.add('$prototype.$setterName = function(v){\n' + 276 buffer.add('$prototype.$setterName = function(v){\n' +
161 ' this.${namer.getName(member)} = v;\n};\n'); 277 ' this.${namer.getName(member)} = v;\n};\n');
162 } 278 }
163 if (compiler.universe.invokedGetters.contains(member.name)) { 279 if (compiler.universe.invokedGetters.contains(member.name)) {
(...skipping 15 matching lines...) Expand all
179 // TODO(floitsch): make sure there are no name clashes. 295 // TODO(floitsch): make sure there are no name clashes.
180 String className = namer.getName(classElement); 296 String className = namer.getName(classElement);
181 297
182 void generateFieldInit(Element member) { 298 void generateFieldInit(Element member) {
183 if (member.isInstanceMember() && member.kind == ElementKind.FIELD) { 299 if (member.isInstanceMember() && member.kind == ElementKind.FIELD) {
184 if (!isFirst) argumentsBuffer.add(', '); 300 if (!isFirst) argumentsBuffer.add(', ');
185 isFirst = false; 301 isFirst = false;
186 String memberName = namer.instanceFieldName(member.name); 302 String memberName = namer.instanceFieldName(member.name);
187 argumentsBuffer.add('${className}_$memberName'); 303 argumentsBuffer.add('${className}_$memberName');
188 bodyBuffer.add(' this.$memberName = ${className}_$memberName;\n'); 304 bodyBuffer.add(' this.$memberName = ${className}_$memberName;\n');
189 } 305 }
190 } 306 }
191 307
192 for (Element element in classElement.members) { 308 for (Element element in classElement.members) {
193 generateFieldInit(element); 309 generateFieldInit(element);
194 } 310 }
195 for (Element element in classElement.backendMembers) { 311 for (Element element in classElement.backendMembers) {
196 generateFieldInit(element); 312 generateFieldInit(element);
197 } 313 }
198 314
199 classElement = classElement.superclass; 315 classElement = classElement.superclass;
200 } while(classElement !== null); 316 } while(classElement !== null);
201 } 317 }
202 318
319 void generateNativeClass(ClassElement classElement, StringBuffer buffer) {
320 addDynamicFunctionIfNecessary(buffer);
321 assert(classElement.backendMembers.isEmpty());
322 String nativeName = classElement.nativeName.stringValue;
323 nativeName = nativeName.substring(2, nativeName.length - 1);
324 for (Element member in classElement.members) {
325 if (member.isInstanceMember()) {
326 String memberName = namer.getName(member);
327 if (member.kind === ElementKind.FUNCTION
328 || member.kind === ElementKind.GENERATIVE_CONSTRUCTOR_BODY
329 || member.kind === ElementKind.GETTER
330 || member.kind === ElementKind.SETTER) {
331 String codeBlock = compiler.universe.generatedCode[member];
332 if (codeBlock == null) continue;
333 buffer.add(
334 "$dynamicName('$memberName').$nativeName = $codeBlock;\n");
335 codeBlock = compiler.universe.generatedBailoutCode[member];
336 if (codeBlock !== null) {
337 String name = namer.getBailoutName(member);
338 buffer.add("$dynamicName('$name').$nativeName = $codeBlock;\n");
339 }
340 FunctionElement function = member;
341 FunctionParameters parameters = function.computeParameters(compiler);
342 if (!parameters.optionalParameters.isEmpty()) {
343 addParameterStubs(
344 member, (name) => "$dynamicName('$name').$nativeName", buffer);
345 }
346 } else if (member.kind === ElementKind.FIELD) {
347 if (compiler.universe.invokedSetters.contains(member.name)) {
348 String setterName = namer.setterName(member.name);
349 buffer.add(
350 "$dynamicName('$setterName').$nativeName = function(v){\n" +
351 ' this.${member.name} = v;\n};\n');
352 }
353 if (compiler.universe.invokedGetters.contains(member.name)) {
354 String getterName = namer.getterName(member.name);
355 buffer.add(
356 "$dynamicName('$getterName').$nativeName = function(){\n" +
357 ' return this.${member.name};\n};\n');
358 }
359 } else {
360 compiler.internalError('unexpected kind: "${member.kind}"',
361 element: member);
362 }
363 }
364 }
365 }
366
203 void generateClass(ClassElement classElement, 367 void generateClass(ClassElement classElement,
204 StringBuffer buffer, 368 StringBuffer buffer,
205 Set<ClassElement> seenClasses) { 369 Set<ClassElement> seenClasses) {
206 if (seenClasses.contains(classElement)) return; 370 if (seenClasses.contains(classElement)) return;
207 seenClasses.add(classElement); 371 seenClasses.add(classElement);
208 ClassElement superclass = classElement.superclass; 372 ClassElement superclass = classElement.superclass;
209 if (superclass !== null) { 373 if (superclass !== null) {
210 generateClass(classElement.superclass, buffer, seenClasses); 374 generateClass(classElement.superclass, buffer, seenClasses);
211 } 375 }
212 376
377 if (classElement.isNative()) {
378 generateNativeClass(classElement, buffer);
379 return;
380 }
381
213 String className = namer.isolatePropertyAccess(classElement); 382 String className = namer.isolatePropertyAccess(classElement);
214 buffer.add('$className = function ${classElement.name}('); 383 buffer.add('$className = function ${classElement.name}(');
215 StringBuffer bodyBuffer = new StringBuffer(); 384 StringBuffer bodyBuffer = new StringBuffer();
216 // If the class is never instantiated we still need to set it up for 385 // If the class is never instantiated we still need to set it up for
217 // inheritance purposes, but we can leave its JavaScript constructor empty. 386 // inheritance purposes, but we can leave its JavaScript constructor empty.
218 if (compiler.universe.instantiatedClasses.contains(classElement)) { 387 if (compiler.universe.instantiatedClasses.contains(classElement)) {
219 generateFieldInits(classElement, buffer, bodyBuffer); 388 generateFieldInits(classElement, buffer, bodyBuffer);
220 } 389 }
221 buffer.add(') {\n'); 390 buffer.add(') {\n');
222 buffer.add(bodyBuffer); 391 buffer.add(bodyBuffer);
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
294 // of static functions) and will not have any enclosingElement. 463 // of static functions) and will not have any enclosingElement.
295 FunctionElement callElement = 464 FunctionElement callElement =
296 new FunctionElement.from(Namer.CLOSURE_INVOCATION_NAME, 465 new FunctionElement.from(Namer.CLOSURE_INVOCATION_NAME,
297 element, 466 element,
298 null); 467 null);
299 String staticName = namer.isolatePropertyAccess(element); 468 String staticName = namer.isolatePropertyAccess(element);
300 int parameterCount = element.parameterCount(compiler); 469 int parameterCount = element.parameterCount(compiler);
301 String invocationName = 470 String invocationName =
302 namer.instanceMethodName(callElement.name, parameterCount); 471 namer.instanceMethodName(callElement.name, parameterCount);
303 buffer.add("$staticName.$invocationName = $staticName;\n"); 472 buffer.add("$staticName.$invocationName = $staticName;\n");
304 addParameterStubs(callElement, staticName, buffer); 473 addParameterStubs(callElement, (name) => '$staticName.$name', buffer);
305 } 474 }
306 } 475 }
307 476
308 void emitDynamicFunctionGetter(StringBuffer buffer, 477 void emitDynamicFunctionGetter(StringBuffer buffer,
309 ClassElement enclosingClass, 478 ClassElement enclosingClass,
310 FunctionElement member) { 479 FunctionElement member) {
311 // For every method that has the same name as a property-get we create a 480 // For every method that has the same name as a property-get we create a
312 // getter that returns a bound closure. Say we have a class 'A' with method 481 // getter that returns a bound closure. Say we have a class 'A' with method
313 // 'foo' and somewhere in the code there is a dynamic property get of 482 // 'foo' and somewhere in the code there is a dynamic property get of
314 // 'foo'. Then we generate the following code (in pseudo Dart): 483 // 'foo'. Then we generate the following code (in pseudo Dart):
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after
347 namer.instanceMethodName(callElement.name, parameterCount); 516 namer.instanceMethodName(callElement.name, parameterCount);
348 String targetName = namer.instanceMethodName(member.name, parameterCount); 517 String targetName = namer.instanceMethodName(member.name, parameterCount);
349 List<String> arguments = new List<String>(parameterCount); 518 List<String> arguments = new List<String>(parameterCount);
350 for (int i = 0; i < parameterCount; i++) { 519 for (int i = 0; i < parameterCount; i++) {
351 arguments[i] = "arg$i"; 520 arguments[i] = "arg$i";
352 } 521 }
353 String joinedArgs = Strings.join(arguments, ", "); 522 String joinedArgs = Strings.join(arguments, ", ");
354 buffer.add("$prototype.$invocationName = function($joinedArgs) {\n"); 523 buffer.add("$prototype.$invocationName = function($joinedArgs) {\n");
355 buffer.add(" return this.self.$targetName($joinedArgs);\n"); 524 buffer.add(" return this.self.$targetName($joinedArgs);\n");
356 buffer.add("};\n"); 525 buffer.add("};\n");
357 addParameterStubs(callElement, prototype, buffer); 526 addParameterStubs(callElement, (name) => '$prototype.$name', buffer);
358 527
359 // And finally the getter. 528 // And finally the getter.
360 String enclosingClassAccess = namer.isolatePropertyAccess(enclosingClass); 529 String enclosingClassAccess = namer.isolatePropertyAccess(enclosingClass);
361 String enclosingClassPrototype = "$enclosingClassAccess.prototype"; 530 String enclosingClassPrototype = "$enclosingClassAccess.prototype";
362 String getterName = namer.getterName(member.name); 531 String getterName = namer.getterName(member.name);
363 String closureClass = namer.isolateAccess(closureClassElement); 532 String closureClass = namer.isolateAccess(closureClassElement);
364 buffer.add("$enclosingClassPrototype.$getterName = function() {\n"); 533 buffer.add("$enclosingClassPrototype.$getterName = function() {\n");
365 buffer.add(" return new $closureClass(this);\n"); 534 buffer.add(" return new $closureClass(this);\n");
366 buffer.add("};\n"); 535 buffer.add("};\n");
367 } 536 }
(...skipping 163 matching lines...) Expand 10 before | Expand all | Expand 10 after
531 emitCallStubForGetters(buffer); 700 emitCallStubForGetters(buffer);
532 emitStaticFinalFieldInitializations(buffer); 701 emitStaticFinalFieldInitializations(buffer);
533 buffer.add('var ${namer.CURRENT_ISOLATE} = new ${namer.ISOLATE}();\n'); 702 buffer.add('var ${namer.CURRENT_ISOLATE} = new ${namer.ISOLATE}();\n');
534 Element main = compiler.mainApp.find(Compiler.MAIN); 703 Element main = compiler.mainApp.find(Compiler.MAIN);
535 buffer.add('${namer.isolateAccess(main)}();\n'); 704 buffer.add('${namer.isolateAccess(main)}();\n');
536 compiler.assembledCode = buffer.toString(); 705 compiler.assembledCode = buffer.toString();
537 }); 706 });
538 return compiler.assembledCode; 707 return compiler.assembledCode;
539 } 708 }
540 } 709 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698