blob: a16db027d9cabfd8014c1ec996c0ad21306a9613 [file] [log] [blame]
/*
*
* 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 org.apache.flex.abc.semantics;
import org.apache.flex.abc.ABCConstants;
import java.math.BigDecimal;
import java.math.MathContext;
/**
* Provides helper functions for for various operations as specified by ECMA.
*
* @see <a href="http://www.ecma-international.org/publications/standards/Ecma-262.htm">ECMA 262 spec</a>
*/
public class ECMASupport
{
private static final long MASK_32_BIT_UINT = 0xffffffffL;
private static final double TWO_TO_THE_31ST = Math.pow(2, 31);
private static final double TWO_TO_THE_32ND = Math.pow(2, 32);
/**
* The abstract operation ToInt32 converts its argument to one of 2^32
* integer values in the range -2^31 through 2^31-1, inclusive. This
* abstract operation functions as follows:
* <ol>
* <li>Let {@code number} be the result of calling ToNumber on the input
* argument.</li>
* <li>If {@code number} is NaN, +0, -0, positive infinity, or negative
* infinity, return +0.</li>
* <li>Let {@code posInt} be {@code sign(number) * floor(abs(number))}.</li>
* <li>Let {@code int32bit} be {@code posInt modulo 2^ 32}; that is, a
* finite integer value k of Number type with positive sign and less than
* 2^32 in magnitude such that the mathematical difference of posInt and k
* is mathematically an integer multiple of 2^32.</li>
* <li>If {@code int32bit} is greater than or equal to 2^31, return
* {@code int32bit - 2^32}, otherwise return {@code int32bit}.</li>
* </ol>
* <em>NOTE Given the above definition of {@code ToInt32}:</em>
* <ul>
* <li>The ToInt32 abstract operation is idempotent: if applied to a result
* that it produced, the second application leaves that value unchanged.</li>
* <li>ToInt32(ToUint32(x)) is equal to ToInt32(x) for all values of x. (It
* is to preserve this latter property that positive infinity and negative
* infinity are mapped to +0.)</li>
* <li>ToInt32 maps -0 to +0.</li>
* </ul>
*
* @param number IEEE number.
* @return 32-bit integer.
*/
public static int toInt32(final double number)
{
final long int32bit = toUInt32(number);
if (int32bit >= TWO_TO_THE_31ST)
return (int)(int32bit - TWO_TO_THE_32ND);
else
return (int)int32bit;
}
public static int toInt32(final Number number)
{
return toInt32(number.doubleValue());
}
public static int toInt32(final Object value)
{
return toInt32(toNumeric(value));
}
/**
* The abstract operation ToUInt32 converts its argument to one of 2^32
* integer values in the range 0 through 2^32-1, inclusive. This abstract
* operation functions as follows:
* <ol>
* <li>Let {@code number} be the result of calling ToNumber on the input
* argument.</li>
* <li>If {@code number} is NaN, +0, -0, positive infinity, or negative
* infinity, return +0.</li>
* <li>Let {@code posInt} be {@code sign(number) x floor(abs(number))}.</li>
* <li>Let {@code int32bit} be {@code posInt modulo 2^ 32}; that is, a
* finite integer value k of Number type with positive sign and less than
* 2^32 in magnitude such that the mathematical difference of posInt and k
* is mathematically an integer multiple of 2^32.</li>
* <li>Return {@code int32bit}.</li>
* </ol>
* <em>NOTE Given the above definition of {@code ToInt32}:</em>
* <ul>
* <li>Step 5 is the only difference between {@link #toUInt32(double)} and
* {@link #toInt32(double)}.</li>
* <li>The ToUint32 abstract operation is idempotent: if applied to a result
* that it produced, the second application leaves that value unchanged.</li>
* <li>ToUint32 maps -0 to +0.</li>
* </ul>
*
* @param number IEEE number.
* @return 32-bit unsigned integer.
*/
public static long toUInt32(final double number)
{
final double posInt = toInteger(number);
final double int32bit = posInt % TWO_TO_THE_32ND;
return (long)int32bit & MASK_32_BIT_UINT;
}
/**
* Version of toUInt32 that operates on Number
*/
public static long toUInt32(final Number value)
{
return toUInt32(value.doubleValue());
}
/**
* Version of toUInt32 that operates on Object
*/
public static long toUInt32(final Object value)
{
return toUInt32(toNumeric(value));
}
/**
* The abstract operation ToInteger converts its argument to an integral
* numeric value. This abstract operation functions as follows:
* <ol>
* <li>Let {@code number} be the result of calling ToNumber on the input
* argument.</li>
* <li>If {@code number} is NaN, return +0.</li>
* <li>If {@code number} is +0, -0, positive infinity, or negative infinity,
* return {@code number}.</li>
* <li>Return the result of computing
* {@code sign(number) x floor(abs(number))}.</li>
* </ol>
*
* @param number IEEE number.
* @return ECMA integer stored in a IEEE number.
*/
public static double toInteger(final double number)
{
if (isNan(number) || 0 == number || Double.isInfinite(number))
return 0;
final double posInt = Math.signum(number) * Math.floor(Math.abs(number));
return posInt;
}
/**
* Converts "anything" to boolean using ECMA algorithm
*
* @param <T> - Number or String
*/
public static <T> boolean toBoolean(T value)
{
assert value != null : "to pass null use ABCConstants.NULL_VALUE";
if (value == ABCConstants.NULL_VALUE) // ECMA: null is always false
return false;
if (value == ABCConstants.UNDEFINED_VALUE) // ECMA: undefined is always false
return false;
if (value instanceof Number)
return toBoolean((Number)value);
if (value instanceof String)
return toBoolean((String)value);
if (value instanceof Boolean)
return (Boolean)value;
return true; // ECMA: non-null object is true
}
/**
* Converts a Number to boolean using ECMA algorithm The following rules
* apply:
* <ol>
* <li>If number is zero, return false</li>
* <li>If number is Nan, return false</li>
* <li>Otherwise, return true</li>
* </ol>
*/
public static boolean toBoolean(Number value)
{
if(isNan(value.doubleValue()) )
return false;
return value.doubleValue() != 0; // works for all number types, because even if there is
// rounding, != 0 will still be correct
}
/**
* Converts a String to boolean using ECMA algorithm The following rules
* apply:
* <ol>
* <li>If value is empty, return false</li>
* <li>Otherwise, return true</li>
* </ol>
*/
public static boolean toBoolean(String value)
{
return !value.isEmpty();
}
/**
* Determines is a specific floating poing number is Nan
*/
public static boolean isNan(final double number)
{
return Double.isNaN(number); // Luckily ECMA uses the standard IEEE conventions for NaN
}
/**
* The Left Shift Operator ( << ) performs a bitwise left shift operation on
* the left operand by the amount specified by the right operand. The
* production
* {@code ShiftExpression : ShiftExpression << AdditiveExpression} is
* evaluated as follows:
* <ol>
* <li>Let lref be the result of evaluating ShiftExpression.</li>
* <li>Let lval be GetValue(lref).</li>
* <li>Let rref be the result of evaluating AdditiveExpression.</li>
* <li>Let rval be GetValue(rref).</li>
* <li>Let lnum be ToInt32(lval).</li>
* <li>Let rnum be ToUint32(rval).</li>
* <li>Let shiftCount be the result of masking out all but the least
* significant 5 bits of rnum, that is, compute rnum & 0x1F.</li>
* <li>Return the result of left shifting lnum by shiftCount bits. The
* result is a signed 32-bit integer.</li>
* </ol>
*
* @param left Value of the left operand.
* @param right Value of the right operand.
* @return 32-bit signed integer.
*/
public static int leftShiftOperation(final Number left, final Number right)
{
final double lval = left.doubleValue();
final double rval = right.doubleValue();
final int lnum = toInt32(lval);
final long rnum = toUInt32(rval);
final long shiftCount = rnum & 0x1F;
return lnum << shiftCount;
}
/**
* Performs a sign-filling bitwise right shift operation on the left operand
* by the amount specified by the right operand. The production
* {@code ShiftExpression : ShiftExpression >> AdditiveExpression} is
* evaluated as follows:
* <ol>
* <li>Let lref be the result of evaluating ShiftExpression.</li>
* <li>Let lval be GetValue(lref).</li>
* <li>Let rref be the result of evaluating AdditiveExpression.</li>
* <li>Let rval be GetValue(rref).</li>
* <li>Let lnum be ToInt32(lval).</li>
* <li>Let rnum be ToUint32(rval).</li>
* <li>Let shiftCount be the result of masking out all but the least
* significant 5 bits of rnum, that is, compute rnum & 0x1F.</li>
* <li>Return the result of performing a sign-extending right shift of lnum
* by shiftCount bits. The most significant bit is propagated. The result is
* a signed 32-bit integer.</li>
* </ol>
*
* @param left Value of the left operand.
* @param right Value of the right operand.
* @return 32-bit signed integer.
*/
public static int signedRightShiftOperation(final Number left, final Number right)
{
final double lval = left.doubleValue();
final double rval = right.doubleValue();
final int lnum = toInt32(lval);
final long rnum = toUInt32(rval);
final long shiftCount = rnum & 0x1F;
return lnum >> shiftCount;
}
/**
* Performs a zero-filling bitwise right shift operation on the left operand
* by the amount specified by the right operand. The production
* {@code ShiftExpression : ShiftExpression >>> AdditiveExpression} is
* evaluated as follows:
* <ol>
* <li>Let lref be the result of evaluating ShiftExpression.</li>
* <li>Let lval be GetValue(lref).</li>
* <li>Let rref be the result of evaluating AdditiveExpression.</li>
* <li>Let rval be GetValue(rref).</li>
* <li>Let lnum be ToUInt32(lval).</li>
* <li>Let rnum be ToUint32(rval).</li>
* <li>Let shiftCount be the result of masking out all but the least
* significant 5 bits of rnum, that is, compute rnum & 0x1F.</li>
* <li>Return the result of performing a zero-filling right shift of lnum by
* shiftCount bits. Vacated bits are filled with zero. The result is an
* unsigned 32-bit integer.</li>
* </ol>
*
* @param left Value of the left operand.
* @param right Value of the right operand.
* @return 32-bit unsigned integer.
*/
public static long unsignedRightShiftOperation(final Number left, final Number right)
{
final double lval = left.doubleValue();
final double rval = right.doubleValue();
final long lnum = toUInt32(lval);
final long rnum = toUInt32(rval);
final long shiftCount = rnum & 0x1F;
return lnum >>> shiftCount;
}
/**
* Performs the logical and (&&) operation on two objects. The production
* {@code LogicalANDExpression : LogicalANDExpression && BitwiseORExpression}
* is evaluated as follows:
* <ol>
* <li>Let lref be the result of evaluating LogicalANDExpression.</li>
* <li>lval be GetValue(lref).</li>
* <li>If ToBoolean(lval) is false, return lval.</li>
* <li>Let rref be the result of evaluating BitwiseORExpression.</li>
* <li>Return GetValue(rref).
*
* @param <T> is the numeric type of the operands, and the return type
* @param left Value of the left operand
* @param right Value of the right operand
*/
public static <T> T logicalAnd(T left, T right)
{
boolean b = toBoolean(left);
return b ? right : left;
}
/**
* Performs the logical or (||) operation on two objects. is evaluated as
* follows:
* <ol>
* <li>Let lref be the result of evaluating LogicalANDExpression.</li>
* <li>lval be GetValue(lref).</li>
* <li>If ToBoolean(lval) is true, return lval.</li>
* <li>Let rref be the result of evaluating BitwiseORExpression.</li>
* <li>Return GetValue(rref).
*
* @param <T> is the numeric type of the operands, and the return type
* @param left Value of the left operand
* @param right Value of the right operand
*/
public static <T> T logicalOr(T left, T right)
{
boolean b = toBoolean(left);
return b ? left : right;
}
/**
* Perform the logical not (!) operation on an object As per the ECMA spec
* this is done as follows
* <ol>
* <li>Let expr be the result of evaluating UnaryExpression</li>
* <li>Let oldValue be ToBoolean(GetValue(expr)).</li>
* <li>If oldValue is true, return false.</li>
* <li>Return true.</li>
* </ol>
*
* @param <T> is the numeric type of the operand
* @param e is the operand
*/
public static <T> boolean logicalNot(T e)
{
return !toBoolean(e);
}
/**
* Implement the ECMA ToNumber (ECMA 262, 3rd Edition section 9.3) algorithm. This will convert any value
* into a numeric value..
*
* @param value the value to convert
* @return the numeric representation of the value, as specified by the ToNumber algorithm
*/
public static <T> Number toNumeric(T value)
{
if( value == ABCConstants.UNDEFINED_VALUE )
return Double.NaN;
if( value == ABCConstants.NULL_VALUE )
return 0;
if( value instanceof Number )
return (Number)value;
if( value instanceof Boolean )
return toNumeric((Boolean) value);
if( value instanceof String )
return toNumeric((String) value);
return null;
}
/**
* Implement ToNumber for boolean values - ECMA 262, 3rd edition section 9.3
*/
private static int toNumeric (Boolean value)
{
return ((Boolean)value).booleanValue() ? 1 : 0;
}
/**
* trim according to ECMA - will get rid of escaped unicode characters
* that java's trim does not remove
*
* For use when converting Strings -> Number
*/
private static String numberTrim(String str) {
StringBuilder buf = new StringBuilder();
for (int i=0; i<str.length(); i++) {
Character c = str.charAt(i);
if (Character.isWhitespace(c)) {
continue;
}
buf.append(c);
}
str = new String(buf);
buf = new StringBuilder();
for (int i=str.length()-1; i>=0; i--) {
Character c = str.charAt(i);
if (Character.isWhitespace(c)) {
continue;
}
buf.append(c);
}
str = new String(buf.reverse());
return str;
}
/**
* Implement ToNumber for string values - ECMA 262, 3rd edition section 9.3.1
*/
private static Number toNumeric(String str )
{
double sign[] = new double[1];
str = numberTrim(str);
str = numberSign(str, sign);
double num;
if (str.equals("")) {
num = 0;
}
else
if (str.equals("Infinity")) {
if (sign[0] > 0) {
num = Double.POSITIVE_INFINITY;
}
else {
num = Double.NEGATIVE_INFINITY;
}
}
else
if (str.equals("NaN")) {
num = Double.NaN;
}
else if (str.startsWith("0x") || str.startsWith("0X")) {
try {
num = sign[0] * Long.valueOf(str.substring(2), 16);
}
catch (NumberFormatException e) {
num = Double.NaN;
}
}
else {
if (!Character.isDigit(str.charAt(str.length()-1)) && str.charAt(str.length()-1) != '.') { // localization?
num = Double.NaN; // "1f" "1F" "1d" "1D" are all NaN in AS3
}
else {
try {
num = sign[0] * Double.valueOf(str);
}
catch (NumberFormatException e) {
num = Double.NaN;
}
}
}
return num;
}
/**
* Helper method for converting strings to numbers - strips off any leaving '-' or '+' characters
* and determines what the sign of the number should be
*/
private static String numberSign(String str, double[] num) {
if(str.length() == 0) {
num[0] = +1;
}
else
if (str.equals("-") || str.equals("+")) {
num[0] = 0; // leave it alone, to produce NaN later
}
else
if(str.startsWith("-")) {
num[0] = -1;
str = str.substring(1);
}
else
if(str.startsWith("+")) {
num[0] = +1;
str = str.substring(1);
}
else {
num[0] = +1;
}
return str;
}
/**
* Implement equality of numbers - see ECMA 262 3d Edition, section 11.9.3
* @param l the left number
* @param r the right number
* @return true if the numbers are equal according to the ECMA spec
*/
public static boolean equals(Number l, Number r)
{
// NaN is not equal to anything, not even itself
double lDouble = l.doubleValue();
if( Double.isNaN(lDouble) )
return false;
return lDouble == r.doubleValue();
}
/**
* Implement equality of strings - see ECMA 262 3d Edition, section 11.9.3
* @param l the left string
* @param r the right string
* @return true if the strings are equal according to the ECMA spec
*/
public static boolean equals(String l, String r)
{
return l.equals(r);
}
/**
* Implement equality of booleans - see ECMA 262 3d Edition, section 11.9.3
* @param l the left boolean
* @param r the right boolean
* @return true if the booleans are equal according to the ECMA spec
*/
public static boolean equals(Boolean l, Boolean r)
{
return l.equals(r);
}
/**
* Implement the Abstract Equality Comparison algorithm - see ECMA 262 3d Edition, section 11.9.3
* @param l the left value
* @param r the right value
* @return true if the value are equal according to the ECMA spec
*/
public static boolean equals(Object l, Object r)
{
ECMAType leftType = getType(l);
ECMAType rightType = getType(r);
if( leftType == rightType)
{
switch (leftType)
{
case Boolean:
return equals((Boolean)l, (Boolean)r);
case Number:
return equals((Number)l, (Number)r);
case String:
return equals((String)l, (String)r);
case Null:
return true;
case Undefined:
return true;
}
}
if( l == r )
return true;
switch (leftType)
{
case Boolean:
return equals(toNumeric((Boolean)l), r);
case Number:
if( rightType == ECMAType.String )
return equals((Number)l, toNumeric((String)r));
break;
case String:
if( rightType == ECMAType.Number )
return equals(toNumeric((String)l), (Number)r);
break;
case Null:
if( rightType == ECMAType.Undefined )
return true;
break;
case Undefined:
if( rightType == ECMAType.Null )
return true;
break;
}
if( rightType == ECMAType.Boolean )
return equals(l, toNumeric((Boolean)r));
return false;
}
/**
* Implement the strict equality comparison algorithm - see ECMA 262 3d Edition, section 11.9.6
*
* Used for '===', '!==', etc.
*
* @param l the left value
* @param r the right value
* @return true if the values are equal according to the ECMA spec
*/
public static boolean strictEquals(Object l, Object r)
{
ECMAType leftType = getType(l);
ECMAType rightType = getType(r);
if( leftType == rightType)
{
switch (leftType)
{
case Boolean:
return equals((Boolean)l, (Boolean)r);
case Number:
return equals((Number)l, (Number)r);
case String:
return equals((String)l, (String)r);
case Null:
return true;
case Undefined:
return true;
}
}
return false;
}
/**
* Helper method for the comparison methods - return an enum representing the type
* of the value to avoid lots of if( instanceof ) checking.
*/
private static ECMAType getType(Object o)
{
if( o instanceof String )
return ECMAType.String;
if( o instanceof Number )
return ECMAType.Number;
if( o instanceof Boolean )
return ECMAType.Boolean;
if( o == ABCConstants.NULL_VALUE )
return ECMAType.Null;
if( o == ABCConstants.UNDEFINED_VALUE )
return ECMAType.Undefined;
assert false : "unknown constant type";
return null;
}
/**
* Enum to represent the various ECMA types we may be folding
*/
private static enum ECMAType
{
Boolean,
Number,
String,
Null,
Undefined
}
/**
* Implement the abstract relational comparison algorithm - see ECMA 262 3rd edition, section 11.8.5
*
* less than, greater than, etc are implement in terms of this algorithm
* @param l the first value to compare
* @param r the second value to compare
* @return true, false, or java null (which means at least one operand was NaN)
*/
private static Boolean relationalCompare(Object l, Object r)
{
ECMAType lType = getType(l);
ECMAType rType = getType(r);
if (lType == ECMAType.String && rType == ECMAType.String )
return relationalCompare((String)l, (String)r);
return relationalCompare(toNumeric(l), toNumeric(r));
}
/**
* Specialized relational compare method for String.
*/
private static Boolean relationalCompare(String l, String r)
{
if( l.compareTo(r) < 0 )
return true;
return false;
}
/**
* Specialized relational compare method for Numbers.
*/
private static Boolean relationalCompare(Number l, Number r)
{
double lVal = l.doubleValue();
double rVal = r.doubleValue();
if( isNan(lVal) || isNan(rVal) )
return null;
return lVal < rVal;
}
/**
* ECMA less-than operator - ECMA 262 3rd edition, section 11.8.1
*/
public static boolean lessThan(Number l, Number r)
{
Boolean res = relationalCompare(l, r);
if( res == null )
return false;
return res;
}
/**
* ECMA less-than operator - ECMA 262 3rd edition, section 11.8.1
*/
public static boolean lessThan(Object l, Object r)
{
Boolean res = relationalCompare(l, r);
if( res == null )
return false;
return res;
}
/**
* ECMA less-than-or-equal operator - ECMA 262 3rd edition, section 11.8.3
*/
public static boolean lessThanEquals(Number l, Number r)
{
Boolean res = relationalCompare(r, l);
if( res == null || res == true )
return false;
return true;
}
/**
* ECMA less-than-or-equal operator - ECMA 262 3rd edition, section 11.8.3
*/
public static boolean lessThanEquals(Object l, Object r)
{
Boolean res = relationalCompare(r, l);
if( res == null || res == true)
return false;
return true;
}
/**
* ECMA greater-than - ECMA 262 3rd edition, section 11.8.2
*/
public static boolean greaterThan(Number l, Number r)
{
Boolean res = relationalCompare(r, l);
if( res == null )
return false;
return res;
}
/**
* ECMA greater-than - ECMA 262 3rd edition, section 11.8.2
*/
public static boolean greaterThan(Object l, Object r)
{
Boolean res = relationalCompare(r, l);
if( res == null )
return false;
return res;
}
/**
* ECMA greater-than-or-equal operator - ECMA 262 3rd edition, section 11.8.4
*/
public static boolean greaterThanEquals(Number l, Number r)
{
Boolean res = relationalCompare(l, r);
if( res == null || res == true )
return false;
return true;
}
/**
* ECMA greater-than-or-equal operator - ECMA 262 3rd edition, section 11.8.4
*/
public static boolean greaterThanEquals(Object l, Object r)
{
Boolean res = relationalCompare(l, r);
if( res == null || res == true)
return false;
return true;
}
/**
* Implement the ECMA ToString algorithm - ECMA 262 3rd edition, section 9.8
* @param o the value to convert to a String
* @return the String representation of the value
*/
public static String toString(Object o)
{
if( o instanceof String )
return (String)o;
if( o instanceof Double )
return toString((Double) o);
if( o instanceof Integer )
return toString((Integer)o);
if( o instanceof Long )
return toString((Long)o);
if( o instanceof Boolean )
return toString((Boolean)o);
if( o == ABCConstants.NULL_VALUE )
return "null";
if( o == ABCConstants.UNDEFINED_VALUE )
return "undefined";
return null;
}
/**
* Implement the ECMA ToString algorithm - ECMA 262 3rd edition, section 9.8.1
*/
public static String toString(Double d)
{
String str = "";
if (d.isNaN() || d.isInfinite()) {
str = "" + d;
}
else {
// machinations for getting formatting to match AVM
BigDecimal bd = new BigDecimal(d.doubleValue()).round(MathContext.DECIMAL64).stripTrailingZeros();
// BigDecimal bd = new BigDecimal(d.doubleValue()).round(new MathContext(15asc , RoundingMode.DOWN)).stripTrailingZeros();
//out.println("dval="+dval+" bd="+bd+" scale="+bd.scale()+" prec="+bd.precision());
if (bd.scale() < 0 && bd.scale() > -21) {
bd = bd.setScale(0);
}
str = "" + bd;
str = str.replaceFirst("E", "e");
}
return str;
}
/**
* Specialized toString for ints - ECMA 262 3rd edition, section 9.8.1
*/
public static String toString(Integer i)
{
return "" + new BigDecimal(i);
}
/**
* Specialized toString for uint's - ECMA 262 3rd edition, section 9.8.1
*/
public static String toString(Long i)
{
return "" + new BigDecimal(i);
}
/**
* Specialized toString for booleans - ECMA 262 3rd edition, section 9.8
*/
public static String toString(Boolean b)
{
if( b.booleanValue() )
return "true";
return "false";
}
}