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 library _js_helper; | 5 library _js_helper; |
6 | 6 |
7 import 'dart:collection'; | 7 import 'dart:collection'; |
8 | 8 |
9 part 'constant_map.dart'; | 9 part 'constant_map.dart'; |
10 part 'native_helper.dart'; | 10 part 'native_helper.dart'; |
(...skipping 676 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
687 if (object == null || object is bool || object is num || object is String) { | 687 if (object == null || object is bool || object is num || object is String) { |
688 throw new ArgumentError(object); | 688 throw new ArgumentError(object); |
689 } | 689 } |
690 JS('void', '#[#] = #', object, key, value); | 690 JS('void', '#[#] = #', object, key, value); |
691 } | 691 } |
692 | 692 |
693 static applyFunction(Function function, | 693 static applyFunction(Function function, |
694 List positionalArguments, | 694 List positionalArguments, |
695 Map<String, dynamic> namedArguments) { | 695 Map<String, dynamic> namedArguments) { |
696 int argumentCount = 0; | 696 int argumentCount = 0; |
697 StringBuffer buffer = new StringBuffer(); | |
698 List arguments = []; | 697 List arguments = []; |
699 | 698 |
700 if (positionalArguments != null) { | 699 if (positionalArguments != null) { |
701 argumentCount += positionalArguments.length; | 700 argumentCount += positionalArguments.length; |
702 arguments.addAll(positionalArguments); | 701 arguments.addAll(positionalArguments); |
703 } | 702 } |
704 | 703 |
704 String selectorName = 'call\$$argumentCount'; | |
705 | |
705 // Sort the named arguments to get the right selector name and | 706 // Sort the named arguments to get the right selector name and |
706 // arguments order. | 707 // arguments order. |
707 if (namedArguments != null && !namedArguments.isEmpty) { | 708 if (namedArguments != null && !namedArguments.isEmpty) { |
708 // Call new List.from to make sure we get a JavaScript array. | 709 // Call new List.from to make sure we get a JavaScript array. |
709 List<String> listOfNamedArguments = | 710 List<String> listOfNamedArguments = |
710 new List<String>.from(namedArguments.keys); | 711 new List<String>.from(namedArguments.keys); |
711 argumentCount += namedArguments.length; | 712 argumentCount += namedArguments.length; |
712 // We're sorting on strings, and the behavior is the same between | 713 // We're sorting on strings, and the behavior is the same between |
713 // Dart string sort and JS string sort. To avoid needing the Dart | 714 // Dart string sort and JS string sort. To avoid needing the Dart |
714 // sort implementation, we use the JavaScript one instead. | 715 // sort implementation, we use the JavaScript one instead. |
715 JS('void', '#.sort()', listOfNamedArguments); | 716 JS('void', '#.sort()', listOfNamedArguments); |
716 listOfNamedArguments.forEach((String name) { | 717 listOfNamedArguments.forEach((String name) { |
717 buffer.add('\$$name'); | 718 selectorName = '$selectorName\$$name'; |
718 arguments.add(namedArguments[name]); | 719 arguments.add(namedArguments[name]); |
719 }); | 720 }); |
720 } | 721 } |
721 | 722 |
722 String selectorName = 'call\$$argumentCount$buffer'; | |
723 var jsFunction = JS('var', '#[#]', function, selectorName); | 723 var jsFunction = JS('var', '#[#]', function, selectorName); |
724 if (jsFunction == null) { | 724 if (jsFunction == null) { |
725 throw new NoSuchMethodError(function, selectorName, arguments, {}); | 725 throw new NoSuchMethodError(function, selectorName, arguments, {}); |
726 } | 726 } |
727 // We bound 'this' to [function] because of how we compile | 727 // We bound 'this' to [function] because of how we compile |
728 // closures: escaped local variables are stored and accessed through | 728 // closures: escaped local variables are stored and accessed through |
729 // [function]. | 729 // [function]. |
730 return JS('var', '#.apply(#, #)', jsFunction, function, arguments); | 730 return JS('var', '#.apply(#, #)', jsFunction, function, arguments); |
731 } | 731 } |
732 | 732 |
(...skipping 222 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
955 * in a subtype test. | 955 * in a subtype test. |
956 */ | 956 */ |
957 throwMalformedSubtypeError(value, type, reasons) { | 957 throwMalformedSubtypeError(value, type, reasons) { |
958 throw new TypeErrorImplementation.malformedSubtype(value, type, reasons); | 958 throw new TypeErrorImplementation.malformedSubtype(value, type, reasons); |
959 } | 959 } |
960 | 960 |
961 throwAbstractClassInstantiationError(className) { | 961 throwAbstractClassInstantiationError(className) { |
962 throw new AbstractClassInstantiationError(className); | 962 throw new AbstractClassInstantiationError(className); |
963 } | 963 } |
964 | 964 |
965 class TypeErrorDecoder { | |
966 static final noSuchMethodPattern = | |
kasperl
2013/01/21 13:54:09
I find that this method is really hard to read and
sra1
2013/01/29 20:54:16
In addition to Kaspers suggestions, don't forget t
ahe
2013/01/29 22:06:46
Stephen, I'm sorry you spent so much time reviewin
| |
967 extractPattern(JS('', | |
968 r'{ toString: function() { return "$receiver$"; } }')); | |
969 static final nullErrorPattern = extractPattern(JS('', 'null')); | |
970 static final undefinedErrorPattern = extractPattern(JS('', 'void 0')); | |
971 | |
972 static extractPattern(expression) { | |
973 var function = JS('', r"""function($expr$) { | |
sra1
2013/01/29 20:54:16
Why are these names $name$?
If they are not substi
ahe
2013/01/29 22:06:46
I haven't documented why I use "$name$", not "name
| |
974 var $argumentsExpr$ = '$arguments$' | |
975 try { | |
976 $expr$.$method$($argumentsExpr$); | |
977 } catch (e) { | |
978 return (e.message""" | |
979 // Subtle bug/feature in V8: it no longer calls toString. | |
sra1
2013/01/29 20:54:16
it? Can you be more specific about what no longer
ahe
2013/01/29 22:06:46
I looked at my new comment. I can still clarify it
| |
980 r""" | |
981 .replace(String({}), '$receiver$') | |
sra1
2013/01/29 20:54:16
Surely there are other receivers?
Try with some DO
ahe
2013/01/29 22:06:46
I don't understand what you're saying.
| |
982 .replace(new RegExp(#, 'g'), '\\$&')); | |
983 } | |
984 }""", ESCAPE_REGEXP); | |
985 String message = JS('String', '(#)(#)', function, expression); | |
986 List<String> match = | |
987 JS('=List', r"#.match(/\\\$[a-zA-Z]+\\\$/g)", message); | |
988 int arguments = JS('int', '#.indexOf(#)', match, r'\$arguments\$'); | |
989 int argumentsExpr = JS('int', '#.indexOf(#)', match, r'\$argumentsExpr\$'); | |
990 int expr = JS('int', '#.indexOf(#)', match, r'\$expr\$'); | |
991 int method = JS('int', '#.indexOf(#)', match, r'\$method\$'); | |
992 int receiver = JS('int', '#.indexOf(#)', match, r'\$receiver\$'); | |
993 | |
994 String pattern = JS('String', | |
995 r"#.replace('\\$arguments\\$', '(.*)')" | |
sra1
2013/01/29 20:54:16
It will be slightly better if emitted JavaScript s
ahe
2013/01/29 22:06:46
Why is that?
| |
996 r".replace('\\$argumentsExpr\\$', '(.*)')" | |
997 r".replace('\\$expr\\$', '(.*)')" | |
998 r".replace('\\$method\\$', '(.*)')" | |
999 r".replace('\\$receiver\\$', '(.*)')", | |
1000 message); | |
1001 | |
1002 return JS('', | |
1003 r'{ arguments: #, argumentsExpr: #, expr: #, method: #, ' | |
1004 r'receiver: #, pattern: #, ' | |
1005 r"""match: function(msg) { | |
1006 var match = new RegExp(this.pattern).exec(msg); | |
1007 if (!match) return void 0; | |
1008 var result = {}; | |
1009 if (this.arguments != -1) result.arguments = match[this.arguments + 1]; | |
1010 if (this.argumentsExpr != -1) result.argumentsExpr = match[this.argumentsExpr + 1]; | |
1011 if (this.expr != -1) result.expr = match[this.expr + 1]; | |
1012 if (this.method != -1) result.method = match[this.method + 1]; | |
1013 if (this.receiver != -1) result.receiver = match[this.receiver + 1]; | |
1014 return result; | |
1015 }}""", | |
1016 arguments, | |
1017 argumentsExpr, | |
1018 expr, | |
1019 method, | |
1020 receiver, | |
1021 pattern); | |
1022 } | |
1023 } | |
1024 | |
1025 class NullError implements NoSuchMethodError { | |
1026 final String _message; | |
1027 final String _method; | |
1028 | |
1029 NullError(this._message, match) | |
1030 : this._method = JS('', '#.method', match); | |
1031 | |
1032 String toString() { | |
1033 if (_method == null) return 'NullError: $_message'; | |
1034 return 'NullError: Cannot call "$_method" on null'; | |
1035 } | |
1036 } | |
1037 | |
1038 class JsNoSuchMethodError implements NoSuchMethodError { | |
1039 final String _message; | |
1040 final String _method; | |
1041 final String _receiver; | |
1042 | |
1043 JsNoSuchMethodError(this._message, match) | |
1044 : this._method = JS('', '#.method', match), | |
1045 this._receiver = JS('', '#.receiver', match); | |
1046 | |
1047 String toString() { | |
1048 if (_method == null) return 'NoSuchMethodError: $_message'; | |
1049 if (_receiver == null) { | |
1050 return 'NoSuchMethodError: Cannot call "$_method" ($_message)'; | |
1051 } | |
1052 return 'NoSuchMethodError: Cannot call "$_method" on "$_receiver" ' | |
1053 '($_message)'; | |
1054 } | |
1055 } | |
1056 | |
965 /** | 1057 /** |
966 * Called from catch blocks in generated code to extract the Dart | 1058 * Called from catch blocks in generated code to extract the Dart |
967 * exception from the thrown value. The thrown value may have been | 1059 * exception from the thrown value. The thrown value may have been |
968 * created by [$throw] or it may be a 'native' JS exception. | 1060 * created by [$throw] or it may be a 'native' JS exception. |
969 * | 1061 * |
970 * Some native exceptions are mapped to new Dart instances, others are | 1062 * Some native exceptions are mapped to new Dart instances, others are |
971 * returned unmodified. | 1063 * returned unmodified. |
972 */ | 1064 */ |
973 unwrapException(ex) { | 1065 unwrapException(ex) { |
974 // Note that we are checking if the object has the property. If it | 1066 // Note that we are checking if the object has the property. If it |
975 // has, it could be set to null if the thrown value is null. | 1067 // has, it could be set to null if the thrown value is null. |
976 if (JS('bool', r'"dartException" in #', ex)) { | 1068 if (JS('bool', r'"dartException" in #', ex)) { |
977 return JS('', r'#.dartException', ex); | 1069 return JS('', r'#.dartException', ex); |
978 } | 1070 } |
979 | 1071 |
980 // Grab hold of the exception message. This field is available on | 1072 // Grab hold of the exception message. This field is available on |
981 // all supported browsers. | 1073 // all supported browsers. |
982 var message = JS('var', r'#.message', ex); | 1074 var message = JS('var', r'#.message', ex); |
983 | 1075 |
984 if (JS('bool', r'# instanceof TypeError', ex)) { | 1076 if (JS('bool', r'# instanceof TypeError', ex)) { |
985 // The type and arguments fields are Chrome specific but they | 1077 var match; |
986 // allow us to get very detailed information about what kind of | 1078 var nsme = TypeErrorDecoder.noSuchMethodPattern; |
987 // exception occurred. | 1079 var nul = TypeErrorDecoder.nullErrorPattern; |
988 var type = JS('var', r'#.type', ex); | 1080 var undef = TypeErrorDecoder.undefinedErrorPattern; |
989 var name = JS('var', r'#.arguments ? #.arguments[0] : ""', ex, ex); | 1081 if ((match = JS('', '#.match(#)', nsme, message)) != null) { |
ahe
2013/01/17 01:08:05
These fields have been removed from V8 (and Chrome
| |
990 if (contains(message, 'JSNull') || | 1082 return new JsNoSuchMethodError(message, match); |
991 type == 'property_not_function' || | 1083 } else if ((match = JS('', '#.match(#)', nul, message)) != null || |
992 type == 'called_non_callable' || | 1084 (match = JS('', '#.match(#)', undef, message)) != null) { |
993 type == 'non_object_property_call' || | 1085 return new NullError(message, match); |
994 type == 'non_object_property_load') { | |
995 return new NoSuchMethodError(null, name, [], {}); | |
996 } else if (type == 'undefined_method') { | |
997 return new NoSuchMethodError('', name, [], {}); | |
998 } | 1086 } |
999 | 1087 |
1000 var ieErrorCode = JS('int', '#.number & 0xffff', ex); | 1088 var ieErrorCode = JS('int', '#.number & 0xffff', ex); |
1001 var ieFacilityNumber = JS('int', '#.number>>16 & 0x1FFF', ex); | 1089 var ieFacilityNumber = JS('int', '#.number>>16 & 0x1FFF', ex); |
1002 // If we cannot use [type] to determine what kind of exception | 1090 if (ieErrorCode == 438 && ieFacilityNumber == 10) { |
1003 // we're dealing with we fall back on looking at the exception | 1091 // Object doesn't support property or method 'foo' which sets the error |
1004 // message if it is available and a string. | 1092 // code 438 in IE. |
1005 if (message is String) { | 1093 return new NoSuchMethodError('', '<unknown>', [], {}); |
1006 if (message.endsWith('is null') || | |
1007 message.endsWith('is undefined') || | |
1008 message.endsWith('is null or undefined') || | |
1009 message.endsWith('of undefined') || | |
1010 message.endsWith('of null')) { | |
1011 return new NoSuchMethodError(null, '<unknown>', [], {}); | |
1012 } else if (contains(message, ' has no method ') || | |
1013 contains(message, ' is not a function') || | |
1014 (ieErrorCode == 438 && ieFacilityNumber == 10)) { | |
1015 // Examples: | |
1016 // x.foo is not a function | |
1017 // 'undefined' is not a function (evaluating 'x.foo(1,2,3)') | |
1018 // Object doesn't support property or method 'foo' which sets the error | |
1019 // code 438 in IE. | |
1020 // TODO(kasperl): Compute the right name if possible. | |
1021 return new NoSuchMethodError('', '<unknown>', [], {}); | |
1022 } | |
1023 } | 1094 } |
1024 | 1095 |
1025 // If we cannot determine what kind of error this is, we fall back | 1096 // If we cannot determine what kind of error this is, we fall back |
1026 // to reporting this as a generic exception. It's probably better | 1097 // to reporting this as a generic exception. It's probably better |
1027 // than nothing. | 1098 // than nothing. |
1028 return new Exception(message is String ? message : ''); | 1099 return new Exception(message is String ? message : ''); |
1029 } | 1100 } |
1030 | 1101 |
1031 if (JS('bool', r'# instanceof RangeError', ex)) { | 1102 if (JS('bool', r'# instanceof RangeError', ex)) { |
1032 if (message is String && contains(message, 'call stack')) { | 1103 if (message is String && contains(message, 'call stack')) { |
(...skipping 615 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1648 // A list representing a type with arguments. | 1719 // A list representing a type with arguments. |
1649 return getTypeArgumentAsString(type); | 1720 return getTypeArgumentAsString(type); |
1650 } else { | 1721 } else { |
1651 // A reference to the constructor. | 1722 // A reference to the constructor. |
1652 return getConstructorName(type); | 1723 return getConstructorName(type); |
1653 } | 1724 } |
1654 } | 1725 } |
1655 | 1726 |
1656 String joinArguments(var types, int startIndex) { | 1727 String joinArguments(var types, int startIndex) { |
1657 bool firstArgument = true; | 1728 bool firstArgument = true; |
1658 StringBuffer buffer = new StringBuffer(); | 1729 String result = ''; |
1659 for (int index = startIndex; index < types.length; index++) { | 1730 for (int index = startIndex; index < types.length; index++) { |
1660 if (firstArgument) { | 1731 if (firstArgument) { |
1661 firstArgument = false; | 1732 firstArgument = false; |
1662 } else { | 1733 } else { |
1663 buffer. add(', '); | 1734 result = '$result, '; |
1664 } | 1735 } |
1665 var argument = types[index]; | 1736 var argument = types[index]; |
1666 buffer.add(runtimeTypeToString(argument)); | 1737 result = '$result${runtimeTypeToString(argument)}'; |
1667 } | 1738 } |
1668 return buffer.toString(); | 1739 return result; |
1669 } | 1740 } |
1670 | 1741 |
1671 String getRuntimeTypeString(var object) { | 1742 String getRuntimeTypeString(var object) { |
1672 String className = isJsArray(object) ? 'List' : getClassName(object); | 1743 String className = isJsArray(object) ? 'List' : getClassName(object); |
1673 var typeInfo = JS('var', r'#.builtin$typeInfo', object); | 1744 var typeInfo = JS('var', r'#.builtin$typeInfo', object); |
1674 if (typeInfo == null) return className; | 1745 if (typeInfo == null) return className; |
1675 return "$className<${joinArguments(typeInfo, 0)}>"; | 1746 return "$className<${joinArguments(typeInfo, 0)}>"; |
1676 } | 1747 } |
1677 | 1748 |
1678 /** | 1749 /** |
(...skipping 29 matching lines...) Expand all Loading... | |
1708 if (len != t.length) return false; | 1779 if (len != t.length) return false; |
1709 for (int i = 1; i < len; i++) { | 1780 for (int i = 1; i < len; i++) { |
1710 if (!isSubtype(s[i], t[i])) { | 1781 if (!isSubtype(s[i], t[i])) { |
1711 return false; | 1782 return false; |
1712 } | 1783 } |
1713 } | 1784 } |
1714 return true; | 1785 return true; |
1715 } | 1786 } |
1716 | 1787 |
1717 createRuntimeType(String name) => new TypeImpl(name); | 1788 createRuntimeType(String name) => new TypeImpl(name); |
OLD | NEW |