| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package flash.tools.debugger.expression; |
| |
| import flash.tools.debugger.Isolate; |
| import flash.tools.debugger.PlayerDebugException; |
| import flash.tools.debugger.Session; |
| import flash.tools.debugger.Value; |
| import flash.tools.debugger.VariableType; |
| import flash.tools.debugger.concrete.DValue; |
| import flash.tools.debugger.events.ExceptionFault; |
| |
| /** |
| * Implementations of some of the conversion functions defined by |
| * the ECMAScript spec ( http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf ). |
| * Please note, these conversion functions should not be considered to |
| * be 100% accurate; they handle all the cases the debugger's expression |
| * evaluator is likely to run into, but there are some edge cases that |
| * fall through the cracks. |
| * |
| * @author Mike Morearty |
| */ |
| public class ECMA |
| { |
| /** Used by defaultValue() etc. */ |
| private enum PreferredType { NUMBER, STRING } |
| |
| /** |
| * ECMA 4.3.2 |
| */ |
| public static boolean isPrimitive(Value v) |
| { |
| v = safeValue(v, Isolate.DEFAULT_ID); |
| Object o = v.getValueAsObject(); |
| return (o == Value.UNDEFINED || o == null || o instanceof Boolean |
| || o instanceof Double || o instanceof String); |
| } |
| |
| private static Value callFunction(Session session, Value v, String functionName, Value[] args, int isolateId) |
| { |
| v = safeValue(v, isolateId); |
| |
| try |
| { |
| return session.getWorkerSession(isolateId).callFunction(v, functionName, args); |
| } |
| catch (PlayerDebugException e) |
| { |
| throw new ExpressionEvaluatorException(e); |
| } |
| } |
| |
| /** |
| * Calls the valueOf() function of an object. |
| */ |
| private static Value callValueOf(Session session, Value v, int isolateId) |
| { |
| v = safeValue(v, isolateId); |
| return callFunction(session, v, "valueOf", new Value[0], isolateId); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Do not confuse this with toString()! toString() represents the official |
| * ECMA definition of [[ToString]], as defined in ECMA section 9.8. This |
| * function, on the other hand, represents calling the toString() function |
| * of an object. |
| */ |
| private static Value callToString(Session session, Value v, int isolateId) |
| { |
| v = safeValue(v, isolateId); |
| return callFunction(session, v, "toString", new Value[0], isolateId); //$NON-NLS-1$ |
| } |
| |
| /** |
| * ECMA 8.6.2.6 |
| * |
| * @param v |
| * @param optionalPreferredType |
| * either NUMBER, STRING, or null. |
| */ |
| public static Value defaultValue(Session session, Value v, |
| PreferredType optionalPreferredType, |
| int isolateId) |
| { |
| v = safeValue(v, isolateId); |
| String typename = v.getTypeName(); |
| int at = typename.indexOf('@'); |
| if (at != -1) |
| typename = typename.substring(0, at); |
| |
| if (optionalPreferredType == null) |
| { |
| if (typename.equals("Date")) //$NON-NLS-1$ |
| optionalPreferredType = PreferredType.STRING; |
| else |
| optionalPreferredType = PreferredType.NUMBER; |
| } |
| |
| if (optionalPreferredType == PreferredType.NUMBER) |
| { |
| Value result = callValueOf(session, v, isolateId); |
| if (isPrimitive(result)) |
| return result; |
| result = callToString(session, v, isolateId); |
| if (isPrimitive(result)) |
| return result; |
| throw new RuntimeException(new PlayerFaultException(new ExceptionFault(ASTBuilder.getLocalizationManager().getLocalizedTextString("typeError"), false, null, isolateId))); //$NON-NLS-1$ |
| } |
| else |
| { |
| Value result = callToString(session, v, isolateId); |
| if (isPrimitive(result)) |
| return result; |
| result = callValueOf(session, v, isolateId); |
| if (isPrimitive(result)) |
| return result; |
| throw new RuntimeException(new PlayerFaultException(new ExceptionFault(ASTBuilder.getLocalizationManager().getLocalizedTextString("typeError"), false, null, isolateId))); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * ECMA 9.1 |
| * |
| * @param v |
| * @param optionalPreferredType |
| * either NUMBER_TYPE, STRING_TYPE, or null. |
| * @return |
| */ |
| public static Value toPrimitive(Session session, Value v, |
| PreferredType optionalPreferredType, int isolateId) |
| { |
| v = safeValue(v, isolateId); |
| switch (v.getType()) |
| { |
| case VariableType.UNDEFINED: |
| case VariableType.NULL: |
| case VariableType.BOOLEAN: |
| case VariableType.NUMBER: |
| case VariableType.STRING: |
| return v; |
| |
| default: |
| return defaultValue(session, v, optionalPreferredType, isolateId); |
| } |
| } |
| |
| /** ECMA 9.2 */ |
| public static boolean toBoolean(Value v) |
| { |
| v = safeValue(v, Isolate.DEFAULT_ID); |
| switch (v.getType()) |
| { |
| case VariableType.UNDEFINED: |
| case VariableType.NULL: |
| return false; |
| case VariableType.BOOLEAN: |
| return ((Boolean) v.getValueAsObject()).booleanValue(); |
| case VariableType.NUMBER: |
| { |
| double d = ((Double) v.getValueAsObject()).doubleValue(); |
| if (d == 0 || Double.isNaN(d)) |
| { |
| return false; |
| } |
| else |
| { |
| return true; |
| } |
| } |
| case VariableType.STRING: |
| return ((String) v.getValueAsObject()).length() != 0; |
| default: |
| return true; |
| } |
| } |
| |
| /** ECMA 9.3 */ |
| public static double toNumber(Session session, Value v) |
| { |
| v = safeValue(v, Isolate.DEFAULT_ID); |
| switch (v.getType()) |
| { |
| case VariableType.UNDEFINED: |
| return Double.NaN; |
| case VariableType.NULL: |
| return 0; |
| case VariableType.BOOLEAN: |
| return ((Boolean) v.getValueAsObject()).booleanValue() ? 1 : 0; |
| case VariableType.NUMBER: |
| return ((Double) v.getValueAsObject()).doubleValue(); |
| case VariableType.STRING: |
| { |
| String s = (String) v.getValueAsObject(); |
| if (s.length() == 0) |
| { |
| return 0; |
| } |
| else |
| { |
| try |
| { |
| return Double.parseDouble(s); |
| } |
| catch (NumberFormatException e) |
| { |
| return Double.NaN; |
| } |
| } |
| } |
| default: |
| return toNumber(session, toPrimitive(session, v, PreferredType.NUMBER, v.getIsolateId())); |
| } |
| } |
| |
| private static final double _2pow31 = Math.pow(2, 31); |
| private static final double _2pow32 = Math.pow(2, 32); |
| |
| /** ECMA 9.5 */ |
| public static int toInt32(Session session, Value v) |
| { |
| v = safeValue(v, Isolate.DEFAULT_ID); |
| double d = toNumber(session, v); |
| if (d == Double.POSITIVE_INFINITY || d == Double.NEGATIVE_INFINITY) |
| { |
| return 0; |
| } |
| else |
| { |
| double sign = Math.signum(d); |
| d = Math.floor(Math.abs(d)); |
| d %= _2pow32; |
| while (d >= _2pow31) |
| d -= _2pow32; |
| return (int) (sign*d); |
| } |
| } |
| |
| /** ECMA 9.6 */ |
| public static long toUint32(Session session, Value v) |
| { |
| v = safeValue(v, Isolate.DEFAULT_ID); |
| long n = toInt32(session, v); |
| if (n < 0) |
| n = n + (long) 0x10000 * (long) 0x10000; |
| return n; |
| } |
| |
| /** ECMA 9.8 */ |
| public static String toString(Session session, Value v) |
| { |
| v = safeValue(v, Isolate.DEFAULT_ID); |
| switch (v.getType()) |
| { |
| case VariableType.UNDEFINED: |
| case VariableType.NULL: |
| case VariableType.BOOLEAN: |
| case VariableType.STRING: |
| return v.getValueAsString(); |
| case VariableType.NUMBER: |
| { |
| double d = ((Double) v.getValueAsObject()).doubleValue(); |
| if (d == (long) d) |
| { |
| return Long.toString((long) d); // avoid the ".0" on the end |
| } |
| else |
| { |
| return v.toString(); |
| } |
| } |
| default: |
| return toString(session, toPrimitive(session, v, PreferredType.STRING, v.getIsolateId())); |
| } |
| } |
| |
| /** ECMA 11.8.5. Returns true, false, or undefined. */ |
| public static Value lessThan(Session session, Value x, Value y) |
| { |
| x = safeValue(x, Isolate.DEFAULT_ID); |
| y = safeValue(y, Isolate.DEFAULT_ID); |
| Value px = toPrimitive(session, x, PreferredType.NUMBER, x.getIsolateId()); |
| Value py = toPrimitive(session, y, PreferredType.NUMBER, y.getIsolateId()); |
| if (px.getType() == VariableType.STRING |
| && py.getType() == VariableType.STRING) |
| { |
| String sx = px.getValueAsString(); |
| String sy = py.getValueAsString(); |
| return DValue.forPrimitive(new Boolean(sx.compareTo(sy) < 0), x.getIsolateId()); |
| } |
| else |
| { |
| double dx = toNumber(session, px); |
| double dy = toNumber(session, py); |
| if (Double.isNaN(dx) || Double.isNaN(dy)) |
| return DValue.forPrimitive(Value.UNDEFINED, x.getIsolateId()); |
| return DValue.forPrimitive(new Boolean(dx < dy), x.getIsolateId()); |
| } |
| } |
| |
| /** ECMA 11.9.3 */ |
| public static boolean equals(Session session, Value xv, Value yv) |
| { |
| xv = safeValue(xv, Isolate.DEFAULT_ID); |
| yv = safeValue(yv, Isolate.DEFAULT_ID); |
| |
| Object x = xv.getValueAsObject(); |
| Object y = yv.getValueAsObject(); |
| |
| if (xv.getType() == yv.getType()) |
| { |
| if (x == Value.UNDEFINED) |
| return true; |
| if (x == null) |
| return true; |
| if (x instanceof Double) |
| { |
| double dx = ((Double) x).doubleValue(); |
| double dy = ((Double) y).doubleValue(); |
| return dx == dy; |
| } |
| if (x instanceof String || x instanceof Boolean) |
| return x.equals(y); |
| |
| // see if they are the same object |
| if (xv.getId() != -1 || yv.getId() != -1) |
| return xv.getId() == yv.getId(); |
| return false; |
| } |
| else |
| { |
| if (x == null && y == Value.UNDEFINED) |
| return true; |
| if (x == Value.UNDEFINED && y == null) |
| return true; |
| if (x instanceof Double && y instanceof String) |
| { |
| double dx = ((Double) x).doubleValue(); |
| double dy = toNumber(session, yv); |
| return dx == dy; |
| } |
| if (x instanceof String && y instanceof Double) |
| { |
| double dx = toNumber(session, xv); |
| double dy = ((Double) y).doubleValue(); |
| return dx == dy; |
| } |
| if (x instanceof Boolean) |
| return equals(session, DValue.forPrimitive(new Double(toNumber(session, xv)), xv.getIsolateId()), yv); |
| if (y instanceof Boolean) |
| return equals(session, xv, DValue.forPrimitive(new Double(toNumber(session, yv)), xv.getIsolateId())); |
| if ((x instanceof String || x instanceof Double) && yv.getType() == VariableType.OBJECT) |
| { |
| return equals(session, xv, toPrimitive(session, yv, null, yv.getIsolateId())); |
| } |
| if (xv.getType() == VariableType.OBJECT && (y instanceof String || y instanceof Double)) |
| { |
| return equals(session, toPrimitive(session, xv, null, xv.getIsolateId()), yv); |
| } |
| return false; |
| } |
| } |
| |
| /** ECMA 11.9.6 */ |
| public static boolean strictEquals(Value xv, Value yv) |
| { |
| xv = safeValue(xv, Isolate.DEFAULT_ID); |
| yv = safeValue(yv, Isolate.DEFAULT_ID); |
| |
| Object x = xv.getValueAsObject(); |
| Object y = yv.getValueAsObject(); |
| |
| if (xv.getType() == yv.getType()) |
| { |
| if (x == Value.UNDEFINED) |
| return true; |
| if (x == null) |
| return true; |
| if (x instanceof Double) |
| { |
| double dx = ((Double) x).doubleValue(); |
| double dy = ((Double) y).doubleValue(); |
| return dx == dy; |
| } |
| if (x instanceof String || x instanceof Boolean) |
| return x.equals(y); |
| |
| // see if they are the same object |
| if (xv.getId() != -1 || yv.getId() != -1) |
| return xv.getId() == yv.getId(); |
| return false; |
| } |
| else |
| { |
| return false; |
| } |
| } |
| |
| /** |
| * Returns a "safe" (non-null) form of the specified Value -- that is, if |
| * the specified Value is null, returns a non-null Value that *represents* |
| * null. |
| * |
| * @param v |
| * any Value, possibly null |
| * @return a non-null Value |
| */ |
| public static Value safeValue(Value v, int isolateId) |
| { |
| if (v == null) |
| { |
| v = DValue.forPrimitive(null, isolateId); |
| assert v != null; |
| } |
| return v; |
| } |
| } |