| OLD | NEW |
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, 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 JS helpers for dart:core. This used to be in a file "core.js". | 6 * Generates JS helpers for dart:core. This used to be in a file "core.js". |
| 7 * Having them in Dart code means we can easily control which are generated. | 7 * Having them in Dart code means we can easily control which are generated. |
| 8 */ | 8 */ |
| 9 // TODO(jmesserly): one idea to make this cleaner: put these as private "native" | 9 // TODO(jmesserly): one idea to make this cleaner: put these as private "native" |
| 10 // methods somewhere in a library that we import. This would be rather elegant | 10 // methods somewhere in a library that we import. This would be rather elegant |
| 11 // because they'd get the right name collision behavior, conversions, | 11 // because they'd get the right name collision behavior, conversions, |
| 12 // include-if-used, etc for free. Not sure if it's worth doing that. | 12 // include-if-used, etc for free. Not sure if it's worth doing that. |
| 13 class CoreJs { | 13 class CoreJs { |
| 14 // These values track if the helper is actually used. If it is we generate it. | 14 // These values track if the helper is actually used. If it is we generate it. |
| 15 bool useTypeNameOf = false; | |
| 16 bool useStackTraceOf = false; | 15 bool useStackTraceOf = false; |
| 17 bool useThrow = false; | 16 bool useThrow = false; |
| 18 bool useVarMethod = false; | |
| 19 bool useGenStub = false; | 17 bool useGenStub = false; |
| 20 bool useMap = false; | 18 bool useMap = false; |
| 21 bool useAssert = false; | 19 bool useAssert = false; |
| 22 bool useNotNullBool = false; | 20 bool useNotNullBool = false; |
| 23 bool useIndex = false; | 21 bool useIndex = false; |
| 24 bool useSetIndex = false; | 22 bool useSetIndex = false; |
| 25 | 23 |
| 26 bool useWrap0 = false; | 24 bool useWrap0 = false; |
| 27 bool useWrap1 = false; | 25 bool useWrap1 = false; |
| 28 bool useIsolates = false; | 26 bool useIsolates = false; |
| 29 | 27 |
| 30 /** An experimental toString implementation. Currently unused. */ | 28 /** An experimental toString implementation. Currently unused. */ |
| 31 bool useToString = false; | 29 bool useToString = false; |
| 32 | 30 |
| 31 // These helpers had to switch to a new pattern, because they can be generated |
| 32 // after everything else. |
| 33 bool _generatedTypeNameOf = false; |
| 34 bool _generatedDynamicProto = false; |
| 35 bool _generatedInherits = false; |
| 36 |
| 33 Map<String, String> _usedOperators; | 37 Map<String, String> _usedOperators; |
| 34 | 38 |
| 35 CoreJs(): _usedOperators = {}; | 39 CodeWriter writer; |
| 40 |
| 41 CoreJs(): _usedOperators = {}, writer = new CodeWriter(); |
| 36 | 42 |
| 37 /** | 43 /** |
| 38 * Generates the special operator method, e.g. $add. | 44 * Generates the special operator method, e.g. $add. |
| 39 * We want to do $add(x, y) instead of x.$add(y) so it doesn't box. | 45 * We want to do $add(x, y) instead of x.$add(y) so it doesn't box. |
| 40 * Same idea for the other methods. | 46 * Same idea for the other methods. |
| 41 */ | 47 */ |
| 42 void useOperator(String name) { | 48 void useOperator(String name) { |
| 43 if (_usedOperators[name] != null) return; | 49 if (_usedOperators[name] != null) return; |
| 44 | 50 |
| 45 var code; | 51 var code; |
| (...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 134 function ${name}(x, y) { | 140 function ${name}(x, y) { |
| 135 return (typeof(x) == 'number' && typeof(y) == 'number') | 141 return (typeof(x) == 'number' && typeof(y) == 'number') |
| 136 ? x ${op} y : x.${name}(y); | 142 ? x ${op} y : x.${name}(y); |
| 137 }"""; | 143 }"""; |
| 138 break; | 144 break; |
| 139 } | 145 } |
| 140 | 146 |
| 141 _usedOperators[name] = code; | 147 _usedOperators[name] = code; |
| 142 } | 148 } |
| 143 | 149 |
| 150 // NOTE: some helpers can't be generated when we generate corelib, |
| 151 // because we don't discover that we need them until later. |
| 152 // Generate on-demand instead |
| 153 void ensureDynamicProto() { |
| 154 if (_generatedDynamicProto) return; |
| 155 _generatedDynamicProto = true; |
| 156 |
| 157 ensureTypeNameOf(); |
| 158 |
| 159 // Usage: |
| 160 // $dynamic(name).SomeTypeName = ... method ...; |
| 161 // $dynamic(name).Object = ... noSuchMethod ...; |
| 162 writer.writeln(@""" |
| 163 function $dynamic(name) { |
| 164 var f = Object.prototype[name]; |
| 165 if (f && f.methods) return f.methods; |
| 166 |
| 167 var methods = {}; |
| 168 if (f) methods.Object = f; |
| 169 function $dynamicBind() { |
| 170 // Find the target method |
| 171 var method; |
| 172 var proto = Object.getPrototypeOf(this); |
| 173 var obj = proto; |
| 174 do { |
| 175 method = methods[obj.$typeNameOf()]; |
| 176 if (method) break; |
| 177 obj = Object.getPrototypeOf(obj); |
| 178 } while (obj); |
| 179 |
| 180 // Patch the prototype, but don't overwrite an existing stub, like |
| 181 // the one on Object.prototype. |
| 182 if (!proto.hasOwnProperty(name)) proto[name] = method || methods.Object; |
| 183 |
| 184 return method.apply(this, Array.prototype.slice.call(arguments)); |
| 185 }; |
| 186 $dynamicBind.methods = methods; |
| 187 Object.prototype[name] = $dynamicBind; |
| 188 return methods; |
| 189 }"""); |
| 190 } |
| 191 |
| 192 void ensureTypeNameOf() { |
| 193 if (_generatedTypeNameOf) return; |
| 194 _generatedTypeNameOf = true; |
| 195 |
| 196 // TODO(sigmund): find a way to make this work on all browsers, including |
| 197 // checking the typeName on prototype objects (so we can fix dynamic |
| 198 // dispatching on $varMethod). |
| 199 writer.writeln(@""" |
| 200 Object.prototype.$typeNameOf = function() { |
| 201 if ((typeof(window) != 'undefined' && window.constructor.name == 'DOMWindow') |
| 202 || typeof(process) != 'undefined') { // fast-path for Chrome and Node |
| 203 return this.constructor.name; |
| 204 } |
| 205 var str = Object.prototype.toString.call(this); |
| 206 str = str.substring(8, str.length - 1); |
| 207 if (str == 'Window') str = 'DOMWindow'; |
| 208 return str; |
| 209 }"""); |
| 210 } |
| 211 |
| 212 |
| 213 /** Generates the $inherits function when it's first used. */ |
| 214 ensureInheritsHelper() { |
| 215 if (_generatedInherits) return; |
| 216 _generatedInherits = true; |
| 217 |
| 218 writer.writeln(@""" |
| 219 /** Implements extends for Dart classes on JavaScript prototypes. */ |
| 220 function $inherits(child, parent) { |
| 221 if (child.prototype.__proto__) { |
| 222 child.prototype.__proto__ = parent.prototype; |
| 223 } else { |
| 224 function tmp() {}; |
| 225 tmp.prototype = parent.prototype; |
| 226 child.prototype = new tmp(); |
| 227 child.prototype.constructor = child; |
| 228 } |
| 229 }"""); |
| 230 } |
| 231 |
| 144 void generate(CodeWriter w) { | 232 void generate(CodeWriter w) { |
| 145 if (useVarMethod) { | 233 // Write any stuff we had queued up, then replace our writer with the one |
| 146 useTypeNameOf = true; | 234 // in WorldGenerator so anything we discover that we need later on will be |
| 147 w.writeln(@""" | 235 // generated on-demand. |
| 148 function $varMethod(name, methods) { | 236 w.write(writer.text); |
| 149 Object.prototype[name] = function() { | 237 writer = w; |
| 150 $patchMethod(this, name, methods); | |
| 151 return this[name].apply(this, Array.prototype.slice.call(arguments)); | |
| 152 }; | |
| 153 } | |
| 154 function $patchMethod(obj, name, methods) { | |
| 155 // Get the prototype to patch. | |
| 156 // Don't overwrite an existing stub, like the one on Object.prototype | |
| 157 var proto = Object.getPrototypeOf(obj); | |
| 158 if (!proto || proto.hasOwnProperty(name)) proto = obj; | |
| 159 var method; | |
| 160 while (obj && !(method = methods[obj.$typeNameOf()])) { | |
| 161 obj = Object.getPrototypeOf(obj); | |
| 162 } | |
| 163 obj[name] = method || methods['Object']; | |
| 164 }"""); | |
| 165 } | |
| 166 | 238 |
| 167 if (useGenStub) { | 239 if (useGenStub) { |
| 168 useThrow = true; | 240 useThrow = true; |
| 169 w.writeln(@""" | 241 w.writeln(@""" |
| 170 /** | 242 /** |
| 171 * Generates a dynamic call stub for a function. | 243 * Generates a dynamic call stub for a function. |
| 172 * Our goal is to create a stub method like this on-the-fly: | 244 * Our goal is to create a stub method like this on-the-fly: |
| 173 * function($0, $1, capture) { this($0, $1, true, capture); } | 245 * function($0, $1, capture) { return this($0, $1, true, capture); } |
| 174 * | 246 * |
| 175 * This stub then replaces the dynamic one on Function, with one that is | 247 * This stub then replaces the dynamic one on Function, with one that is |
| 176 * specialized for that particular function, taking into account its default | 248 * specialized for that particular function, taking into account its default |
| 177 * arguments. | 249 * arguments. |
| 178 */ | 250 */ |
| 179 Function.prototype.$genStub = function(argsLength, names) { | 251 Function.prototype.$genStub = function(argsLength, names) { |
| 180 // TODO(jmesserly): only emit $genStub if actually needed | |
| 181 | |
| 182 // Fast path: if no named arguments and arg count matches | 252 // Fast path: if no named arguments and arg count matches |
| 183 if (this.length == argsLength && !names) { | 253 if (this.length == argsLength && !names) { |
| 184 return this; | 254 return this; |
| 185 } | 255 } |
| 186 | 256 |
| 187 function $throwArgMismatch() { | 257 function $throwArgMismatch() { |
| 188 // TODO(jmesserly): better error message | 258 // TODO(jmesserly): better error message |
| 189 $throw(new ClosureArgumentMismatchException()); | 259 $throw(new ClosureArgumentMismatchException()); |
| 190 } | 260 } |
| 191 | 261 |
| (...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 246 return (e && e.stack) ? e.stack : null; | 316 return (e && e.stack) ? e.stack : null; |
| 247 }"""); | 317 }"""); |
| 248 } | 318 } |
| 249 | 319 |
| 250 if (useNotNullBool) { | 320 if (useNotNullBool) { |
| 251 useThrow = true; | 321 useThrow = true; |
| 252 // This pattern chosen because IE9 does really badly with typeof, and | 322 // This pattern chosen because IE9 does really badly with typeof, and |
| 253 // it's still decent on other browsers. | 323 // it's still decent on other browsers. |
| 254 w.writeln(@""" | 324 w.writeln(@""" |
| 255 function $notnull_bool(test) { | 325 function $notnull_bool(test) { |
| 256 return (test === true || test === false) ? test : test.is$bool(); // TypeError | 326 if (test === true || test === false) return test; |
| 327 $throw(new TypeError(test, 'bool')); |
| 257 }"""); | 328 }"""); |
| 258 } | 329 } |
| 259 | 330 |
| 260 if (useAssert) { | 331 if (useAssert) { |
| 261 useThrow = true; | 332 useThrow = true; |
| 262 w.writeln(@""" | 333 w.writeln(@""" |
| 263 function $assert(test, text, url, line, column) { | 334 function $assert(test, text, url, line, column) { |
| 264 if (typeof test == 'function') test = test(); | 335 if (typeof test == 'function') test = test(); |
| 265 if (!test) $throw(new AssertError(text, url, line, column)); | 336 if (!test) $throw(new AssertError(text, url, line, column)); |
| 266 }"""); | 337 }"""); |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 298 if (o == null) return 'null'; | 369 if (o == null) return 'null'; |
| 299 var t = typeof(o); | 370 var t = typeof(o); |
| 300 if (t == 'object') { return o.toString(); } | 371 if (t == 'object') { return o.toString(); } |
| 301 else if (t == 'string') { return o; } | 372 else if (t == 'string') { return o; } |
| 302 else if (t == 'bool') { return ''+o; } | 373 else if (t == 'bool') { return ''+o; } |
| 303 else if (t == 'number') { return ''+o; } | 374 else if (t == 'number') { return ''+o; } |
| 304 else return o.toString(); | 375 else return o.toString(); |
| 305 }"""); | 376 }"""); |
| 306 } | 377 } |
| 307 | 378 |
| 308 if (useTypeNameOf) { | |
| 309 // TODO(sigmund): find a way to make this work on all browsers, including | |
| 310 // checking the typeName on prototype objects (so we can fix dynamic | |
| 311 // dispatching on $varMethod). | |
| 312 w.writeln(@""" | |
| 313 Object.prototype.$typeNameOf = function() { | |
| 314 if ((typeof(window) != 'undefined' && window.constructor.name == 'DOMWindow') | |
| 315 || typeof(process) != 'undefined') { // fast-path for Chrome and Node | |
| 316 return this.constructor.name; | |
| 317 } | |
| 318 var str = Object.prototype.toString.call(this); | |
| 319 str = str.substring(8, str.length - 1); | |
| 320 if (str == 'Window') | |
| 321 str = 'DOMWindow'; | |
| 322 return str; | |
| 323 }"""); | |
| 324 } | |
| 325 | |
| 326 if (useIndex) { | 379 if (useIndex) { |
| 327 // If not overridden, $index and $setindex fall back to JS [] and []= | 380 // If not overridden, $index and $setindex fall back to JS [] and []= |
| 328 // accessors | 381 // accessors |
| 329 // TODO(jimhug): This fallback could be very confusing in a few cases - | 382 // TODO(jimhug): This fallback could be very confusing in a few cases - |
| 330 // because of the bizare default [] rules in JS. We need to revisit this | 383 // because of the bizare default [] rules in JS. We need to revisit this |
| 331 // to get the right errors - at least in checked mode (once we have that). | 384 // to get the right errors - at least in checked mode (once we have that). |
| 332 // TODO(jmesserly): do perf analysis, figure out if this is worth it and | 385 // TODO(jmesserly): do perf analysis, figure out if this is worth it and |
| 333 // what the cost of $index $setindex is on all browsers | 386 // what the cost of $index $setindex is on all browsers |
| 334 w.writeln(@""" | 387 w.writeln(@""" |
| 335 Object.prototype.$index = function(i) { return this[i]; } | 388 Object.prototype.$index = function(i) { return this[i]; } |
| (...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 393 w.writeln(@"function $wrap_call$1(fn) { return fn; }"); | 446 w.writeln(@"function $wrap_call$1(fn) { return fn; }"); |
| 394 } | 447 } |
| 395 } | 448 } |
| 396 | 449 |
| 397 // Write operator helpers | 450 // Write operator helpers |
| 398 for (var opImpl in orderValuesByKeys(_usedOperators)) { | 451 for (var opImpl in orderValuesByKeys(_usedOperators)) { |
| 399 w.writeln(opImpl); | 452 w.writeln(opImpl); |
| 400 } | 453 } |
| 401 } | 454 } |
| 402 } | 455 } |
| OLD | NEW |