org.apache.royale.abc.semantics.ECMASupport Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of compiler Show documentation
Show all versions of compiler Show documentation
The Apache Royale Compiler
/*
*
* 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.royale.abc.semantics;
import org.apache.royale.abc.ABCConstants;
import java.math.BigDecimal;
import java.math.MathContext;
/**
* Provides helper functions for for various operations as specified by ECMA.
*
* @see ECMA 262 spec
*/
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:
*
* - Let {@code number} be the result of calling ToNumber on the input
* argument.
* - If {@code number} is NaN, +0, -0, positive infinity, or negative
* infinity, return +0.
* - Let {@code posInt} be {@code sign(number) * floor(abs(number))}.
* - 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.
* - If {@code int32bit} is greater than or equal to 2^31, return
* {@code int32bit - 2^32}, otherwise return {@code int32bit}.
*
* NOTE Given the above definition of {@code ToInt32}:
*
* - The ToInt32 abstract operation is idempotent: if applied to a result
* that it produced, the second application leaves that value unchanged.
* - 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.)
* - ToInt32 maps -0 to +0.
*
*
* @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:
*
* - Let {@code number} be the result of calling ToNumber on the input
* argument.
* - If {@code number} is NaN, +0, -0, positive infinity, or negative
* infinity, return +0.
* - Let {@code posInt} be {@code sign(number) x floor(abs(number))}.
* - 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.
* - Return {@code int32bit}.
*
* NOTE Given the above definition of {@code ToInt32}:
*
* - Step 5 is the only difference between {@link #toUInt32(double)} and
* {@link #toInt32(double)}.
* - The ToUint32 abstract operation is idempotent: if applied to a result
* that it produced, the second application leaves that value unchanged.
* - ToUint32 maps -0 to +0.
*
*
* @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:
*
* - Let {@code number} be the result of calling ToNumber on the input
* argument.
* - If {@code number} is NaN, return +0.
* - If {@code number} is +0, -0, positive infinity, or negative infinity,
* return {@code number}.
* - Return the result of computing
* {@code sign(number) x floor(abs(number))}.
*
*
* @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 - Number or String
*/
public static 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:
*
* - If number is zero, return false
* - If number is Nan, return false
* - Otherwise, return true
*
*/
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:
*
* - If value is empty, return false
* - Otherwise, return true
*
*/
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:
*
* - Let lref be the result of evaluating ShiftExpression.
* - Let lval be GetValue(lref).
* - Let rref be the result of evaluating AdditiveExpression.
* - Let rval be GetValue(rref).
* - Let lnum be ToInt32(lval).
* - Let rnum be ToUint32(rval).
* - Let shiftCount be the result of masking out all but the least
* significant 5 bits of rnum, that is, compute rnum & 0x1F.
* - Return the result of left shifting lnum by shiftCount bits. The
* result is a signed 32-bit integer.
*
*
* @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:
*
* - Let lref be the result of evaluating ShiftExpression.
* - Let lval be GetValue(lref).
* - Let rref be the result of evaluating AdditiveExpression.
* - Let rval be GetValue(rref).
* - Let lnum be ToInt32(lval).
* - Let rnum be ToUint32(rval).
* - Let shiftCount be the result of masking out all but the least
* significant 5 bits of rnum, that is, compute rnum & 0x1F.
* - 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.
*
*
* @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:
*
* - Let lref be the result of evaluating ShiftExpression.
* - Let lval be GetValue(lref).
* - Let rref be the result of evaluating AdditiveExpression.
* - Let rval be GetValue(rref).
* - Let lnum be ToUInt32(lval).
* - Let rnum be ToUint32(rval).
* - Let shiftCount be the result of masking out all but the least
* significant 5 bits of rnum, that is, compute rnum & 0x1F.
* - 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.
*
*
* @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:
*
* - Let lref be the result of evaluating LogicalANDExpression.
* - lval be GetValue(lref).
* - If ToBoolean(lval) is false, return lval.
* - Let rref be the result of evaluating BitwiseORExpression.
* - Return GetValue(rref).
*
* @param
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 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:
*
* - Let lref be the result of evaluating LogicalANDExpression.
* - lval be GetValue(lref).
* - If ToBoolean(lval) is true, return lval.
* - Let rref be the result of evaluating BitwiseORExpression.
* - Return GetValue(rref).
*
* @param
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 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
*
* - Let expr be the result of evaluating UnaryExpression
* - Let oldValue be ToBoolean(GetValue(expr)).
* - If oldValue is true, return false.
* - Return true.
*
*
* @param is the numeric type of the operand
* @param e is the operand
*/
public static 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 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=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";
}
}