blob: 652ec97d68c18b05dea1048b531f752037c5765f [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.codehaus.groovy.syntax;
import antlr.collections.AST;
import org.codehaus.groovy.antlr.ASTRuntimeException;
import java.math.BigInteger;
import java.math.BigDecimal;
/**
* Helper class for processing Groovy numeric literals.
*
* @author Brian Larson
* @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
*/
public class Numbers
{
//---------------------------------------------------------------------------
// LEXING SUPPORT
/**
* Returns true if the specified character is a base-10 digit.
*/
public static boolean isDigit( char c )
{
return c >= '0' && c <= '9';
}
/**
* Returns true if the specific character is a base-8 digit.
*/
public static boolean isOctalDigit( char c )
{
return c >= '0' && c <= '7';
}
/**
* Returns true if the specified character is a base-16 digit.
*/
public static boolean isHexDigit( char c )
{
return isDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f');
}
/**
* Returns true if the specified character is a valid type specifier
* for a numeric value.
*/
public static boolean isNumericTypeSpecifier( char c, boolean isDecimal )
{
if( isDecimal )
{
switch( c )
{
case 'G':
case 'g':
case 'D':
case 'd':
case 'F':
case 'f':
return true;
}
}
else
{
switch( c )
{
case 'G':
case 'g':
case 'I':
case 'i':
case 'L':
case 'l':
return true;
}
}
return false;
}
//---------------------------------------------------------------------------
// PARSING SUPPORT
private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE);
private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE);
private static final BigInteger MAX_INTEGER = BigInteger.valueOf(Integer.MAX_VALUE);
private static final BigInteger MIN_INTEGER = BigInteger.valueOf(Integer.MIN_VALUE);
private static final BigDecimal MAX_DOUBLE = new BigDecimal(String.valueOf(Double.MAX_VALUE));
private static final BigDecimal MIN_DOUBLE = MAX_DOUBLE.negate();
private static final BigDecimal MAX_FLOAT = new BigDecimal(String.valueOf(Float.MAX_VALUE));
private static final BigDecimal MIN_FLOAT = MAX_FLOAT.negate();
/**
* Builds a Number from the given integer descriptor. Creates the narrowest
* type possible, or a specific type, if specified.
*
* @param text literal text to parse
* @return instantiated Number object
* @throws NumberFormatException if the number does not fit within the type
* requested by the type specifier suffix (invalid numbers don't make
* it here)
*/
@Deprecated
public static Number parseInteger(String text ) {
return parseInteger(null, text);
}
/**
* Builds a Number from the given integer descriptor. Creates the narrowest
* type possible, or a specific type, if specified.
*
* @param reportNode at node for error reporting in the parser
* @param text literal text to parse
* @return instantiated Number object
* @throws NumberFormatException if the number does not fit within the type
* requested by the type specifier suffix (invalid numbers don't make
* it here)
*/
public static Number parseInteger(AST reportNode, String text )
{
// remove place holder underscore before starting
text = text.replace("_", "");
char c = ' ';
int length = text.length();
//
// Strip off the sign, if present
boolean negative = false;
if( (c = text.charAt(0)) == '-' || c == '+' )
{
negative = (c == '-');
text = text.substring( 1, length );
length -= 1;
}
//
// Determine radix (default is 10).
int radix = 10;
if( text.charAt(0) == '0' && length > 1 ) {
c = text.charAt(1);
if( c == 'X' || c == 'x' ) {
radix = 16;
text = text.substring( 2, length);
length -= 2;
} else if ( c == 'B' || c == 'b' ) {
radix = 2;
text = text.substring(2, length);
length -= 2;
} else {
radix = 8;
}
}
//
// Strip off any type specifier and convert it to lower
// case, if present.
char type = 'x'; // pick best fit
if( isNumericTypeSpecifier(text.charAt(length-1), false) )
{
type = Character.toLowerCase( text.charAt(length-1) );
text = text.substring( 0, length-1);
length -= 1;
}
//
// Add the sign back, if necessary
if( negative )
{
text = "-" + text;
}
//
// Build the specified type or, if no type was specified, the
// smallest type in which the number will fit.
BigInteger value = new BigInteger( text, radix );
switch (type)
{
case 'i':
if (radix==10 && reportNode != null && (value.compareTo(MAX_INTEGER) > 0 || value.compareTo(MIN_INTEGER) < 0) ) {
throw new ASTRuntimeException(reportNode, "Number of value "+value+" does not fit in the range of int, but int was enforced.");
} else {
return Integer.valueOf(value.intValue());
}
case 'l':
if (radix==10 && reportNode != null && (value.compareTo(MAX_LONG) > 0 || value.compareTo(MIN_LONG) < 0) ) {
throw new ASTRuntimeException(reportNode, "Number of value "+value+" does not fit in the range of long, but long was enforced.");
} else {
return new Long( value.longValue() );
}
case 'g':
return value ;
default:
// If not specified, we will return the narrowest possible
// of Integer, Long, and BigInteger.
if( value.compareTo(MAX_INTEGER) <= 0 && value.compareTo(MIN_INTEGER) >= 0 )
{
return Integer.valueOf(value.intValue());
}
else if( value.compareTo(MAX_LONG) <= 0 && value.compareTo(MIN_LONG) >= 0 )
{
return new Long(value.longValue());
}
return value;
}
}
/**
* Builds a Number from the given decimal descriptor. Uses BigDecimal,
* unless, Double or Float is requested.
*
* @param text literal text to parse
* @return instantiated Number object
* @throws NumberFormatException if the number does not fit within the type
* requested by the type specifier suffix (invalid numbers don't make
* it here)
*/
public static Number parseDecimal( String text )
{
text = text.replace("_", "");
int length = text.length();
//
// Strip off any type specifier and convert it to lower
// case, if present.
char type = 'x';
if( isNumericTypeSpecifier(text.charAt(length-1), true) )
{
type = Character.toLowerCase( text.charAt(length-1) );
text = text.substring( 0, length-1 );
length -= 1;
}
//
// Build the specified type or default to BigDecimal
BigDecimal value = new BigDecimal( text );
switch( type )
{
case 'f':
if( value.compareTo(MAX_FLOAT) <= 0 && value.compareTo(MIN_FLOAT) >= 0)
{
return new Float( text );
}
throw new NumberFormatException( "out of range" );
case 'd':
if( value.compareTo(MAX_DOUBLE) <= 0 && value.compareTo(MIN_DOUBLE) >= 0)
{
return new Double( text );
}
throw new NumberFormatException( "out of range" );
case 'g':
default:
return value;
}
}
}