blob: ad8d98c36270294e1cba181592e2c88446c1bcc2 [file] [log] [blame]
// 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.
library _js_helper;
import 'dart:collection';
part 'constant_map.dart';
part 'native_helper.dart';
part 'regexp_helper.dart';
part 'string_helper.dart';
part 'mirror_opt_in_message.dart';
// Performance critical helper methods.
add(var a, var b) => (a is num && b is num)
? JS('num', r'# + #', a, b)
: add$slow(a, b);
sub(var a, var b) => (a is num && b is num)
? JS('num', r'# - #', a, b)
: sub$slow(a, b);
div(var a, var b) => (a is num && b is num)
? JS('num', r'# / #', a, b)
: div$slow(a, b);
mul(var a, var b) => (a is num && b is num)
? JS('num', r'# * #', a, b)
: mul$slow(a, b);
gt(var a, var b) => (a is num && b is num)
? JS('bool', r'# > #', a, b)
: gt$slow(a, b);
ge(var a, var b) => (a is num && b is num)
? JS('bool', r'# >= #', a, b)
: ge$slow(a, b);
lt(var a, var b) => (a is num && b is num)
? JS('bool', r'# < #', a, b)
: lt$slow(a, b);
le(var a, var b) => (a is num && b is num)
? JS('bool', r'# <= #', a, b)
: le$slow(a, b);
gtB(var a, var b) => (a is num && b is num)
? JS('bool', r'# > #', a, b)
: identical(gt$slow(a, b), true);
geB(var a, var b) => (a is num && b is num)
? JS('bool', r'# >= #', a, b)
: identical(ge$slow(a, b), true);
ltB(var a, var b) => (a is num && b is num)
? JS('bool', r'# < #', a, b)
: identical(lt$slow(a, b), true);
leB(var a, var b) => (a is num && b is num)
? JS('bool', r'# <= #', a, b)
: identical(le$slow(a, b), true);
index(var a, var index) {
// The type test may cause a NoSuchMethodError to be thrown but
// that matches the specification of what the indexing operator is
// supposed to do.
bool isJsArrayOrString = JS('bool',
r'typeof # == "string" || #.constructor === Array',
a, a);
if (isJsArrayOrString) {
var key = JS('int', '# >>> 0', index);
if (identical(key, index) && key < JS('int', r'#.length', a)) {
return JS('var', r'#[#]', a, key);
}
}
return index$slow(a, index);
}
indexSet(var a, var index, var value) {
// The type test may cause a NoSuchMethodError to be thrown but
// that matches the specification of what the indexing operator is
// supposed to do.
bool isMutableJsArray = JS('bool',
r'#.constructor === Array && !#.immutable$list',
a, a);
if (isMutableJsArray) {
var key = JS('int', '# >>> 0', index);
if (identical(key, index) && key < JS('int', r'#.length', a)) {
JS('void', r'#[#] = #', a, key, value);
return;
}
}
indexSet$slow(a, index, value);
}
/**
* Returns true if both arguments are numbers.
*
* If only the first argument is a number, an
* [ArgumentError] with the other argument is thrown.
*/
bool checkNumbers(var a, var b) {
if (a is num) {
if (b is num) {
return true;
} else {
throw new ArgumentError(b);
}
}
return false;
}
bool isJsArray(var value) {
return value != null && JS('bool', r'#.constructor === Array', value);
}
add$slow(var a, var b) {
if (checkNumbers(a, b)) {
return JS('num', r'# + #', a, b);
}
return UNINTERCEPTED(a + b);
}
div$slow(var a, var b) {
if (checkNumbers(a, b)) {
return JS('num', r'# / #', a, b);
}
return UNINTERCEPTED(a / b);
}
mul$slow(var a, var b) {
if (checkNumbers(a, b)) {
return JS('num', r'# * #', a, b);
}
return UNINTERCEPTED(a * b);
}
sub$slow(var a, var b) {
if (checkNumbers(a, b)) {
return JS('num', r'# - #', a, b);
}
return UNINTERCEPTED(a - b);
}
mod(var a, var b) {
if (checkNumbers(a, b)) {
// Euclidean Modulo.
num result = JS('num', r'# % #', a, b);
if (result == 0) return 0; // Make sure we don't return -0.0.
if (result > 0) return result;
if (JS('num', '#', b) < 0) {
return result - JS('num', '#', b);
} else {
return result + JS('num', '#', b);
}
}
return UNINTERCEPTED(a % b);
}
tdiv(var a, var b) {
if (checkNumbers(a, b)) {
return (JS('num', r'# / #', a, b)).truncate();
}
return UNINTERCEPTED(a ~/ b);
}
eq(var a, var b) {
if (JS('bool', r'# == null', a)) return JS('bool', r'# == null', b);
if (JS('bool', r'# == null', b)) return false;
if (JS('bool', r'typeof # === "object"', a)) {
if (JS_HAS_EQUALS(a)) {
return UNINTERCEPTED(a == b);
}
}
// TODO(lrn): is NaN === NaN ? Is -0.0 === 0.0 ?
return JS('bool', r'# === #', a, b);
}
bool eqB(var a, var b) {
if (JS('bool', r'# == null', a)) return JS('bool', r'# == null', b);
if (JS('bool', r'# == null', b)) return false;
if (JS('bool', r'typeof # === "object"', a)) {
if (JS_HAS_EQUALS(a)) {
return identical(UNINTERCEPTED(a == b), true);
}
}
// TODO(lrn): is NaN === NaN ? Is -0.0 === 0.0 ?
return JS('bool', r'# === #', a, b);
}
eqq(var a, var b) {
return JS('bool', r'# === #', a, b);
}
gt$slow(var a, var b) {
if (checkNumbers(a, b)) {
return JS('bool', r'# > #', a, b);
}
return UNINTERCEPTED(a > b);
}
ge$slow(var a, var b) {
if (checkNumbers(a, b)) {
return JS('bool', r'# >= #', a, b);
}
return UNINTERCEPTED(a >= b);
}
lt$slow(var a, var b) {
if (checkNumbers(a, b)) {
return JS('bool', r'# < #', a, b);
}
return UNINTERCEPTED(a < b);
}
le$slow(var a, var b) {
if (checkNumbers(a, b)) {
return JS('bool', r'# <= #', a, b);
}
return UNINTERCEPTED(a <= b);
}
shl(var a, var b) {
// TODO(floitsch): inputs must be integers.
if (checkNumbers(a, b)) {
if (JS('num', '#', b) < 0) throw new ArgumentError(b);
// JavaScript only looks at the last 5 bits of the shift-amount. Shifting
// by 33 is hence equivalent to a shift by 1.
if (JS('bool', r'# > 31', b)) return 0;
return JS('num', r'(# << #) >>> 0', a, b);
}
return UNINTERCEPTED(a << b);
}
shr(var a, var b) {
// TODO(floitsch): inputs must be integers.
if (checkNumbers(a, b)) {
if (JS('num', '#', b) < 0) throw new ArgumentError(b);
if (JS('num', '#', a) > 0) {
// JavaScript only looks at the last 5 bits of the shift-amount. In JS
// shifting by 33 is hence equivalent to a shift by 1. Shortcut the
// computation when that happens.
if (JS('bool', r'# > 31', b)) return 0;
// Given that 'a' is positive we must not use '>>'. Otherwise a number
// that has the 31st bit set would be treated as negative and shift in
// ones.
return JS('num', r'# >>> #', a, b);
}
// For negative numbers we just clamp the shift-by amount. 'a' could be
// negative but not have its 31st bit set. The ">>" would then shift in
// 0s instead of 1s. Therefore we cannot simply return 0xFFFFFFFF.
if (JS('num', '#', b) > 31) b = 31;
return JS('num', r'(# >> #) >>> 0', a, b);
}
return UNINTERCEPTED(a >> b);
}
and(var a, var b) {
// TODO(floitsch): inputs must be integers.
if (checkNumbers(a, b)) {
return JS('num', r'(# & #) >>> 0', a, b);
}
return UNINTERCEPTED(a & b);
}
or(var a, var b) {
// TODO(floitsch): inputs must be integers.
if (checkNumbers(a, b)) {
return JS('num', r'(# | #) >>> 0', a, b);
}
return UNINTERCEPTED(a | b);
}
xor(var a, var b) {
// TODO(floitsch): inputs must be integers.
if (checkNumbers(a, b)) {
return JS('num', r'(# ^ #) >>> 0', a, b);
}
return UNINTERCEPTED(a ^ b);
}
not(var a) {
if (JS('bool', r'typeof # === "number"', a)) {
return JS('num', r'(~#) >>> 0', a);
}
return UNINTERCEPTED(~a);
}
neg(var a) {
if (JS('bool', r'typeof # === "number"', a)) return JS('num', r'-#', a);
return UNINTERCEPTED(-a);
}
index$slow(var a, var index) {
if (a is String || isJsArray(a)) {
if (index is !int) {
if (index is !num) throw new ArgumentError(index);
if (!identical(index.truncate(), index)) throw new ArgumentError(index);
}
if (index < 0 || index >= a.length) {
throw new RangeError.value(index);
}
return JS('', r'#[#]', a, index);
}
return UNINTERCEPTED(a[index]);
}
void indexSet$slow(var a, var index, var value) {
if (isJsArray(a)) {
if (index is !int) {
throw new ArgumentError(index);
}
if (index < 0 || index >= a.length) {
throw new RangeError.value(index);
}
checkMutable(a, 'indexed set');
JS('void', r'#[#] = #', a, index, value);
return;
}
UNINTERCEPTED(a[index] = value);
}
checkMutable(list, reason) {
if (JS('bool', r'!!(#.immutable$list)', list)) {
throw new UnsupportedError(reason);
}
}
checkGrowable(list, reason) {
if (JS('bool', r'!!(#.fixed$length)', list)) {
throw new UnsupportedError(reason);
}
}
String S(value) {
var res = value.toString();
if (res is !String) throw new ArgumentError(value);
return res;
}
class ListIterator<T> implements Iterator<T> {
int i;
List<T> list;
ListIterator(List<T> this.list) : i = 0;
bool get hasNext => i < JS('int', r'#.length', list);
T next() {
if (!hasNext) throw new StateError("No more elements");
var value = JS('', r'#[#]', list, i);
i += 1;
return value;
}
}
createInvocationMirror(name, internalName, type, arguments, argumentNames) =>
new JSInvocationMirror(name, internalName, type, arguments, argumentNames);
class JSInvocationMirror implements InvocationMirror {
static const METHOD = 0;
static const GETTER = 1;
static const SETTER = 2;
final String memberName;
final String _internalName;
final int _kind;
final List _arguments;
final List _namedArgumentNames;
/** Map from argument name to index in _arguments. */
Map<String,dynamic> _namedIndices = null;
JSInvocationMirror(this.memberName,
this._internalName,
this._kind,
this._arguments,
this._namedArgumentNames);
bool get isMethod => _kind == METHOD;
bool get isGetter => _kind == GETTER;
bool get isSetter => _kind == SETTER;
bool get isAccessor => _kind != METHOD;
List get positionalArguments {
if (isGetter) return null;
var list = [];
var argumentCount =
_arguments.length - _namedArgumentNames.length;
for (var index = 0 ; index < argumentCount ; index++) {
list.add(_arguments[index]);
}
return list;
}
Map<String,dynamic> get namedArguments {
if (isAccessor) return null;
var map = <String,dynamic>{};
int namedArgumentCount = _namedArgumentNames.length;
int namedArgumentsStartIndex = _arguments.length - namedArgumentCount;
for (int i = 0; i < namedArgumentCount; i++) {
map[_namedArgumentNames[i]] = _arguments[namedArgumentsStartIndex + i];
}
return map;
}
invokeOn(Object object) {
List arguments = _arguments;
if (!isJsArray(arguments)) arguments = new List.from(arguments);
return JS("var", "#[#].apply(#, #)",
object, _internalName, object, arguments);
}
}
class Primitives {
static int hashCodeSeed = 0;
static bool mirrorsEnabled = false;
static int objectHashCode(object) {
int hash = JS('var', r'#.$identityHash', object);
if (hash == null) {
// TOOD(ahe): We should probably randomize this somehow.
hash = ++hashCodeSeed;
JS('void', r'#.$identityHash = #', object, hash);
}
return hash;
}
/**
* This is the low-level method that is used to implement
* [print]. It is possible to override this function from JavaScript
* by defining a function in JavaScript called "dartPrint".
*/
static void printString(String string) {
if ((MIRROR_OPT_IN_MESSAGE == string)) {
// Turn on mirrors. Also, make sure that this message isn't easy
// to suppress by not calling dartPrint.
mirrorsEnabled = true;
} else if (JS('bool', r'typeof dartPrint == "function"')) {
// Support overriding print from JavaScript.
JS('void', r'dartPrint(#)', string);
return;
}
// Inside browser.
if (JS('bool', r'typeof window == "object"')) {
// On IE, the console is only defined if dev tools is open.
if (JS('bool', r'typeof console == "object"')) {
JS('void', r'console.log(#)', string);
}
return;
}
// Running in d8, the V8 developer shell, or in Firefox' js-shell.
if (JS('bool', r'typeof print == "function"')) {
JS('void', r'print(#)', string);
return;
}
// This is somewhat nasty, but we don't want to drag in a bunch of
// dependencies to handle a situation that cannot happen. So we
// avoid using Dart [:throw:] and Dart [toString].
JS('void', "throw 'Unable to print message: ' + String(#)", string);
}
static int parseInt(String string) {
checkString(string);
var match = JS('=List',
r'/^\s*[+-]?(?:0(x)[a-f0-9]+|\d+)\s*$/i.exec(#)',
string);
if (match == null) {
throw new FormatException(string);
}
var base = 10;
if (match[1] != null) base = 16;
var result = JS('num', r'parseInt(#, #)', string, base);
if (result.isNaN) throw new FormatException(string);
return result;
}
static double parseDouble(String string) {
checkString(string);
// Notice that JS parseFloat accepts garbage at the end of the string.
// Accept, ignoring leading and trailing whitespace:
// - NaN
// - [+/-]Infinity
// - a Dart double literal
if (!JS('bool',
r'/^\s*(?:NaN|[+-]?(?:Infinity|'
r'(?:\.\d+|\d+(?:\.\d+)?)(?:[eE][+-]?\d+)?))\s*$/.test(#)',
string)) {
throw new FormatException(string);
}
var result = JS('num', r'parseFloat(#)', string);
if (result.isNaN && string != 'NaN') {
throw new FormatException(string);
}
return result;
}
/** [: r"$".charCodeAt(0) :] */
static const int DOLLAR_CHAR_VALUE = 36;
static String objectTypeName(Object object) {
String name = constructorNameFallback(object);
if (name == 'Object') {
// Try to decompile the constructor by turning it into a string
// and get the name out of that. If the decompiled name is a
// string, we use that instead of the very generic 'Object'.
var decompiled = JS('var', r'#.match(/^\s*function\s*(\S*)\s*\(/)[1]',
JS('var', r'String(#.constructor)', object));
if (decompiled is String) name = decompiled;
}
// TODO(kasperl): If the namer gave us a fresh global name, we may
// want to remove the numeric suffix that makes it unique too.
if (identical(name.charCodeAt(0), DOLLAR_CHAR_VALUE)) name = name.substring(1);
return name;
}
static String objectToString(Object object) {
String name = objectTypeName(object);
return "Instance of '$name'";
}
static List newList(length) {
// TODO(sra): For good concrete type analysis we need the JS-type to
// specifically name the JavaScript Array implementation. 'List' matches
// all the dart:html types that implement List<T>.
if (length == null) return JS('=List', r'new Array()');
if ((length is !int) || (length < 0)) {
throw new ArgumentError(length);
}
var result = JS('=List', r'new Array(#)', length);
JS('void', r'#.fixed$length = #', result, true);
return result;
}
static num dateNow() => JS('num', r'Date.now()');
static num numMicroseconds() {
if (JS('bool', 'typeof window != "undefined" && window !== null')) {
var performance = JS('var', 'window.performance');
if (performance != null &&
JS('bool', 'typeof #.webkitNow == "function"', performance)) {
return JS('num', '#.webkitNow()', performance);
}
}
return 1000 * dateNow();
}
// This is to avoid stack overflows due to very large argument arrays in
// apply(). It fixes http://dartbug.com/6919
static String _fromCharCodeApply(List<int> array) {
String result = "";
const kMaxApply = 500;
int end = array.length;
for (var i = 0; i < end; i += kMaxApply) {
var subarray;
if (end <= kMaxApply) {
subarray = array;
} else {
subarray = JS('List<int>', r'array.slice(#, #)',
i, i + kMaxApply < end ? i + kMaxApply : end);
}
result = JS('String', '# + String.fromCharCode.apply(#, #)',
result, null, subarray);
}
return result;
}
static String stringFromCodePoints(codePoints) {
List<int> a = <int>[];
for (var i in codePoints) {
if (i is !int) throw new ArgumentError(i);
if (i <= 0xffff) {
a.add(i);
} else if (i <= 0x10ffff) {
a.add(0xd800 + ((((i - 0x10000) >> 10) & 0x3ff)));
a.add(0xdc00 + (i & 0x3ff));
} else {
throw new ArgumentError(i);
}
}
return _fromCharCodeApply(a);
}
static String stringFromCharCodes(charCodes) {
for (var i in charCodes) {
if (i is !int) throw new ArgumentError(i);
if (i < 0) throw new ArgumentError(i);
if (i > 0xffff) return stringFromCodePoints(charCodes);
}
return _fromCharCodeApply(charCodes);
}
static String getTimeZoneName(receiver) {
// When calling toString on a Date it will emit the timezone in parenthesis.
// Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)".
// We extract this name using a regexp.
var d = lazyAsJsDate(receiver);
return JS('String', r'/\((.*)\)/.exec(#.toString())[1]', d);
}
static int getTimeZoneOffsetInMinutes(receiver) {
// Note that JS and Dart disagree on the sign of the offset.
return -JS('int', r'#.getTimezoneOffset()', lazyAsJsDate(receiver));
}
static valueFromDecomposedDate(years, month, day, hours, minutes, seconds,
milliseconds, isUtc) {
final int MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000;
checkInt(years);
checkInt(month);
checkInt(day);
checkInt(hours);
checkInt(minutes);
checkInt(seconds);
checkInt(milliseconds);
checkBool(isUtc);
var jsMonth = month - 1;
var value;
if (isUtc) {
value = JS('num', r'Date.UTC(#, #, #, #, #, #, #)',
years, jsMonth, day, hours, minutes, seconds, milliseconds);
} else {
value = JS('num', r'new Date(#, #, #, #, #, #, #).valueOf()',
years, jsMonth, day, hours, minutes, seconds, milliseconds);
}
if (value.isNaN ||
value < -MAX_MILLISECONDS_SINCE_EPOCH ||
value > MAX_MILLISECONDS_SINCE_EPOCH) {
throw new ArgumentError();
}
if (years <= 0 || years < 100) return patchUpY2K(value, years, isUtc);
return value;
}
static patchUpY2K(value, years, isUtc) {
var date = JS('', r'new Date(#)', value);
if (isUtc) {
JS('num', r'#.setUTCFullYear(#)', date, years);
} else {
JS('num', r'#.setFullYear(#)', date, years);
}
return JS('num', r'#.valueOf()', date);
}
// Lazily keep a JS Date stored in the JS object.
static lazyAsJsDate(receiver) {
if (JS('bool', r'#.date === (void 0)', receiver)) {
JS('void', r'#.date = new Date(#)', receiver,
receiver.millisecondsSinceEpoch);
}
return JS('Date', r'#.date', receiver);
}
// The getters for date and time parts below add a positive integer to ensure
// that the result is really an integer, because the JavaScript implementation
// may return -0.0 instead of 0.
static getYear(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCFullYear() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getFullYear() + 0)', lazyAsJsDate(receiver));
}
static getMonth(receiver) {
return (receiver.isUtc)
? JS('int', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver))
: JS('int', r'#.getMonth() + 1', lazyAsJsDate(receiver));
}
static getDay(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getDate() + 0)', lazyAsJsDate(receiver));
}
static getHours(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getHours() + 0)', lazyAsJsDate(receiver));
}
static getMinutes(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver));
}
static getSeconds(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver));
}
static getMilliseconds(receiver) {
return (receiver.isUtc)
? JS('int', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver))
: JS('int', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver));
}
static getWeekday(receiver) {
int weekday = (receiver.isUtc)
? JS('int', r'#.getUTCDay() + 0', lazyAsJsDate(receiver))
: JS('int', r'#.getDay() + 0', lazyAsJsDate(receiver));
// Adjust by one because JS weeks start on Sunday.
return (weekday + 6) % 7 + 1;
}
static valueFromDateString(str) {
if (str is !String) throw new ArgumentError(str);
var value = JS('num', r'Date.parse(#)', str);
if (value.isNaN) throw new ArgumentError(str);
return value;
}
static getProperty(object, key) {
if (object == null || object is bool || object is num || object is String) {
throw new ArgumentError(object);
}
return JS('var', '#[#]', object, key);
}
static void setProperty(object, key, value) {
if (object == null || object is bool || object is num || object is String) {
throw new ArgumentError(object);
}
JS('void', '#[#] = #', object, key, value);
}
static applyFunction(Function function,
List positionalArguments,
Map<String, dynamic> namedArguments) {
int argumentCount = 0;
StringBuffer buffer = new StringBuffer();
List arguments = [];
if (positionalArguments != null) {
argumentCount += positionalArguments.length;
arguments.addAll(positionalArguments);
}
// Sort the named arguments to get the right selector name and
// arguments order.
if (namedArguments != null && !namedArguments.isEmpty) {
// Call new List.from to make sure we get a JavaScript array.
List<String> listOfNamedArguments =
new List<String>.from(namedArguments.keys);
argumentCount += namedArguments.length;
// We're sorting on strings, and the behavior is the same between
// Dart string sort and JS string sort. To avoid needing the Dart
// sort implementation, we use the JavaScript one instead.
JS('void', '#.sort()', listOfNamedArguments);
listOfNamedArguments.forEach((String name) {
buffer.add('\$$name');
arguments.add(namedArguments[name]);
});
}
String selectorName = 'call\$$argumentCount$buffer';
var jsFunction = JS('var', '#[#]', function, selectorName);
if (jsFunction == null) {
throw new NoSuchMethodError(function, selectorName, arguments, {});
}
// We bound 'this' to [function] because of how we compile
// closures: escaped local variables are stored and accessed through
// [function].
return JS('var', '#.apply(#, #)', jsFunction, function, arguments);
}
static getConstructor(String className) {
// TODO(ahe): How to safely access $?
return JS('var', r'$[#]', className);
}
}
/**
* Called by generated code to throw an illegal-argument exception,
* for example, if a non-integer index is given to an optimized
* indexed access.
*/
iae(argument) {
throw new ArgumentError(argument);
}
/**
* Called by generated code to throw an index-out-of-range exception,
* for example, if a bounds check fails in an optimized indexed
* access.
*/
ioore(index) {
throw new RangeError.value(index);
}
listInsertRange(receiver, start, length, initialValue) {
if (length == 0) {
return;
}
if (length is !int) throw new ArgumentError(length);
if (length < 0) throw new ArgumentError(length);
if (start is !int) throw new ArgumentError(start);
var receiverLength = JS('num', r'#.length', receiver);
if (start < 0 || start > receiverLength) {
throw new RangeError.value(start);
}
receiver.length = receiverLength + length;
Arrays.copy(receiver,
start,
receiver,
start + length,
receiverLength - start);
if (initialValue != null) {
for (int i = start; i < start + length; i++) {
receiver[i] = initialValue;
}
}
receiver.length = receiverLength + length;
}
stringLastIndexOfUnchecked(receiver, element, start)
=> JS('int', r'#.lastIndexOf(#, #)', receiver, element, start);
checkNull(object) {
if (object == null) throw new ArgumentError(null);
return object;
}
checkNum(value) {
if (value is !num) {
throw new ArgumentError(value);
}
return value;
}
checkInt(value) {
if (value is !int) {
throw new ArgumentError(value);
}
return value;
}
checkBool(value) {
if (value is !bool) {
throw new ArgumentError(value);
}
return value;
}
checkString(value) {
if (value is !String) {
throw new ArgumentError(value);
}
return value;
}
class MathNatives {
static int parseInt(str) {
checkString(str);
if (!JS('bool',
r'/^\s*[+-]?(?:0[xX][abcdefABCDEF0-9]+|\d+)\s*$/.test(#)',
str)) {
throw new FormatException(str);
}
var trimmed = str.trim();
var base = 10;;
if ((trimmed.length > 2 && (trimmed[1] == 'x' || trimmed[1] == 'X')) ||
(trimmed.length > 3 && (trimmed[2] == 'x' || trimmed[2] == 'X'))) {
base = 16;
}
var ret = JS('num', r'parseInt(#, #)', trimmed, base);
if (ret.isNaN) throw new FormatException(str);
return ret;
}
static double parseDouble(String str) {
checkString(str);
var ret = JS('num', r'parseFloat(#)', str);
if (ret == 0 && (str.startsWith("0x") || str.startsWith("0X"))) {
// TODO(ahe): This is unspecified, but tested by co19.
ret = JS('num', r'parseInt(#)', str);
}
if (ret.isNaN && str != 'NaN' && str != '-NaN') {
throw new FormatException(str);
}
return ret;
}
static double sqrt(num value)
=> JS('double', r'Math.sqrt(#)', checkNum(value));
static double sin(num value)
=> JS('double', r'Math.sin(#)', checkNum(value));
static double cos(num value)
=> JS('double', r'Math.cos(#)', checkNum(value));
static double tan(num value)
=> JS('double', r'Math.tan(#)', checkNum(value));
static double acos(num value)
=> JS('double', r'Math.acos(#)', checkNum(value));
static double asin(num value)
=> JS('double', r'Math.asin(#)', checkNum(value));
static double atan(num value)
=> JS('double', r'Math.atan(#)', checkNum(value));
static double atan2(num a, num b)
=> JS('double', r'Math.atan2(#, #)', checkNum(a), checkNum(b));
static double exp(num value)
=> JS('double', r'Math.exp(#)', checkNum(value));
static double log(num value)
=> JS('double', r'Math.log(#)', checkNum(value));
static num pow(num value, num exponent) {
checkNum(value);
checkNum(exponent);
return JS('num', r'Math.pow(#, #)', value, exponent);
}
static double random() => JS('double', r'Math.random()');
}
/**
* Throws the given Dart object as an exception by wrapping it in a
* proper JavaScript error object and then throwing that. That gives
* us a reasonable stack trace on most JavaScript implementations. The
* code in [unwrapException] deals with getting the original Dart
* object out of the wrapper again.
*/
$throw(ex) {
if (ex == null) ex = const NullThrownError();
var jsError = JS('var', r'new Error()');
JS('void', r'#.name = #', jsError, ex);
JS('void', r'#.description = #', jsError, ex);
JS('void', r'#.dartException = #', jsError, ex);
JS('void', r'#.toString = #', jsError,
DART_CLOSURE_TO_JS(toStringWrapper));
JS('void', r'throw #', jsError);
}
/**
* This method is installed as JavaScript toString method on exception
* objects in [$throw]. So JavaScript 'this' binds to an instance of
* JavaScript Error to which we have added a property 'dartException'
* which holds a Dart object.
*/
toStringWrapper() => JS('', r'this.dartException').toString();
makeLiteralListConst(list) {
JS('bool', r'#.immutable$list = #', list, true);
JS('bool', r'#.fixed$length = #', list, true);
return list;
}
throwRuntimeError(message) {
throw new RuntimeError(message);
}
throwAbstractClassInstantiationError(className) {
throw new AbstractClassInstantiationError(className);
}
/**
* Called from catch blocks in generated code to extract the Dart
* exception from the thrown value. The thrown value may have been
* created by [$throw] or it may be a 'native' JS exception.
*
* Some native exceptions are mapped to new Dart instances, others are
* returned unmodified.
*
* TODO(erikcorry): Fix this to produce consistent result regardless of
* minification.
*/
unwrapException(ex) {
// Note that we are checking if the object has the property. If it
// has, it could be set to null if the thrown value is null.
if (JS('bool', r'"dartException" in #', ex)) {
return JS('', r'#.dartException', ex);
}
// Grab hold of the exception message. This field is available on
// all supported browsers.
var message = JS('var', r'#.message', ex);
if (JS('bool', r'# instanceof TypeError', ex)) {
// The type and arguments fields are Chrome specific but they
// allow us to get very detailed information about what kind of
// exception occurred.
var type = JS('var', r'#.type', ex);
var name = JS('var', r'#.arguments ? #.arguments[0] : ""', ex, ex);
if (contains(message, 'JSNull') ||
type == 'property_not_function' ||
type == 'called_non_callable' ||
type == 'non_object_property_call' ||
type == 'non_object_property_load') {
return new NoSuchMethodError(null, name, [], {});
} else if (type == 'undefined_method') {
return new NoSuchMethodError('', name, [], {});
}
var ieErrorCode = JS('int', '#.number & 0xffff', ex);
var ieFacilityNumber = JS('int', '#.number>>16 & 0x1FFF', ex);
// If we cannot use [type] to determine what kind of exception
// we're dealing with we fall back on looking at the exception
// message if it is available and a string.
if (message is String) {
if (message.endsWith('is null') ||
message.endsWith('is undefined') ||
message.endsWith('is null or undefined')) {
return new NoSuchMethodError(null, '<unknown>', [], {});
} else if (contains(message, ' is not a function') ||
(ieErrorCode == 438 && ieFacilityNumber == 10)) {
// Examples:
// x.foo is not a function
// 'undefined' is not a function (evaluating 'x.foo(1,2,3)')
// Object doesn't support property or method 'foo' which sets the error
// code 438 in IE.
// TODO(kasperl): Compute the right name if possible.
return new NoSuchMethodError('', '<unknown>', [], {});
}
}
// If we cannot determine what kind of error this is, we fall back
// to reporting this as a generic exception. It's probably better
// than nothing.
return new Exception(message is String ? message : '');
}
if (JS('bool', r'# instanceof RangeError', ex)) {
if (message is String && contains(message, 'call stack')) {
return new StackOverflowError();
}
// In general, a RangeError is thrown when trying to pass a number
// as an argument to a function that does not allow a range that
// includes that number.
return new ArgumentError();
}
// Check for the Firefox specific stack overflow signal.
if (JS('bool',
r"typeof InternalError == 'function' && # instanceof InternalError",
ex)) {
if (message is String && message == 'too much recursion') {
return new StackOverflowError();
}
}
// Just return the exception. We should not wrap it because in case
// the exception comes from the DOM, it is a JavaScript
// object backed by a native Dart class.
return ex;
}
/**
* Called by generated code to fetch the stack trace from an
* exception.
*/
StackTrace getTraceFromException(exception) {
return new StackTrace(JS("var", r"#.stack", exception));
}
class StackTrace {
var stack;
StackTrace(this.stack);
String toString() => stack != null ? stack : '';
}
/**
* Called by generated code to build a map literal. [keyValuePairs] is
* a list of key, value, key, value, ..., etc.
*/
makeLiteralMap(List keyValuePairs) {
Iterator iterator = keyValuePairs.iterator();
Map result = new LinkedHashMap();
while (iterator.hasNext) {
String key = iterator.next();
var value = iterator.next();
result[key] = value;
}
return result;
}
invokeClosure(Function closure,
var isolate,
int numberOfArguments,
var arg1,
var arg2) {
if (numberOfArguments == 0) {
return JS_CALL_IN_ISOLATE(isolate, () => closure());
} else if (numberOfArguments == 1) {
return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1));
} else if (numberOfArguments == 2) {
return JS_CALL_IN_ISOLATE(isolate, () => closure(arg1, arg2));
} else {
throw new Exception(
'Unsupported number of arguments for wrapped closure');
}
}
/**
* Called by generated code to convert a Dart closure to a JS
* closure when the Dart closure is passed to the DOM.
*/
convertDartClosureToJS(closure, int arity) {
if (closure == null) return null;
var function = JS('var', r'#.$identity', closure);
if (JS('bool', r'!!#', function)) return function;
function = JS("var", r"""function() {
return #(#, #, #, arguments[0], arguments[1]);
}""",
DART_CLOSURE_TO_JS(invokeClosure),
closure,
JS_CURRENT_ISOLATE(),
arity);
JS('void', r'#.$identity = #', closure, function);
return function;
}
/**
* Super class for Dart closures.
*/
class Closure implements Function {
String toString() => "Closure";
}
bool jsHasOwnProperty(var jsObject, String property) {
return JS('bool', r'#.hasOwnProperty(#)', jsObject, property);
}
jsPropertyAccess(var jsObject, String property) {
return JS('var', r'#[#]', jsObject, property);
}
/**
* Called at the end of unaborted switch cases to get the singleton
* FallThroughError exception that will be thrown.
*/
getFallThroughError() => const FallThroughErrorImplementation();
/**
* Represents the type Dynamic. The compiler treats this specially.
*/
abstract class Dynamic_ {
}
/**
* A metadata annotation describing the types instantiated by a native element.
*
* The annotation is valid on a native method and a field of a native class.
*
* By default, a field of a native class is seen as an instantiation point for
* all native classes that are a subtype of the field's type, and a native
* method is seen as an instantiation point fo all native classes that are a
* subtype of the method's return type, or the argument types of the declared
* type of the method's callback parameter.
*
* An @[Creates] annotation overrides the default set of instantiated types. If
* one or more @[Creates] annotations are present, the type of the native
* element is ignored, and the union of @[Creates] annotations is used instead.
* The names in the strings are resolved and the program will fail to compile
* with dart2js if they do not name types.
*
* The argument to [Creates] is a string. The string is parsed as the names of
* one or more types, separated by vertical bars `|`. There are some special
* names:
*
* * `=List`. This means 'exactly List', which is the JavaScript Array
* implementation of [List] and no other implementation.
*
* * `=Object`. This means 'exactly Object', which is a plain JavaScript object
* with properties and none of the subtypes of Object.
*
* Example: we may know that a method always returns a specific implementation:
*
* @Creates('_NodeList')
* List<Node> getElementsByTagName(String tag) native;
*
* Useful trick: A method can be marked as not instantiating any native classes
* with the annotation `@Creates('Null')`. This is useful for fields on native
* classes that are used only in Dart code.
*
* @Creates('Null')
* var _cachedFoo;
*/
class Creates {
final String types;
const Creates(this.types);
}
/**
* A metadata annotation describing the types returned or yielded by a native
* element.
*
* The annotation is valid on a native method and a field of a native class.
*
* By default, a native method or field is seen as returning or yielding all
* subtypes if the method return type or field type. This annotation allows a
* more precise set of types to be specified.
*
* See [Creates] for the syntax of the argument.
*
* Example: IndexedDB keys are numbers, strings and JavaScript Arrays of keys.
*
* @Returns('String|num|=List')
* dynamic key;
*
* // Equivalent:
* @Returns('String') @Returns('num') @Returns('=List')
* dynamic key;
*/
class Returns {
final String types;
const Returns(this.types);
}
/**
* Represents the type of Null. The compiler treats this specially.
* TODO(lrn): Null should be defined in core. It's a class, like int.
* It just happens to act differently in assignability tests and,
* like int, can't be extended or implemented.
*/
class Null {
factory Null() {
throw new UnsupportedError('new Null()');
}
}
setRuntimeTypeInfo(target, typeInfo) {
assert(typeInfo == null || isJsArray(typeInfo));
// We have to check for null because factories may return null.
if (target != null) JS('var', r'#.builtin$typeInfo = #', target, typeInfo);
}
getRuntimeTypeInfo(target) {
if (target == null) return null;
var res = JS('var', r'#.builtin$typeInfo', target);
// If the object does not have runtime type information, return an
// empty literal, to avoid null checks.
// TODO(ngeoffray): Make the object a top-level field to avoid
// allocating a new object every single time.
return (res == null) ? JS('var', '{}') : res;
}
/**
* The following methods are called by the runtime to implement
* checked mode and casts. We specialize each primitive type (eg int, bool), and
* use the compiler's convention to do is-checks on regular objects.
*/
boolConversionCheck(value) {
boolTypeCheck(value);
assert(value != null);
return value;
}
stringTypeCheck(value) {
if (value == null) return value;
if (value is String) return value;
throw new TypeErrorImplementation(value, 'String');
}
stringTypeCast(value) {
if (value is String || value == null) return value;
// TODO(lrn): When reified types are available, pass value.class and String.
throw new CastErrorImplementation(
Primitives.objectTypeName(value), 'String');
}
doubleTypeCheck(value) {
if (value == null) return value;
if (value is double) return value;
throw new TypeErrorImplementation(value, 'double');
}
doubleTypeCast(value) {
if (value is double || value == null) return value;
throw new CastErrorImplementation(
Primitives.objectTypeName(value), 'double');
}
numTypeCheck(value) {
if (value == null) return value;
if (value is num) return value;
throw new TypeErrorImplementation(value, 'num');
}
numTypeCast(value) {
if (value is num || value == null) return value;
throw new CastErrorImplementation(
Primitives.objectTypeName(value), 'num');
}
boolTypeCheck(value) {
if (value == null) return value;
if (value is bool) return value;
throw new TypeErrorImplementation(value, 'bool');
}
boolTypeCast(value) {
if (value is bool || value == null) return value;
throw new CastErrorImplementation(
Primitives.objectTypeName(value), 'bool');
}
functionTypeCheck(value) {
if (value == null) return value;
if (value is Function) return value;
throw new TypeErrorImplementation(value, 'Function');
}
functionTypeCast(value) {
if (value is Function || value == null) return value;
throw new CastErrorImplementation(
Primitives.objectTypeName(value), 'Function');
}
intTypeCheck(value) {
if (value == null) return value;
if (value is int) return value;
throw new TypeErrorImplementation(value, 'int');
}
intTypeCast(value) {
if (value is int || value == null) return value;
throw new CastErrorImplementation(
Primitives.objectTypeName(value), 'int');
}
void propertyTypeError(value, property) {
// Cuts the property name to the class name.
String name = property.substring(3, property.length);
throw new TypeErrorImplementation(value, name);
}
void propertyTypeCastError(value, property) {
// Cuts the property name to the class name.
String actualType = Primitives.objectTypeName(value);
String expectedType = property.substring(3, property.length);
throw new CastErrorImplementation(actualType, expectedType);
}
/**
* For types that are not supertypes of native (eg DOM) types,
* we emit a simple property check to check that an object implements
* that type.
*/
propertyTypeCheck(value, property) {
if (value == null) return value;
if (JS('bool', '!!#[#]', value, property)) return value;
propertyTypeError(value, property);
}
/**
* For types that are not supertypes of native (eg DOM) types,
* we emit a simple property check to check that an object implements
* that type.
*/
propertyTypeCast(value, property) {
if (value == null || JS('bool', '!!#[#]', value, property)) return value;
propertyTypeCastError(value, property);
}
/**
* For types that are supertypes of native (eg DOM) types, we emit a
* call because we cannot add a JS property to their prototype at load
* time.
*/
callTypeCheck(value, property) {
if (value == null) return value;
if ((identical(JS('String', 'typeof #', value), 'object'))
&& JS('bool', '#[#]()', value, property)) {
return value;
}
propertyTypeError(value, property);
}
/**
* For types that are supertypes of native (eg DOM) types, we emit a
* call because we cannot add a JS property to their prototype at load
* time.
*/
callTypeCast(value, property) {
if (value == null
|| ((JS('bool', 'typeof # === "object"', value))
&& JS('bool', '#[#]()', value, property))) {
return value;
}
propertyTypeCastError(value, property);
}
/**
* Specialization of the type check for num and String and their
* supertype since [value] can be a JS primitive.
*/
numberOrStringSuperTypeCheck(value, property) {
if (value == null) return value;
if (value is String) return value;
if (value is num) return value;
if (JS('bool', '!!#[#]', value, property)) return value;
propertyTypeError(value, property);
}
numberOrStringSuperTypeCast(value, property) {
if (value is String) return value;
if (value is num) return value;
return propertyTypeCast(value, property);
}
numberOrStringSuperNativeTypeCheck(value, property) {
if (value == null) return value;
if (value is String) return value;
if (value is num) return value;
if (JS('bool', '#[#]()', value, property)) return value;
propertyTypeError(value, property);
}
numberOrStringSuperNativeTypeCast(value, property) {
if (value == null) return value;
if (value is String) return value;
if (value is num) return value;
if (JS('bool', '#[#]()', value, property)) return value;
propertyTypeCastError(value, property);
}
/**
* Specialization of the type check for String and its supertype
* since [value] can be a JS primitive.
*/
stringSuperTypeCheck(value, property) {
if (value == null) return value;
if (value is String) return value;
if (JS('bool', '!!#[#]', value, property)) return value;
propertyTypeError(value, property);
}
stringSuperTypeCast(value, property) {
if (value is String) return value;
return propertyTypeCast(value, property);
}
stringSuperNativeTypeCheck(value, property) {
if (value == null) return value;
if (value is String) return value;
if (JS('bool', '#[#]()', value, property)) return value;
propertyTypeError(value, property);
}
stringSuperNativeTypeCast(value, property) {
if (value is String || value == null) return value;
if (JS('bool', '#[#]()', value, property)) return value;
propertyTypeCastError(value, property);
}
/**
* Specialization of the type check for List and its supertypes,
* since [value] can be a JS array.
*/
listTypeCheck(value) {
if (value == null) return value;
if (value is List) return value;
throw new TypeErrorImplementation(value, 'List');
}
listTypeCast(value) {
if (value is List || value == null) return value;
throw new CastErrorImplementation(
Primitives.objectTypeName(value), 'List');
}
listSuperTypeCheck(value, property) {
if (value == null) return value;
if (value is List) return value;
if (JS('bool', '!!#[#]', value, property)) return value;
propertyTypeError(value, property);
}
listSuperTypeCast(value, property) {
if (value is List) return value;
return propertyTypeCast(value, property);
}
listSuperNativeTypeCheck(value, property) {
if (value == null) return value;
if (value is List) return value;
if (JS('bool', '#[#]()', value, property)) return value;
propertyTypeError(value, property);
}
listSuperNativeTypeCast(value, property) {
if (value is List || value == null) return value;
if (JS('bool', '#[#]()', value, property)) return value;
propertyTypeCastError(value, property);
}
voidTypeCheck(value) {
if (value == null) return value;
throw new TypeErrorImplementation(value, 'void');
}
/**
* Special interface recognized by the compiler and implemented by DOM
* objects that support integer indexing. This interface is not
* visible to anyone, and is only injected into special libraries.
*/
abstract class JavaScriptIndexingBehavior {
}
// TODO(lrn): These exceptions should be implemented in core.
// When they are, remove the 'Implementation' here.
/** Thrown by type assertions that fail. */
class TypeErrorImplementation implements TypeError {
final String message;
TypeErrorImplementation(Object value, String type)
: message = "type '${Primitives.objectTypeName(value)}' is not a subtype "
"of type '$type'";
String toString() => message;
}
/** Thrown by the 'as' operator if the cast isn't valid. */
class CastErrorImplementation implements CastError {
// TODO(lrn): Rename to CastError (and move implementation into core).
// TODO(lrn): Change actualType and expectedType to "Type" when reified
// types are available.
final Object actualType;
final Object expectedType;
CastErrorImplementation(this.actualType, this.expectedType);
String toString() {
return "CastError: Casting value of type $actualType to"
" incompatible type $expectedType";
}
}
class FallThroughErrorImplementation implements FallThroughError {
const FallThroughErrorImplementation();
String toString() => "Switch case fall-through.";
}
/**
* Helper function for implementing asserts. The compiler treats this specially.
*/
void assertHelper(condition) {
if (condition is Function) condition = condition();
if (condition is !bool) {
throw new TypeErrorImplementation(condition, 'bool');
}
// Compare to true to avoid boolean conversion check in checked
// mode.
if (!identical(condition, true)) throw new AssertionError();
}
/**
* Called by generated code when a method that must be statically
* resolved cannot be found.
*/
void throwNoSuchMethod(obj, name, arguments, expectedArgumentNames) {
throw new NoSuchMethodError(obj, name, arguments, const {},
expectedArgumentNames);
}
/**
* Called by generated code when a static field's initializer references the
* field that is currently being initialized.
*/
void throwCyclicInit(String staticName) {
throw new RuntimeError("Cyclic initialization for static $staticName");
}
class TypeImpl implements Type {
final String typeName;
TypeImpl(this.typeName);
toString() => typeName;
int get hashCode => typeName.hashCode;
bool operator ==(other) {
if (other is !TypeImpl) return false;
return typeName == other.typeName;
}
}
String getClassName(var object) {
return JS('String', r'#.constructor.builtin$cls', object);
}
String getRuntimeTypeString(var object) {
String className = isJsArray(object) ? 'List' : getClassName(object);
var typeInfo = JS('var', r'#.builtin$typeInfo', object);
if (typeInfo == null) return className;
StringBuffer arguments = new StringBuffer();
for (var i = 0; i < typeInfo.length; i++) {
if (i > 0) {
arguments.add(', ');
}
var argument = typeInfo[i];
if (argument == null) {
argument = 'dynamic';
}
arguments.add(argument);
}
return '$className<$arguments>';
}
createRuntimeType(String name) => new TypeImpl(name);