ICU-13513 Continuing down the data-driven test file. Mainly currency improvements.

X-SVN-Rev: 40735
This commit is contained in:
Shane Carr 2017-12-14 10:24:35 +00:00
parent 68340c8464
commit 8a232528c1
12 changed files with 241 additions and 120 deletions

View file

@ -884,6 +884,12 @@ public abstract class DecimalQuantity_AbstractBCD implements DecimalQuantity {
*/
protected abstract void shiftLeft(int numDigits);
/**
* Removes digits from the end of the BCD list. This may result in an invalid BCD representation; it is
* the caller's responsibility to follow-up with a call to {@link #compact}.
*
* @param numDigits The number of zeros to add.
*/
protected abstract void shiftRight(int numDigits);
/**

View file

@ -19,6 +19,7 @@ import com.ibm.icu.number.NumberFormatter.SignDisplay;
import com.ibm.icu.number.NumberFormatter.UnitWidth;
import com.ibm.icu.text.DecimalFormatSymbols;
import com.ibm.icu.util.Currency;
import com.ibm.icu.util.CurrencyAmount;
import com.ibm.icu.util.ULocale;
/**
@ -58,7 +59,7 @@ public class NumberParserImpl {
parser.addMatcher(new MinusSignMatcher());
parser.addMatcher(new ScientificMatcher(symbols));
parser.addMatcher(new CurrencyMatcher(locale));
parser.addMatcher(new RequirementsMatcher());
parser.addMatcher(new RequireNumberMatcher());
parser.freeze();
return parser;
@ -67,9 +68,8 @@ public class NumberParserImpl {
public static Number parseStatic(String input,
ParsePosition ppos,
DecimalFormatProperties properties,
DecimalFormatSymbols symbols,
boolean parseCurrency) {
NumberParserImpl parser = createParserFromProperties(properties, symbols, parseCurrency);
DecimalFormatSymbols symbols) {
NumberParserImpl parser = createParserFromProperties(properties, symbols, false);
ParsedNumber result = new ParsedNumber();
parser.parse(input, true, result);
ppos.setIndex(result.charsConsumed);
@ -80,6 +80,29 @@ public class NumberParserImpl {
}
}
public static CurrencyAmount parseStaticCurrency(String input,
ParsePosition ppos,
DecimalFormatProperties properties,
DecimalFormatSymbols symbols) {
NumberParserImpl parser = createParserFromProperties(properties, symbols, true);
ParsedNumber result = new ParsedNumber();
parser.parse(input, true, result);
ppos.setIndex(result.charsConsumed);
if (result.charsConsumed > 0) {
// TODO: Clean this up
Currency currency;
if (result.currencyCode != null) {
currency = Currency.getInstance(result.currencyCode);
} else {
assert 0 != (result.flags & ParsedNumber.FLAG_HAS_DEFAULT_CURRENCY);
currency = CustomSymbolCurrency.resolve(properties.getCurrency(), symbols.getULocale(), symbols);
}
return new CurrencyAmount(result.getDouble(), currency);
} else {
return null;
}
}
public static NumberParserImpl createParserFromProperties(
DecimalFormatProperties properties,
DecimalFormatSymbols symbols,
@ -89,28 +112,41 @@ public class NumberParserImpl {
Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
boolean isStrict = properties.getParseMode() == ParseMode.STRICT;
////////////////////////
/// CURRENCY MATCHER ///
////////////////////////
if (parseCurrency) {
parser.addMatcher(new CurrencyMatcher(locale));
}
//////////////////////
/// AFFIX MATCHERS ///
//////////////////////
// Set up a pattern modifier with mostly defaults to generate AffixMatchers.
MutablePatternModifier mod = new MutablePatternModifier(false);
AffixPatternProvider provider = new PropertiesAffixPatternProvider(properties);
mod.setPatternInfo(provider);
AffixPatternProvider patternInfo = new PropertiesAffixPatternProvider(properties);
mod.setPatternInfo(patternInfo);
mod.setPatternAttributes(SignDisplay.AUTO, false);
mod.setSymbols(symbols, currency, UnitWidth.SHORT, null);
// Figure out which flags correspond to this pattern modifier. Note: negatives are taken care of in the
// generateFromPatternModifier function.
int flags = 0;
if (provider.containsSymbolType(AffixUtils.TYPE_PERCENT)) {
if (patternInfo.containsSymbolType(AffixUtils.TYPE_PERCENT)) {
flags |= ParsedNumber.FLAG_PERCENT;
}
if (provider.containsSymbolType(AffixUtils.TYPE_PERMILLE)) {
if (patternInfo.containsSymbolType(AffixUtils.TYPE_PERMILLE)) {
flags |= ParsedNumber.FLAG_PERMILLE;
}
if (patternInfo.hasCurrencySign()) {
flags |= ParsedNumber.FLAG_HAS_DEFAULT_CURRENCY;
}
AffixMatcher.generateFromPatternModifier(mod, flags, !isStrict, parser);
parseCurrency = parseCurrency || patternInfo.hasCurrencySign();
AffixMatcher.generateFromPatternModifier(mod, flags, !isStrict && !parseCurrency, parser);
///////////////////////////////
/// OTHER STANDARD MATCHERS ///
@ -128,34 +164,44 @@ public class NumberParserImpl {
decimalMatcher.grouping2 = properties.getSecondaryGroupingSize();
decimalMatcher.integerOnly = properties.getParseIntegerOnly();
parser.addMatcher(decimalMatcher);
parser.addMatcher(new ScientificMatcher(symbols));
parser.addMatcher(new RequirementsMatcher());
if (!properties.getParseNoExponent()) {
parser.addMatcher(new ScientificMatcher(symbols));
}
//////////////////
/// VALIDATORS ///
//////////////////
parser.addMatcher(new RequireNumberMatcher());
if (isStrict) {
parser.addMatcher(new RequireAffixMatcher());
}
if (isStrict && properties.getMinimumExponentDigits() > 0) {
parser.addMatcher(new RequireExponentMatcher());
}
////////////////////////
/// CURRENCY MATCHER ///
////////////////////////
if (parseCurrency) {
parser.addMatcher(new CurrencyMatcher(locale));
parser.addMatcher(new RequireCurrencyMatcher());
}
////////////////////////
/// OTHER ATTRIBUTES ///
////////////////////////
parser.setIgnoreCase(!properties.getParseCaseSensitive());
parser.freeze();
return parser;
}
private final List<NumberParseMatcher> matchers;
private Comparator<ParsedNumber> comparator;
private boolean ignoreCase;
private boolean frozen;
public NumberParserImpl() {
matchers = new ArrayList<NumberParseMatcher>();
comparator = ParsedNumber.COMPARATOR; // default value
ignoreCase = true;
frozen = false;
}
@ -167,13 +213,17 @@ public class NumberParserImpl {
this.comparator = comparator;
}
public void setIgnoreCase(boolean ignoreCase) {
this.ignoreCase = ignoreCase;
}
public void freeze() {
frozen = true;
}
public void parse(String input, boolean greedy, ParsedNumber result) {
assert frozen;
StringSegment segment = new StringSegment(input);
StringSegment segment = new StringSegment(input, ignoreCase);
if (greedy) {
parseGreedyRecursive(segment, result);
} else {

View file

@ -45,6 +45,7 @@ public class ParsedNumber {
public static final int FLAG_PERCENT = 0x0002;
public static final int FLAG_PERMILLE = 0x0004;
public static final int FLAG_HAS_EXPONENT = 0x0008;
public static final int FLAG_HAS_DEFAULT_CURRENCY = 0x0010;
/** A Comparator that favors ParsedNumbers with the most chars consumed. */
public static final Comparator<ParsedNumber> COMPARATOR = new Comparator<ParsedNumber>() {

View file

@ -21,4 +21,9 @@ public class RequireAffixMatcher implements NumberParseMatcher {
}
}
@Override
public String toString() {
return "<RequireAffix>";
}
}

View file

@ -0,0 +1,28 @@
// © 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;
/**
* @author sffc
*
*/
public class RequireCurrencyMatcher implements NumberParseMatcher {
@Override
public boolean match(StringSegment segment, ParsedNumber result) {
return false;
}
@Override
public void postProcess(ParsedNumber result) {
if (result.currencyCode == null && 0 == (result.flags & ParsedNumber.FLAG_HAS_DEFAULT_CURRENCY)) {
result.clear();
}
}
@Override
public String toString() {
return "<RequireCurrency>";
}
}

View file

@ -20,4 +20,9 @@ public class RequireExponentMatcher implements NumberParseMatcher {
}
}
@Override
public String toString() {
return "<RequireExponent>";
}
}

View file

@ -6,7 +6,7 @@ package com.ibm.icu.impl.number.parse;
* @author sffc
*
*/
public class RequirementsMatcher implements NumberParseMatcher {
public class RequireNumberMatcher implements NumberParseMatcher {
@Override
public boolean match(StringSegment segment, ParsedNumber result) {
@ -21,4 +21,9 @@ public class RequirementsMatcher implements NumberParseMatcher {
}
}
@Override
public String toString() {
return "<RequireNumber>";
}
}

View file

@ -2,6 +2,8 @@
// License & terms of use: http://www.unicode.org/copyright.html#License
package com.ibm.icu.impl.number.parse;
import com.ibm.icu.lang.UCharacter;
/**
* A mutable class allowing for a String with a variable offset and length. The charAt, length, and subSequence methods
* all operate relative to the fixed offset into the String.
@ -12,11 +14,13 @@ public class StringSegment implements CharSequence {
private final String str;
private int start;
private int end;
private final boolean ignoreCase;
public StringSegment(String str) {
public StringSegment(String str, boolean ignoreCase) {
this.str = str;
this.start = 0;
this.end = str.length();
this.ignoreCase = ignoreCase;
}
public int getOffset() {
@ -90,9 +94,21 @@ public class StringSegment implements CharSequence {
*/
public int getCommonPrefixLength(CharSequence other) {
int offset = 0;
for (; offset < Math.min(length(), other.length()); offset++) {
if (charAt(offset) != other.charAt(offset)) {
break;
for (; offset < Math.min(length(), other.length());) {
if (ignoreCase) {
// NOTE: Character.codePointAt() returns the leading surrogate if it is the only char left in the
// string. UCharacter.foldCase() will simply return the same integer since it is not a valid code point.
int cp1 = Character.codePointAt(this, offset);
int cp2 = Character.codePointAt(other, offset);
if (cp1 != cp2 && UCharacter.foldCase(cp1, true) != UCharacter.foldCase(cp2, true)) {
break;
}
offset += Character.charCount(cp1);
} else {
// Case folding is not necessary. Use a slightly faster code path comparing chars with chars.
if (charAt(offset) != other.charAt(offset)) {
break;
}
}
}
return offset;

View file

@ -750,9 +750,9 @@ public class Currency extends MeasureUnit {
public static TextTrieMap<CurrencyStringInfo> getParsingTrie(ULocale locale, int type) {
List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = getCurrencyTrieVec(locale);
if (type == Currency.LONG_NAME) {
return currencyTrieVec.get(0);
} else {
return currencyTrieVec.get(1);
} else {
return currencyTrieVec.get(0);
}
}
@ -765,9 +765,9 @@ public class Currency extends MeasureUnit {
ULocale locale, int startingCp, int type) {
List<TextTrieMap<CurrencyStringInfo>> currencyTrieVec = getCurrencyTrieVec(locale);
if (type == Currency.LONG_NAME) {
return currencyTrieVec.get(0).openParseState(startingCp);
} else {
return currencyTrieVec.get(1).openParseState(startingCp);
} else {
return currencyTrieVec.get(0).openParseState(startingCp);
}
}

View file

@ -954,12 +954,12 @@ set negativeSuffix 9N
begin
parse output breaks
// S is the only implementation that passes these cases.
// C consumes the '9' as a digit and assumes number is negative
// C and P consume the '9' as a digit and assumes number is negative
// J and JDK bail
6549K 654 CJK
// C consumes the '9' as a digit and assumes number is negative
6549K 654 CJKP
// C and P consume the '9' as a digit and assumes number is negative
// J and JDK bail
6549N -654 CJK
6549N -654 CJKP
test really strange prefix
set locale en
@ -1031,16 +1031,17 @@ $53.45 fail USD J
USD 53.45 53.45 USD J
53.45USD 53.45 USD CJ
USD53.45 53.45 USD
(7.92) USD -7.92 USD
// Right now, P will not parse the affix unless it contains the exact currency GBP.
(7.92) USD -7.92 USD P
(7.92) GBP -7.92 GBP
(7.926) USD -7.926 USD
(7.926 USD) -7.926 USD CJ
(USD 7.926) -7.926 USD CJ
USD (7.926) -7.926 USD CJ
USD (7.92) -7.92 USD CJ
(7.92)USD -7.92 USD CJ
USD(7.92) -7.92 USD CJ
(8) USD -8 USD
(7.926) USD -7.926 USD P
(7.926 USD) -7.926 USD CJP
(USD 7.926) -7.926 USD CJP
USD (7.926) -7.926 USD CJP
USD (7.92) -7.92 USD CJP
(7.92)USD -7.92 USD CJP
USD(7.92) -7.92 USD CJP
(8) USD -8 USD P
-8 USD -8 USD C
67 USD 67 USD
53.45$ fail USD
@ -1053,16 +1054,16 @@ US Dollars53.45 53.45 USD
US Dollar53.45 53.45 USD
US Dollat53.45 fail USD
53.45US Dollar 53.45 USD CJ
US Dollars (53.45) -53.45 USD CJ
(53.45) US Dollars -53.45 USD
(53.45) Euros -53.45 EUR
US Dollar (53.45) -53.45 USD CJ
(53.45) US Dollar -53.45 USD
US Dollars(53.45) -53.45 USD CJ
(53.45)US Dollars -53.45 USD CJ
US Dollar(53.45) -53.45 USD CJ
US Dollars (53.45) -53.45 USD CJP
(53.45) US Dollars -53.45 USD P
(53.45) Euros -53.45 EUR P
US Dollar (53.45) -53.45 USD CJP
(53.45) US Dollar -53.45 USD P
US Dollars(53.45) -53.45 USD CJP
(53.45)US Dollars -53.45 USD CJP
US Dollar(53.45) -53.45 USD CJP
US Dollat(53.45) fail USD
(53.45)US Dollar -53.45 USD CJ
(53.45)US Dollar -53.45 USD CJP
test parse currency ISO negative
@ -1078,13 +1079,15 @@ $53.45 fail USD J
USD 53.45 53.45 USD J
53.45USD 53.45 USD CJ
USD53.45 53.45 USD
-7.92 USD -7.92 USD
// FIXME: Fix this one
-7.92 USD -7.92 USD P
-7.92 GBP -7.92 GBP
-7.926 USD -7.926 USD
USD -7.926 -7.926 USD CJ
-7.92USD -7.92 USD CJ
USD-7.92 -7.92 USD CJ
-8 USD -8 USD
// FIXME: Fix this one
-7.926 USD -7.926 USD P
USD -7.926 -7.926 USD CJP
-7.92USD -7.92 USD CJP
USD-7.92 -7.92 USD CJP
-8 USD -8 USD P
67 USD 67 USD
53.45$ fail USD
US Dollars 53.45 53.45 USD J
@ -1112,16 +1115,16 @@ $53.45 fail USD J
USD 53.45 53.45 USD J
53.45USD 53.45 USD CJ
USD53.45 53.45 USD
(7.92) USD -7.92 USD
(7.92) GBP -7.92 GBP
(7.926) USD -7.926 USD
(7.926 USD) -7.926 USD CJ
(USD 7.926) -7.926 USD CJ
USD (7.926) -7.926 USD CJ
USD (7.92) -7.92 USD CJ
(7.92)USD -7.92 USD CJ
USD(7.92) -7.92 USD CJ
(8) USD -8 USD
(7.92) USD -7.92 USD P
(7.92) GBP -7.92 GBP P
(7.926) USD -7.926 USD P
(7.926 USD) -7.926 USD CJP
(USD 7.926) -7.926 USD CJP
USD (7.926) -7.926 USD CJP
USD (7.92) -7.92 USD CJP
(7.92)USD -7.92 USD CJP
USD(7.92) -7.92 USD CJP
(8) USD -8 USD P
-8 USD -8 USD C
67 USD 67 USD
// J throws a NullPointerException on the next case
@ -1150,16 +1153,16 @@ $53.45 fail USD J
USD 53.45 53.45 USD J
53.45USD 53.45 USD CJ
USD53.45 53.45 USD
(7.92) USD -7.92 USD
(7.92) GBP -7.92 GBP
(7.926) USD -7.926 USD
(7.926 USD) -7.926 USD CJ
(USD 7.926) -7.926 USD CJ
USD (7.926) -7.926 USD CJ
USD (7.92) -7.92 USD CJ
(7.92)USD -7.92 USD CJ
USD(7.92) -7.92 USD CJ
(8) USD -8 USD
(7.92) USD -7.92 USD P
(7.92) GBP -7.92 GBP P
(7.926) USD -7.926 USD P
(7.926 USD) -7.926 USD CJP
(USD 7.926) -7.926 USD CJP
USD (7.926) -7.926 USD CJP
USD (7.92) -7.92 USD CJP
(7.92)USD -7.92 USD CJP
USD(7.92) -7.92 USD CJP
(8) USD -8 USD P
-8 USD -8 USD C
67 USD 67 USD
53.45$ fail USD
@ -1188,16 +1191,16 @@ USD 53.45 53.45 USD J
53.45USD 53.45 USD CJ
USD53.45 53.45 USD
// S fails these because '(' is an incomplete prefix.
(7.92) USD -7.92 USD CJS
(7.92) GBP -7.92 GBP CJS
(7.926) USD -7.926 USD CJS
(7.926 USD) -7.926 USD CJS
(USD 7.926) -7.926 USD J
USD (7.926) -7.926 USD CJS
USD (7.92) -7.92 USD CJS
(7.92)USD -7.92 USD CJS
USD(7.92) -7.92 USD CJS
(8) USD -8 USD CJS
(7.92) USD -7.92 USD CJSP
(7.92) GBP -7.92 GBP CJSP
(7.926) USD -7.926 USD CJSP
(7.926 USD) -7.926 USD CJSP
(USD 7.926) -7.926 USD JP
USD (7.926) -7.926 USD CJSP
USD (7.92) -7.92 USD CJSP
(7.92)USD -7.92 USD CJSP
USD(7.92) -7.92 USD CJSP
(8) USD -8 USD CJSP
-8 USD -8 USD C
67 USD 67 USD C
53.45$ fail USD

View file

@ -600,8 +600,7 @@ public class NumberFormatDataDrivenTest {
actual = NumberParserImpl.parseStatic(tuple.parse,
ppos,
properties,
DecimalFormatSymbols.getInstance(tuple.locale),
false);
DecimalFormatSymbols.getInstance(tuple.locale));
} catch (IllegalArgumentException e) {
return "parse exception: " + e.getMessage();
}
@ -635,39 +634,39 @@ public class NumberFormatDataDrivenTest {
}
}
// @Override
// public String parseCurrency(DataDrivenNumberFormatTestData tuple) {
// String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
// DecimalFormatProperties properties;
// ParsePosition ppos = new ParsePosition(0);
// CurrencyAmount actual;
// try {
// properties = PatternStringParser.parseToProperties(
// pattern,
// tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS
// : PatternStringParser.IGNORE_ROUNDING_NEVER);
// propertiesFromTuple(tuple, properties);
// actual = NumberParserImpl.parseStatic(tuple.parse,
// ppos,
// properties,
// DecimalFormatSymbols.getInstance(tuple.locale));
// } catch (ParseException e) {
// e.printStackTrace();
// return "parse exception: " + e.getMessage();
// }
// if (ppos.getIndex() == 0 || actual.getCurrency().getCurrencyCode().equals("XXX")) {
// return "Parse failed; got " + actual + ", but expected " + tuple.output;
// }
// BigDecimal expectedNumber = new BigDecimal(tuple.output);
// if (expectedNumber.compareTo(new BigDecimal(actual.getNumber().toString())) != 0) {
// return "Wrong number: Expected: " + expectedNumber + ", got: " + actual;
// }
// String expectedCurrency = tuple.outputCurrency;
// if (!expectedCurrency.equals(actual.getCurrency().toString())) {
// return "Wrong currency: Expected: " + expectedCurrency + ", got: " + actual;
// }
// return null;
// }
@Override
public String parseCurrency(DataDrivenNumberFormatTestData tuple) {
String pattern = (tuple.pattern == null) ? "0" : tuple.pattern;
DecimalFormatProperties properties;
ParsePosition ppos = new ParsePosition(0);
CurrencyAmount actual;
try {
properties = PatternStringParser.parseToProperties(
pattern,
tuple.currency != null ? PatternStringParser.IGNORE_ROUNDING_ALWAYS
: PatternStringParser.IGNORE_ROUNDING_NEVER);
propertiesFromTuple(tuple, properties);
actual = NumberParserImpl.parseStaticCurrency(tuple.parse,
ppos,
properties,
DecimalFormatSymbols.getInstance(tuple.locale));
} catch (IllegalArgumentException e) {
e.printStackTrace();
return "parse exception: " + e.getMessage();
}
if (ppos.getIndex() == 0 || actual.getCurrency().getCurrencyCode().equals("XXX")) {
return "Parse failed; got " + actual + ", but expected " + tuple.output;
}
BigDecimal expectedNumber = new BigDecimal(tuple.output);
if (expectedNumber.compareTo(new BigDecimal(actual.getNumber().toString())) != 0) {
return "Wrong number: Expected: " + expectedNumber + ", got: " + actual;
}
String expectedCurrency = tuple.outputCurrency;
if (!expectedCurrency.equals(actual.getCurrency().toString())) {
return "Wrong currency: Expected: " + expectedCurrency + ", got: " + actual;
}
return null;
}
};
/**

View file

@ -47,6 +47,8 @@ public class NumberParserTest {
{ 3, "-𝟱𝟭𝟰𝟮𝟯-", "0", 11, -51423. },
{ 3, "a51423US dollars", "a0¤¤¤", 16, 51423. },
{ 3, "a 51423 US dollars", "a0¤¤¤", 18, 51423. },
{ 3, "514.23 USD", "0", 10, 514.23 },
{ 3, "514.23 GBP", "0", 10, 514.23 },
{ 3, "a 𝟱𝟭𝟰𝟮𝟯 b", "a0b", 14, 51423. },
{ 3, "-a 𝟱𝟭𝟰𝟮𝟯 b", "a0b", 15, -51423. },
{ 3, "a -𝟱𝟭𝟰𝟮𝟯 b", "a0b", 15, -51423. },
@ -58,6 +60,7 @@ public class NumberParserTest {
{ 2, "a40b", "a0'0b'", 4, 4. }, // slow code path find the suffix "0b"
{ 3, "𝟱.𝟭𝟰𝟮E𝟯", "0", 12, 5142. },
{ 3, "𝟱.𝟭𝟰𝟮E-𝟯", "0", 13, 0.005142 },
{ 3, "𝟱.𝟭𝟰𝟮e-𝟯", "0", 13, 0.005142 },
{ 3, "5,142.50 Canadian dollars", "0", 25, 5142.5 },
{ 3, "0", "0", 1, 0.0 } };