<commit_before>// 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('coreimpl.dart');

#source('constant_map.dart');
#source('native_helper.dart');
#source('regexp_helper.dart');
#source('string_helper.dart');

// Performance critical helper methods.
add(var a, var b) => (a is num && b is num)
    ? JS('num', @'# + #', a, b)
    : add$slow(a, b);

sub(var a, var b) => (a is num && b is num)
    ? JS('num', @'# - #', a, b)
    : sub$slow(a, b);

div(var a, var b) => (a is num && b is num)
    ? JS('num', @'# / #', a, b)
    : div$slow(a, b);

mul(var a, var b) => (a is num && b is num)
    ? JS('num', @'# * #', a, b)
    : mul$slow(a, b);

gt(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# > #', a, b)
    : gt$slow(a, b);

ge(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# >= #', a, b)
    : ge$slow(a, b);

lt(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# < #', a, b)
    : lt$slow(a, b);

le(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# <= #', a, b)
    : le$slow(a, b);

gtB(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# > #', a, b)
    : gt$slow(a, b) === true;

geB(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# >= #', a, b)
    : ge$slow(a, b) === true;

ltB(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# < #', a, b)
    : lt$slow(a, b) === true;

leB(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# <= #', a, b)
    : le$slow(a, b) === true;

index(var a, var index) {
  // The type test may cause a NullPointerException to be thrown but
  // that matches the specification of what the indexing operator is
  // supposed to do.
  bool isJsArrayOrString = JS('bool',
      @'typeof # == "string" || #.constructor === Array',
      a, a);
  if (isJsArrayOrString) {
    var key = JS('int', '# >>> 0', index);
    if (key === index && key < JS('int', @'#.length', a)) {
      return JS('var', @'#[#]', a, key);
    }
  }
  return index$slow(a, index);
}

indexSet(var a, var index, var value) {
  // The type test may cause a NullPointerException to be thrown but
  // that matches the specification of what the indexing operator is
  // supposed to do.
  bool isMutableJsArray = JS('bool',
      @'#.constructor === Array && !#.immutable$list',
      a, a);
  if (isMutableJsArray) {
    var key = JS('int', '# >>> 0', index);
    if (key === index && key < JS('int', @'#.length', a)) {
      JS('void', @'#[#] = #', 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
 * [IllegalArgumentException] with the other argument is thrown.
 */
bool checkNumbers(var a, var b) {
  if (a is num) {
    if (b is num) {
      return true;
    } else {
      checkNull(b);
      throw new IllegalArgumentException(b);
    }
  }
  return false;
}

bool isJsArray(var value) {
  return value !== null && JS('bool', @'#.constructor === Array', value);
}

add$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('num', @'# + #', a, b);
  } else if (a is String) {
    // TODO(lrn): Remove when we disable String.operator+
    b = b.toString();
    if (b is String) {
      return JS('String', @'# + #', a, b);
    }
    checkNull(b);
    throw new IllegalArgumentException(b);
  }
  return UNINTERCEPTED(a + b);
}

div$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('num', @'# / #', a, b);
  }
  return UNINTERCEPTED(a / b);
}

mul$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('num', @'# * #', a, b);
  }
  return UNINTERCEPTED(a * b);
}

sub$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('num', @'# - #', a, b);
  }
  return UNINTERCEPTED(a - b);
}

mod(var a, var b) {
  if (checkNumbers(a, b)) {
    // Euclidean Modulo.
    int result = JS('num', @'# % #', a, b);
    if (result == 0) return 0;  // Make sure we don't return -0.0.
    if (result > 0) return result;
    // TODO(floitsch): Find cleaner way of forcing the type.
    b = JS('num', '#', b);
    if (b < 0) {
      return result - b;
    } else {
      return result + b;
    }
  }
  return UNINTERCEPTED(a % b);
}

tdiv(var a, var b) {
  if (checkNumbers(a, b)) {
    // TODO(floitsch): Find cleaner way of forcing the type.
    a = JS('num', '#', a);
    b = JS('num', '#', b);
    return (a / b).truncate();
  }
  return UNINTERCEPTED(a ~/ b);
}

eq(var a, var b) {
  if (JS('bool', @'typeof # === "object"', a)) {
    if (JS_HAS_EQUALS(a)) {
      return UNINTERCEPTED(a == b);
    } else {
      return JS('bool', @'# === #', a, b);
    }
  }
  // TODO(lrn): is NaN === NaN ? Is -0.0 === 0.0 ?
  return JS('bool', @'# === #', a, b);
}

bool eqB(var a, var b) {
  if (JS('bool', @'typeof # === "object"', a)) {
    if (JS_HAS_EQUALS(a)) {
      return UNINTERCEPTED(a == b) === true;
    } else {
      return JS('bool', @'# === #', a, b);
    }
  }
  // TODO(lrn): is NaN === NaN ? Is -0.0 === 0.0 ?
  return JS('bool', @'# === #', a, b);
}

eqq(var a, var b) {
  return JS('bool', @'# === #', a, b);
}

eqNull(var a) {
  if (JS('bool', @'typeof # === "object"', a)) {
    if (JS_HAS_EQUALS(a)) {
      return UNINTERCEPTED(a == null);
    } else {
      return false;
    }
  } else {
    return JS('bool', @'typeof # === "undefined"', a);
  }
}

bool eqNullB(var a) {
  if (JS('bool', @'typeof # === "object"', a)) {
    if (JS_HAS_EQUALS(a)) {
      return UNINTERCEPTED(a == null) === true;
    } else {
      return false;
    }
  } else {
    return JS('bool', @'typeof # === "undefined"', a);
  }
}

gt$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('bool', @'# > #', a, b);
  }
  return UNINTERCEPTED(a > b);
}

ge$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('bool', @'# >= #', a, b);
  }
  return UNINTERCEPTED(a >= b);
}

lt$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('bool', @'# < #', a, b);
  }
  return UNINTERCEPTED(a < b);
}

le$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('bool', @'# <= #', a, b);
  }
  return UNINTERCEPTED(a <= b);
}

shl(var a, var b) {
  // TODO(floitsch): inputs must be integers.
  if (checkNumbers(a, b)) {
    // TODO(floitsch): Find cleaner way of forcing the type.
    a = JS('num', '#', a);
    b = JS('num', '#', b);
    if (b < 0) throw new IllegalArgumentException(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 (b > 31) return 0;
    return JS('num', @'(# << #) >>> 0', a, b);
  }
  return UNINTERCEPTED(a << b);
}

shr(var a, var b) {
  // TODO(floitsch): inputs must be integers.
  if (checkNumbers(a, b)) {
    // TODO(floitsch): Find cleaner way of forcing the type.
    a = JS('num', '#', a);
    b = JS('num', '#', b);
    if (b < 0) throw new IllegalArgumentException(b);
    if (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 (b > 31) 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', @'# >>> #', 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 (b > 31) b = 31;
    return JS('num', @'(# >> #) >>> 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', @'(# & #) >>> 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', @'(# | #) >>> 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', @'(# ^ #) >>> 0', a, b);
  }
  return UNINTERCEPTED(a ^ b);
}

not(var a) {
  if (JS('bool', @'typeof # === "number"', a)) {
    return JS('num', @'(~#) >>> 0', a);
  }
  return UNINTERCEPTED(~a);
}

neg(var a) {
  if (JS('bool', @'typeof # === "number"', a)) return JS('num', @'-#', 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 IllegalArgumentException(index);
      if (index.truncate() !== index) throw new IllegalArgumentException(index);
    }
    if (index < 0 || index >= a.length) {
      throw new IndexOutOfRangeException(index);
    }
    return JS('Object', @'#[#]', a, index);
  }
  return UNINTERCEPTED(a[index]);
}

void indexSet$slow(var a, var index, var value) {
  if (isJsArray(a)) {
    if (index is !int) {
      throw new IllegalArgumentException(index);
    }
    if (index < 0 || index >= a.length) {
      throw new IndexOutOfRangeException(index);
    }
    checkMutable(a, 'indexed set');
    JS('Object', @'#[#] = #', a, index, value);
    return;
  }
  UNINTERCEPTED(a[index] = value);
}

checkMutable(list, reason) {
  if (JS('bool', @'!!(#.immutable$list)', list)) {
    throw new UnsupportedOperationException(reason);
  }
}

checkGrowable(list, reason) {
  if (JS('bool', @'!!(#.fixed$length)', list)) {
    throw new UnsupportedOperationException(reason);
  }
}

String S(value) {
  var res = value.toString();
  if (res is !String) throw new IllegalArgumentException(value);
  return res;
}

class ListIterator<T> implements Iterator<T> {
  int i;
  List<T> list;
  ListIterator(List<T> this.list) : i = 0;
  bool hasNext() => i < JS('int', @'#.length', list);
  T next() {
    if (!hasNext()) throw new NoMoreElementsException();
    var value = JS('Object', @'#[#]', list, i);
    i += 1;
    return value;
  }
}

class Primitives {
  static void printString(String string) {
    var hasConsole = JS('bool', @'typeof console == "object"');
    if (hasConsole) {
      JS('void', @'console.log(#)', string);
    } else {
      JS('void', @'write(#)', string);
      JS('void', @'write("\n")');
    }
  }

  /** [: @"$".charCodeAt(0) :] */
  static final int DOLLAR_CHAR_VALUE = 36;

  static String objectToString(Object object) {
    String name = getTypeNameOf(object);
    // 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 (name.charCodeAt(0) === DOLLAR_CHAR_VALUE) name = name.substring(1);
    return "Instance of '$name'";
  }

  static List newList(length) {
    if (length === null) return JS('Object', @'new Array()');
    if ((length is !int) || (length < 0)) {
      throw new IllegalArgumentException(length);
    }
    var result = JS('Object', @'new Array(#)', length);
    JS('void', @'#.fixed$length = #', result, true);
    return result;
  }

  static num dateNow() => JS('num', @'Date.now()');

  static String stringFromCharCodes(charCodes) {
    for (var i in charCodes) {
      if (i is !int) throw new IllegalArgumentException(i);<commit_msg>Put back the ability to get the name of a class by decompiling its constructor function.<commit_after>// 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('coreimpl.dart');

#source('constant_map.dart');
#source('native_helper.dart');
#source('regexp_helper.dart');
#source('string_helper.dart');

// Performance critical helper methods.
add(var a, var b) => (a is num && b is num)
    ? JS('num', @'# + #', a, b)
    : add$slow(a, b);

sub(var a, var b) => (a is num && b is num)
    ? JS('num', @'# - #', a, b)
    : sub$slow(a, b);

div(var a, var b) => (a is num && b is num)
    ? JS('num', @'# / #', a, b)
    : div$slow(a, b);

mul(var a, var b) => (a is num && b is num)
    ? JS('num', @'# * #', a, b)
    : mul$slow(a, b);

gt(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# > #', a, b)
    : gt$slow(a, b);

ge(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# >= #', a, b)
    : ge$slow(a, b);

lt(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# < #', a, b)
    : lt$slow(a, b);

le(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# <= #', a, b)
    : le$slow(a, b);

gtB(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# > #', a, b)
    : gt$slow(a, b) === true;

geB(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# >= #', a, b)
    : ge$slow(a, b) === true;

ltB(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# < #', a, b)
    : lt$slow(a, b) === true;

leB(var a, var b) => (a is num && b is num)
    ? JS('bool', @'# <= #', a, b)
    : le$slow(a, b) === true;

index(var a, var index) {
  // The type test may cause a NullPointerException to be thrown but
  // that matches the specification of what the indexing operator is
  // supposed to do.
  bool isJsArrayOrString = JS('bool',
      @'typeof # == "string" || #.constructor === Array',
      a, a);
  if (isJsArrayOrString) {
    var key = JS('int', '# >>> 0', index);
    if (key === index && key < JS('int', @'#.length', a)) {
      return JS('var', @'#[#]', a, key);
    }
  }
  return index$slow(a, index);
}

indexSet(var a, var index, var value) {
  // The type test may cause a NullPointerException to be thrown but
  // that matches the specification of what the indexing operator is
  // supposed to do.
  bool isMutableJsArray = JS('bool',
      @'#.constructor === Array && !#.immutable$list',
      a, a);
  if (isMutableJsArray) {
    var key = JS('int', '# >>> 0', index);
    if (key === index && key < JS('int', @'#.length', a)) {
      JS('void', @'#[#] = #', 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
 * [IllegalArgumentException] with the other argument is thrown.
 */
bool checkNumbers(var a, var b) {
  if (a is num) {
    if (b is num) {
      return true;
    } else {
      checkNull(b);
      throw new IllegalArgumentException(b);
    }
  }
  return false;
}

bool isJsArray(var value) {
  return value !== null && JS('bool', @'#.constructor === Array', value);
}

add$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('num', @'# + #', a, b);
  } else if (a is String) {
    // TODO(lrn): Remove when we disable String.operator+
    b = b.toString();
    if (b is String) {
      return JS('String', @'# + #', a, b);
    }
    checkNull(b);
    throw new IllegalArgumentException(b);
  }
  return UNINTERCEPTED(a + b);
}

div$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('num', @'# / #', a, b);
  }
  return UNINTERCEPTED(a / b);
}

mul$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('num', @'# * #', a, b);
  }
  return UNINTERCEPTED(a * b);
}

sub$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('num', @'# - #', a, b);
  }
  return UNINTERCEPTED(a - b);
}

mod(var a, var b) {
  if (checkNumbers(a, b)) {
    // Euclidean Modulo.
    int result = JS('num', @'# % #', a, b);
    if (result == 0) return 0;  // Make sure we don't return -0.0.
    if (result > 0) return result;
    // TODO(floitsch): Find cleaner way of forcing the type.
    b = JS('num', '#', b);
    if (b < 0) {
      return result - b;
    } else {
      return result + b;
    }
  }
  return UNINTERCEPTED(a % b);
}

tdiv(var a, var b) {
  if (checkNumbers(a, b)) {
    // TODO(floitsch): Find cleaner way of forcing the type.
    a = JS('num', '#', a);
    b = JS('num', '#', b);
    return (a / b).truncate();
  }
  return UNINTERCEPTED(a ~/ b);
}

eq(var a, var b) {
  if (JS('bool', @'typeof # === "object"', a)) {
    if (JS_HAS_EQUALS(a)) {
      return UNINTERCEPTED(a == b);
    } else {
      return JS('bool', @'# === #', a, b);
    }
  }
  // TODO(lrn): is NaN === NaN ? Is -0.0 === 0.0 ?
  return JS('bool', @'# === #', a, b);
}

bool eqB(var a, var b) {
  if (JS('bool', @'typeof # === "object"', a)) {
    if (JS_HAS_EQUALS(a)) {
      return UNINTERCEPTED(a == b) === true;
    } else {
      return JS('bool', @'# === #', a, b);
    }
  }
  // TODO(lrn): is NaN === NaN ? Is -0.0 === 0.0 ?
  return JS('bool', @'# === #', a, b);
}

eqq(var a, var b) {
  return JS('bool', @'# === #', a, b);
}

eqNull(var a) {
  if (JS('bool', @'typeof # === "object"', a)) {
    if (JS_HAS_EQUALS(a)) {
      return UNINTERCEPTED(a == null);
    } else {
      return false;
    }
  } else {
    return JS('bool', @'typeof # === "undefined"', a);
  }
}

bool eqNullB(var a) {
  if (JS('bool', @'typeof # === "object"', a)) {
    if (JS_HAS_EQUALS(a)) {
      return UNINTERCEPTED(a == null) === true;
    } else {
      return false;
    }
  } else {
    return JS('bool', @'typeof # === "undefined"', a);
  }
}

gt$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('bool', @'# > #', a, b);
  }
  return UNINTERCEPTED(a > b);
}

ge$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('bool', @'# >= #', a, b);
  }
  return UNINTERCEPTED(a >= b);
}

lt$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('bool', @'# < #', a, b);
  }
  return UNINTERCEPTED(a < b);
}

le$slow(var a, var b) {
  if (checkNumbers(a, b)) {
    return JS('bool', @'# <= #', a, b);
  }
  return UNINTERCEPTED(a <= b);
}

shl(var a, var b) {
  // TODO(floitsch): inputs must be integers.
  if (checkNumbers(a, b)) {
    // TODO(floitsch): Find cleaner way of forcing the type.
    a = JS('num', '#', a);
    b = JS('num', '#', b);
    if (b < 0) throw new IllegalArgumentException(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 (b > 31) return 0;
    return JS('num', @'(# << #) >>> 0', a, b);
  }
  return UNINTERCEPTED(a << b);
}

shr(var a, var b) {
  // TODO(floitsch): inputs must be integers.
  if (checkNumbers(a, b)) {
    // TODO(floitsch): Find cleaner way of forcing the type.
    a = JS('num', '#', a);
    b = JS('num', '#', b);
    if (b < 0) throw new IllegalArgumentException(b);
    if (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 (b > 31) 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', @'# >>> #', 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 (b > 31) b = 31;
    return JS('num', @'(# >> #) >>> 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', @'(# & #) >>> 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', @'(# | #) >>> 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', @'(# ^ #) >>> 0', a, b);
  }
  return UNINTERCEPTED(a ^ b);
}

not(var a) {
  if (JS('bool', @'typeof # === "number"', a)) {
    return JS('num', @'(~#) >>> 0', a);
  }
  return UNINTERCEPTED(~a);
}

neg(var a) {
  if (JS('bool', @'typeof # === "number"', a)) return JS('num', @'-#', 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 IllegalArgumentException(index);
      if (index.truncate() !== index) throw new IllegalArgumentException(index);
    }
    if (index < 0 || index >= a.length) {
      throw new IndexOutOfRangeException(index);
    }
    return JS('Object', @'#[#]', a, index);
  }
  return UNINTERCEPTED(a[index]);
}

void indexSet$slow(var a, var index, var value) {
  if (isJsArray(a)) {
    if (index is !int) {
      throw new IllegalArgumentException(index);
    }
    if (index < 0 || index >= a.length) {
      throw new IndexOutOfRangeException(index);
    }
    checkMutable(a, 'indexed set');
    JS('Object', @'#[#] = #', a, index, value);
    return;
  }
  UNINTERCEPTED(a[index] = value);
}

checkMutable(list, reason) {
  if (JS('bool', @'!!(#.immutable$list)', list)) {
    throw new UnsupportedOperationException(reason);
  }
}

checkGrowable(list, reason) {
  if (JS('bool', @'!!(#.fixed$length)', list)) {
    throw new UnsupportedOperationException(reason);
  }
}

String S(value) {
  var res = value.toString();
  if (res is !String) throw new IllegalArgumentException(value);
  return res;
}

class ListIterator<T> implements Iterator<T> {
  int i;
  List<T> list;
  ListIterator(List<T> this.list) : i = 0;
  bool hasNext() => i < JS('int', @'#.length', list);
  T next() {
    if (!hasNext()) throw new NoMoreElementsException();
    var value = JS('Object', @'#[#]', list, i);
    i += 1;
    return value;
  }
}

class Primitives {
  static void printString(String string) {
    var hasConsole = JS('bool', @'typeof console == "object"');
    if (hasConsole) {
      JS('void', @'console.log(#)', string);
    } else {
      JS('void', @'write(#)', string);
      JS('void', @'write("\n")');
    }
  }

  /** [: @"$".charCodeAt(0) :] */
  static final int DOLLAR_CHAR_VALUE = 36;

  static String objectToString(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', @'#.match(/^\s*function\s*(\S*)\s*\(/)[1]',
                          JS('var', @'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 (name.charCodeAt(0) === DOLLAR_CHAR_VALUE) name = name.substring(1);
    return "Instance of '$name'";
  }

  static List newList(length) {
    if (length === null) return JS('Object', @'new Array()');
    if ((length is !int) || (length < 0)) {
      throw new IllegalArgumentException(length);
    }
    var result = JS('Object', @'new Array(#)', length);
    JS('void', @'#.fixed$length = #', result, true);
    return result;
  }

  static num dateNow() => JS('num', @'Date.now()');

  static String stringFromCharCodes(charCodes) {
    for (var i in charCodes) {
      if (i is !int) throw new IllegalArgumentException(i);
{'fraction_non_alphanumeric': 0.2027888275803071, 'fraction_numerical': 0.005517772359810086, 'mean_word_length': 2.756426735218509, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 139, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 2, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5555133', 'n_tokens_mistral': 8991, 'n_tokens_neox': 8710, 'n_words': 2658}