Ignore:
Timestamp:
Mar 1, 2016, 4:39:01 PM (9 years ago)
Author:
[email protected]
Message:

[ES6] Add support for Unicode regular expressions
https://bugs.webkit.org/show_bug.cgi?id=154842

Reviewed by Filip Pizlo.

Source/JavaScriptCore:

Added processing of Unicode regular expressions to the Yarr interpreter.

Changed parsing of regular expression patterns and PatternTerms to process characters as
UChar32 in the Yarr code. The parser converts matched surrogate pairs into the appropriate
Unicode character when the expression is parsed. When matching a unicode expression and
reading source characters, we convert proper surrogate pair into a Unicode character and
advance the source cursor, "pos", one more position. The exception to this is when we
know when generating a fixed character atom that we need to match a unicode character
that doesn't fit in 16 bits. The code calls this an extendedUnicodeCharacter and has a
helper to determine this.

Added 'u' flag and 'unicode' identifier to regular expression classes. Added an "isUnicode"
parameter to YarrPattern pattern() and internal users of that function.

Updated the generation of the canonicalization tables to include a new set a tables that
follow the ES 6.0, 21.2.2.8.2 Step 2. Renamed the YarrCanonicalizeUCS2.* files to
YarrCanonicalizeUnicode.*.

Added a new Layout/js test that tests the added functionality. Updated other tests that
have minor es6 unicode checks and look for valid flags.

Ran the ChakraCore Unicode regular expression tests as well.

  • inspector/ContentSearchUtilities.cpp:

(Inspector::ContentSearchUtilities::findMagicComment):

  • yarr/RegularExpression.cpp:

(JSC::Yarr::RegularExpression::Private::compile):
Updated use of pattern().

  • runtime/CommonIdentifiers.h:
  • runtime/RegExp.cpp:

(JSC::regExpFlags):
(JSC::RegExpFunctionalTestCollector::outputOneTest):
(JSC::RegExp::finishCreation):
(JSC::RegExp::compile):
(JSC::RegExp::compileMatchOnly):

  • runtime/RegExp.h:
  • runtime/RegExpKey.h:
  • runtime/RegExpPrototype.cpp:

(JSC::regExpProtoFuncCompile):
(JSC::flagsString):
(JSC::regExpProtoGetterMultiline):
(JSC::regExpProtoGetterUnicode):
(JSC::regExpProtoGetterFlags):
Updated for new 'y' (unicode) flag. Add check to use the interpreter for unicode regular expressions.

  • tests/es6.yaml:
  • tests/stress/static-getter-in-names.js:

Updated tests for new flag and for passing the minimal es6 regular expression processing.

  • yarr/Yarr.h: Updated the size of information now kept for backtracking.
  • yarr/YarrCanonicalizeUCS2.cpp: Removed.
  • yarr/YarrCanonicalizeUCS2.h: Removed.
  • yarr/YarrCanonicalizeUCS2.js: Removed.
  • yarr/YarrCanonicalizeUnicode.cpp: Copied from Source/JavaScriptCore/yarr/YarrCanonicalizeUCS2.cpp.
  • yarr/YarrCanonicalizeUnicode.h: Copied from Source/JavaScriptCore/yarr/YarrCanonicalizeUCS2.h.

(JSC::Yarr::canonicalCharacterSetInfo):
(JSC::Yarr::canonicalRangeInfoFor):
(JSC::Yarr::getCanonicalPair):
(JSC::Yarr::isCanonicallyUnique):
(JSC::Yarr::areCanonicallyEquivalent):
(JSC::Yarr::rangeInfoFor): Deleted.

  • yarr/YarrCanonicalizeUnicode.js: Copied from Source/JavaScriptCore/yarr/YarrCanonicalizeUCS2.js.

(printHeader):
(printFooter):
(hex):
(canonicalize):
(canonicalizeUnicode):
(createUCS2CanonicalGroups):
(createUnicodeCanonicalGroups):
(cu.in.groupedCanonically.characters.sort): Deleted.
(cu.in.groupedCanonically.else): Deleted.
Refactored to output two sets of tables, one for UCS2 and one for Unicode. The UCS2 tables follow
the legacy canonicalization rules now specified in ES 6.0, 21.2.2.8.2 Step 3. The new Unicode
tables follow the rules specified in ES 6.0, 21.2.2.8.2 Step 2. Eliminated the unused Latin1 tables.

  • yarr/YarrInterpreter.cpp:

(JSC::Yarr::Interpreter::InputStream::InputStream):
(JSC::Yarr::Interpreter::InputStream::readChecked):
(JSC::Yarr::Interpreter::InputStream::readSurrogatePairChecked):
(JSC::Yarr::Interpreter::InputStream::reread):
(JSC::Yarr::Interpreter::InputStream::prev):
(JSC::Yarr::Interpreter::testCharacterClass):
(JSC::Yarr::Interpreter::checkCharacter):
(JSC::Yarr::Interpreter::checkSurrogatePair):
(JSC::Yarr::Interpreter::checkCasedCharacter):
(JSC::Yarr::Interpreter::tryConsumeBackReference):
(JSC::Yarr::Interpreter::backtrackPatternCharacter):
(JSC::Yarr::Interpreter::matchCharacterClass):
(JSC::Yarr::Interpreter::backtrackCharacterClass):
(JSC::Yarr::Interpreter::matchParenthesesTerminalEnd):
(JSC::Yarr::Interpreter::matchDisjunction):
(JSC::Yarr::Interpreter::Interpreter):
(JSC::Yarr::ByteCompiler::assertionWordBoundary):
(JSC::Yarr::ByteCompiler::atomPatternCharacter):

  • yarr/YarrInterpreter.h:

(JSC::Yarr::ByteTerm::ByteTerm):
(JSC::Yarr::BytecodePattern::BytecodePattern):

  • yarr/YarrJIT.cpp:

(JSC::Yarr::YarrGenerator::optimizeAlternative):
(JSC::Yarr::YarrGenerator::matchCharacterClassRange):
(JSC::Yarr::YarrGenerator::matchCharacterClass):
(JSC::Yarr::YarrGenerator::notAtEndOfInput):
(JSC::Yarr::YarrGenerator::jumpIfCharNotEquals):
(JSC::Yarr::YarrGenerator::generatePatternCharacterOnce):
(JSC::Yarr::YarrGenerator::generatePatternCharacterFixed):
(JSC::Yarr::YarrGenerator::generatePatternCharacterGreedy):
(JSC::Yarr::YarrGenerator::backtrackPatternCharacterNonGreedy):

  • yarr/YarrParser.h:

(JSC::Yarr::Parser::CharacterClassParserDelegate::atomPatternCharacter):
(JSC::Yarr::Parser::Parser):
(JSC::Yarr::Parser::parseEscape):
(JSC::Yarr::Parser::consumePossibleSurrogatePair):
(JSC::Yarr::Parser::parseCharacterClass):
(JSC::Yarr::Parser::parseTokens):
(JSC::Yarr::Parser::parse):
(JSC::Yarr::Parser::atEndOfPattern):
(JSC::Yarr::Parser::patternRemaining):
(JSC::Yarr::Parser::peek):
(JSC::Yarr::parse):

  • yarr/YarrPattern.cpp:

(JSC::Yarr::CharacterClassConstructor::CharacterClassConstructor):
(JSC::Yarr::CharacterClassConstructor::append):
(JSC::Yarr::CharacterClassConstructor::putChar):
(JSC::Yarr::CharacterClassConstructor::putUnicodeIgnoreCase):
(JSC::Yarr::CharacterClassConstructor::putRange):
(JSC::Yarr::CharacterClassConstructor::charClass):
(JSC::Yarr::CharacterClassConstructor::addSorted):
(JSC::Yarr::CharacterClassConstructor::addSortedRange):
(JSC::Yarr::YarrPatternConstructor::YarrPatternConstructor):
(JSC::Yarr::YarrPatternConstructor::assertionWordBoundary):
(JSC::Yarr::YarrPatternConstructor::atomPatternCharacter):
(JSC::Yarr::YarrPatternConstructor::atomCharacterClassBegin):
(JSC::Yarr::YarrPatternConstructor::atomCharacterClassAtom):
(JSC::Yarr::YarrPatternConstructor::atomCharacterClassRange):
(JSC::Yarr::YarrPatternConstructor::setupAlternativeOffsets):
(JSC::Yarr::YarrPattern::compile):
(JSC::Yarr::YarrPattern::YarrPattern):

  • yarr/YarrPattern.h:

(JSC::Yarr::CharacterRange::CharacterRange):
(JSC::Yarr::CharacterClass::CharacterClass):
(JSC::Yarr::PatternTerm::PatternTerm):
(JSC::Yarr::YarrPattern::reset):

  • yarr/YarrSyntaxChecker.cpp:

(JSC::Yarr::SyntaxChecker::assertionBOL):
(JSC::Yarr::SyntaxChecker::assertionEOL):
(JSC::Yarr::SyntaxChecker::assertionWordBoundary):
(JSC::Yarr::SyntaxChecker::atomPatternCharacter):
(JSC::Yarr::SyntaxChecker::atomBuiltInCharacterClass):
(JSC::Yarr::SyntaxChecker::atomCharacterClassBegin):
(JSC::Yarr::SyntaxChecker::atomCharacterClassAtom):
(JSC::Yarr::checkSyntax):

LayoutTests:

Added a new test for the added unicode regular expression processing.

Updated several tests for the y flag changes and "unicode" property.

  • js/regexp-unicode-expected.txt: Added.
  • js/regexp-unicode.html: Added.
  • js/script-tests/regexp-unicode.js: Added.

New test.

  • js/Object-getOwnPropertyNames-expected.txt:
  • js/regexp-flags-expected.txt:
  • js/script-tests/Object-getOwnPropertyNames.js:
  • js/script-tests/regexp-flags.js:

(RegExp.prototype.hasOwnProperty):
Updated tests.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/yarr/YarrInterpreter.cpp

    r194496 r197426  
    11/*
    2  * Copyright (C) 2009 Apple Inc. All rights reserved.
     2 * Copyright (C) 2009, 2013, 2016 Apple Inc. All rights reserved.
    33 * Copyright (C) 2010 Peter Varga ([email protected]), University of Szeged
    44 *
     
    2929
    3030#include "Yarr.h"
    31 #include "YarrCanonicalizeUCS2.h"
     31#include "YarrCanonicalizeUnicode.h"
    3232#include <wtf/BumpPointerAllocator.h>
    3333#include <wtf/DataLog.h>
     
    4545
    4646    struct BackTrackInfoPatternCharacter {
     47        uintptr_t begin; // Only needed for unicode patterns
    4748        uintptr_t matchAmount;
    4849    };
    4950    struct BackTrackInfoCharacterClass {
     51        uintptr_t begin; // Only needed for unicode patterns
    5052        uintptr_t matchAmount;
    5153    };
     
    168170    class InputStream {
    169171    public:
    170         InputStream(const CharType* input, unsigned start, unsigned length)
     172        InputStream(const CharType* input, unsigned start, unsigned length, bool decodeSurrogatePairs)
    171173            : input(input)
    172174            , pos(start)
    173175            , length(length)
     176            , decodeSurrogatePairs(decodeSurrogatePairs)
    174177        {
    175178        }
     
    205208            unsigned p = pos - negativePositionOffest;
    206209            ASSERT(p < length);
    207             return input[p];
     210            int result = input[p];
     211            if (U16_IS_LEAD(result) && decodeSurrogatePairs && p + 1 < length
     212                && U16_IS_TRAIL(input[p + 1])) {
     213                if (atEnd())
     214                    return -1;
     215               
     216                result = U16_GET_SUPPLEMENTARY(result, input[p + 1]);
     217                next();
     218            }
     219            return result;
     220        }
     221       
     222        int readSurrogatePairChecked(unsigned negativePositionOffest)
     223        {
     224            RELEASE_ASSERT(pos >= negativePositionOffest);
     225            unsigned p = pos - negativePositionOffest;
     226            ASSERT(p < length);
     227            if (p + 1 >= length)
     228                return -1;
     229
     230            int first = input[p];
     231            if (U16_IS_LEAD(first) && U16_IS_TRAIL(input[p + 1]))
     232                return U16_GET_SUPPLEMENTARY(first, input[p + 1]);
     233
     234            return -1;
    208235        }
    209236
     
    211238        {
    212239            ASSERT(from < length);
    213             return input[from];
     240            int result = input[from];
     241            if (U16_IS_LEAD(result) && decodeSurrogatePairs && from + 1 < length
     242                && U16_IS_TRAIL(input[from + 1])) {
     243               
     244                result = U16_GET_SUPPLEMENTARY(result, input[from + 1]);
     245            }
     246            return result;
    214247        }
    215248
     
    282315        unsigned pos;
    283316        unsigned length;
     317        bool decodeSurrogatePairs;
    284318    };
    285319
    286320    bool testCharacterClass(CharacterClass* characterClass, int ch)
    287321    {
    288         if (ch & 0xFF80) {
     322        if (ch & 0x1FFF80) {
    289323            for (unsigned i = 0; i < characterClass->m_matchesUnicode.size(); ++i)
    290324                if (ch == characterClass->m_matchesUnicode[i])
     
    310344    }
    311345
     346    bool checkSurrogatePair(int testUnicodeChar, unsigned negativeInputOffset)
     347    {
     348        return testUnicodeChar == input.readSurrogatePairChecked(negativeInputOffset);
     349    }
     350
    312351    bool checkCasedCharacter(int loChar, int hiChar, unsigned negativeInputOffset)
    313352    {
     
    329368            return false;
    330369
    331         if (pattern->m_ignoreCase) {
    332             for (unsigned i = 0; i < matchSize; ++i) {
    333                 int oldCh = input.reread(matchBegin + i);
    334                 int ch = input.readChecked(negativeInputOffset + matchSize - i);
    335 
    336                 if (oldCh == ch)
    337                     continue;
    338 
    339                 // The definition for canonicalize (see ES 5.1, 15.10.2.8) means that
     370        for (unsigned i = 0; i < matchSize; ++i) {
     371            int oldCh = input.reread(matchBegin + i);
     372            int ch;
     373            if (!U_IS_BMP(oldCh)) {
     374                ch = input.readSurrogatePairChecked(negativeInputOffset + matchSize - i);
     375                ++i;
     376            } else
     377                ch = input.readChecked(negativeInputOffset + matchSize - i);
     378
     379            if (oldCh == ch)
     380                continue;
     381
     382            if (pattern->m_ignoreCase) {
     383                // The definition for canonicalize (see ES 6.0, 15.10.2.8) means that
    340384                // unicode values are never allowed to match against ascii ones.
    341385                if (isASCII(oldCh) || isASCII(ch)) {
    342386                    if (toASCIIUpper(oldCh) == toASCIIUpper(ch))
    343387                        continue;
    344                 } else if (areCanonicallyEquivalent(oldCh, ch))
     388                } else if (areCanonicallyEquivalent(oldCh, ch, unicode ? CanonicalMode::Unicode : CanonicalMode::UCS2))
    345389                    continue;
    346 
    347                 input.uncheckInput(matchSize);
    348                 return false;
    349             }
    350         } else {
    351             for (unsigned i = 0; i < matchSize; ++i) {
    352                 if (!checkCharacter(input.reread(matchBegin + i), negativeInputOffset + matchSize - i)) {
    353                     input.uncheckInput(matchSize);
    354                     return false;
    355                 }
    356             }
     390            }
     391
     392            input.uncheckInput(matchSize);
     393            return false;
    357394        }
    358395
     
    397434            if (backTrack->matchAmount) {
    398435                --backTrack->matchAmount;
    399                 input.uncheckInput(1);
     436                if (unicode && !U_IS_BMP(term.atom.patternCharacter))
     437                    input.uncheckInput(2);
     438                else
     439                    input.uncheckInput(1);
    400440                return true;
    401441            }
     
    408448                    return true;
    409449            }
    410             input.uncheckInput(backTrack->matchAmount);
     450            input.setPos(backTrack->begin);
    411451            break;
    412452        }
     
    447487    {
    448488        ASSERT(term.type == ByteTerm::TypeCharacterClass);
    449         BackTrackInfoPatternCharacter* backTrack = reinterpret_cast<BackTrackInfoPatternCharacter*>(context->frame + term.frameLocation);
     489        BackTrackInfoCharacterClass* backTrack = reinterpret_cast<BackTrackInfoCharacterClass*>(context->frame + term.frameLocation);
    450490
    451491        switch (term.atom.quantityType) {
    452492        case QuantifierFixedCount: {
     493            if (unicode) {
     494                backTrack->begin = input.getPos();
     495                unsigned matchAmount = 0;
     496                for (matchAmount = 0; matchAmount < term.atom.quantityCount; ++matchAmount) {
     497                    if (!checkCharacterClass(term.atom.characterClass, term.invert(), term.inputPosition - matchAmount)) {
     498                        input.setPos(backTrack->begin);
     499                        return false;
     500                    }
     501                }
     502
     503                return true;
     504            }
     505
    453506            for (unsigned matchAmount = 0; matchAmount < term.atom.quantityCount; ++matchAmount) {
    454507                if (!checkCharacterClass(term.atom.characterClass, term.invert(), term.inputPosition - matchAmount))
     
    459512
    460513        case QuantifierGreedy: {
     514            backTrack->begin = input.getPos();
    461515            unsigned matchAmount = 0;
    462516            while ((matchAmount < term.atom.quantityCount) && input.checkInput(1)) {
     
    473527
    474528        case QuantifierNonGreedy:
     529            backTrack->begin = input.getPos();
    475530            backTrack->matchAmount = 0;
    476531            return true;
     
    484539    {
    485540        ASSERT(term.type == ByteTerm::TypeCharacterClass);
    486         BackTrackInfoPatternCharacter* backTrack = reinterpret_cast<BackTrackInfoPatternCharacter*>(context->frame + term.frameLocation);
     541        BackTrackInfoCharacterClass* backTrack = reinterpret_cast<BackTrackInfoCharacterClass*>(context->frame + term.frameLocation);
    487542
    488543        switch (term.atom.quantityType) {
    489544        case QuantifierFixedCount:
     545            if (unicode)
     546                input.setPos(backTrack->begin);
    490547            break;
    491548
    492549        case QuantifierGreedy:
    493550            if (backTrack->matchAmount) {
     551                if (unicode) {
     552                    // Rematch one less match
     553                    input.setPos(backTrack->begin);
     554                    --backTrack->matchAmount;
     555                    for (unsigned matchAmount = 0; (matchAmount < backTrack->matchAmount) && input.checkInput(1); ++matchAmount) {
     556                        if (!checkCharacterClass(term.atom.characterClass, term.invert(), term.inputPosition + 1)) {
     557                            input.uncheckInput(1);
     558                            break;
     559                        }
     560                    }
     561                    return true;
     562                }
    494563                --backTrack->matchAmount;
    495564                input.uncheckInput(1);
     
    504573                    return true;
    505574            }
    506             input.uncheckInput(backTrack->matchAmount);
     575            input.setPos(backTrack->begin);
    507576            break;
    508577        }
     
    774843            return false;
    775844
    776         // Successful match! Okay, what's next? - loop around and try to match moar!
     845        // Successful match! Okay, what's next? - loop around and try to match more!
    777846        context->term -= (term.atom.parenthesesWidth + 1);
    778847        return true;
     
    11551224        case ByteTerm::TypePatternCharacterOnce:
    11561225        case ByteTerm::TypePatternCharacterFixed: {
     1226            if (unicode) {
     1227                if (!U_IS_BMP(currentTerm().atom.patternCharacter)) {
     1228                    for (unsigned matchAmount = 0; matchAmount < currentTerm().atom.quantityCount; ++matchAmount) {
     1229                        if (!checkSurrogatePair(currentTerm().atom.patternCharacter, currentTerm().inputPosition - matchAmount)) {
     1230                            BACKTRACK();
     1231                        }
     1232                    }
     1233                    MATCH_NEXT();
     1234                }
     1235            }
     1236            unsigned position = input.getPos(); // May need to back out reading a surrogate pair.
     1237
    11571238            for (unsigned matchAmount = 0; matchAmount < currentTerm().atom.quantityCount; ++matchAmount) {
    1158                 if (!checkCharacter(currentTerm().atom.patternCharacter, currentTerm().inputPosition - matchAmount))
     1239                if (!checkCharacter(currentTerm().atom.patternCharacter, currentTerm().inputPosition - matchAmount)) {
     1240                    input.setPos(position);
    11591241                    BACKTRACK();
     1242                }
    11601243            }
    11611244            MATCH_NEXT();
     
    11771260        case ByteTerm::TypePatternCharacterNonGreedy: {
    11781261            BackTrackInfoPatternCharacter* backTrack = reinterpret_cast<BackTrackInfoPatternCharacter*>(context->frame + currentTerm().frameLocation);
     1262            backTrack->begin = input.getPos();
    11791263            backTrack->matchAmount = 0;
    11801264            MATCH_NEXT();
     
    11831267        case ByteTerm::TypePatternCasedCharacterOnce:
    11841268        case ByteTerm::TypePatternCasedCharacterFixed: {
     1269            if (unicode) {
     1270                // Case insensitive matching of unicode charaters are handled as TypeCharacterClass
     1271                ASSERT(U_IS_BMP(currentTerm().atom.patternCharacter));
     1272
     1273                unsigned position = input.getPos(); // May need to back out reading a surrogate pair.
     1274               
     1275                for (unsigned matchAmount = 0; matchAmount < currentTerm().atom.quantityCount; ++matchAmount) {
     1276                    if (!checkCasedCharacter(currentTerm().atom.casedCharacter.lo, currentTerm().atom.casedCharacter.hi, currentTerm().inputPosition - matchAmount)) {
     1277                        input.setPos(position);
     1278                        BACKTRACK();
     1279                    }
     1280                }
     1281                MATCH_NEXT();
     1282            }
     1283
    11851284            for (unsigned matchAmount = 0; matchAmount < currentTerm().atom.quantityCount; ++matchAmount) {
    11861285                if (!checkCasedCharacter(currentTerm().atom.casedCharacter.lo, currentTerm().atom.casedCharacter.hi, currentTerm().inputPosition - matchAmount))
     
    11911290        case ByteTerm::TypePatternCasedCharacterGreedy: {
    11921291            BackTrackInfoPatternCharacter* backTrack = reinterpret_cast<BackTrackInfoPatternCharacter*>(context->frame + currentTerm().frameLocation);
     1292
     1293            // Case insensitive matching of unicode charaters are handled as TypeCharacterClass
     1294            ASSERT(!unicode || U_IS_BMP(currentTerm().atom.patternCharacter));
     1295
    11931296            unsigned matchAmount = 0;
    11941297            while ((matchAmount < currentTerm().atom.quantityCount) && input.checkInput(1)) {
     
    12051308        case ByteTerm::TypePatternCasedCharacterNonGreedy: {
    12061309            BackTrackInfoPatternCharacter* backTrack = reinterpret_cast<BackTrackInfoPatternCharacter*>(context->frame + currentTerm().frameLocation);
     1310
     1311            // Case insensitive matching of unicode charaters are handled as TypeCharacterClass
     1312            ASSERT(!unicode || U_IS_BMP(currentTerm().atom.patternCharacter));
     1313           
    12071314            backTrack->matchAmount = 0;
    12081315            MATCH_NEXT();
     
    14401547    Interpreter(BytecodePattern* pattern, unsigned* output, const CharType* input, unsigned length, unsigned start)
    14411548        : pattern(pattern)
     1549        , unicode(pattern->m_unicode)
    14421550        , output(output)
    1443         , input(input, start, length)
     1551        , input(input, start, length, pattern->m_unicode)
    14441552        , allocatorPool(0)
    14451553        , remainingMatchCount(matchLimit)
     
    14491557private:
    14501558    BytecodePattern* pattern;
     1559    bool unicode;
    14511560    unsigned* output;
    14521561    InputStream input;
     
    15071616    }
    15081617
    1509     void atomPatternCharacter(UChar ch, unsigned inputPosition, unsigned frameLocation, Checked<unsigned> quantityCount, QuantifierType quantityType)
     1618    void atomPatternCharacter(UChar32 ch, unsigned inputPosition, unsigned frameLocation, Checked<unsigned> quantityCount, QuantifierType quantityType)
    15101619    {
    15111620        if (m_pattern.m_ignoreCase) {
    1512             ASSERT(u_tolower(ch) <= 0xFFFF);
    1513             ASSERT(u_toupper(ch) <= 0xFFFF);
    1514 
    1515             UChar lo = u_tolower(ch);
    1516             UChar hi = u_toupper(ch);
     1621            ASSERT(u_tolower(ch) <= UCHAR_MAX_VALUE);
     1622            ASSERT(u_toupper(ch) <= UCHAR_MAX_VALUE);
     1623
     1624            UChar32 lo = u_tolower(ch);
     1625            UChar32 hi = u_toupper(ch);
    15171626
    15181627            if (lo != hi) {
Note: See TracChangeset for help on using the changeset viewer.