From c61fe60b2f5b0dc8ea60ba63ca556862091e066b Mon Sep 17 00:00:00 2001
From: Jared Davis
- * This parsing mechanism is intended to be as close as possible to the
- * numeric literals supported by the Java programming language itself.
- *
- * This parsing mechanism is intended to be as close as possible to the
- * numeric literals supported by the Java programming language itself.
- *
+ * This parsing mechanism is intended to be as close as possible to the + * numeric literals supported by the Java programming language itself. + *
+ * + * @param s The string from which the numeric literal should be parsed. + * @param pos The offset from which the literal should be parsed. If parsing + * is successful, the position will be advanced to the next index + * after the parsed literal. + * @return The parsed numeric value, of a type consistent with Java's support + * for numeric primitives—or for values outside the normal range + * of Java primitives, {@link BigInteger} or {@link BigDecimal} as + * appropriate. Returns null if a numeric literal is not detected. + */ + + public static Number parseAllNumbers(String s, Position pos) { + ParseNumberResults result = ParseNumber.identifyNumber(s, pos.get()); + ParseNumber.processNumber(s, result); + if (result.number != null) { + pos.inc(result.getLength()); + } + return result.number; + } + + + // identify + + private static ParseNumberResults identifyNumber(String s, int startingPosition) { + ParseNumberResults results = new ParseNumberResults(); + results.beginGroup[1] = startingPosition; + if (s == null || s.isEmpty()) { + results.numberType = NumberType.NOT_A_NUMBER; + return results; + } + int len = s.length(); + if (startingPosition >= len) { + results.numberType = NumberType.NOT_A_NUMBER; + return results; + } + int start = startingPosition; + + // Handle optional leading sign + char first = s.charAt(startingPosition); + if (first == '-' || first == '+') { + results.ndxSign = startingPosition; + start++; + if (len == 1) { + results.numberType = NumberType.NOT_A_NUMBER; + return results; + } + } + + // Check for Hexadecimal prefix (0x or 0X) + if (start + 2 < len && s.charAt(start) == '0' && + (s.charAt(start + 1) == 'x' || s.charAt(start + 1) == 'X')) { + results.numberType = NumberType.HEXADECIMAL; + extractHexNumber(s, start + 2, len, results); + return results; + } + + if (start + 2 < len && s.charAt(start) == '0' && + (s.charAt(start + 1) == 'b' || s.charAt(start + 1) == 'B')) { + results.numberType = NumberType.BINARY; + extractBinaryNumber(s, start + 2, len, results); + return results; + } + + // try decimal, internally checks for octal + extractDecimalNumber(s, start, len, results); + return results; + } + + // extract + +/** + * Attempts to parse a decimal literal (integer or otherwise; e.g., {@code 1234567890}, + * {@code 1234.0987} or {@code 1.2e34} or {@code 01234}). Parsing supports base 10 and base 8. + * + * + * @param s The string from which the numeric literal should be parsed. + * @param start The index of the string to start the parsing after the sign and 0[Xx] prefix. + * @param end The index of the last char to parse. + * @param results contains the parsing details calculated in this method. + * @return true if a potential decimal literal is found, false otherwise + * + * + * Based on regular expression (([-+]?[0-9]+(\.[0-9]*)?([Ee][-+]?[0-9]+)?)([DdFfLl])?).* + * group 1 = entire matching string. NB: re has .* postfix but this method does not include .* in this group. + * group 2 is from start of string including optional groups 3 and 4 + * group 3 is \. then [0-9]* + * group 4 is [Ee][-+]?[0-9]+ + * group 5 is [DdFfLl]? + * + * group 6 is not in re. [-+]? at start of string + * group 7 is not in re. [0-9]+ after sign, before group 3 + * group 8 is not in re. Leads to group 3 or 4 or 5 or end + * + * Octal encoding detection + * If we have (no group 3) and (no group 4) then do a scan for an octal value in group 2 + * Octal IFF + * group 7 starts with 0; does not contain 8 or 9; is longer than 1 + * group 5 == empty or Ll + */ + + private static boolean extractDecimalNumber(String s, int start, int end, ParseNumberResults results) { + char c; + + results.setSignGroup(6); + + boolean havedot = false; + boolean haveSign = false; + boolean haveFour = false; + + int group = 7; + results.beginGroup[group] = start; + + bigWhile: + while (start <= end) { + if (start == end) { + results.endGroup[group] = start; + break; + } + c = s.charAt(start); + switch (group) { + case 7: // [0-9]+ digits after sign and before decimal point + if (isDecimalDigit(c)) { + start++; + } else { + if (results.beginGroup[group] == start) { + // need at least 1 digit + results.numberType = NumberType.NOT_A_NUMBER; + break bigWhile; + } + results.endGroup[group] = start; + group = 8; + } + break; + + case 8: // transition to group 3, 4, 5 or end + if (c == '.') { + if (havedot) { // only one . allowed + results.numberType = NumberType.NOT_A_NUMBER; + break bigWhile; + } + havedot = true; + group = 3; + results.beginGroup[group] = start++; + break; + } + if (isE(c)) { + if (haveFour) { // one group 4 allowed + results.numberType = NumberType.NOT_A_NUMBER; + break bigWhile; + } + haveFour = true; + group = 4; + results.beginGroup[group] = start++; + break; + } + if (isADoubleOrFloatSuffix(c) || isL(c)) { + group = 5; + results.beginGroup[group] = start++; + results.endGroup[group] = start; + results.numberType = isL(c) ? NumberType.INTEGER: NumberType.DOUBLE; + // break bigWhile fall through below + } + break bigWhile; + case 3: // [0-9] 0..n times + if (isDecimalDigit(c)) { + start++; + } else { + results.endGroup[group] = start; + group = 8; + } + break; + case 4: // just [-+]?[0-9]+ of group 4. [Ee] done in 8 + if (isASign(c)) { + if (haveSign) { // only one sign allowed + results.numberType = NumberType.NOT_A_NUMBER; + break bigWhile; + } + haveSign = true; + start++; + } else if (isDecimalDigit(c)) { + start++; + } else { + results.endGroup[group] = start; + group = 8; // back to 8 for group 5 + } + break; + } // switch group + } // bigWhile + results.endGroup[1] = start; + + if (results.endGroup[7] - results.beginGroup[7] == 0) + results.numberType = NumberType.NOT_A_NUMBER; // need at least 1 digit in number before decimal + + if (results.beginGroup[4] != -1) { // we got an E + int minLength = (haveSign) ? 3 : 2; + if (results.endGroup[4] - results.beginGroup[4] < minLength) + results.numberType = NumberType.NOT_A_NUMBER; // need at least 1 digit in exponent + } + + if (results.numberType == null) { + if ((results.beginGroup[4] != -1) || (results.endGroup[3] != -1)) { + results.numberType = NumberType.DOUBLE; + } else { + results.numberType = NumberType.INTEGER; + } + } + + if (results.numberType != NumberType.NOT_A_NUMBER) { + // group 2 length = group 1 length - group 5 length + results.beginGroup[2] = results.beginGroup[1]; + results.endGroup[2] = results.endGroup[1]; + if (results.beginGroup[5] != -1) { + results.endGroup[2]--; + } + } + + // octal encoding ? + if (results.numberType == NumberType.INTEGER) { + if (s.charAt(results.beginGroup[7]) == '0') { + boolean octal = (results.endGroup[7] - results.beginGroup[7] > 1); + if (octal) { + for (int i = results.beginGroup[7] + 1; i < results.endGroup[7]; i++) { + if (!isOctalDigit(s.charAt(i))) { + octal = false; + break; + } + } + } + if (octal) { + if (results.beginGroup[5] != -1) { + if (!isL(s.charAt(results.beginGroup[5]))) { + octal = false; + } + } + } + if (octal) { + results.numberType = NumberType.OCTAL; + } + } + } + + return (results.numberType != NumberType.NOT_A_NUMBER); + } + + + + /** + * Parses a binary literal (e.g., {@code 0b010101000011}). + * + * @param s The string from which the numeric literal should be parsed. + * @param start The index of the string to start the parsing after the sign and 0[Bb] prefix. + * @param end The index of the last char to parse. + * @param results contains the parsing details calculated in this method. + * @return true if a potential binary literal is found, false otherwise + + * based on Regular Expression (([-+]?)0[Bb]([01]+)([Ll]?)).* + * group 1 = entire matching string. NB: re has .* postfix but this method does not include .* in this group. + * group 2 [-+]? This is processed by the caller and placed into ParseNumberResults ndxSign. + * group none 0[Bb] must exist. This is processed by the caller. + * group 3 [01]+ + * group 4 [lL]? + */ + + private static boolean extractBinaryNumber(String s, int start, int end, ParseNumberResults results) { + char c; + results.setSignGroup(2); + + int group = 3; + results.beginGroup[group] = start; + + bigWhile: + while (start <= end) { + if (start == end) { + results.endGroup[group] = start; + break; + } + c = s.charAt(start); + switch (group) { + case 3: // [01]+ + if (isBinaryDigit(c)) { + start++; + } else { + results.endGroup[group] = start; + group = 4; + } + break; + case 4: // [Ll]? + if (isL(c)) { + results.beginGroup[group] = start; + results.endGroup[group] = ++start; + } + // ok - we are done + break bigWhile; + } // switch group + } // while bigWhile + results.endGroup[1] = start; + // need at least 1 digit in group 3 + if (results.endGroup[3] - results.beginGroup[3] == 0) + results.numberType = NumberType.NOT_A_NUMBER; + return (results.numberType == NumberType.BINARY); + } + + /** + * Parses a hexidecimal literal. Both hexadecimal integer (e.g., + * {@code 0xfedcba9876543210}) and hexidecimal floating point (e.g., + * {@code 0xfedcba.98765432p10f}) are supported. + * + * @param s The string from which the numeric literal should be parsed. + * @param start The index of the string to start the parsing after the sign and 0[Xx] prefix. + * @param end The index of the last char to parse. + * @param results contains the parsing details calculated in this method. + * @return true if a potential hexidecimal literal is found, false otherwise + * + * + * based on Regular Expression (([-+]?)0[Xx]([0-9a-fA-F]+)([Ll]|(\\.[0-9a-fA-F]*)?[Pp]([-+]?)([0-9]+)([DdFf]|)|)).* + * group 1 = entire matching string. NB: re has .* postfix but this method does not include .* in this group. + * group 2 [-+]? This is processed by the caller and placed into ParseNumberResults ndxSign. + * group none 0[Xx] This is processed by the caller. + * group 3 [0-9a-fA-F]+ repeat. + * group 4 includes groups 5, 6, 7 and 8 or [lL]? + * group 5 (\\.[0-9a-fA-F]*)? Entire group is optional + * group 6 requires a [Pp] prefix which is not part of the group. [-+]? + * group 7 [0-9]+ + * group 8 [DdFf]? + * group 9, not in re, is [Pp] which is required before groups 6, 7. + */ + + private static boolean extractHexNumber(String s, int start, int end, ParseNumberResults results) { + char c; + + results.setSignGroup(2); + + int group = 3; + results.beginGroup[3] = start; + bigWhile: + while (start <= end) { + if (start == end) { + results.endGroup[group] = start; + break; + } + c = s.charAt(start); // start may not change when group changes + switch (group) { + case 3: // [0-9a-fA-F]+ + if (isHexDigit(c)) { + start++; + } else { + results.endGroup[group] = start; + group = 4; + results.beginGroup[group] = start; + } + break; + case 4: // [Ll]? group end is adjusted after switch to include groups 5-8. + if (isL(c)) { + results.endGroup[group] = ++start; + break bigWhile; // end of re. No need to continue parsing chars + } + if (c == '.') { + group = 5; + results.beginGroup[group] = start++; + break; + } + if (isP(c)) { + start++; + group = 9; + break; + } + // we are done, there is no group 4 + results.beginGroup[group] = -1; + break bigWhile; + case 5: // \.[0-9a-fA-F]* 0..n + if (isHexDigit(c)) { + start++; + } else { + results.endGroup[group] = start; + if (isP(c)) { + group = 9; + results.beginGroup[group] = start; + results.endGroup[group] = ++start; + } + } + break; + case 9: // start -1 == p or P + if (isASign(c)) { + group = 6; + results.beginGroup[group] = start; + results.endGroup[group] = ++start; + } + group = 7; + break; + case 7: // [0-9]+ enforce at least 1 char in this group + if (isDecimalDigit(c)) { + if (results.beginGroup[group] == -1) results.beginGroup[group] = start; + start++; + } else { + if (results.beginGroup[group] == -1) { + // need at least 1 digit to be valid + results.numberType = NumberType.NOT_A_NUMBER; + break bigWhile; + } else { + results.endGroup[group] = start; + group = 8; + } + } + break; + case 8: // [DdFf]? + if (isADoubleOrFloatSuffix(c)) { + results.beginGroup[8] = start; + results.endGroup[8] = ++start; + } + break bigWhile; + } // switch group + } // while bigWhile + + results.endGroup[1] = start; + results.endGroup[4] = Math.max(results.endGroup[4], Math.max(Math.max(results.endGroup[5], results.endGroup[6]), Math.max(results.endGroup[7], results.endGroup[8]))); + // P (in group 9) requires at least one entry in group 7 + if ((results.beginGroup[9] != -1) && (results.beginGroup[7] == -1)) + results.numberType = NumberType.NOT_A_NUMBER; + // group 3 must have at least 1 digit + if (results.endGroup[3] - results.beginGroup[3] == 0) + results.numberType = NumberType.NOT_A_NUMBER; + return (results.numberType == NumberType.HEXADECIMAL); + } + + // process extracted results + + private static void processNumber(String in, ParseNumberResults results) { + switch (results.numberType) { + case INTEGER: + case DOUBLE: + results.number = processDecimal(in, results); + break; + case HEXADECIMAL: + results.number = processHex(in, results); + break; + case BINARY: + results.number = processBinary(in, results); + break; + case OCTAL: + results.number = processOctal(in, results); + break; + } + } + + private static Number processHex(final String s, ParseNumberResults results) { + final String sign = results.getGroup(s, 2); // + or - or "" + final String integer = results.getGroup(s, 3); // hex digits before decimal point + final String suffix = results.getGroup(s, 4); // L or floating point expression + final boolean forceLong = "L".equalsIgnoreCase(String.valueOf(suffix)); + + final Number result; + if (forceLong || suffix.isEmpty()) { + // Integer notation. + final String number = sign + integer; + result = parseIntegerToNumber(number, forceLong, 16); + } else { + // Floating point notation. + final String token = results.getGroup(s, 1); // entire matched literal + final String expSuffix = results.getGroup(s, 8); // f or d or nothing + final boolean forceFloat = "F".equalsIgnoreCase(expSuffix); + final boolean forceDouble = "D".equalsIgnoreCase(expSuffix); + // NB: The BigDecimal code does not understand floating point + // hex strings, so the following invocation will never produce + // a larger-than-double-precision floating point BigDecimal. + // It's a convenient way to support float and double precision, + // but for BigDecimal support, we would need to process the + // matched groups above, converting hex to base 10 first. + result = parseDecimalToNumber(token, forceFloat, forceDouble); + } + return result;//verifyResult(result, m, pos); + } + + private static Number processBinary(String s, ParseNumberResults results) { + final String sign = results.getGroup(s, 2); + final String number = sign + results.getGroup(s, 3); + + final boolean forceLong = !results.getGroup(s, 4).isEmpty(); + final Number result = parseIntegerToNumber(number, forceLong, 2); + return result; + } + + // NB: uses decimal regular expression group numbers + private static Number processOctal(String s, ParseNumberResults results) { + final String number = results.getGroup(s, 2); + final String expSuffix = results.getGroup(s, 5); // Ll nothing + final boolean forceLong = "L".equalsIgnoreCase(expSuffix); + final Number result = parseIntegerToNumber(number, forceLong, 8); + return result; + } + + private static Number processDecimal(String s, ParseNumberResults results) { + final String numberStr = results.getGroup(s, 2); + final String force = results.getGroup(s, 5); + final boolean forceLong = "l".equalsIgnoreCase(force); + final boolean forceFloat = "f".equalsIgnoreCase(force); + final boolean forceDouble = "d".equalsIgnoreCase(force); + Number result = null; + if (results.numberType == NumberType.INTEGER) { + // No decimal point and no exponent part. So this *might* be an integer! + result = parseIntegerToNumber(numberStr, forceLong, 10); + } + if (result == null && !forceLong) { + result = parseDecimalToNumber(numberStr, forceFloat, forceDouble); + } + return result; + } + + + // parse resulting Strings and flags to Number + + /** + * Parses an Integer literal (e.g., {@code 1234}). + * Attempts to minimize swallowed Exceptions by parsing as a long first. + * + * @param number The string from which the numeric literal should be parsed. + * @param forceLong boolean flag to require a long Number be returned + * @param base radix for conversion + * @return The parsed numeric value—an {@link Integer} if sufficiently + * small, or a {@link Long} if needed or if the {@code L} suffix is + * given; or a {@link BigInteger} if the value is too large even for + * {@code long} or null. + */ + + private static Number parseIntegerToNumber(final String number, + final boolean forceLong, final int base) { + + // parse to long, convert to int if in range and not forced to a long + long result ; + try { + result = Long.parseLong(number, base); + if (forceLong || result < Integer.MIN_VALUE || result > Integer.MAX_VALUE) { + return result; + } + return (int) result; // NB: range check above to avoid silent loss + } catch (NumberFormatException e) { + // will not fit in long or bad text + ignoredExceptions++; // not thread safe, ok for rough metric + if (!forceLong) { + try { + return new BigInteger(number, base); + } catch (final NumberFormatException exc) { + ignoredExceptions++; + } + } + } + return null; + } + + + /** + * Parses a Decimal literal (e.g., {@code 1234.1}). + * + * @param number The string from which the numeric literal should be parsed. + * @param forceFloat boolean flag to require a float Number be returned + * @param forceDouble boolean flag to require a double Number be returned + * @return The parsed numeric value—an {@link Double}, {@link Float} or {@link BigDecimal} or null + */ + private static Number parseDecimalToNumber(final String number, + final boolean forceFloat, final boolean forceDouble) { + if (forceFloat) { + // Try to fit it into a float. + try { + return Float.parseFloat(number); + } catch (final NumberFormatException exc) { + // NB: No action needed. + ignoredExceptions++; + } + } else { + // Try to fit it into a double. + try { + return Double.parseDouble(number); + } catch (final NumberFormatException exc) { + // NB: No action needed. + ignoredExceptions++; + } + } + + if (!forceDouble && !forceFloat) { + // Try to treat it as a BigDecimal. + try { + return new BigDecimal(number); + } catch (final NumberFormatException exc) { + // NB: No action needed. + ignoredExceptions++; + } + } + + return null; + } + + // Helpers + + private static boolean isHexDigit(char c) { + return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')); + } + + private static boolean isOctalDigit(char c) { + return (c >= '0' && c <= '7'); + } + + private static boolean isL(char c) { + return (c == 'L' || c == 'l'); + } + + private static boolean isP(char c) { + return (c == 'P' || c == 'p'); + } + + private static boolean isE(char c) { + return (c == 'E' || c == 'e'); + } + + private static boolean isDecimalDigit(char c) { + return (c >= '0' && c <= '9'); + } + + private static boolean isBinaryDigit(char c) { + return (c == '0' || c == '1'); + } + + private static boolean isASign(char c) { + return (c == '+' || c == '-'); + } + + private static boolean isADoubleOrFloatSuffix(char c) { + return (c == 'D' || c == 'd' || c == 'F' || c == 'f'); + } + + public enum NumberType { + NOT_A_NUMBER, + INTEGER, // or LONG or BIGINT + DOUBLE, // or FLOAT + HEXADECIMAL, + OCTAL, // INTEGER or LONG + BINARY, // INTEGER, DOUBLE, FLOAT + } + + +} From 1236d22ae4134c38713d3bf1dbad0bff7fb2b7da Mon Sep 17 00:00:00 2001 From: Jared DavisBased on regular expression (but not exact) (([-+]?[0-9]+(\.[0-9]*)?([Ee][-+]?[0-9]+)?)([DdFfLl])?).* * group 1 = entire matching string. NB: re has .* postfix but this method does not include .* in this group. * group 2 is from start of string including optional groups 3 and 4 * group 3 is \. then [0-9]* * group 4 is [Ee][-+]?[0-9]+ * group 5 is [DdFfLl]? - * * group 6 is not in re. [-+]? at start of string * group 7 is not in re. Digits after sign, before group 3. re has [0-9]+ but changed to [0-9]* to match java numeric parsing * group 8 is not in re. Leads to group 3 or 4 or 5 or end * - * Octal encoding detection + *
Octal encoding detection * If we have (no group 3) and (no group 4) then do a scan for an octal value in group 2 * Octal IFF * group 7 starts with 0; does not contain 8 or 9; is longer than 1 @@ -155,12 +151,12 @@ private static boolean extractDecimalNumber(String s, int start, int end, ParseN boolean haveFour = false; int group = 7; - results.beginGroup[group] = start; + results.getBeginGroup()[group] = start; bigWhile: while (start <= end) { if (start == end) { - results.endGroup[group] = start; + results.getEndGroup()[group] = start; break; } c = s.charAt(start); @@ -171,14 +167,14 @@ private static boolean extractDecimalNumber(String s, int start, int end, ParseN } else { // removed this to match java - it is now a little slower /* - // this does not follow java - eg parsing +.2 is valid in java. + // this does not follow java - e.g. parsing +.2 is valid in java. if (results.beginGroup[group] == start) { // need at least 1 digit results.numberType = NumberType.NOT_A_NUMBER; break bigWhile; } */ - results.endGroup[group] = start; + results.getEndGroup()[group] = start; group = 8; } break; @@ -191,7 +187,7 @@ private static boolean extractDecimalNumber(String s, int start, int end, ParseN } havedot = true; group = 3; - results.beginGroup[group] = start++; + results.getBeginGroup()[group] = start++; break; } if (isE(c)) { @@ -201,13 +197,13 @@ private static boolean extractDecimalNumber(String s, int start, int end, ParseN } haveFour = true; group = 4; - results.beginGroup[group] = start++; + results.getBeginGroup()[group] = start++; break; } if (isADoubleOrFloatSuffix(c) || isL(c)) { group = 5; - results.beginGroup[group] = start++; - results.endGroup[group] = start; + results.getBeginGroup()[group] = start++; + results.getEndGroup()[group] = start; results.numberType = isL(c) ? NumberType.INTEGER: NumberType.DOUBLE; // break bigWhile fall through below } @@ -216,7 +212,7 @@ private static boolean extractDecimalNumber(String s, int start, int end, ParseN if (isDecimalDigit(c)) { start++; } else { - results.endGroup[group] = start; + results.getEndGroup()[group] = start; group = 8; } break; @@ -231,28 +227,28 @@ private static boolean extractDecimalNumber(String s, int start, int end, ParseN } else if (isDecimalDigit(c)) { start++; } else { - results.endGroup[group] = start; + results.getEndGroup()[group] = start; group = 8; // back to 8 for group 5 } break; } // switch group } // bigWhile - results.endGroup[1] = start; + results.getEndGroup()[1] = start; - int sevenLength = results.endGroup[7] - results.beginGroup[7]; - int threeLength = results.endGroup[3] - results.beginGroup[3]; + int sevenLength = results.getEndGroup()[7] - results.getBeginGroup()[7]; + int threeLength = results.getEndGroup()[3] - results.getBeginGroup()[3]; if ((sevenLength == 0) && (threeLength < 2)) results.numberType = NumberType.NOT_A_NUMBER; // need at least 1 digit in number after decimal - if (results.beginGroup[4] != -1) { // we got an E + if (results.getBeginGroup()[4] != -1) { // we got an E int minLength = (haveSign) ? 3 : 2; - if (results.endGroup[4] - results.beginGroup[4] < minLength) + if (results.getEndGroup()[4] - results.getBeginGroup()[4] < minLength) results.numberType = NumberType.NOT_A_NUMBER; // need at least 1 digit in exponent } if (results.numberType == null) { - if ((results.beginGroup[4] != -1) || (threeLength > 0)) { + if ((results.getBeginGroup()[4] != -1) || (threeLength > 0)) { results.numberType = NumberType.DOUBLE; } else { results.numberType = NumberType.INTEGER; @@ -261,19 +257,19 @@ private static boolean extractDecimalNumber(String s, int start, int end, ParseN if (results.numberType != NumberType.NOT_A_NUMBER) { // group 2 length = group 1 length - group 5 length - results.beginGroup[2] = results.beginGroup[1]; - results.endGroup[2] = results.endGroup[1]; - if (results.beginGroup[5] != -1) { - results.endGroup[2]--; + results.getBeginGroup()[2] = results.getBeginGroup()[1]; + results.getEndGroup()[2] = results.getEndGroup()[1]; + if (results.getBeginGroup()[5] != -1) { + results.getEndGroup()[2]--; } } // octal encoding ? if (results.numberType == NumberType.INTEGER) { - if (s.charAt(results.beginGroup[7]) == '0') { + if (s.charAt(results.getBeginGroup()[7]) == '0') { boolean octal = (sevenLength > 1); if (octal) { - for (int i = results.beginGroup[7] + 1; i < results.endGroup[7]; i++) { + for (int i = results.getBeginGroup()[7] + 1; i < results.getEndGroup()[7]; i++) { if (!isOctalDigit(s.charAt(i))) { octal = false; break; @@ -281,8 +277,8 @@ private static boolean extractDecimalNumber(String s, int start, int end, ParseN } } if (octal) { - if (results.beginGroup[5] != -1) { - if (!isL(s.charAt(results.beginGroup[5]))) { + if (results.getBeginGroup()[5] != -1) { + if (!isL(s.charAt(results.getBeginGroup()[5]))) { octal = false; } } @@ -306,8 +302,8 @@ private static boolean extractDecimalNumber(String s, int start, int end, ParseN * @param end The index of the last char to parse. * @param results contains the parsing details calculated in this method. * @return true if a potential binary literal is found, false otherwise - - * based on Regular Expression (([-+]?)0[Bb]([01]+)([Ll]?)).* + * + *
based on Regular Expression (([-+]?)0[Bb]([01]+)([Ll]?)).* * group 1 = entire matching string. NB: re has .* postfix but this method does not include .* in this group. * group 2 [-+]? This is processed by the caller and placed into ParseNumberResults ndxSign. * group none 0[Bb] must exist. This is processed by the caller. @@ -320,12 +316,12 @@ private static boolean extractBinaryNumber(String s, int start, int end, ParseNu results.setSignGroup(2); int group = 3; - results.beginGroup[group] = start; + results.getBeginGroup()[group] = start; bigWhile: while (start <= end) { if (start == end) { - results.endGroup[group] = start; + results.getEndGroup()[group] = start; break; } c = s.charAt(start); @@ -334,39 +330,39 @@ private static boolean extractBinaryNumber(String s, int start, int end, ParseNu if (isBinaryDigit(c)) { start++; } else { - results.endGroup[group] = start; + results.getEndGroup()[group] = start; group = 4; } break; case 4: // [Ll]? if (isL(c)) { - results.beginGroup[group] = start; - results.endGroup[group] = ++start; + results.getBeginGroup()[group] = start; + results.getEndGroup()[group] = ++start; } // ok - we are done break bigWhile; } // switch group } // while bigWhile - results.endGroup[1] = start; + results.getEndGroup()[1] = start; // need at least 1 digit in group 3 - if (results.endGroup[3] - results.beginGroup[3] == 0) + if (results.getEndGroup()[3] - results.getBeginGroup()[3] == 0) results.numberType = NumberType.NOT_A_NUMBER; return (results.numberType == NumberType.BINARY); } /** - * Parses a hexidecimal literal. Both hexadecimal integer (e.g., - * {@code 0xfedcba9876543210}) and hexidecimal floating point (e.g., + * Parses a hexadecimal literal. Both hexadecimal integer (e.g., + * {@code 0xfedcba9876543210}) and hexadecimal floating point (e.g., * {@code 0xfedcba.98765432p10f}) are supported. * * @param s The string from which the numeric literal should be parsed. * @param start The index of the string to start the parsing after the sign and 0[Xx] prefix. * @param end The index of the last char to parse. * @param results contains the parsing details calculated in this method. - * @return true if a potential hexidecimal literal is found, false otherwise + * @return true if a potential hexadecimal literal is found, false otherwise * * - * based on Regular Expression (([-+]?)0[Xx]([0-9a-fA-F]+)([Ll]|(\\.[0-9a-fA-F]*)?[Pp]([-+]?)([0-9]+)([DdFf]|)|)).* + *
based on Regular Expression (([-+]?)0[Xx]([0-9a-fA-F]+)([Ll]|(\\.[0-9a-fA-F]*)?[Pp]([-+]?)([0-9]+)([DdFf]|)|)).*
* group 1 = entire matching string. NB: re has .* postfix but this method does not include .* in this group.
* group 2 [-+]? This is processed by the caller and placed into ParseNumberResults ndxSign.
* group none 0[Xx] This is processed by the caller.
@@ -385,11 +381,11 @@ private static boolean extractHexNumber(String s, int start, int end, ParseNumbe
results.setSignGroup(2);
int group = 3;
- results.beginGroup[3] = start;
+ results.getBeginGroup()[3] = start;
bigWhile:
while (start <= end) {
if (start == end) {
- results.endGroup[group] = start;
+ results.getEndGroup()[group] = start;
break;
}
c = s.charAt(start); // start may not change when group changes
@@ -398,19 +394,19 @@ private static boolean extractHexNumber(String s, int start, int end, ParseNumbe
if (isHexDigit(c)) {
start++;
} else {
- results.endGroup[group] = start;
+ results.getEndGroup()[group] = start;
group = 4;
- results.beginGroup[group] = start;
+ results.getBeginGroup()[group] = start;
}
break;
case 4: // [Ll]? group end is adjusted after switch to include groups 5-8.
if (isL(c)) {
- results.endGroup[group] = ++start;
+ results.getEndGroup()[group] = ++start;
break bigWhile; // end of re. No need to continue parsing chars
}
if (c == '.') {
group = 5;
- results.beginGroup[group] = start++;
+ results.getBeginGroup()[group] = start++;
break;
}
if (isP(c)) {
@@ -419,59 +415,59 @@ private static boolean extractHexNumber(String s, int start, int end, ParseNumbe
break;
}
// we are done, there is no group 4
- results.beginGroup[group] = -1;
+ results.getBeginGroup()[group] = -1;
break bigWhile;
case 5: // \.[0-9a-fA-F]* 0..n
if (isHexDigit(c)) {
start++;
} else {
- results.endGroup[group] = start;
+ results.getEndGroup()[group] = start;
if (isP(c)) {
group = 9;
- results.beginGroup[group] = start;
- results.endGroup[group] = ++start;
+ results.getBeginGroup()[group] = start;
+ results.getEndGroup()[group] = ++start;
}
}
break;
case 9: // start -1 == p or P
if (isASign(c)) {
group = 6;
- results.beginGroup[group] = start;
- results.endGroup[group] = ++start;
+ results.getBeginGroup()[group] = start;
+ results.getEndGroup()[group] = ++start;
}
group = 7;
break;
case 7: // [0-9]+ enforce at least 1 char in this group
if (isDecimalDigit(c)) {
- if (results.beginGroup[group] == -1) results.beginGroup[group] = start;
+ if (results.getBeginGroup()[group] == -1) results.getBeginGroup()[group] = start;
start++;
} else {
- if (results.beginGroup[group] == -1) {
+ if (results.getBeginGroup()[group] == -1) {
// need at least 1 digit to be valid
results.numberType = NumberType.NOT_A_NUMBER;
break bigWhile;
} else {
- results.endGroup[group] = start;
+ results.getEndGroup()[group] = start;
group = 8;
}
}
break;
case 8: // [DdFf]?
if (isADoubleOrFloatSuffix(c)) {
- results.beginGroup[8] = start;
- results.endGroup[8] = ++start;
+ results.getBeginGroup()[8] = start;
+ results.getEndGroup()[8] = ++start;
}
break bigWhile;
} // switch group
} // while bigWhile
- results.endGroup[1] = start;
- results.endGroup[4] = Math.max(results.endGroup[4], Math.max(Math.max(results.endGroup[5], results.endGroup[6]), Math.max(results.endGroup[7], results.endGroup[8])));
+ results.getEndGroup()[1] = start;
+ results.getEndGroup()[4] = Math.max(results.getEndGroup()[4], Math.max(Math.max(results.getEndGroup()[5], results.getEndGroup()[6]), Math.max(results.getEndGroup()[7], results.getEndGroup()[8])));
// P (in group 9) requires at least one entry in group 7
- if ((results.beginGroup[9] != -1) && (results.beginGroup[7] == -1))
+ if ((results.getBeginGroup()[9] != -1) && (results.getBeginGroup()[7] == -1))
results.numberType = NumberType.NOT_A_NUMBER;
// group 3 must have at least 1 digit
- if (results.endGroup[3] - results.beginGroup[3] == 0)
+ if (results.getEndGroup()[3] - results.getBeginGroup()[3] == 0)
results.numberType = NumberType.NOT_A_NUMBER;
return (results.numberType == NumberType.HEXADECIMAL);
}
@@ -482,16 +478,16 @@ private static void processNumber(String in, ParseNumberResults results) {
switch (results.numberType) {
case INTEGER:
case DOUBLE:
- results.number = processDecimal(in, results);
+ results.setNumber(processDecimal(in, results));
break;
case HEXADECIMAL:
- results.number = processHex(in, results);
+ results.setNumber(processHex(in, results));
break;
case BINARY:
- results.number = processBinary(in, results);
+ results.setNumber(processBinary(in, results));
break;
case OCTAL:
- results.number = processOctal(in, results);
+ results.setNumber(processOctal(in, results));
break;
}
}
@@ -529,8 +525,7 @@ private static Number processBinary(String s, ParseNumberResults results) {
final String number = sign + results.getGroup(s, 3);
final boolean forceLong = !results.getGroup(s, 4).isEmpty();
- final Number result = parseIntegerToNumber(number, forceLong, 2);
- return result;
+ return parseIntegerToNumber(number, forceLong, 2);
}
// NB: uses decimal regular expression group numbers
@@ -538,8 +533,7 @@ private static Number processOctal(String s, ParseNumberResults results) {
final String number = results.getGroup(s, 2);
final String expSuffix = results.getGroup(s, 5); // Ll nothing
final boolean forceLong = "L".equalsIgnoreCase(expSuffix);
- final Number result = parseIntegerToNumber(number, forceLong, 8);
- return result;
+ return parseIntegerToNumber(number, forceLong, 8);
}
private static Number processDecimal(String s, ParseNumberResults results) {
diff --git a/src/main/java/org/scijava/parsington/ParseNumberResults.java b/src/main/java/org/scijava/parsington/ParseNumberResults.java
index 8e0f17f..783216f 100644
--- a/src/main/java/org/scijava/parsington/ParseNumberResults.java
+++ b/src/main/java/org/scijava/parsington/ParseNumberResults.java
@@ -1,15 +1,20 @@
package org.scijava.parsington;
public class ParseNumberResults {
- int ndxSign = -1;
+ private int signNdx = -1;
ParseNumber.NumberType numberType;
- int[] beginGroup = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
- int[] endGroup = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
- Number number =null;
+ private final int[] beginGroup = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
+ private final int[] endGroup = {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1};
+ private Number number = null;
+
+
+ void setSignIndex(int signNdx) {
+ this.signNdx = signNdx;
+ }
void setSignGroup(int signGroup) {
- if (ndxSign != -1) {
- beginGroup[signGroup] = ndxSign;
+ if (signNdx != -1) {
+ beginGroup[signGroup] = signNdx;
endGroup[signGroup] = beginGroup[signGroup] + 1;
}
}
@@ -30,4 +35,18 @@ int getLength() {
return 0;
}
+ Number getNumber() {
+ return number;
+ }
+ void setNumber(Number number) {
+ this.number = number;
+ }
+
+ public int[] getBeginGroup() {
+ return beginGroup;
+ }
+
+ public int[] getEndGroup() {
+ return endGroup;
+ }
}
From 6f3faad4a7799b07fc3dc6dd579970f43f2f413d Mon Sep 17 00:00:00 2001
From: Jared Davis