001 /* 002 $Id: Numbers.java,v 1.3 2004/04/07 20:19:20 cpoirier Exp $ 003 004 Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved. 005 006 Redistribution and use of this software and associated documentation 007 ("Software"), with or without modification, are permitted provided 008 that the following conditions are met: 009 010 1. Redistributions of source code must retain copyright 011 statements and notices. Redistributions must also contain a 012 copy of this document. 013 014 2. Redistributions in binary form must reproduce the 015 above copyright notice, this list of conditions and the 016 following disclaimer in the documentation and/or other 017 materials provided with the distribution. 018 019 3. The name "groovy" must not be used to endorse or promote 020 products derived from this Software without prior written 021 permission of The Codehaus. For written permission, 022 please contact info@codehaus.org. 023 024 4. Products derived from this Software may not be called "groovy" 025 nor may "groovy" appear in their names without prior written 026 permission of The Codehaus. "groovy" is a registered 027 trademark of The Codehaus. 028 029 5. Due credit should be given to The Codehaus - 030 http://groovy.codehaus.org/ 031 032 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS 033 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 034 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 035 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 036 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 037 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 038 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 039 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 040 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 041 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 042 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 043 OF THE POSSIBILITY OF SUCH DAMAGE. 044 045 */ 046 047 package org.codehaus.groovy.syntax; 048 049 import java.math.BigInteger; 050 import java.math.BigDecimal; 051 052 /** 053 * Helper class for processing Groovy numeric literals. 054 * 055 * @author Brian Larson 056 * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a> 057 * 058 * @version $Id: Numbers.java,v 1.3 2004/04/07 20:19:20 cpoirier Exp $ 059 */ 060 061 public class Numbers 062 { 063 064 065 066 //--------------------------------------------------------------------------- 067 // LEXING SUPPORT 068 069 070 /** 071 * Returns true if the specified character is a base-10 digit. 072 */ 073 074 public static boolean isDigit( char c ) 075 { 076 return c >= '0' && c <= '9'; 077 } 078 079 080 /** 081 * Returns true if the specific character is a base-8 digit. 082 */ 083 084 public static boolean isOctalDigit( char c ) 085 { 086 return c >= '0' && c <= '7'; 087 } 088 089 090 /** 091 * Returns true if the specified character is a base-16 digit. 092 */ 093 094 public static boolean isHexDigit( char c ) 095 { 096 return isDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); 097 } 098 099 100 101 /** 102 * Returns true if the specified character is a valid type specifier 103 * for a numeric value. 104 */ 105 106 public static boolean isNumericTypeSpecifier( char c, boolean isDecimal ) 107 { 108 if( isDecimal ) 109 { 110 switch( c ) 111 { 112 case 'G': 113 case 'g': 114 case 'D': 115 case 'd': 116 case 'F': 117 case 'f': 118 return true; 119 } 120 } 121 else 122 { 123 switch( c ) 124 { 125 case 'G': 126 case 'g': 127 case 'I': 128 case 'i': 129 case 'L': 130 case 'l': 131 return true; 132 } 133 } 134 135 return false; 136 } 137 138 139 140 141 142 //--------------------------------------------------------------------------- 143 // PARSING SUPPORT 144 145 146 private static final BigInteger MAX_LONG = BigInteger.valueOf(Long.MAX_VALUE); 147 private static final BigInteger MIN_LONG = BigInteger.valueOf(Long.MIN_VALUE); 148 149 private static final BigInteger MAX_INTEGER = BigInteger.valueOf(Integer.MAX_VALUE); 150 private static final BigInteger MIN_INTEGER = BigInteger.valueOf(Integer.MIN_VALUE); 151 152 private static final BigDecimal MAX_DOUBLE = new BigDecimal(String.valueOf(Double.MAX_VALUE)); 153 private static final BigDecimal MIN_DOUBLE = MAX_DOUBLE.negate(); 154 155 private static final BigDecimal MAX_FLOAT = new BigDecimal(String.valueOf(Float.MAX_VALUE)); 156 private static final BigDecimal MIN_FLOAT = MAX_FLOAT.negate(); 157 158 159 160 /** 161 * Builds a Number from the given integer descriptor. Creates the narrowest 162 * type possible, or a specific type, if specified. 163 * 164 * @param text literal text to parse 165 * @return instantiated Number object 166 * @throws NumberFormatException if the number does not fit within the type 167 * requested by the type specifier suffix (invalid numbers don't make 168 * it here) 169 */ 170 171 public static Number parseInteger( String text ) 172 { 173 char c = ' '; 174 int length = text.length(); 175 176 177 // 178 // Strip off the sign, if present 179 180 boolean negative = false; 181 if( (c = text.charAt(0)) == '-' || c == '+' ) 182 { 183 negative = (c == '-'); 184 text = text.substring( 1, length ); 185 length -= 1; 186 } 187 188 189 // 190 // Determine radix (default is 10). 191 192 int radix = 10; 193 if( text.charAt(0) == '0' && length > 1 ) 194 { 195 if( (c = text.charAt(1)) == 'X' || c == 'x' ) 196 { 197 radix = 16; 198 text = text.substring( 2, length); 199 length -= 2; 200 } 201 else 202 { 203 radix = 8; 204 } 205 } 206 207 208 // 209 // Strip off any type specifier and convert it to lower 210 // case, if present. 211 212 char type = 'x'; // pick best fit 213 if( isNumericTypeSpecifier(text.charAt(length-1), false) ) 214 { 215 type = Character.toLowerCase( text.charAt(length-1) ); 216 text = text.substring( 0, length-1); 217 218 length -= 1; 219 } 220 221 222 // 223 // Add the sign back, if necessary 224 225 if( negative ) 226 { 227 text = "-" + text; 228 } 229 230 231 // 232 // Build the specified type or, if no type was specified, the 233 // smallest type in which the number will fit. 234 235 switch (type) 236 { 237 case 'i': 238 return new Integer( Integer.parseInt(text, radix) ); 239 240 case 'l': 241 return new Long( Long.parseLong(text, radix) ); 242 243 case 'g': 244 return new BigInteger( text, radix ); 245 246 default: 247 248 // 249 // If not specified, we will return the narrowest possible 250 // of Integer, Long, and BigInteger. 251 252 BigInteger value = new BigInteger( text, radix ); 253 254 if( value.compareTo(MAX_INTEGER) <= 0 && value.compareTo(MIN_INTEGER) >= 0 ) 255 { 256 return new Integer(value.intValue()); 257 } 258 else if( value.compareTo(MAX_LONG) <= 0 && value.compareTo(MIN_LONG) >= 0 ) 259 { 260 return new Long(value.longValue()); 261 } 262 263 return value; 264 } 265 } 266 267 268 269 /** 270 * Builds a Number from the given decimal descriptor. Uses BigDecimal, 271 * unless, Double or Float is requested. 272 * 273 * @param text literal text to parse 274 * @return instantiated Number object 275 * @throws NumberFormatException if the number does not fit within the type 276 * requested by the type specifier suffix (invalid numbers don't make 277 * it here) 278 */ 279 280 public static Number parseDecimal( String text ) 281 { 282 int length = text.length(); 283 284 285 // 286 // Strip off any type specifier and convert it to lower 287 // case, if present. 288 289 char type = 'x'; 290 if( isNumericTypeSpecifier(text.charAt(length-1), true) ) 291 { 292 type = Character.toLowerCase( text.charAt(length-1) ); 293 text = text.substring( 0, length-1 ); 294 295 length -= 1; 296 } 297 298 299 // 300 // Build the specified type or default to BigDecimal 301 302 BigDecimal value = new BigDecimal( text ); 303 switch( type ) 304 { 305 case 'f': 306 if( value.compareTo(MAX_FLOAT) <= 0 && value.compareTo(MIN_FLOAT) >= 0) 307 { 308 return new Float( text ); 309 } 310 throw new NumberFormatException( "out of range" ); 311 312 case 'd': 313 if( value.compareTo(MAX_DOUBLE) <= 0 && value.compareTo(MIN_DOUBLE) >= 0) 314 { 315 return new Double( text ); 316 } 317 throw new NumberFormatException( "out of range" ); 318 319 case 'g': 320 default: 321 return value; 322 } 323 } 324 325 }