/*
*
* 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.compiler.internal.tree.as;
import java.math.BigInteger;
import org.apache.flex.abc.semantics.ECMASupport;
import org.apache.flex.compiler.constants.IASLanguageConstants.BuiltinType;
import org.apache.flex.compiler.internal.parsing.as.ASToken;
import org.apache.flex.compiler.internal.parsing.as.ASTokenTypes;
import org.apache.flex.compiler.tree.ASTNodeID;
import org.apache.flex.compiler.tree.as.INumericLiteralNode;
public class NumericLiteralNode extends LiteralNode implements INumericLiteralNode
{
private static final String HEX_PREFIX = "0x";
private static final String POSITIVE_HEX_PREFIX = "+0x";
private static final String NEGATIVE_HEX_PREFIX = "-0x";
private static Number hexToDec(String unsignedHexString, boolean isNegative)
{
if (unsignedHexString.length() < 16)
{
final Long abs = Long.valueOf(unsignedHexString, 16);
return isNegative ? -abs : abs;
}
final BigInteger bi = new BigInteger(unsignedHexString, 16);
return isNegative ? -bi.doubleValue() : bi.doubleValue();
}
/**
* Constructor.
*
* @param text The text of the numeric literal.
*/
public NumericLiteralNode(String text)
{
super(LiteralType.NUMBER, text);
assert text != null && text.length() > 0 : "The text of a NumericLiteralNode cannot be null or empty";
String lowerCaseText = text.toLowerCase();
isHex = lowerCaseText.startsWith(HEX_PREFIX) ||
lowerCaseText.startsWith(NEGATIVE_HEX_PREFIX) ||
lowerCaseText.startsWith(POSITIVE_HEX_PREFIX);
}
/**
* Constructor.
*/
public NumericLiteralNode(ASToken t)
{
super(t, LiteralType.NUMBER);
isHex = (t.getType() == ASTokenTypes.TOKEN_LITERAL_HEX_NUMBER);
}
/**
* Copy constructor.
*
* @param other The node to copy.
*/
protected NumericLiteralNode(NumericLiteralNode other)
{
super(other);
this.numericValue = other.numericValue;
this.isHex = other.isHex;
}
/**
* Lazy evaluate numeric value from token text and cache the value.
*/
private INumericValue numericValue;
private final boolean isHex;
//
// NodeBase overrides
//
@Override
public ASTNodeID getNodeID()
{
// AS3 defines INT / INT(0) = NaN. In order to make the constant folding in
// CG phase be able to process "division by integer zero", we use a special
// AST node ID {@code LiteralIntegerZeroID} for INT(0) constants.
try
{
final INumericValue numeric = getNumericValue();
switch (numeric.getAssumedType())
{
case INT:
{
return numeric.toNumber() == 0 ?
ASTNodeID.LiteralIntegerZeroID :
ASTNodeID.LiteralIntegerID;
}
case UINT:
{
return numeric.toNumber() == 0 ?
ASTNodeID.LiteralIntegerZeroID :
ASTNodeID.LiteralUintID;
}
default:
{
return ASTNodeID.LiteralDoubleID;
}
}
}
catch (NumberFormatException cannot_convert)
{
return ASTNodeID.LiteralNumberID;
}
}
//
// ExpressionNodeBase overrides
//
@Override
protected NumericLiteralNode copy()
{
return new NumericLiteralNode(this);
}
//
// INumericLiteralNode implementations
//
@Override
public INumericValue getNumericValue() throws NumberFormatException
{
if (numericValue == null)
{
final Number decimal;
final String lowerCaseValue = value.toLowerCase();
if (isHex && lowerCaseValue.startsWith(HEX_PREFIX))
decimal = hexToDec(value.substring(HEX_PREFIX.length()), false);
else if (isHex && lowerCaseValue.startsWith(POSITIVE_HEX_PREFIX))
decimal = hexToDec(value.substring(POSITIVE_HEX_PREFIX.length()), false);
else if (isHex && lowerCaseValue.startsWith(NEGATIVE_HEX_PREFIX))
decimal = hexToDec(value.substring(NEGATIVE_HEX_PREFIX.length()), true);
else
decimal = Double.parseDouble(value);
numericValue = new NumericValue(decimal, value, isHex);
}
return numericValue;
}
//
// Other methods
//
/**
* @return <code>true</code> if the numeric literal is in hexadecimal format.
*/
public boolean isHex()
{
return isHex;
}
//
// Inner types
//
private static final class NumericValue implements INumericValue
{
/**
* Raw text form of the numeric literal.
*/
private final String text;
/**
* It's either a {@link Long} or {@link Double}.
*/
private final Number number;
/**
* Inferred ABC type.
*/
private final BuiltinType baseType;
private NumericValue(Number number, String text, boolean isHex)
{
this.number = number;
this.text = text.trim();
final double doubleValue = number.doubleValue();
if (text.contains(".") ||
number.equals(Double.POSITIVE_INFINITY) ||
number.equals(Double.NEGATIVE_INFINITY) ||
number.equals(Double.NaN))
{
baseType = BuiltinType.NUMBER;
}
else if (doubleValue == 0 && text.startsWith("-"))
{
baseType = BuiltinType.NUMBER;
}
else if (Math.floor(doubleValue) == doubleValue)
{
if (doubleValue >= MIN_INT_VALUE && doubleValue <= MAX_INT_VALUE)
baseType = BuiltinType.INT;
else if (doubleValue >= 0 && doubleValue <= MAX_UINT_VALUE)
baseType = BuiltinType.UINT;
else
baseType = BuiltinType.NUMBER;
}
else
{
baseType = BuiltinType.NUMBER;
}
}
@Override
public final BuiltinType getAssumedType()
{
return baseType;
}
@Override
public final double toInteger()
{
return ECMASupport.toInteger(number.doubleValue());
}
@Override
public final double toNumber()
{
return number.doubleValue();
}
@Override
public final int toInt32()
{
return ECMASupport.toInt32(number.doubleValue());
}
@Override
public final long toUint32()
{
if (number instanceof Long)
return number.longValue();
return ECMASupport.toUInt32(number.doubleValue());
}
@Override
public final String toString()
{
return text;
}
}
}