mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
2125 lines
69 KiB
Java
2125 lines
69 KiB
Java
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1997-1999 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
package org.mozilla.javascript;
|
|
|
|
import java.util.*;
|
|
import java.lang.reflect.*;
|
|
|
|
/**
|
|
* This is the class that implements the runtime.
|
|
*
|
|
* @author Norris Boyd
|
|
*/
|
|
|
|
public class ScriptRuntime {
|
|
|
|
/**
|
|
* No instances should be created.
|
|
*/
|
|
protected ScriptRuntime() {
|
|
}
|
|
|
|
/*
|
|
* There's such a huge space (and some time) waste for the Foo.class
|
|
* syntax: the compiler sticks in a test of a static field in the
|
|
* enclosing class for null and the code for creating the class value.
|
|
* It has to do this since the reference has to get pushed off til
|
|
* executiontime (i.e. can't force an early load), but for the
|
|
* 'standard' classes - especially those in java.lang, we can trust
|
|
* that they won't cause problems by being loaded early.
|
|
*/
|
|
|
|
public final static Class UndefinedClass = Undefined.class;
|
|
public final static Class ScriptableClass = Scriptable.class;
|
|
public final static Class StringClass = String.class;
|
|
public final static Class NumberClass = Number.class;
|
|
public final static Class BooleanClass = Boolean.class;
|
|
public final static Class ByteClass = Byte.class;
|
|
public final static Class ShortClass = Short.class;
|
|
public final static Class IntegerClass = Integer.class;
|
|
public final static Class LongClass = Long.class;
|
|
public final static Class FloatClass = Float.class;
|
|
public final static Class DoubleClass = Double.class;
|
|
public final static Class CharacterClass = Character.class;
|
|
public final static Class ObjectClass = Object.class;
|
|
public final static Class FunctionClass = Function.class;
|
|
public final static Class ClassClass = Class.class;
|
|
|
|
/**
|
|
* Convert the value to a boolean.
|
|
*
|
|
* See ECMA 9.2.
|
|
*/
|
|
public static boolean toBoolean(Object val) {
|
|
if (val == null)
|
|
return false;
|
|
if (val instanceof Scriptable) {
|
|
if (Context.getContext().isVersionECMA1()) {
|
|
// pure ECMA
|
|
return val != Undefined.instance;
|
|
}
|
|
// ECMA extension
|
|
val = ((Scriptable) val).getDefaultValue(BooleanClass);
|
|
if (val instanceof Scriptable)
|
|
throw errorWithClassName("msg.primitive.expected", val);
|
|
// fall through
|
|
}
|
|
if (val instanceof String)
|
|
return ((String) val).length() != 0;
|
|
if (val instanceof Number) {
|
|
double d = ((Number) val).doubleValue();
|
|
return (d == d && d != 0.0);
|
|
}
|
|
if (val instanceof Boolean)
|
|
return ((Boolean) val).booleanValue();
|
|
throw errorWithClassName("msg.invalid.type", val);
|
|
}
|
|
|
|
/**
|
|
* Convert the value to a number.
|
|
*
|
|
* See ECMA 9.3.
|
|
*/
|
|
public static double toNumber(Object val) {
|
|
if (val != null && val instanceof Scriptable) {
|
|
val = ((Scriptable) val).getDefaultValue(NumberClass);
|
|
if (val != null && val instanceof Scriptable)
|
|
throw errorWithClassName("msg.primitive.expected", val);
|
|
// fall through
|
|
}
|
|
if (val == null)
|
|
return +0.0;
|
|
if (val instanceof String)
|
|
return toNumber((String) val);
|
|
if (val instanceof Number)
|
|
return ((Number) val).doubleValue();
|
|
if (val instanceof Boolean)
|
|
return ((Boolean) val).booleanValue() ? 1 : +0.0;
|
|
throw errorWithClassName("msg.invalid.type", val);
|
|
}
|
|
|
|
// This definition of NaN is identical to that in java.lang.Double
|
|
// except that it is not final. This is a workaround for a bug in
|
|
// the Microsoft VM, versions 2.01 and 3.0P1, that causes some uses
|
|
// (returns at least) of Double.NaN to be converted to 1.0.
|
|
// So we use ScriptRuntime.NaN instead of Double.NaN.
|
|
public static double NaN = 0.0d / 0.0;
|
|
public static Double NaNobj = new Double(0.0d / 0.0);
|
|
|
|
// A similar problem exists for negative zero.
|
|
public static double negativeZero = -0.0;
|
|
|
|
/*
|
|
* Helper function for toNumber, parseInt, and TokenStream.getToken.
|
|
*/
|
|
static double stringToNumber(String s, int start, int radix) {
|
|
char digitMax = '9';
|
|
char lowerCaseBound = 'a';
|
|
char upperCaseBound = 'A';
|
|
int len = s.length();
|
|
if (radix < 10) {
|
|
digitMax = (char) ('0' + radix - 1);
|
|
}
|
|
if (radix > 10) {
|
|
lowerCaseBound = (char) ('a' + radix - 10);
|
|
upperCaseBound = (char) ('A' + radix - 10);
|
|
}
|
|
int end;
|
|
double sum = 0.0;
|
|
for (end=start; end < len; end++) {
|
|
char c = s.charAt(end);
|
|
int newDigit;
|
|
if ('0' <= c && c <= digitMax)
|
|
newDigit = c - '0';
|
|
else if ('a' <= c && c < lowerCaseBound)
|
|
newDigit = c - 'a' + 10;
|
|
else if ('A' <= c && c < upperCaseBound)
|
|
newDigit = c - 'A' + 10;
|
|
else
|
|
break;
|
|
sum = sum*radix + newDigit;
|
|
}
|
|
if (start == end) {
|
|
return NaN;
|
|
}
|
|
if (sum >= 9007199254740992.0) {
|
|
if (radix == 10) {
|
|
/* If we're accumulating a decimal number and the number
|
|
* is >= 2^53, then the result from the repeated multiply-add
|
|
* above may be inaccurate. Call Java to get the correct
|
|
* answer.
|
|
*/
|
|
try {
|
|
return Double.valueOf(s.substring(start, end)).doubleValue();
|
|
} catch (NumberFormatException nfe) {
|
|
return NaN;
|
|
}
|
|
} else if (radix == 2 || radix == 4 || radix == 8 ||
|
|
radix == 16 || radix == 32)
|
|
{
|
|
/* The number may also be inaccurate for one of these bases.
|
|
* This happens if the addition in value*radix + digit causes
|
|
* a round-down to an even least significant mantissa bit
|
|
* when the first dropped bit is a one. If any of the
|
|
* following digits in the number (which haven't been added
|
|
* in yet) are nonzero then the correct action would have
|
|
* been to round up instead of down. An example of this
|
|
* occurs when reading the number 0x1000000000000081, which
|
|
* rounds to 0x1000000000000000 instead of 0x1000000000000100.
|
|
*/
|
|
BinaryDigitReader bdr = new BinaryDigitReader(radix, s, start, end);
|
|
int bit;
|
|
sum = 0.0;
|
|
|
|
/* Skip leading zeros. */
|
|
do {
|
|
bit = bdr.getNextBinaryDigit();
|
|
} while (bit == 0);
|
|
|
|
if (bit == 1) {
|
|
/* Gather the 53 significant bits (including the leading 1) */
|
|
sum = 1.0;
|
|
for (int j = 52; j != 0; j--) {
|
|
bit = bdr.getNextBinaryDigit();
|
|
if (bit < 0)
|
|
return sum;
|
|
sum = sum*2 + bit;
|
|
}
|
|
/* bit54 is the 54th bit (the first dropped from the mantissa) */
|
|
int bit54 = bdr.getNextBinaryDigit();
|
|
if (bit54 >= 0) {
|
|
double factor = 2.0;
|
|
int sticky = 0; /* sticky is 1 if any bit beyond the 54th is 1 */
|
|
int bit3;
|
|
|
|
while ((bit3 = bdr.getNextBinaryDigit()) >= 0) {
|
|
sticky |= bit3;
|
|
factor *= 2;
|
|
}
|
|
sum += bit54 & (bit | sticky);
|
|
sum *= factor;
|
|
}
|
|
}
|
|
}
|
|
/* We don't worry about inaccurate numbers for any other base. */
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
|
|
/**
|
|
* ToNumber applied to the String type
|
|
*
|
|
* See ECMA 9.3.1
|
|
*/
|
|
public static double toNumber(String s) {
|
|
int len = s.length();
|
|
int start = 0;
|
|
char startChar;
|
|
for (;;) {
|
|
if (start == len) {
|
|
// Empty or contains only whitespace
|
|
return +0.0;
|
|
}
|
|
startChar = s.charAt(start);
|
|
if (!Character.isWhitespace(startChar))
|
|
break;
|
|
start++;
|
|
}
|
|
|
|
if (startChar == '0' && start+2 < len &&
|
|
Character.toLowerCase(s.charAt(start+1)) == 'x')
|
|
// A hexadecimal number
|
|
return stringToNumber(s, start + 2, 16);
|
|
|
|
if ((startChar == '+' || startChar == '-') && start+3 < len &&
|
|
s.charAt(start+1) == '0' &&
|
|
Character.toLowerCase(s.charAt(start+2)) == 'x') {
|
|
// A hexadecimal number
|
|
double val = stringToNumber(s, start + 3, 16);
|
|
return startChar == '-' ? -val : val;
|
|
}
|
|
|
|
int end = len - 1;
|
|
char endChar;
|
|
while (Character.isWhitespace(endChar = s.charAt(end)))
|
|
end--;
|
|
if (endChar == 'y') {
|
|
// check for "Infinity"
|
|
if (startChar == '+' || startChar == '-')
|
|
start++;
|
|
String sub = s.substring(start, end+1);
|
|
if (sub.equals("Infinity"))
|
|
return startChar == '-'
|
|
? Double.NEGATIVE_INFINITY
|
|
: Double.POSITIVE_INFINITY;
|
|
return NaN;
|
|
}
|
|
// A non-hexadecimal, non-infinity number:
|
|
// just try a normal floating point conversion
|
|
String sub = s.substring(start, end+1);
|
|
if (MSJVM_BUG_WORKAROUNDS) {
|
|
// The MS JVM will accept non-conformant strings
|
|
// rather than throwing a NumberFormatException
|
|
// as it should.
|
|
for (int i=sub.length()-1; i >= 0; i--) {
|
|
char c = sub.charAt(i);
|
|
if (('0' <= c && c <= '9') || c == '.' ||
|
|
c == 'e' || c == 'E' ||
|
|
c == '+' || c == '-')
|
|
continue;
|
|
return NaN;
|
|
}
|
|
}
|
|
try {
|
|
return Double.valueOf(sub).doubleValue();
|
|
} catch (NumberFormatException ex) {
|
|
return NaN;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper function for builtin objects that use the varargs form.
|
|
* ECMA function formal arguments are undefined if not supplied;
|
|
* this function pads the argument array out to the expected
|
|
* length, if necessary.
|
|
*/
|
|
public static Object[] padArguments(Object[] args, int count) {
|
|
if (count < args.length)
|
|
return args;
|
|
|
|
int i;
|
|
Object[] result = new Object[count];
|
|
for (i = 0; i < args.length; i++) {
|
|
result[i] = args[i];
|
|
}
|
|
|
|
for (; i < count; i++) {
|
|
result[i] = Undefined.instance;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Work around Microsoft Java VM bugs. */
|
|
private final static boolean MSJVM_BUG_WORKAROUNDS = true;
|
|
|
|
/**
|
|
* For escaping strings printed by object and array literals; not quite
|
|
* the same as 'escape.'
|
|
*/
|
|
public static String escapeString(String s) {
|
|
// ack! Java lacks \v.
|
|
String escapeMap = "\bb\ff\nn\rr\tt\u000bv\"\"''";
|
|
StringBuffer result = new StringBuffer(s.length());
|
|
|
|
for(int i=0; i < s.length(); i++) {
|
|
char c = s.charAt(i);
|
|
|
|
// an ordinary print character
|
|
if (c >= ' ' && c <= '~' // string.h isprint()
|
|
&& c != '"')
|
|
{
|
|
result.append(c);
|
|
continue;
|
|
}
|
|
|
|
// an \escaped sort of character
|
|
int index;
|
|
if ((index = escapeMap.indexOf(c)) >= 0) {
|
|
result.append("\\");
|
|
result.append(escapeMap.charAt(index + 1));
|
|
continue;
|
|
}
|
|
|
|
// 2-digit hex?
|
|
if (c < 256) {
|
|
String hex = Integer.toHexString((int) c);
|
|
if (hex.length() == 1) {
|
|
result.append("\\x0");
|
|
result.append(hex);
|
|
} else {
|
|
result.append("\\x");
|
|
result.append(hex);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// nope. Unicode.
|
|
String hex = Integer.toHexString((int) c);
|
|
// cool idiom courtesy Shaver.
|
|
result.append("\\u");
|
|
for (int l = hex.length(); l < 4; l++)
|
|
result.append("0");
|
|
result.append(hex);
|
|
}
|
|
|
|
return result.toString();
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert the value to a string.
|
|
*
|
|
* See ECMA 9.8.
|
|
*/
|
|
public static String toString(Object val) {
|
|
for (;;) {
|
|
if (val == null)
|
|
return "null";
|
|
if (val instanceof Scriptable) {
|
|
val = ((Scriptable) val).getDefaultValue(StringClass);
|
|
if (val != Undefined.instance && val instanceof Scriptable) {
|
|
throw errorWithClassName("msg.primitive.expected", val);
|
|
}
|
|
continue;
|
|
}
|
|
if (val instanceof Number) {
|
|
// XXX should we just teach NativeNumber.stringValue()
|
|
// about Numbers?
|
|
return numberToString(((Number) val).doubleValue(), 10);
|
|
}
|
|
return val.toString();
|
|
}
|
|
}
|
|
|
|
public static String numberToString(double d, int base) {
|
|
if (d != d)
|
|
return "NaN";
|
|
if (d == Double.POSITIVE_INFINITY)
|
|
return "Infinity";
|
|
if (d == Double.NEGATIVE_INFINITY)
|
|
return "-Infinity";
|
|
if (d == 0.0)
|
|
return "0";
|
|
|
|
if ((base < 2) || (base > 36)) {
|
|
Object[] args = { Integer.toString(base) };
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.bad.radix", args));
|
|
}
|
|
|
|
if (base != 10) {
|
|
/* Monkey see, Rhino do */
|
|
if (d < 0)
|
|
return "-" + Long.toString( ((long)-d) & 0xFFFFFFFFL, base);
|
|
else
|
|
return Long.toString( ((long)d) & 0xFFFFFFFFL, base);
|
|
}
|
|
else {
|
|
// Okay, this is gross. But Java doesn't seem to have any number
|
|
// formatting routines exposed.
|
|
// So we let Java format it and then selectively reformat it using
|
|
// string operations to get the appropriate bounds on exponential
|
|
// representation as well as little things like case of 'e' and
|
|
// always including the sign of the exponent.
|
|
// OPT: format from the floating point number directly.
|
|
|
|
final int MIN_NO_EXP = -6;
|
|
final int MAX_NO_EXP = 20;
|
|
|
|
String s = Double.toString(d);
|
|
char[] chars = s.toCharArray();
|
|
int decimal = -1;
|
|
int e = -1;
|
|
boolean negative = false;
|
|
boolean negativeExponent = false;
|
|
for (int i=0; i < chars.length; i++) {
|
|
switch (chars[i]) {
|
|
|
|
case '-':
|
|
if (e == -1)
|
|
negative = true;
|
|
else
|
|
negativeExponent = true;
|
|
break;
|
|
|
|
case '.':
|
|
decimal = i;
|
|
break;
|
|
|
|
case 'E':
|
|
e = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (e == -1) {
|
|
if (decimal == chars.length - 2 && chars[chars.length-1] == '0') {
|
|
// Format 1.0 as "1", not "1.0"
|
|
return new String(chars, 0, chars.length - 2);
|
|
}
|
|
return s;
|
|
}
|
|
int exponent = Integer.parseInt(new String(chars, e+1,
|
|
chars.length - e - 1));
|
|
StringBuffer sb = new StringBuffer();
|
|
boolean singleDigit = decimal + 2 == e && chars[decimal+1] == '0';
|
|
if (exponent < MIN_NO_EXP || exponent > MAX_NO_EXP) {
|
|
int len = singleDigit ? 1 : e;
|
|
if (negative) {
|
|
sb.append('-');
|
|
sb.append(chars, 1, len);
|
|
} else {
|
|
sb.append(chars, 0, len);
|
|
}
|
|
sb.append('e');
|
|
sb.append(exponent < 0 ? '-' : '+');
|
|
int expStart = e + (exponent < 0 ? 2 : 1);
|
|
sb.append(chars, expStart, chars.length - expStart);
|
|
return sb.toString();
|
|
}
|
|
if (negative)
|
|
sb.append('-');
|
|
if (exponent < 0) {
|
|
sb.append("0.");
|
|
for (int i=exponent+1; i < 0; i++)
|
|
sb.append('0');
|
|
}
|
|
sb.append(chars[decimal - 1]);
|
|
int fractionalLength = 0;
|
|
if (!singleDigit) {
|
|
fractionalLength = e - decimal - 1;
|
|
if (exponent > 0 && exponent < fractionalLength) {
|
|
sb.append(chars, decimal + 1, exponent);
|
|
sb.append('.');
|
|
sb.append(chars, decimal + exponent + 1,
|
|
fractionalLength - exponent);
|
|
} else {
|
|
sb.append(chars, decimal + 1, fractionalLength);
|
|
}
|
|
}
|
|
if (exponent > 0) {
|
|
for (int i = fractionalLength; i < exponent; i++)
|
|
sb.append('0');
|
|
}
|
|
return sb.toString();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert the value to an object.
|
|
*
|
|
* See ECMA 9.9.
|
|
*/
|
|
public static Scriptable toObject(Scriptable scope, Object val) {
|
|
return toObject(scope, val, null);
|
|
}
|
|
|
|
public static Scriptable toObject(Scriptable scope, Object val,
|
|
Class staticClass)
|
|
{
|
|
if (val == null) {
|
|
throw Context.reportRuntimeError(
|
|
getMessage("msg.null.to.object", null));
|
|
}
|
|
if (val instanceof Scriptable) {
|
|
if (val == Undefined.instance) {
|
|
throw Context.reportRuntimeError(
|
|
getMessage("msg.undef.to.object", null));
|
|
}
|
|
return (Scriptable) val;
|
|
}
|
|
String className = val instanceof String ? "String" :
|
|
val instanceof Number ? "Number" :
|
|
val instanceof Boolean ? "Boolean" :
|
|
null;
|
|
|
|
if (className == null) {
|
|
// Extension: Wrap as a LiveConnect object.
|
|
Object wrapped = NativeJavaObject.wrap(scope, val, staticClass);
|
|
if (wrapped instanceof Scriptable)
|
|
return (Scriptable) wrapped;
|
|
throw errorWithClassName("msg.invalid.type", val);
|
|
}
|
|
|
|
Object[] args = { val };
|
|
scope = ScriptableObject.getTopLevelScope(scope);
|
|
Scriptable result = newObject(Context.getContext(), scope,
|
|
className, args);
|
|
return result;
|
|
}
|
|
|
|
public static Scriptable newObject(Context cx, Scriptable scope,
|
|
String constructorName, Object[] args)
|
|
{
|
|
Exception re = null;
|
|
try {
|
|
return cx.newObject(scope, constructorName, args);
|
|
}
|
|
catch (NotAFunctionException e) {
|
|
re = e;
|
|
}
|
|
catch (PropertyException e) {
|
|
re = e;
|
|
}
|
|
catch (JavaScriptException e) {
|
|
re = e;
|
|
}
|
|
throw cx.reportRuntimeError(re.getMessage());
|
|
}
|
|
|
|
/**
|
|
*
|
|
* See ECMA 9.4.
|
|
*/
|
|
public static double toInteger(Object val) {
|
|
double d = toNumber(val);
|
|
|
|
// if it's NaN
|
|
if (d != d)
|
|
return +0.0;
|
|
|
|
if (d == 0.0 ||
|
|
d == Double.POSITIVE_INFINITY ||
|
|
d == Double.NEGATIVE_INFINITY)
|
|
return d;
|
|
|
|
if (d > 0.0)
|
|
return Math.floor(d);
|
|
else
|
|
return Math.ceil(d);
|
|
}
|
|
|
|
// convenience method
|
|
public static double toInteger(double d) {
|
|
// if it's NaN
|
|
if (d != d)
|
|
return +0.0;
|
|
|
|
if (d == 0.0 ||
|
|
d == Double.POSITIVE_INFINITY ||
|
|
d == Double.NEGATIVE_INFINITY)
|
|
return d;
|
|
|
|
if (d > 0.0)
|
|
return Math.floor(d);
|
|
else
|
|
return Math.ceil(d);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* See ECMA 9.5.
|
|
*/
|
|
public static int toInt32(Object val) {
|
|
// 0x100000000 gives me a numeric overflow...
|
|
double two32 = 4294967296.0;
|
|
double two31 = 2147483648.0;
|
|
|
|
// short circuit for common small values; TokenStream
|
|
// returns them as Bytes.
|
|
if (val instanceof Byte)
|
|
return ((Number)val).intValue();
|
|
|
|
double d = toNumber(val);
|
|
if (d != d || d == 0.0 ||
|
|
d == Double.POSITIVE_INFINITY ||
|
|
d == Double.NEGATIVE_INFINITY)
|
|
return 0;
|
|
|
|
d = Math.IEEEremainder(d, two32);
|
|
|
|
d = d >= 0
|
|
? d
|
|
: d + two32;
|
|
|
|
if (d >= two31)
|
|
return (int)(d - two32);
|
|
else
|
|
return (int)d;
|
|
}
|
|
|
|
public static int toInt32(double d) {
|
|
// 0x100000000 gives me a numeric overflow...
|
|
double two32 = 4294967296.0;
|
|
double two31 = 2147483648.0;
|
|
|
|
if (d != d || d == 0.0 ||
|
|
d == Double.POSITIVE_INFINITY ||
|
|
d == Double.NEGATIVE_INFINITY)
|
|
return 0;
|
|
|
|
d = Math.IEEEremainder(d, two32);
|
|
|
|
d = d >= 0
|
|
? d
|
|
: d + two32;
|
|
|
|
if (d >= two31)
|
|
return (int)(d - two32);
|
|
else
|
|
return (int)d;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* See ECMA 9.6.
|
|
*/
|
|
|
|
// must return long to hold an _unsigned_ int
|
|
public static long toUint32(double d) {
|
|
// 0x100000000 gives me a numeric overflow...
|
|
double two32 = 4294967296.0;
|
|
|
|
if (d != d || d == 0.0 ||
|
|
d == Double.POSITIVE_INFINITY ||
|
|
d == Double.NEGATIVE_INFINITY)
|
|
return 0;
|
|
|
|
if (d > 0.0)
|
|
d = Math.floor(d);
|
|
else
|
|
d = Math.ceil(d);
|
|
|
|
d = Math.IEEEremainder(d, two32);
|
|
|
|
d = d >= 0
|
|
? d
|
|
: d + two32;
|
|
|
|
return (long) Math.floor(d);
|
|
}
|
|
|
|
public static long toUint32(Object val) {
|
|
return toUint32(toNumber(val));
|
|
}
|
|
|
|
/**
|
|
*
|
|
* See ECMA 9.7.
|
|
*/
|
|
public static char toUint16(Object val) {
|
|
long int16 = 0x10000;
|
|
|
|
double d = toNumber(val);
|
|
if (d != d || d == 0.0 ||
|
|
d == Double.POSITIVE_INFINITY ||
|
|
d == Double.NEGATIVE_INFINITY)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
d = Math.IEEEremainder(d, int16);
|
|
|
|
d = d >= 0
|
|
? d
|
|
: d + int16;
|
|
|
|
return (char) Math.floor(d);
|
|
}
|
|
|
|
/**
|
|
* Unwrap a JavaScriptException. Sleight of hand so that we don't
|
|
* javadoc JavaScriptException.getRuntimeValue().
|
|
*/
|
|
public static Object unwrapJavaScriptException(JavaScriptException jse) {
|
|
return jse.value;
|
|
}
|
|
|
|
public static Object getProp(Object obj, String id, Scriptable scope) {
|
|
Scriptable start;
|
|
try {
|
|
start = (Scriptable) obj;
|
|
}
|
|
catch (ClassCastException e) {
|
|
start = toObject(scope, obj);
|
|
}
|
|
if (start == null) {
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.null.to.object", null));
|
|
}
|
|
Scriptable m = start;
|
|
do {
|
|
Object result = m.get(id, start);
|
|
if (result != Scriptable.NOT_FOUND)
|
|
return result;
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
return Undefined.instance;
|
|
}
|
|
|
|
public static Object getTopLevelProp(Scriptable scope, String id) {
|
|
Scriptable s = ScriptableObject.getTopLevelScope(scope);
|
|
Object v;
|
|
do {
|
|
v = s.get(id, s);
|
|
if (v != Scriptable.NOT_FOUND)
|
|
return v;
|
|
s = s.getPrototype();
|
|
} while (s != null);
|
|
return v;
|
|
}
|
|
|
|
|
|
|
|
/***********************************************************************/
|
|
|
|
public static Scriptable getProto(Object obj, Scriptable scope) {
|
|
Scriptable s;
|
|
try {
|
|
s = (Scriptable) obj;
|
|
}
|
|
catch (ClassCastException e) {
|
|
s = toObject(scope, obj);
|
|
}
|
|
if (s == null) {
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.null.to.object", null));
|
|
}
|
|
return s.getPrototype();
|
|
}
|
|
|
|
public static Scriptable getParent(Object obj) {
|
|
Scriptable s;
|
|
try {
|
|
s = (Scriptable) obj;
|
|
}
|
|
catch (ClassCastException e) {
|
|
return null;
|
|
}
|
|
if (s == null) {
|
|
return null;
|
|
}
|
|
return getThis(s.getParentScope());
|
|
}
|
|
|
|
public static Scriptable getParent(Object obj, Scriptable scope) {
|
|
Scriptable s;
|
|
try {
|
|
s = (Scriptable) obj;
|
|
}
|
|
catch (ClassCastException e) {
|
|
s = toObject(scope, obj);
|
|
}
|
|
if (s == null) {
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.null.to.object", null));
|
|
}
|
|
return s.getParentScope();
|
|
}
|
|
|
|
public static Object setProto(Object obj, Object value, Scriptable scope) {
|
|
Scriptable start;
|
|
try {
|
|
start = (Scriptable) obj;
|
|
}
|
|
catch (ClassCastException e) {
|
|
start = toObject(scope, obj);
|
|
}
|
|
Scriptable result = value == null ? null : toObject(scope, value);
|
|
Scriptable s = result;
|
|
while (s != null) {
|
|
if (s == start) {
|
|
Object[] args = { "__proto__" };
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.cyclic.value", args));
|
|
}
|
|
s = s.getPrototype();
|
|
}
|
|
if (start == null) {
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.null.to.object", null));
|
|
}
|
|
start.setPrototype(result);
|
|
return result;
|
|
}
|
|
|
|
public static Object setParent(Object obj, Object value, Scriptable scope) {
|
|
Scriptable start;
|
|
try {
|
|
start = (Scriptable) obj;
|
|
}
|
|
catch (ClassCastException e) {
|
|
start = toObject(scope, obj);
|
|
}
|
|
Scriptable result = value == null ? null : toObject(scope, value);
|
|
Scriptable s = result;
|
|
while (s != null) {
|
|
if (s == start) {
|
|
Object[] args = { "__parent__" };
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.cyclic.value", args));
|
|
}
|
|
s = s.getParentScope();
|
|
}
|
|
if (start == null) {
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.null.to.object", null));
|
|
}
|
|
start.setParentScope(result);
|
|
return result;
|
|
}
|
|
|
|
public static Object setProp(Object obj, String id, Object value,
|
|
Scriptable scope)
|
|
{
|
|
Scriptable start;
|
|
try {
|
|
start = (Scriptable) obj;
|
|
}
|
|
catch (ClassCastException e) {
|
|
start = toObject(scope, obj);
|
|
}
|
|
if (start == null) {
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.null.to.object", null));
|
|
}
|
|
Scriptable m = start;
|
|
do {
|
|
if (m.has(id, start)) {
|
|
m.put(id, start, value);
|
|
return value;
|
|
}
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
|
|
start.put(id, start, value);
|
|
return value;
|
|
}
|
|
|
|
// An arbitrary single character int that will serve as an overloaded
|
|
// return value that must be disambiguated by the caller. We choose
|
|
// '5' here since it's less common than 0, 1, etc.
|
|
private static final int NO_INDEX = 5;
|
|
private static final char NO_INDEX_CHAR = '5';
|
|
|
|
// The length of the decimal string representation of Integer.MAX_VALUE.
|
|
private static final int MAX_VALUE_LENGTH =
|
|
Integer.toString(Integer.MAX_VALUE).length();
|
|
|
|
private static int indexFromString(String str) {
|
|
/* It must be a string. */
|
|
int len = str.length();
|
|
char c;
|
|
if (len > 0 && '0' <= (c = str.charAt(0)) && c <= '9' &&
|
|
len <= MAX_VALUE_LENGTH)
|
|
{
|
|
int index = c - '0';
|
|
int oldIndex = 0;
|
|
int i = 1;
|
|
if (index != 0) {
|
|
while (i < len && '0' <= (c = str.charAt(i)) && c <= '9') {
|
|
oldIndex = index;
|
|
index = 10*index + (c - '0');
|
|
i++;
|
|
}
|
|
}
|
|
/* Make sure all characters were consumed and that it couldn't
|
|
* have overflowed.
|
|
*/
|
|
if (i == len &&
|
|
(oldIndex < (Integer.MAX_VALUE / 10) ||
|
|
(oldIndex == (Integer.MAX_VALUE / 10) &&
|
|
c < (Integer.MAX_VALUE % 10))))
|
|
{
|
|
return index;
|
|
}
|
|
}
|
|
return NO_INDEX;
|
|
}
|
|
|
|
static String getStringId(Object id) {
|
|
if (id instanceof Number) {
|
|
double d = ((Number) id).doubleValue();
|
|
int index = (int) d;
|
|
if (((double) index) == d)
|
|
return null;
|
|
return toString(id);
|
|
}
|
|
String s = toString(id);
|
|
int index = indexFromString(s);
|
|
if (index != NO_INDEX || (s.length() == 1 &&
|
|
s.charAt(0) == NO_INDEX_CHAR))
|
|
{
|
|
return null;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static int getIntId(Object id) {
|
|
if (id instanceof Number) {
|
|
double d = ((Number) id).doubleValue();
|
|
int index = (int) d;
|
|
if (((double) index) == d)
|
|
return index;
|
|
return 0;
|
|
}
|
|
String s = toString(id);
|
|
int index = indexFromString(s);
|
|
if (index != NO_INDEX || (s.length() == 1 &&
|
|
s.charAt(0) == NO_INDEX_CHAR))
|
|
{
|
|
return index;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public static Object getElem(Object obj, Object id, Scriptable scope) {
|
|
int index;
|
|
String s;
|
|
if (id instanceof Number) {
|
|
double d = ((Number) id).doubleValue();
|
|
index = (int) d;
|
|
s = ((double) index) == d ? null : toString(id);
|
|
} else {
|
|
s = toString(id);
|
|
index = indexFromString(s);
|
|
if (index != NO_INDEX)
|
|
s = null;
|
|
else if (s.length() == 1 && s.charAt(0) == NO_INDEX_CHAR) {
|
|
// disambiguate return value: was actually a number
|
|
s = null;
|
|
}
|
|
}
|
|
Scriptable start = obj instanceof Scriptable
|
|
? (Scriptable) obj
|
|
: toObject(scope, obj);
|
|
Scriptable m = start;
|
|
if (s != null) {
|
|
if (s.equals("__proto__"))
|
|
return start.getPrototype();
|
|
if (s.equals("__parent__"))
|
|
return start.getParentScope();
|
|
while (m != null) {
|
|
Object result = m.get(s, start);
|
|
if (result != Scriptable.NOT_FOUND)
|
|
return result;
|
|
m = m.getPrototype();
|
|
}
|
|
return Undefined.instance;
|
|
}
|
|
while (m != null) {
|
|
Object result = m.get(index, start);
|
|
if (result != Scriptable.NOT_FOUND)
|
|
return result;
|
|
m = m.getPrototype();
|
|
}
|
|
return Undefined.instance;
|
|
}
|
|
|
|
|
|
/*
|
|
* A cheaper and less general version of the above for well-known argument
|
|
* types.
|
|
*/
|
|
public static Object getElem(Scriptable obj, int index)
|
|
{
|
|
Scriptable m = obj;
|
|
while (m != null) {
|
|
Object result = m.get(index, obj);
|
|
if (result != Scriptable.NOT_FOUND)
|
|
return result;
|
|
m = m.getPrototype();
|
|
}
|
|
return Undefined.instance;
|
|
}
|
|
|
|
public static Object setElem(Object obj, Object id, Object value,
|
|
Scriptable scope)
|
|
{
|
|
int index;
|
|
String s;
|
|
if (id instanceof Number) {
|
|
double d = ((Number) id).doubleValue();
|
|
index = (int) d;
|
|
s = ((double) index) == d ? null : toString(id);
|
|
} else {
|
|
s = toString(id);
|
|
index = indexFromString(s);
|
|
if (index != NO_INDEX)
|
|
s = null;
|
|
else if (s.length() == 1 && s.charAt(0) == NO_INDEX_CHAR) {
|
|
// disambiguate return value: was actually a number
|
|
s = null;
|
|
}
|
|
}
|
|
|
|
Scriptable start = obj instanceof Scriptable
|
|
? (Scriptable) obj
|
|
: toObject(scope, obj);
|
|
Scriptable m = start;
|
|
if (s != null) {
|
|
if (s.equals("__proto__"))
|
|
return setProto(obj, value, scope);
|
|
if (s.equals("__parent__"))
|
|
return setParent(obj, value, scope);
|
|
|
|
do {
|
|
if (m.has(s, start)) {
|
|
m.put(s, start, value);
|
|
return value;
|
|
}
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
start.put(s, start, value);
|
|
return value;
|
|
}
|
|
|
|
do {
|
|
if (m.has(index, start)) {
|
|
m.put(index, start, value);
|
|
return value;
|
|
}
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
start.put(index, start, value);
|
|
return value;
|
|
}
|
|
|
|
/*
|
|
* A cheaper and less general version of the above for well-known argument
|
|
* types.
|
|
*/
|
|
public static Object setElem(Scriptable obj, int index, Object value)
|
|
{
|
|
Scriptable m = obj;
|
|
do {
|
|
if (m.has(index, obj)) {
|
|
m.put(index, obj, value);
|
|
return value;
|
|
}
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
obj.put(index, obj, value);
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* The delete operator
|
|
*
|
|
* See ECMA 11.4.1
|
|
*
|
|
* In ECMA 0.19, the description of the delete operator (11.4.1)
|
|
* assumes that the [[Delete]] method returns a value. However,
|
|
* the definition of the [[Delete]] operator (8.6.2.5) does not
|
|
* define a return value. Here we assume that the [[Delete]]
|
|
* method doesn't return a value.
|
|
*/
|
|
public static Object delete(Object obj, Object id) {
|
|
if (!(obj instanceof Scriptable))
|
|
return Boolean.TRUE;
|
|
FlattenedObject f = new FlattenedObject((Scriptable) obj);
|
|
return f.deleteProperty(id) ? Boolean.TRUE : Boolean.FALSE;
|
|
}
|
|
|
|
/**
|
|
* Looks up a name in the scope chain and returns its value.
|
|
*/
|
|
public static Object name(Scriptable scopeChain, String id) {
|
|
Scriptable obj = scopeChain;
|
|
Object prop;
|
|
while (obj != null) {
|
|
Scriptable m = obj;
|
|
do {
|
|
Object result = m.get(id, obj);
|
|
if (result != Scriptable.NOT_FOUND)
|
|
return result;
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
obj = obj.getParentScope();
|
|
}
|
|
Object[] args = { id.toString() };
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.is.not.defined", args));
|
|
}
|
|
|
|
/**
|
|
* Returns the object in the scope chain that has a given property.
|
|
*
|
|
* The order of evaluation of an assignment expression involves
|
|
* evaluating the lhs to a reference, evaluating the rhs, and then
|
|
* modifying the reference with the rhs value. This method is used
|
|
* to 'bind' the given name to an object containing that property
|
|
* so that the side effects of evaluating the rhs do not affect
|
|
* which property is modified.
|
|
* Typically used in conjunction with setName.
|
|
*
|
|
* See ECMA 10.1.4
|
|
*/
|
|
public static Scriptable bind(Scriptable scope, String id) {
|
|
Scriptable obj = scope;
|
|
Object prop;
|
|
while (obj != null) {
|
|
Scriptable m = obj;
|
|
do {
|
|
if (m.has(id, obj))
|
|
return obj;
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
obj = obj.getParentScope();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static Scriptable getBase(Scriptable scope, String id) {
|
|
Scriptable obj = scope;
|
|
Object prop;
|
|
while (obj != null) {
|
|
Scriptable m = obj;
|
|
do {
|
|
if (m.get(id, obj) != Scriptable.NOT_FOUND)
|
|
return obj;
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
obj = obj.getParentScope();
|
|
}
|
|
Object[] args = { id };
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.is.not.defined", args));
|
|
}
|
|
|
|
public static Scriptable getThis(Scriptable base) {
|
|
while (base instanceof NativeWith)
|
|
base = base.getPrototype();
|
|
if (base instanceof NativeCall)
|
|
base = ScriptableObject.getTopLevelScope(base);
|
|
return base;
|
|
}
|
|
|
|
public static Object setName(Scriptable bound, Object value,
|
|
Scriptable scope, String id)
|
|
{
|
|
if (bound == null) {
|
|
// "newname = 7;", where 'newname' has not yet
|
|
// been defined, creates a new property in the
|
|
// global object. Find the global object by
|
|
// walking up the scope chain.
|
|
Scriptable next = scope;
|
|
do {
|
|
bound = next;
|
|
next = bound.getParentScope();
|
|
} while (next != null);
|
|
|
|
bound.put(id, bound, value);
|
|
String[] args = { id };
|
|
String message = getMessage("msg.assn.create", args);
|
|
Context.reportWarning(message);
|
|
return value;
|
|
}
|
|
return setProp(bound, id, value, scope);
|
|
}
|
|
|
|
public static Enumeration initEnum(Object value, Scriptable scope) {
|
|
Scriptable m = toObject(scope, value);
|
|
return new IdEnumeration(m);
|
|
}
|
|
|
|
public static Object nextEnum(Enumeration enum) {
|
|
// OPT this could be more efficient; should junk the Enumeration
|
|
// interface
|
|
if (!enum.hasMoreElements())
|
|
return null;
|
|
return enum.nextElement();
|
|
}
|
|
|
|
public static Object call(Context cx, Object fun, Object thisArg,
|
|
Object[] args)
|
|
throws JavaScriptException
|
|
{
|
|
// OPT: call fun directly rather than having runtime mediate
|
|
Function function;
|
|
try {
|
|
function = (Function) fun;
|
|
}
|
|
catch (ClassCastException e) {
|
|
Object[] errorArgs = { toString(fun) };
|
|
throw cx.reportRuntimeError(cx.getMessage
|
|
("msg.isnt.function", errorArgs));
|
|
}
|
|
|
|
Scriptable thisObj;
|
|
try {
|
|
thisObj = (Scriptable) thisArg;
|
|
}
|
|
catch (ClassCastException e) {
|
|
thisObj = ScriptRuntime.toObject(
|
|
ScriptableObject.getTopLevelScope(function), thisArg);
|
|
}
|
|
|
|
return function.call(cx, function.getParentScope(), thisObj, args);
|
|
}
|
|
|
|
private static Object callOrNewSpecial(Context cx, Scriptable scope,
|
|
Object fun, Object jsThis, Object thisArg,
|
|
Object[] args, boolean isCall,
|
|
String filename, int lineNumber)
|
|
throws JavaScriptException
|
|
{
|
|
if (fun instanceof FunctionObject) {
|
|
FunctionObject fo = (FunctionObject) fun;
|
|
Member m = fo.method;
|
|
Class cl = m.getDeclaringClass();
|
|
String name = m.getName();
|
|
if (name.equals("eval") && cl == NativeGlobal.class)
|
|
return NativeGlobal.evalSpecial(cx, scope, thisArg, args,
|
|
filename, lineNumber);
|
|
if (name.equals("js_Closure") && cl == NativeClosure.class)
|
|
return NativeClosure.newClosureSpecial(cx, scope, args, fo);
|
|
if (name.equals("With") && cl == NativeWith.class)
|
|
return NativeWith.newWithSpecial(cx, args, fo, !isCall);
|
|
if (name.equals("jsFunction_exec") && cl == NativeScript.class)
|
|
return ((NativeScript)jsThis).exec(cx, scope);
|
|
}
|
|
|
|
if (isCall)
|
|
return call(cx, fun, thisArg, args);
|
|
return newObject(cx, fun, args, scope);
|
|
}
|
|
|
|
public static Object callSpecial(Context cx, Object fun,
|
|
Object thisArg, Object[] args,
|
|
Scriptable enclosingThisArg,
|
|
Scriptable scope, String filename,
|
|
int lineNumber)
|
|
throws JavaScriptException
|
|
{
|
|
return callOrNewSpecial(cx, scope, fun, thisArg,
|
|
enclosingThisArg, args, true,
|
|
filename, lineNumber);
|
|
}
|
|
|
|
/**
|
|
* Operator new.
|
|
*
|
|
* See ECMA 11.2.2
|
|
*/
|
|
public static Scriptable newObject(Context cx, Object fun,
|
|
Object[] args, Scriptable scope)
|
|
throws JavaScriptException
|
|
{
|
|
Function f;
|
|
try {
|
|
f = (Function) fun;
|
|
if (f != null) {
|
|
return f.construct(cx, scope, args);
|
|
}
|
|
// else fall through to error
|
|
} catch (ClassCastException e) {
|
|
// fall through to error
|
|
}
|
|
Object[] errorArgs = { toString(fun) };
|
|
throw Context.reportRuntimeError(cx.getMessage
|
|
("msg.isnt.function", errorArgs));
|
|
}
|
|
|
|
public static Scriptable newObjectSpecial(Context cx, Object fun,
|
|
Object[] args, Scriptable scope)
|
|
throws JavaScriptException
|
|
{
|
|
return (Scriptable) callOrNewSpecial(cx, scope, fun, null, null, args,
|
|
false, null, -1);
|
|
}
|
|
|
|
/**
|
|
* The typeof operator
|
|
*/
|
|
public static String typeof(Object value) {
|
|
if (value == Undefined.instance)
|
|
return "undefined";
|
|
if (value == null)
|
|
return "object";
|
|
if (value instanceof Scriptable)
|
|
return (value instanceof Function) ? "function" : "object";
|
|
if (value instanceof String)
|
|
return "string";
|
|
if (value instanceof Number)
|
|
return "number";
|
|
if (value instanceof Boolean)
|
|
return "boolean";
|
|
throw errorWithClassName("msg.invalid.type", value);
|
|
}
|
|
|
|
/**
|
|
* The typeof operator that correctly handles the undefined case
|
|
*/
|
|
public static String typeofName(Scriptable scope, String id) {
|
|
Object val = bind(scope, id);
|
|
if (val == null)
|
|
return "undefined";
|
|
return typeof(getProp(val, id, scope));
|
|
}
|
|
|
|
// neg:
|
|
// implement the '-' operator inline in the caller
|
|
// as "-toNumber(val)"
|
|
|
|
// not:
|
|
// implement the '!' operator inline in the caller
|
|
// as "!toBoolean(val)"
|
|
|
|
// bitnot:
|
|
// implement the '~' operator inline in the caller
|
|
// as "~toInt32(val)"
|
|
|
|
public static Object add(Object val1, Object val2) {
|
|
if (val1 instanceof Scriptable)
|
|
val1 = ((Scriptable) val1).getDefaultValue(null);
|
|
if (val2 instanceof Scriptable)
|
|
val2 = ((Scriptable) val2).getDefaultValue(null);
|
|
if (!(val1 instanceof String) && !(val2 instanceof String))
|
|
if ((val1 instanceof Number) && (val2 instanceof Number))
|
|
return new Double(((Number)val1).doubleValue() +
|
|
((Number)val2).doubleValue());
|
|
else
|
|
return new Double(toNumber(val1) + toNumber(val2));
|
|
return toString(val1) + toString(val2);
|
|
}
|
|
|
|
public static Object postIncrement(Object value) {
|
|
if (value instanceof Number)
|
|
value = new Double(((Number)value).doubleValue() + 1.0);
|
|
else
|
|
value = new Double(toNumber(value) + 1.0);
|
|
return value;
|
|
}
|
|
|
|
public static Object postIncrement(Scriptable scopeChain, String id) {
|
|
Scriptable obj = scopeChain;
|
|
Object prop;
|
|
while (obj != null) {
|
|
Scriptable m = obj;
|
|
do {
|
|
Object result = m.get(id, obj);
|
|
if (result != Scriptable.NOT_FOUND) {
|
|
Object newValue = result;
|
|
if (newValue instanceof Number) {
|
|
newValue = new Double(
|
|
((Number)newValue).doubleValue() + 1.0);
|
|
m.put(id, obj, newValue);
|
|
return result;
|
|
}
|
|
else {
|
|
newValue = new Double(toNumber(newValue) + 1.0);
|
|
m.put(id, obj, newValue);
|
|
return new Double(toNumber(result));
|
|
}
|
|
}
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
obj = obj.getParentScope();
|
|
}
|
|
throw Context.reportRuntimeError(id.toString() + " is not defined"); // XXX
|
|
}
|
|
|
|
public static Object postIncrement(Object obj, String id, Scriptable scope) {
|
|
Scriptable start;
|
|
try {
|
|
start = (Scriptable) obj;
|
|
}
|
|
catch (ClassCastException e) {
|
|
start = toObject(scope, obj);
|
|
}
|
|
if (start == null) {
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.null.to.object", null));
|
|
}
|
|
Scriptable m = start;
|
|
do {
|
|
Object result = m.get(id, start);
|
|
if (result != Scriptable.NOT_FOUND) {
|
|
Object newValue = result;
|
|
if (newValue instanceof Number) {
|
|
newValue = new Double(
|
|
((Number)newValue).doubleValue() + 1.0);
|
|
m.put(id, start, newValue);
|
|
return result;
|
|
}
|
|
else {
|
|
newValue = new Double(toNumber(newValue) + 1.0);
|
|
m.put(id, start, newValue);
|
|
return new Double(toNumber(result));
|
|
}
|
|
}
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
return Undefined.instance;
|
|
}
|
|
|
|
public static Object postIncrementElem(Object obj,
|
|
Object index, Scriptable scope) {
|
|
Object oldValue = getElem(obj, index, scope);
|
|
if (oldValue == Undefined.instance)
|
|
return Undefined.instance;
|
|
double resultValue = toNumber(oldValue);
|
|
Double newValue = new Double(resultValue + 1.0);
|
|
setElem(obj, index, newValue, scope);
|
|
return new Double(resultValue);
|
|
}
|
|
|
|
public static Object postDecrementElem(Object obj,
|
|
Object index, Scriptable scope) {
|
|
Object oldValue = getElem(obj, index, scope);
|
|
if (oldValue == Undefined.instance)
|
|
return Undefined.instance;
|
|
double resultValue = toNumber(oldValue);
|
|
Double newValue = new Double(resultValue - 1.0);
|
|
setElem(obj, index, newValue, scope);
|
|
return new Double(resultValue);
|
|
}
|
|
|
|
public static Object postDecrement(Object value) {
|
|
if (value instanceof Number)
|
|
value = new Double(((Number)value).doubleValue() - 1.0);
|
|
else
|
|
value = new Double(toNumber(value) - 1.0);
|
|
return value;
|
|
}
|
|
|
|
public static Object postDecrement(Scriptable scopeChain, String id) {
|
|
Scriptable obj = scopeChain;
|
|
Object prop;
|
|
while (obj != null) {
|
|
Scriptable m = obj;
|
|
do {
|
|
Object result = m.get(id, obj);
|
|
if (result != Scriptable.NOT_FOUND) {
|
|
Object newValue = result;
|
|
if (newValue instanceof Number) {
|
|
newValue = new Double(
|
|
((Number)newValue).doubleValue() - 1.0);
|
|
m.put(id, obj, newValue);
|
|
return result;
|
|
}
|
|
else {
|
|
newValue = new Double(toNumber(newValue) - 1.0);
|
|
m.put(id, obj, newValue);
|
|
return new Double(toNumber(result));
|
|
}
|
|
}
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
obj = obj.getParentScope();
|
|
}
|
|
throw Context.reportRuntimeError(id.toString() + " is not defined"); // XXX
|
|
}
|
|
|
|
public static Object postDecrement(Object obj, String id, Scriptable scope) {
|
|
Scriptable start;
|
|
try {
|
|
start = (Scriptable) obj;
|
|
}
|
|
catch (ClassCastException e) {
|
|
start = toObject(scope, obj);
|
|
}
|
|
if (start == null) {
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.null.to.object", null));
|
|
}
|
|
Scriptable m = start;
|
|
do {
|
|
Object result = m.get(id, start);
|
|
if (result != Scriptable.NOT_FOUND) {
|
|
Object newValue = result;
|
|
if (newValue instanceof Number) {
|
|
newValue = new Double(
|
|
((Number)newValue).doubleValue() - 1.0);
|
|
m.put(id, start, newValue);
|
|
return result;
|
|
}
|
|
else {
|
|
newValue = new Double(toNumber(newValue) - 1.0);
|
|
m.put(id, start, newValue);
|
|
return new Double(toNumber(result));
|
|
}
|
|
}
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
return Undefined.instance;
|
|
}
|
|
|
|
public static Object toPrimitive(Object val) {
|
|
if (val == null || !(val instanceof Scriptable)) {
|
|
return val;
|
|
}
|
|
Object result = ((Scriptable) val).getDefaultValue(null);
|
|
if (result != null && result instanceof Scriptable)
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.bad.default.value", null));
|
|
return result;
|
|
}
|
|
|
|
private static Class getTypeOfValue(Object obj) {
|
|
if (obj == null)
|
|
return ScriptableClass;
|
|
if (obj == Undefined.instance)
|
|
return UndefinedClass;
|
|
if (obj instanceof Scriptable)
|
|
return ScriptableClass;
|
|
if (obj instanceof Number)
|
|
return NumberClass;
|
|
return obj.getClass();
|
|
}
|
|
|
|
/**
|
|
* Equality
|
|
*
|
|
* See ECMA 11.9
|
|
*/
|
|
public static boolean eq(Object x, Object y) {
|
|
Object xCopy = x; // !!! JIT bug in Cafe 2.1
|
|
Object yCopy = y; // need local copies, otherwise their values get blown below
|
|
for (;;) {
|
|
Class typeX = getTypeOfValue(x);
|
|
Class typeY = getTypeOfValue(y);
|
|
if (typeX == typeY) {
|
|
if (typeX == UndefinedClass)
|
|
return true;
|
|
if (typeX == NumberClass)
|
|
return ((Number) x).doubleValue() ==
|
|
((Number) y).doubleValue();
|
|
if (typeX == StringClass || typeX == BooleanClass)
|
|
return xCopy.equals(yCopy); // !!! JIT bug in Cafe 2.1
|
|
if (typeX == ScriptableClass) {
|
|
if (x == y)
|
|
return true;
|
|
if (x instanceof NativeJavaObject &&
|
|
y instanceof NativeJavaObject)
|
|
{
|
|
return ((NativeJavaObject) x).unwrap() ==
|
|
((NativeJavaObject) y).unwrap();
|
|
}
|
|
return false;
|
|
}
|
|
throw new RuntimeException(); // shouldn't get here
|
|
}
|
|
if (x == null && y == Undefined.instance)
|
|
return true;
|
|
if (x == Undefined.instance && y == null)
|
|
return true;
|
|
if (typeX == NumberClass &&
|
|
typeY == StringClass)
|
|
{
|
|
return ((Number) x).doubleValue() == toNumber(y);
|
|
}
|
|
if (typeX == StringClass &&
|
|
typeY == NumberClass)
|
|
{
|
|
return toNumber(x) == ((Number) y).doubleValue();
|
|
}
|
|
if (typeX == BooleanClass) {
|
|
x = new Double(toNumber(x));
|
|
xCopy = x; // !!! JIT bug in Cafe 2.1
|
|
continue;
|
|
}
|
|
if (typeY == BooleanClass) {
|
|
y = new Double(toNumber(y));
|
|
yCopy = y; // !!! JIT bug in Cafe 2.1
|
|
continue;
|
|
}
|
|
if ((typeX == StringClass ||
|
|
typeX == NumberClass) &&
|
|
typeY == ScriptableClass && y != null)
|
|
{
|
|
y = toPrimitive(y);
|
|
yCopy = y; // !!! JIT bug in Cafe 2.1
|
|
continue;
|
|
}
|
|
if (typeX == ScriptableClass && x != null &&
|
|
(typeY == StringClass ||
|
|
typeY == NumberClass))
|
|
{
|
|
x = toPrimitive(x);
|
|
xCopy = x; // !!! JIT bug in Cafe 2.1
|
|
continue;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public static Boolean eqB(Object x, Object y) {
|
|
if (eq(x,y))
|
|
return Boolean.TRUE;
|
|
else
|
|
return Boolean.FALSE;
|
|
}
|
|
|
|
public static Boolean neB(Object x, Object y) {
|
|
if (eq(x,y))
|
|
return Boolean.FALSE;
|
|
else
|
|
return Boolean.TRUE;
|
|
}
|
|
|
|
public static boolean shallowEq(Object x, Object y) {
|
|
Class type = getTypeOfValue(x);
|
|
if (type != getTypeOfValue(y))
|
|
return false;
|
|
if (type == StringClass || type == BooleanClass)
|
|
return x.equals(y);
|
|
if (type == NumberClass)
|
|
return ((Number) x).doubleValue() ==
|
|
((Number) y).doubleValue();
|
|
if (type == ScriptableClass) {
|
|
if (x == y)
|
|
return true;
|
|
if (x instanceof NativeJavaObject && y instanceof NativeJavaObject)
|
|
return ((NativeJavaObject) x).unwrap() ==
|
|
((NativeJavaObject) y).unwrap();
|
|
return false;
|
|
}
|
|
if (type == UndefinedClass)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
public static Boolean seqB(Object x, Object y) {
|
|
if (shallowEq(x,y))
|
|
return Boolean.TRUE;
|
|
else
|
|
return Boolean.FALSE;
|
|
}
|
|
|
|
public static Boolean sneB(Object x, Object y) {
|
|
if (shallowEq(x,y))
|
|
return Boolean.FALSE;
|
|
else
|
|
return Boolean.TRUE;
|
|
}
|
|
|
|
/**
|
|
* The instanceof operator.
|
|
*
|
|
* @return a instanceof b
|
|
*/
|
|
public static boolean instanceOf(Object a, Object b) {
|
|
|
|
// Check RHS is an object
|
|
if (! (b instanceof Scriptable)) {
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.instanceof.not.object",
|
|
null));
|
|
}
|
|
|
|
// for primitive values on LHS, return false
|
|
// XXX we may want to change this so that
|
|
// 5 instanceof Number == true
|
|
if (! (a instanceof Scriptable))
|
|
return false;
|
|
|
|
return ((Scriptable)b).hasInstance((Scriptable)a);
|
|
}
|
|
|
|
/**
|
|
* Delegates to
|
|
*
|
|
* @return true iff rhs appears in lhs' proto chain
|
|
*/
|
|
protected static boolean jsDelegatesTo(Scriptable lhs, Scriptable rhs) {
|
|
Scriptable proto = lhs.getPrototype();
|
|
|
|
while (proto != null) {
|
|
if (proto.equals(rhs)) return true;
|
|
proto = proto.getPrototype();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* The in operator.
|
|
*
|
|
* This is a new JS 1.3 language feature. The in operator mirrors
|
|
* the operation of the for .. in construct, and tests whether the
|
|
* rhs has the property given by the lhs. It is different from the for .. in construct in that:
|
|
* <BR> - it doesn't perform ToObject on the right hand side
|
|
* <BR> - it returns true for DontEnum properties.
|
|
* @param a the left hand operand
|
|
* @param b the right hand operand
|
|
*
|
|
* @return true if property name or element number a is a property of b
|
|
*/
|
|
public static boolean in(Object a, Object b) {
|
|
if (!(b instanceof Scriptable)) {
|
|
throw Context.reportRuntimeError(getMessage
|
|
("msg.in.not.object",
|
|
null));
|
|
}
|
|
// OPT do it here rather than making a new FlattenedObject.
|
|
FlattenedObject rhs = new FlattenedObject((Scriptable)b);
|
|
return rhs.hasProperty(a);
|
|
}
|
|
|
|
public static Boolean cmp_LTB(Object val1, Object val2) {
|
|
if (cmp_LT(val1, val2) == 1)
|
|
return Boolean.TRUE;
|
|
else
|
|
return Boolean.FALSE;
|
|
}
|
|
|
|
public static int cmp_LT(Object val1, Object val2) {
|
|
if (val1 instanceof Scriptable)
|
|
val1 = ((Scriptable) val1).getDefaultValue(NumberClass);
|
|
if (val2 instanceof Scriptable)
|
|
val2 = ((Scriptable) val2).getDefaultValue(NumberClass);
|
|
if (!(val1 instanceof String) || !(val2 instanceof String)) {
|
|
double d1 = toNumber(val1);
|
|
if (d1 != d1)
|
|
return 0;
|
|
double d2 = toNumber(val2);
|
|
if (d2 != d2)
|
|
return 0;
|
|
return d1 < d2 ? 1 : 0;
|
|
}
|
|
return toString(val1).compareTo(toString(val2)) < 0 ? 1 : 0;
|
|
}
|
|
|
|
public static Boolean cmp_LEB(Object val1, Object val2) {
|
|
if (cmp_LE(val1, val2) == 1)
|
|
return Boolean.TRUE;
|
|
else
|
|
return Boolean.FALSE;
|
|
}
|
|
|
|
public static int cmp_LE(Object val1, Object val2) {
|
|
if (val1 instanceof Scriptable)
|
|
val1 = ((Scriptable) val1).getDefaultValue(NumberClass);
|
|
if (val2 instanceof Scriptable)
|
|
val2 = ((Scriptable) val2).getDefaultValue(NumberClass);
|
|
if (!(val1 instanceof String) || !(val2 instanceof String)) {
|
|
double d1 = toNumber(val1);
|
|
if (d1 != d1)
|
|
return 0;
|
|
double d2 = toNumber(val2);
|
|
if (d2 != d2)
|
|
return 0;
|
|
return d1 <= d2 ? 1 : 0;
|
|
}
|
|
return toString(val1).compareTo(toString(val2)) <= 0 ? 1 : 0;
|
|
}
|
|
|
|
// lt:
|
|
// implement the '<' operator inline in the caller
|
|
// as "compare(val1, val2) == 1"
|
|
|
|
// le:
|
|
// implement the '<=' operator inline in the caller
|
|
// as "compare(val2, val1) == 0"
|
|
|
|
// gt:
|
|
// implement the '>' operator inline in the caller
|
|
// as "compare(val2, val1) == 1"
|
|
|
|
// ge:
|
|
// implement the '>=' operator inline in the caller
|
|
// as "compare(val1, val2) == 0"
|
|
|
|
// ------------------
|
|
// Statements
|
|
// ------------------
|
|
|
|
public static void main(String scriptClassName, String[] args)
|
|
throws JavaScriptException
|
|
{
|
|
Context cx = Context.enter();
|
|
|
|
Scriptable global = cx.initStandardObjects(null);
|
|
|
|
// Set up "arguments" in the global scope to contain the command
|
|
// line arguments after the name of the script to execute
|
|
Scriptable argsObj;
|
|
try {
|
|
argsObj = cx.newObject(global, "Array");
|
|
}
|
|
catch (PropertyException e) {
|
|
throw WrappedException.wrapException(e);
|
|
}
|
|
catch (NotAFunctionException e) {
|
|
throw WrappedException.wrapException(e);
|
|
}
|
|
catch (JavaScriptException e) {
|
|
throw WrappedException.wrapException(e);
|
|
}
|
|
for (int i=1; i < args.length; i++) {
|
|
argsObj.put(i-1, argsObj, args[i]);
|
|
}
|
|
global.put("arguments", global, argsObj);
|
|
|
|
try {
|
|
Class cl = Class.forName(scriptClassName);
|
|
Script script = (Script) cl.newInstance();
|
|
script.exec(cx, global);
|
|
return;
|
|
}
|
|
catch (ClassNotFoundException e) {
|
|
}
|
|
catch (InstantiationException e) {
|
|
}
|
|
catch (IllegalAccessException e) {
|
|
}
|
|
throw new RuntimeException("Error creating script object");
|
|
}
|
|
|
|
public static Scriptable initScript(Context cx, Scriptable scope,
|
|
NativeFunction funObj,
|
|
Scriptable thisObj)
|
|
{
|
|
String[] names = funObj.names;
|
|
if (names != null) {
|
|
ScriptableObject so;
|
|
try {
|
|
/* Global var definitions are supposed to be DONTDELETE
|
|
* so we try to create them that way by hoping that the
|
|
* scope is a ScriptableObject which provides access to
|
|
* setting the attributes.
|
|
*/
|
|
so = (ScriptableObject) scope;
|
|
} catch (ClassCastException x) {
|
|
// oh well, we tried.
|
|
so = null;
|
|
}
|
|
|
|
for (int i=funObj.names.length-1; i > 0; i--) {
|
|
String name = funObj.names[i];
|
|
// Don't overwrite existing def if already defined in object
|
|
// or prototypes of object.
|
|
if (!hasProp(scope, name)) {
|
|
if (so != null)
|
|
so.defineProperty(name, Undefined.instance,
|
|
ScriptableObject.PERMANENT);
|
|
else
|
|
scope.put(name, scope, Undefined.instance);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cx.getDebugLevel() > 0) {
|
|
// need an activation to store debug information.
|
|
new NativeCall(cx, scope, funObj, thisObj);
|
|
}
|
|
|
|
return scope;
|
|
}
|
|
|
|
public static Scriptable initVarObj(Context cx, Scriptable scope,
|
|
NativeFunction funObj,
|
|
Scriptable thisObj, Object[] args)
|
|
{
|
|
NativeCall result = new NativeCall(cx, scope, funObj, thisObj, args);
|
|
String[] names = funObj.names;
|
|
if (names != null) {
|
|
for (int i=funObj.argCount+1; i < funObj.names.length; i++) {
|
|
String name = funObj.names[i];
|
|
result.put(name, result, Undefined.instance);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public static void popActivation(Context cx) {
|
|
NativeCall current = cx.currentActivation;
|
|
if (current != null) {
|
|
cx.currentActivation = current.caller;
|
|
current.caller = null;
|
|
}
|
|
}
|
|
|
|
public static Scriptable newScope() {
|
|
return new NativeObject();
|
|
}
|
|
|
|
public static Scriptable enterWith(Object value, Scriptable scope) {
|
|
return new NativeWith(scope, toObject(scope, value));
|
|
}
|
|
|
|
public static Scriptable leaveWith(Scriptable scope) {
|
|
return scope.getParentScope();
|
|
}
|
|
|
|
public static NativeFunction defineFunction(Scriptable scope,
|
|
String className,
|
|
String fnName,
|
|
Object loadingObject)
|
|
throws ClassNotFoundException
|
|
{
|
|
NativeFunction fn;
|
|
try {
|
|
ClassLoader loader = loadingObject.getClass().getClassLoader();
|
|
Class clazz = (loader == null) ? Class.forName(className)
|
|
: loader.loadClass(className);
|
|
fn = createFunctionObject(scope, clazz);
|
|
}
|
|
catch (ClassNotFoundException e) {
|
|
throw WrappedException.wrapException(e);
|
|
}
|
|
if (fnName != null && fnName.length() != 0)
|
|
setName(scope, fn, scope, fnName);
|
|
|
|
return fn;
|
|
}
|
|
|
|
public static NativeFunction createFunctionObject(Scriptable scope,
|
|
Class functionClass)
|
|
{
|
|
Constructor[] ctors = functionClass.getConstructors();
|
|
|
|
NativeFunction result = null;
|
|
Object[] initArgs = { scope };
|
|
try {
|
|
result = (NativeFunction) ctors[0].newInstance(initArgs);
|
|
}
|
|
catch (InstantiationException e) {
|
|
throw WrappedException.wrapException(e);
|
|
}
|
|
catch (IllegalAccessException e) {
|
|
throw WrappedException.wrapException(e);
|
|
}
|
|
catch (IllegalArgumentException e) {
|
|
throw WrappedException.wrapException(e);
|
|
}
|
|
catch (InvocationTargetException e) {
|
|
throw WrappedException.wrapException(e);
|
|
}
|
|
|
|
result.setPrototype(ScriptableObject.getClassPrototype(scope, "Function"));
|
|
result.setParentScope(scope);
|
|
|
|
return result;
|
|
}
|
|
|
|
static void checkDeprecated(Context cx, String name) {
|
|
int version = cx.getLanguageVersion();
|
|
if (version >= Context.VERSION_1_4 || version == Context.VERSION_DEFAULT) {
|
|
Object[] errArgs = { name };
|
|
String msg = getMessage("msg.deprec.ctor",
|
|
errArgs);
|
|
if (version == Context.VERSION_DEFAULT)
|
|
Context.reportWarning(msg);
|
|
else
|
|
throw Context.reportRuntimeError(msg);
|
|
}
|
|
}
|
|
|
|
public static String getMessage(String messageId, Object[] arguments) {
|
|
return Context.getMessage(messageId, arguments);
|
|
}
|
|
|
|
public static RegExpProxy getRegExpProxy(Context cx) {
|
|
return cx.getRegExpProxy();
|
|
}
|
|
|
|
public static NativeCall getCurrentActivation(Context cx) {
|
|
return cx.currentActivation;
|
|
}
|
|
|
|
public static void setCurrentActivation(Context cx,
|
|
NativeCall activation)
|
|
{
|
|
cx.currentActivation = activation;
|
|
}
|
|
|
|
private static boolean hasProp(Scriptable start, String name) {
|
|
Scriptable m = start;
|
|
do {
|
|
if (m.has(name, start))
|
|
return true;
|
|
m = m.getPrototype();
|
|
} while (m != null);
|
|
return false;
|
|
}
|
|
|
|
private static RuntimeException errorWithClassName(String msg, Object val)
|
|
{
|
|
Object[] args = { val.getClass().getName() };
|
|
return Context.reportRuntimeError(getMessage(msg, args));
|
|
}
|
|
|
|
static public Object[] emptyArgs = new Object[0];
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* This is the enumeration needed by the for..in statement.
|
|
*
|
|
* See ECMA 12.6.3.
|
|
*
|
|
* IdEnumeration maintains a Hashtable to make sure a given
|
|
* id is enumerated only once across multiple objects in a
|
|
* prototype chain.
|
|
*
|
|
* XXX - ECMA delete doesn't hide properties in the prototype,
|
|
* but js/ref does. This means that the js/ref for..in can
|
|
* avoid maintaining a hash table and instead perform lookups
|
|
* to see if a given property has already been enumerated.
|
|
*
|
|
*/
|
|
class IdEnumeration implements Enumeration {
|
|
IdEnumeration(Scriptable m) {
|
|
used = new Hashtable(27);
|
|
changeObject(m);
|
|
next = getNext();
|
|
}
|
|
|
|
public boolean hasMoreElements() {
|
|
return next != null;
|
|
}
|
|
|
|
public Object nextElement() {
|
|
Object result = next;
|
|
|
|
// only key used; 'next' as value for convenience
|
|
used.put(next, next);
|
|
|
|
next = getNext();
|
|
return result;
|
|
}
|
|
|
|
private void changeObject(Scriptable m) {
|
|
obj = m;
|
|
if (obj != null) {
|
|
array = m.getIds();
|
|
if (array.length == 0)
|
|
changeObject(obj.getPrototype());
|
|
}
|
|
index = 0;
|
|
}
|
|
|
|
private Object getNext() {
|
|
if (obj == null)
|
|
return null;
|
|
Object result;
|
|
for (;;) {
|
|
if (index == array.length) {
|
|
changeObject(obj.getPrototype());
|
|
if (obj == null)
|
|
return null;
|
|
}
|
|
result = array[index++];
|
|
if (result instanceof String) {
|
|
if (!obj.has((String) result, obj))
|
|
continue; // must have been deleted
|
|
} else {
|
|
if (!obj.has(((Number) result).intValue(), obj))
|
|
continue; // must have been deleted
|
|
}
|
|
if (!used.containsKey(result)) {
|
|
break;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
private Object next;
|
|
private Scriptable obj;
|
|
private int index;
|
|
private Object[] array;
|
|
private Hashtable used;
|
|
}
|