]> granicus.if.org Git - icu/commitdiff
ICU-13513 Adding plumbing for "lead chars" smoke test. ~24% performance improvement...
authorShane Carr <shane@unicode.org>
Sat, 16 Dec 2017 06:50:35 +0000 (06:50 +0000)
committerShane Carr <shane@unicode.org>
Sat, 16 Dec 2017 06:50:35 +0000 (06:50 +0000)
X-SVN-Rev: 40741

23 files changed:
icu4j/main/classes/core/src/com/ibm/icu/impl/TextTrieMap.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/AffixMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/CurrencyMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/DecimalMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/IgnorablesMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/MinusSignMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NanMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParseMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/NumberParserImpl.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PercentMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PermilleMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/PlusSignMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RangeMatcher.java [new file with mode: 0644]
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireAffixMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireCurrencyMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireDecimalSeparatorMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireExponentMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RequireNumberMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ScientificMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/SymbolMatcher.java
icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ValidationMatcher.java [new file with mode: 0644]
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberParserTest.java

index 1ec86ebee155655b9d7a7d8589acd6aefec8736a..71662ddfd2b4b78e21d38e8c8442d5999a68b965 100644 (file)
@@ -15,6 +15,7 @@ import java.util.ListIterator;
 
 import com.ibm.icu.lang.UCharacter;
 import com.ibm.icu.text.UTF16;
+import com.ibm.icu.text.UnicodeSet;
 
 /**
  * TextTrieMap is a trie implementation for supporting
@@ -115,6 +116,10 @@ public class TextTrieMap<V> {
         }
     }
 
+    public void putLeadChars(UnicodeSet output) {
+        _root.putLeadChars(output);
+    }
+
     /**
      * Creates an object that consumes code points one at a time and returns intermediate prefix
      * matches.  Returns null if no match exists.
@@ -377,6 +382,15 @@ public class TextTrieMap<V> {
             return match;
         }
 
+        public void putLeadChars(UnicodeSet output) {
+            if (_children == null) {
+                return;
+            }
+            for (Node child : _children) {
+                output.add(child._text[0]);
+            }
+        }
+
         public class StepResult {
           public Node node;
           public int offset;
index c1aaf9c5df3c65e267f7202fc9c15272ac2b51aa..a8e1aac3bfe19409132be1d37eb7ce1b53b4f997 100644 (file)
@@ -156,6 +156,14 @@ public class AffixMatcher implements NumberParseMatcher {
         return false;
     }
 
+    @Override
+    public UnicodeSet getLeadChars(boolean ignoreCase) {
+        UnicodeSet leadChars = new UnicodeSet();
+        ParsingUtils.putLeadingChar(prefix, leadChars, ignoreCase);
+        ParsingUtils.putLeadingChar(suffix, leadChars, ignoreCase);
+        return leadChars.freeze();
+    }
+
     @Override
     public void postProcess(ParsedNumber result) {
         // Check to see if our affix is the one that was matched. If so, set the flags in the result.
index 88401d759421273ce1da7fec7d4e4da682ad2fdc..e61a1bfbb9866c46aa76f88909aeb5c347f49d16 100644 (file)
@@ -5,6 +5,7 @@ package com.ibm.icu.impl.number.parse;
 import java.util.Iterator;
 
 import com.ibm.icu.impl.TextTrieMap;
+import com.ibm.icu.text.UnicodeSet;
 import com.ibm.icu.util.Currency;
 import com.ibm.icu.util.Currency.CurrencyStringInfo;
 import com.ibm.icu.util.ULocale;
@@ -42,6 +43,14 @@ public class CurrencyMatcher implements NumberParseMatcher {
         return trieOutput.partialMatch;
     }
 
+    @Override
+    public UnicodeSet getLeadChars(boolean ignoreCase) {
+        UnicodeSet leadChars = new UnicodeSet();
+        longNameTrie.putLeadChars(leadChars);
+        symbolTrie.putLeadChars(leadChars);
+        return leadChars.freeze();
+    }
+
     @Override
     public void postProcess(ParsedNumber result) {
         // No-op
index b395ca7f273a25dc71e69c933dc13a3868a22909..12ff1e9640e6f534c014290f28aa869a98367057 100644 (file)
@@ -190,6 +190,19 @@ public class DecimalMatcher implements NumberParseMatcher {
         return segment.length() == 0 || hasPartialPrefix || segment.isLeadingSurrogate();
     }
 
+    private static final UnicodeSet UNISET_DIGITS = new UnicodeSet("[:digit:]");
+
+    @Override
+    public UnicodeSet getLeadChars(boolean ignoreCase) {
+        UnicodeSet leadChars = new UnicodeSet();
+        ParsingUtils.putLeadSurrogates(UNISET_DIGITS, leadChars);
+        for (int i = 0; i < digitStrings.length; i++) {
+            ParsingUtils.putLeadingChar(digitStrings[i], leadChars, ignoreCase);
+        }
+        ParsingUtils.putLeadSurrogates(separatorSet, leadChars);
+        return leadChars.freeze();
+    }
+
     @Override
     public void postProcess(ParsedNumber result) {
         // No-op
index 76d98b20ccecb0bdb6d532feabf09b82b52fad8c..0e008b4120670931994ddc8ced68a97f719b9c11 100644 (file)
@@ -8,7 +8,7 @@ import com.ibm.icu.text.UnicodeSet;
  * @author sffc
  *
  */
-public class IgnorablesMatcher implements NumberParseMatcher {
+public class IgnorablesMatcher extends RangeMatcher {
 
     // BiDi characters are skipped over and ignored at any point in the string, even in strict mode.
     static final UnicodeSet UNISET_BIDI = new UnicodeSet("[[\\u200E\\u200F\\u061C]]").freeze();
@@ -37,32 +37,22 @@ public class IgnorablesMatcher implements NumberParseMatcher {
         }
     }
 
-    private final UnicodeSet ignorables;
-
     private IgnorablesMatcher(UnicodeSet ignorables) {
-        this.ignorables = ignorables;
+        super(ignorables);
     }
 
     @Override
-    public boolean match(StringSegment segment, ParsedNumber result) {
-        while (segment.length() > 0) {
-            int cp = segment.getCodePoint();
-            if (cp == -1 || !ignorables.contains(cp)) {
-                break;
-            }
-            segment.adjustOffset(Character.charCount(cp));
-            // Note: Do not touch the charsConsumed.
-        }
-        return segment.length() == 0 || segment.isLeadingSurrogate();
+    protected boolean isDisabled(ParsedNumber result) {
+        return false;
     }
 
     @Override
-    public void postProcess(ParsedNumber result) {
+    protected void accept(StringSegment segment, ParsedNumber result) {
         // No-op
     }
 
     @Override
     public String toString() {
-        return "<WhitespaceMatcher>";
+        return "<IgnorablesMatcher>";
     }
 }
index 1e4e4f1abc8aeaa80f5f7aac57e0b714849fb17c..e41ffc8c5c46b8e06956815755cfe0cb475ecab8 100644 (file)
@@ -21,8 +21,9 @@ public class MinusSignMatcher extends SymbolMatcher {
     }
 
     @Override
-    protected void accept(ParsedNumber result) {
+    protected void accept(StringSegment segment, ParsedNumber result) {
         result.flags |= ParsedNumber.FLAG_NEGATIVE;
+        result.setCharsConsumed(segment);
     }
 
     @Override
index 61bca78769ab6be611336ad11ae1bc6d4aa8768d..6e03beb65dc7aadafc5c4171e370d3156982c93f 100644 (file)
@@ -3,37 +3,27 @@
 package com.ibm.icu.impl.number.parse;
 
 import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.UnicodeSet;
 
 /**
  * @author sffc
  *
  */
-public class NanMatcher implements NumberParseMatcher {
-
-    private final String nanString;
+public class NanMatcher extends SymbolMatcher {
 
     public NanMatcher(DecimalFormatSymbols symbols) {
-        nanString = symbols.getNaN();
+        super(symbols.getNaN(), UnicodeSet.EMPTY);
     }
 
     @Override
-    public boolean match(StringSegment segment, ParsedNumber result) {
-        int overlap = segment.getCommonPrefixLength(nanString);
-        if (overlap == nanString.length()) {
-            result.flags |= ParsedNumber.FLAG_NAN;
-            segment.adjustOffset(overlap);
-            result.setCharsConsumed(segment);
-            return false;
-        } else if (overlap == segment.length()) {
-            return true;
-        } else {
-            return false;
-        }
+    protected boolean isDisabled(ParsedNumber result) {
+        return result.seenNumber();
     }
 
     @Override
-    public void postProcess(ParsedNumber result) {
-        // No-op
+    protected void accept(StringSegment segment, ParsedNumber result) {
+        result.flags |= ParsedNumber.FLAG_NAN;
+        result.setCharsConsumed(segment);
     }
 
     @Override
index 5b878699905a27a9250ee19a40db1e7b3dd58a2e..760c30013c9894135e2b07d3e3a664f1747837d3 100644 (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.text.UnicodeSet;
+
 /**
  * @author sffc
  *
@@ -21,6 +23,14 @@ public interface NumberParseMatcher {
      */
     public boolean match(StringSegment segment, ParsedNumber result);
 
+    /**
+     * Should return a set representing all possible chars (UTF-16 code units) that could be the first char that this
+     * matcher can consume. This method is only called during construction phase, and its return value is used to skip
+     * this matcher unless a segment begins with a char in this set. To make this matcher always run, return
+     * {@link UnicodeSet#ALL_CODE_POINTS}.
+     */
+    public UnicodeSet getLeadChars(boolean ignoreCase);
+
     /**
      * Method called at the end of a parse, after all matchers have failed to consume any more chars. Allows a matcher
      * to make final modifications to the result given the knowledge that no more matches are possible.
index 36abbbc6aad1af789769463878dce52f5643f85e..f115e6eb95fc0e2c3c417671a6449c5499455d1d 100644 (file)
@@ -28,10 +28,11 @@ import com.ibm.icu.util.ULocale;
  *
  */
 public class NumberParserImpl {
+    @Deprecated
     public static NumberParserImpl createParserFromPattern(String pattern, boolean strictGrouping) {
         // Temporary frontend for testing.
 
-        NumberParserImpl parser = new NumberParserImpl();
+        NumberParserImpl parser = new NumberParserImpl(true);
         ULocale locale = new ULocale("en_IN");
         DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
 
@@ -94,11 +95,17 @@ public class NumberParserImpl {
         }
     }
 
+    public static NumberParserImpl createDefaultParserForLocale(ULocale loc) {
+        DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(loc);
+        DecimalFormatProperties properties = PatternStringParser.parseToProperties("0");
+        return createParserFromProperties(properties, symbols, false);
+    }
+
     public static NumberParserImpl createParserFromProperties(
             DecimalFormatProperties properties,
             DecimalFormatSymbols symbols,
             boolean parseCurrency) {
-        NumberParserImpl parser = new NumberParserImpl();
+        NumberParserImpl parser = new NumberParserImpl(!properties.getParseCaseSensitive());
         ULocale locale = symbols.getULocale();
         Currency currency = CustomSymbolCurrency.resolve(properties.getCurrency(), locale, symbols);
         boolean isStrict = properties.getParseMode() == ParseMode.STRICT;
@@ -169,38 +176,40 @@ public class NumberParserImpl {
             parser.addMatcher(new RequireDecimalSeparatorMatcher());
         }
 
-        ////////////////////////
-        /// OTHER ATTRIBUTES ///
-        ////////////////////////
-
-        parser.setIgnoreCase(!properties.getParseCaseSensitive());
-
-        System.out.println(parser);
-
         parser.freeze();
         return parser;
     }
 
+    private final boolean ignoreCase;
     private final List<NumberParseMatcher> matchers;
+    private final List<UnicodeSet> leadCharses;
     private Comparator<ParsedNumber> comparator;
-    private boolean ignoreCase;
     private boolean frozen;
 
-    public NumberParserImpl() {
+    public NumberParserImpl(boolean ignoreCase) {
         matchers = new ArrayList<NumberParseMatcher>();
+        leadCharses = new ArrayList<UnicodeSet>();
         comparator = ParsedNumber.COMPARATOR; // default value
-        ignoreCase = true;
+        this.ignoreCase = ignoreCase;
         frozen = false;
     }
 
     public void addMatcher(NumberParseMatcher matcher) {
         assert !frozen;
         this.matchers.add(matcher);
+        UnicodeSet leadChars = matcher.getLeadChars(ignoreCase);
+        assert leadChars.isFrozen();
+        this.leadCharses.add(leadChars);
     }
 
     public void addMatchers(Collection<? extends NumberParseMatcher> matchers) {
         assert !frozen;
         this.matchers.addAll(matchers);
+        for (NumberParseMatcher matcher : matchers) {
+            UnicodeSet leadChars = matcher.getLeadChars(ignoreCase);
+            assert leadChars.isFrozen();
+            this.leadCharses.add(leadChars);
+        }
     }
 
     public void setComparator(Comparator<ParsedNumber> comparator) {
@@ -208,11 +217,6 @@ public class NumberParserImpl {
         this.comparator = comparator;
     }
 
-    public void setIgnoreCase(boolean ignoreCase) {
-        assert !frozen;
-        this.ignoreCase = ignoreCase;
-    }
-
     public void freeze() {
         frozen = true;
     }
@@ -237,7 +241,11 @@ public class NumberParserImpl {
         }
 
         int initialOffset = segment.getOffset();
+        char leadChar = ignoreCase ? ParsingUtils.getCaseFoldedLeadingChar(segment) : segment.charAt(0);
         for (int i = 0; i < matchers.size(); i++) {
+            if (!leadCharses.get(i).contains(leadChar)) {
+                continue;
+            }
             NumberParseMatcher matcher = matchers.get(i);
             matcher.match(segment, result);
             if (segment.getOffset() != initialOffset) {
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ParsingUtils.java
new file mode 100644 (file)
index 0000000..412dd49
--- /dev/null
@@ -0,0 +1,62 @@
+// © 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 com.ibm.icu.lang.UCharacter;
+import com.ibm.icu.text.UTF16;
+import com.ibm.icu.text.UnicodeSet;
+import com.ibm.icu.text.UnicodeSet.EntryRange;
+
+/**
+ * A collection of utility functions used by the number parsing package.
+ */
+public class ParsingUtils {
+
+    /**
+     * Adds all chars and lead surrogates from input into output.
+     */
+    public static void putLeadSurrogates(UnicodeSet input, UnicodeSet output) {
+        if (input.isEmpty()) {
+            return;
+        }
+        for (EntryRange range : input.ranges()) {
+            if (range.codepointEnd <= 0xFFFF) {
+                // All BMP chars
+                output.add(range.codepoint, range.codepointEnd);
+            } else {
+                // Need to get the lead surrogates
+                // TODO: Make this more efficient?
+                if (range.codepoint <= 0xFFFF) {
+                    output.add(range.codepoint, 0xFFFF);
+                }
+                for (int cp = Math.max(0x10000, range.codepoint); cp <= range.codepointEnd; cp++) {
+                    output.add(UTF16.getLeadSurrogate(cp));
+                }
+            }
+        }
+    }
+
+    /**
+     * Adds the first char of the given string to leadChars, performing case-folding if necessary.
+     */
+    public static void putLeadingChar(String str, UnicodeSet leadChars, boolean ignoreCase) {
+        if (str.isEmpty()) {
+            return;
+        }
+        if (ignoreCase) {
+            leadChars.add(getCaseFoldedLeadingChar(str));
+        } else {
+            leadChars.add(str.charAt(0));
+        }
+    }
+
+    public static char getCaseFoldedLeadingChar(CharSequence str) {
+        int cp = UCharacter.foldCase(Character.codePointAt(str, 0), true);
+        if (cp <= 0xFFFF) {
+            return (char) cp;
+        } else {
+            return UTF16.getLeadSurrogate(cp);
+        }
+    }
+
+}
index e10ebb0b899ea2392562b332fcfc7137e1376dfd..5dca1c4861725a41a827ad02769659c517ac8060 100644 (file)
@@ -21,8 +21,9 @@ public class PercentMatcher extends SymbolMatcher {
     }
 
     @Override
-    protected void accept(ParsedNumber result) {
+    protected void accept(StringSegment segment, ParsedNumber result) {
         result.flags |= ParsedNumber.FLAG_PERCENT;
+        result.setCharsConsumed(segment);
     }
 
     @Override
index c3b4d106c10abe6f75a569d843ecadba6b9e703c..f8ea624078e511f51c2fa9543331c555a34dca85 100644 (file)
@@ -21,8 +21,9 @@ public class PermilleMatcher extends SymbolMatcher {
     }
 
     @Override
-    protected void accept(ParsedNumber result) {
+    protected void accept(StringSegment segment, ParsedNumber result) {
         result.flags |= ParsedNumber.FLAG_PERMILLE;
+        result.setCharsConsumed(segment);
     }
 
     @Override
index b9cdff8558452d0d3a83eb84ecbfd622f9c969d2..254c779bdc6be1c70e0f9fd22d1c3bad445ad23d 100644 (file)
@@ -21,8 +21,8 @@ public class PlusSignMatcher extends SymbolMatcher {
     }
 
     @Override
-    protected void accept(ParsedNumber result) {
-        // No-op
+    protected void accept(StringSegment segment, ParsedNumber result) {
+        result.setCharsConsumed(segment);
     }
 
     @Override
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RangeMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/RangeMatcher.java
new file mode 100644 (file)
index 0000000..512e6cf
--- /dev/null
@@ -0,0 +1,57 @@
+// © 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 com.ibm.icu.text.UnicodeSet;
+
+/**
+ * @author sffc
+ *
+ */
+public abstract class RangeMatcher implements NumberParseMatcher {
+    protected final UnicodeSet uniSet;
+
+    protected RangeMatcher(UnicodeSet uniSet) {
+        this.uniSet = uniSet;
+    }
+
+    @Override
+    public boolean match(StringSegment segment, ParsedNumber result) {
+        // Smoke test first; this matcher might be disabled.
+        if (isDisabled(result)) {
+            return false;
+        }
+
+        while (segment.length() > 0) {
+            int cp = segment.getCodePoint();
+            if (cp != -1 && uniSet.contains(cp)) {
+                segment.adjustOffset(Character.charCount(cp));
+                accept(segment, result);
+                continue;
+            }
+
+            // If we get here, the code point didn't match the uniSet.
+            return segment.isLeadingSurrogate();
+        }
+
+        // If we get here, we consumed the entire string segment.
+        return true;
+    }
+
+    @Override
+    public UnicodeSet getLeadChars(boolean ignoreCase) {
+        UnicodeSet leadChars = new UnicodeSet();
+        ParsingUtils.putLeadSurrogates(uniSet, leadChars);
+        return leadChars.freeze();
+    }
+
+    @Override
+    public void postProcess(ParsedNumber result) {
+        // No-op
+    }
+
+    protected abstract boolean isDisabled(ParsedNumber result);
+
+    protected abstract void accept(StringSegment segment, ParsedNumber result);
+
+}
index dfbf98767e9aba26c037ac5e6b7582f89121787d..280ca7e7c22e5ab0182c3b5e3d1e8d069192f11a 100644 (file)
@@ -6,12 +6,7 @@ package com.ibm.icu.impl.number.parse;
  * @author sffc
  *
  */
-public class RequireAffixMatcher implements NumberParseMatcher {
-
-    @Override
-    public boolean match(StringSegment segment, ParsedNumber result) {
-        return false;
-    }
+public class RequireAffixMatcher extends ValidationMatcher {
 
     @Override
     public void postProcess(ParsedNumber result) {
index ef917cec2fcff7dd0d5a5c43e5533f74a9dd078c..d022321e60dcb7d19688fea5d460ac170096e6ac 100644 (file)
@@ -6,12 +6,7 @@ package com.ibm.icu.impl.number.parse;
  * @author sffc
  *
  */
-public class RequireCurrencyMatcher implements NumberParseMatcher {
-
-    @Override
-    public boolean match(StringSegment segment, ParsedNumber result) {
-        return false;
-    }
+public class RequireCurrencyMatcher extends ValidationMatcher {
 
     @Override
     public void postProcess(ParsedNumber result) {
index 2348e48b60711fa88a10c902df393a97dd4160fc..38ade076949ab7a195b00edeb2978033a010d086 100644 (file)
@@ -6,12 +6,7 @@ package com.ibm.icu.impl.number.parse;
  * @author sffc
  *
  */
-public class RequireDecimalSeparatorMatcher implements NumberParseMatcher {
-
-    @Override
-    public boolean match(StringSegment segment, ParsedNumber result) {
-        return false;
-    }
+public class RequireDecimalSeparatorMatcher extends ValidationMatcher {
 
     @Override
     public void postProcess(ParsedNumber result) {
index 76de80dc1bf472e14dcf4e16056cdce35b71fb6f..562df7ba4bf1b3658e626ff5e1caf267a118085e 100644 (file)
@@ -6,12 +6,7 @@ package com.ibm.icu.impl.number.parse;
  * @author sffc
  *
  */
-public class RequireExponentMatcher implements NumberParseMatcher {
-
-    @Override
-    public boolean match(StringSegment segment, ParsedNumber result) {
-        return false;
-    }
+public class RequireExponentMatcher extends ValidationMatcher {
 
     @Override
     public void postProcess(ParsedNumber result) {
index c7a168ce4da8dca1aaf4c5216650406c1e6bce8a..d25a29fedf58f8104e4baf2035aff7bf0982567a 100644 (file)
@@ -6,12 +6,7 @@ package com.ibm.icu.impl.number.parse;
  * @author sffc
  *
  */
-public class RequireNumberMatcher implements NumberParseMatcher {
-
-    @Override
-    public boolean match(StringSegment segment, ParsedNumber result) {
-        return false;
-    }
+public class RequireNumberMatcher extends ValidationMatcher {
 
     @Override
     public void postProcess(ParsedNumber result) {
index 90b9b9a46c448bbb9e03633cc56b7c45997c8b7d..48c4932637110221c1e7ce131b0fff22a4d57d30 100644 (file)
@@ -3,6 +3,7 @@
 package com.ibm.icu.impl.number.parse;
 
 import com.ibm.icu.text.DecimalFormatSymbols;
+import com.ibm.icu.text.UnicodeSet;
 
 /**
  * @author sffc
@@ -66,6 +67,13 @@ public class ScientificMatcher implements NumberParseMatcher {
         return false;
     }
 
+    @Override
+    public UnicodeSet getLeadChars(boolean ignoreCase) {
+        UnicodeSet leadChars = new UnicodeSet();
+        ParsingUtils.putLeadingChar(exponentSeparatorString, leadChars, ignoreCase);
+        return leadChars.freeze();
+    }
+
     @Override
     public void postProcess(ParsedNumber result) {
         // No-op
index efa17be120d6dc868ac45a52d0434f6f89680261..5f5f4f111f82d4eed040773f1471057faedcbfa2 100644 (file)
@@ -9,8 +9,8 @@ import com.ibm.icu.text.UnicodeSet;
  *
  */
 public abstract class SymbolMatcher implements NumberParseMatcher {
-    private final String string;
-    private final UnicodeSet uniSet;
+    protected final String string;
+    protected final UnicodeSet uniSet;
 
     protected SymbolMatcher(String symbolString, UnicodeSet symbolUniSet) {
         string = symbolString;
@@ -26,17 +26,25 @@ public abstract class SymbolMatcher implements NumberParseMatcher {
 
         int cp = segment.getCodePoint();
         if (cp != -1 && uniSet.contains(cp)) {
-            accept(result);
             segment.adjustOffset(Character.charCount(cp));
+            accept(segment, result);
             return false;
         }
         int overlap = segment.getCommonPrefixLength(string);
         if (overlap == string.length()) {
-            accept(result);
             segment.adjustOffset(string.length());
+            accept(segment, result);
             return false;
         }
-        return overlap == segment.length();
+        return overlap == segment.length() || segment.isLeadingSurrogate();
+    }
+
+    @Override
+    public UnicodeSet getLeadChars(boolean ignoreCase) {
+        UnicodeSet leadChars = new UnicodeSet();
+        ParsingUtils.putLeadSurrogates(uniSet, leadChars);
+        ParsingUtils.putLeadingChar(string, leadChars, ignoreCase);
+        return leadChars.freeze();
     }
 
     @Override
@@ -46,5 +54,5 @@ public abstract class SymbolMatcher implements NumberParseMatcher {
 
     protected abstract boolean isDisabled(ParsedNumber result);
 
-    protected abstract void accept(ParsedNumber result);
+    protected abstract void accept(StringSegment segment, ParsedNumber result);
 }
diff --git a/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ValidationMatcher.java b/icu4j/main/classes/core/src/com/ibm/icu/impl/number/parse/ValidationMatcher.java
new file mode 100644 (file)
index 0000000..4fbbe81
--- /dev/null
@@ -0,0 +1,22 @@
+// © 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 com.ibm.icu.text.UnicodeSet;
+
+/**
+ * A Matcher used only for post-process validation, not for consuming characters at runtime.
+ */
+public abstract class ValidationMatcher implements NumberParseMatcher {
+
+    @Override
+    public boolean match(StringSegment segment, ParsedNumber result) {
+        return false;
+    }
+
+    @Override
+    public UnicodeSet getLeadChars(boolean ignoreCase) {
+        return UnicodeSet.EMPTY;
+    }
+
+}
index cf32cb58be7aac9bf401c2c7971607d0a1a905bd..fea1e9ed898d6e42d3eaeb65294dac696c96ba25 100644 (file)
@@ -85,8 +85,8 @@ public class NumberParserTest {
                 ParsedNumber resultObject = new ParsedNumber();
                 parser.parse(input, true, resultObject);
                 assertNotNull(message, resultObject.quantity);
-                assertEquals(message, resultDouble, resultObject.getNumber().doubleValue(), 0.0);
                 assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
+                assertEquals(message, resultDouble, resultObject.getNumber().doubleValue(), 0.0);
             }
 
             if (0 != (flags & 0x02)) {
@@ -94,8 +94,8 @@ public class NumberParserTest {
                 ParsedNumber resultObject = new ParsedNumber();
                 parser.parse(input, false, resultObject);
                 assertNotNull(message, resultObject.quantity);
-                assertEquals(message, resultDouble, resultObject.getNumber().doubleValue(), 0.0);
                 assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
+                assertEquals(message, resultDouble, resultObject.getNumber().doubleValue(), 0.0);
             }
 
             if (0 != (flags & 0x04)) {
@@ -104,8 +104,8 @@ public class NumberParserTest {
                 ParsedNumber resultObject = new ParsedNumber();
                 parser.parse(input, true, resultObject);
                 assertNotNull(message, resultObject.quantity);
-                assertEquals(message, resultDouble, resultObject.getNumber().doubleValue(), 0.0);
                 assertEquals(message, expectedCharsConsumed, resultObject.charsConsumed);
+                assertEquals(message, resultDouble, resultObject.getNumber().doubleValue(), 0.0);
             }
         }
     }