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 }