001: /*
002: $Id: Numbers.java 1036 2004-04-07 20:19:21Z cpoirier $
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 1036 2004-04-07 20:19:21Z cpoirier $
059: */
060:
061: public class Numbers {
062:
063: //---------------------------------------------------------------------------
064: // LEXING SUPPORT
065:
066: /**
067: * Returns true if the specified character is a base-10 digit.
068: */
069:
070: public static boolean isDigit(char c) {
071: return c >= '0' && c <= '9';
072: }
073:
074: /**
075: * Returns true if the specific character is a base-8 digit.
076: */
077:
078: public static boolean isOctalDigit(char c) {
079: return c >= '0' && c <= '7';
080: }
081:
082: /**
083: * Returns true if the specified character is a base-16 digit.
084: */
085:
086: public static boolean isHexDigit(char c) {
087: return isDigit(c) || (c >= 'A' && c <= 'F')
088: || (c >= 'a' && c <= 'f');
089: }
090:
091: /**
092: * Returns true if the specified character is a valid type specifier
093: * for a numeric value.
094: */
095:
096: public static boolean isNumericTypeSpecifier(char c,
097: boolean isDecimal) {
098: if (isDecimal) {
099: switch (c) {
100: case 'G':
101: case 'g':
102: case 'D':
103: case 'd':
104: case 'F':
105: case 'f':
106: return true;
107: }
108: } else {
109: switch (c) {
110: case 'G':
111: case 'g':
112: case 'I':
113: case 'i':
114: case 'L':
115: case 'l':
116: return true;
117: }
118: }
119:
120: return false;
121: }
122:
123: //---------------------------------------------------------------------------
124: // PARSING SUPPORT
125:
126: private static final BigInteger MAX_LONG = BigInteger
127: .valueOf(Long.MAX_VALUE);
128: private static final BigInteger MIN_LONG = BigInteger
129: .valueOf(Long.MIN_VALUE);
130:
131: private static final BigInteger MAX_INTEGER = BigInteger
132: .valueOf(Integer.MAX_VALUE);
133: private static final BigInteger MIN_INTEGER = BigInteger
134: .valueOf(Integer.MIN_VALUE);
135:
136: private static final BigDecimal MAX_DOUBLE = new BigDecimal(String
137: .valueOf(Double.MAX_VALUE));
138: private static final BigDecimal MIN_DOUBLE = MAX_DOUBLE.negate();
139:
140: private static final BigDecimal MAX_FLOAT = new BigDecimal(String
141: .valueOf(Float.MAX_VALUE));
142: private static final BigDecimal MIN_FLOAT = MAX_FLOAT.negate();
143:
144: /**
145: * Builds a Number from the given integer descriptor. Creates the narrowest
146: * type possible, or a specific type, if specified.
147: *
148: * @param text literal text to parse
149: * @return instantiated Number object
150: * @throws NumberFormatException if the number does not fit within the type
151: * requested by the type specifier suffix (invalid numbers don't make
152: * it here)
153: */
154:
155: public static Number parseInteger(String text) {
156: char c = ' ';
157: int length = text.length();
158:
159: //
160: // Strip off the sign, if present
161:
162: boolean negative = false;
163: if ((c = text.charAt(0)) == '-' || c == '+') {
164: negative = (c == '-');
165: text = text.substring(1, length);
166: length -= 1;
167: }
168:
169: //
170: // Determine radix (default is 10).
171:
172: int radix = 10;
173: if (text.charAt(0) == '0' && length > 1) {
174: if ((c = text.charAt(1)) == 'X' || c == 'x') {
175: radix = 16;
176: text = text.substring(2, length);
177: length -= 2;
178: } else {
179: radix = 8;
180: }
181: }
182:
183: //
184: // Strip off any type specifier and convert it to lower
185: // case, if present.
186:
187: char type = 'x'; // pick best fit
188: if (isNumericTypeSpecifier(text.charAt(length - 1), false)) {
189: type = Character.toLowerCase(text.charAt(length - 1));
190: text = text.substring(0, length - 1);
191:
192: length -= 1;
193: }
194:
195: //
196: // Add the sign back, if necessary
197:
198: if (negative) {
199: text = "-" + text;
200: }
201:
202: //
203: // Build the specified type or, if no type was specified, the
204: // smallest type in which the number will fit.
205:
206: switch (type) {
207: case 'i':
208: return new Integer(Integer.parseInt(text, radix));
209:
210: case 'l':
211: return new Long(Long.parseLong(text, radix));
212:
213: case 'g':
214: return new BigInteger(text, radix);
215:
216: default:
217:
218: //
219: // If not specified, we will return the narrowest possible
220: // of Integer, Long, and BigInteger.
221:
222: BigInteger value = new BigInteger(text, radix);
223:
224: if (value.compareTo(MAX_INTEGER) <= 0
225: && value.compareTo(MIN_INTEGER) >= 0) {
226: return new Integer(value.intValue());
227: } else if (value.compareTo(MAX_LONG) <= 0
228: && value.compareTo(MIN_LONG) >= 0) {
229: return new Long(value.longValue());
230: }
231:
232: return value;
233: }
234: }
235:
236: /**
237: * Builds a Number from the given decimal descriptor. Uses BigDecimal,
238: * unless, Double or Float is requested.
239: *
240: * @param text literal text to parse
241: * @return instantiated Number object
242: * @throws NumberFormatException if the number does not fit within the type
243: * requested by the type specifier suffix (invalid numbers don't make
244: * it here)
245: */
246:
247: public static Number parseDecimal(String text) {
248: int length = text.length();
249:
250: //
251: // Strip off any type specifier and convert it to lower
252: // case, if present.
253:
254: char type = 'x';
255: if (isNumericTypeSpecifier(text.charAt(length - 1), true)) {
256: type = Character.toLowerCase(text.charAt(length - 1));
257: text = text.substring(0, length - 1);
258:
259: length -= 1;
260: }
261:
262: //
263: // Build the specified type or default to BigDecimal
264:
265: BigDecimal value = new BigDecimal(text);
266: switch (type) {
267: case 'f':
268: if (value.compareTo(MAX_FLOAT) <= 0
269: && value.compareTo(MIN_FLOAT) >= 0) {
270: return new Float(text);
271: }
272: throw new NumberFormatException("out of range");
273:
274: case 'd':
275: if (value.compareTo(MAX_DOUBLE) <= 0
276: && value.compareTo(MIN_DOUBLE) >= 0) {
277: return new Double(text);
278: }
279: throw new NumberFormatException("out of range");
280:
281: case 'g':
282: default:
283: return value;
284: }
285: }
286:
287: }
|