blob: 84db6cdbbbe68c994a0ee5e2acf8ea561558c18f [file] [log] [blame]
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number.parse;
import static com.ibm.icu.impl.number.parse.ParsingUtils.safeContains;
import com.ibm.icu.impl.StaticUnicodeSets;
import com.ibm.icu.impl.StringSegment;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.text.UnicodeSet;
/**
* @author sffc
*
*/
public class ScientificMatcher implements NumberParseMatcher {
private final String exponentSeparatorString;
private final DecimalMatcher exponentMatcher;
private final IgnorablesMatcher ignorablesMatcher;
private final String customMinusSign;
private final String customPlusSign;
public static ScientificMatcher getInstance(DecimalFormatSymbols symbols, Grouper grouper) {
// TODO: Static-initialize most common instances?
return new ScientificMatcher(symbols, grouper);
}
private ScientificMatcher(DecimalFormatSymbols symbols, Grouper grouper) {
exponentSeparatorString = symbols.getExponentSeparator();
exponentMatcher = DecimalMatcher.getInstance(symbols,
grouper,
ParsingUtils.PARSE_FLAG_INTEGER_ONLY | ParsingUtils.PARSE_FLAG_GROUPING_DISABLED);
ignorablesMatcher = IgnorablesMatcher.getInstance(ParsingUtils.PARSE_FLAG_STRICT_IGNORABLES);
String minusSign = symbols.getMinusSignString();
customMinusSign = safeContains(minusSignSet(), minusSign) ? null : minusSign;
String plusSign = symbols.getPlusSignString();
customPlusSign = safeContains(plusSignSet(), plusSign) ? null : plusSign;
}
private static UnicodeSet minusSignSet() {
return StaticUnicodeSets.get(StaticUnicodeSets.Key.MINUS_SIGN);
}
private static UnicodeSet plusSignSet() {
return StaticUnicodeSets.get(StaticUnicodeSets.Key.PLUS_SIGN);
}
@Override
public boolean match(StringSegment segment, ParsedNumber result) {
// Only accept scientific notation after the mantissa.
if (!result.seenNumber()) {
return false;
}
// Only accept one exponent per string.
if (0 != (result.flags & ParsedNumber.FLAG_HAS_EXPONENT)) {
return false;
}
// First match the scientific separator, and then match another number after it.
// NOTE: This is guarded by the smoke test; no need to check exponentSeparatorString length again.
int initialOffset = segment.getOffset();
int overlap = segment.getCommonPrefixLength(exponentSeparatorString);
if (overlap == exponentSeparatorString.length()) {
// Full exponent separator match.
// First attempt to get a code point, returning true if we can't get one.
if (segment.length() == overlap) {
return true;
}
segment.adjustOffset(overlap);
// Allow ignorables before the sign.
// Note: call site is guarded by the segment.length() check above.
ignorablesMatcher.match(segment, null);
if (segment.length() == 0) {
segment.setOffset(initialOffset);
return true;
}
// Allow a sign, and then try to match digits.
int exponentSign = 1;
if (segment.startsWith(minusSignSet())) {
exponentSign = -1;
segment.adjustOffsetByCodePoint();
} else if (segment.startsWith(plusSignSet())) {
segment.adjustOffsetByCodePoint();
} else if (segment.startsWith(customMinusSign)) {
overlap = segment.getCommonPrefixLength(customMinusSign);
if (overlap != customMinusSign.length()) {
// Partial custom sign match
segment.setOffset(initialOffset);
return true;
}
exponentSign = -1;
segment.adjustOffset(overlap);
} else if (segment.startsWith(customPlusSign)) {
overlap = segment.getCommonPrefixLength(customPlusSign);
if (overlap != customPlusSign.length()) {
// Partial custom sign match
segment.setOffset(initialOffset);
return true;
}
segment.adjustOffset(overlap);
}
// Return true if the segment is empty.
if (segment.length() == 0) {
segment.setOffset(initialOffset);
return true;
}
// Allow ignorables after the sign.
// Note: call site is guarded by the segment.length() check above.
ignorablesMatcher.match(segment, null);
if (segment.length() == 0) {
segment.setOffset(initialOffset);
return true;
}
// We are supposed to accept E0 after NaN, so we need to make sure result.quantity is available.
boolean wasNull = (result.quantity == null);
if (wasNull) {
result.quantity = new DecimalQuantity_DualStorageBCD();
}
int digitsOffset = segment.getOffset();
boolean digitsReturnValue = exponentMatcher.match(segment, result, exponentSign);
if (wasNull) {
result.quantity = null;
}
if (segment.getOffset() != digitsOffset) {
// At least one exponent digit was matched.
result.flags |= ParsedNumber.FLAG_HAS_EXPONENT;
} else {
// No exponent digits were matched
segment.setOffset(initialOffset);
}
return digitsReturnValue;
} else if (overlap == segment.length()) {
// Partial exponent separator match
return true;
}
// No match
return false;
}
@Override
public boolean smokeTest(StringSegment segment) {
return segment.startsWith(exponentSeparatorString);
}
@Override
public void postProcess(ParsedNumber result) {
// No-op
}
@Override
public String toString() {
return "<ScientificMatcher " + exponentSeparatorString + ">";
}
}