intl/icu/source/i18n/plurrule.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/intl/icu/source/i18n/plurrule.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1596 @@
     1.4 +/*
     1.5 +*******************************************************************************
     1.6 +* Copyright (C) 2007-2013, International Business Machines Corporation and
     1.7 +* others. All Rights Reserved.
     1.8 +*******************************************************************************
     1.9 +*
    1.10 +* File plurrule.cpp
    1.11 +*/
    1.12 +
    1.13 +#include <math.h>
    1.14 +#include <stdio.h>
    1.15 +
    1.16 +#include "unicode/utypes.h"
    1.17 +#include "unicode/localpointer.h"
    1.18 +#include "unicode/plurrule.h"
    1.19 +#include "unicode/upluralrules.h"
    1.20 +#include "unicode/ures.h"
    1.21 +#include "charstr.h"
    1.22 +#include "cmemory.h"
    1.23 +#include "cstring.h"
    1.24 +#include "digitlst.h"
    1.25 +#include "hash.h"
    1.26 +#include "locutil.h"
    1.27 +#include "mutex.h"
    1.28 +#include "patternprops.h"
    1.29 +#include "plurrule_impl.h"
    1.30 +#include "putilimp.h"
    1.31 +#include "ucln_in.h"
    1.32 +#include "ustrfmt.h"
    1.33 +#include "uassert.h"
    1.34 +#include "uvectr32.h"
    1.35 +
    1.36 +#if !UCONFIG_NO_FORMATTING
    1.37 +
    1.38 +U_NAMESPACE_BEGIN
    1.39 +
    1.40 +#define ARRAY_SIZE(array) (int32_t)(sizeof array  / sizeof array[0])
    1.41 +
    1.42 +static const UChar PLURAL_KEYWORD_OTHER[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,0};
    1.43 +static const UChar PLURAL_DEFAULT_RULE[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,COLON,SPACE,LOW_N,0};
    1.44 +static const UChar PK_IN[]={LOW_I,LOW_N,0};
    1.45 +static const UChar PK_NOT[]={LOW_N,LOW_O,LOW_T,0};
    1.46 +static const UChar PK_IS[]={LOW_I,LOW_S,0};
    1.47 +static const UChar PK_MOD[]={LOW_M,LOW_O,LOW_D,0};
    1.48 +static const UChar PK_AND[]={LOW_A,LOW_N,LOW_D,0};
    1.49 +static const UChar PK_OR[]={LOW_O,LOW_R,0};
    1.50 +static const UChar PK_VAR_N[]={LOW_N,0};
    1.51 +static const UChar PK_VAR_I[]={LOW_I,0};
    1.52 +static const UChar PK_VAR_F[]={LOW_F,0};
    1.53 +static const UChar PK_VAR_T[]={LOW_T,0};
    1.54 +static const UChar PK_VAR_V[]={LOW_V,0};
    1.55 +static const UChar PK_VAR_J[]={LOW_J,0};
    1.56 +static const UChar PK_WITHIN[]={LOW_W,LOW_I,LOW_T,LOW_H,LOW_I,LOW_N,0};
    1.57 +static const UChar PK_DECIMAL[]={LOW_D,LOW_E,LOW_C,LOW_I,LOW_M,LOW_A,LOW_L,0};
    1.58 +static const UChar PK_INTEGER[]={LOW_I,LOW_N,LOW_T,LOW_E,LOW_G,LOW_E,LOW_R,0};
    1.59 +
    1.60 +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralRules)
    1.61 +UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralKeywordEnumeration)
    1.62 +
    1.63 +PluralRules::PluralRules(UErrorCode& /*status*/)
    1.64 +:   UObject(),
    1.65 +    mRules(NULL)
    1.66 +{
    1.67 +}
    1.68 +
    1.69 +PluralRules::PluralRules(const PluralRules& other)
    1.70 +: UObject(other),
    1.71 +    mRules(NULL)
    1.72 +{
    1.73 +    *this=other;
    1.74 +}
    1.75 +
    1.76 +PluralRules::~PluralRules() {
    1.77 +    delete mRules;
    1.78 +}
    1.79 +
    1.80 +PluralRules*
    1.81 +PluralRules::clone() const {
    1.82 +    return new PluralRules(*this);
    1.83 +}
    1.84 +
    1.85 +PluralRules&
    1.86 +PluralRules::operator=(const PluralRules& other) {
    1.87 +    if (this != &other) {
    1.88 +        delete mRules;
    1.89 +        if (other.mRules==NULL) {
    1.90 +            mRules = NULL;
    1.91 +        }
    1.92 +        else {
    1.93 +            mRules = new RuleChain(*other.mRules);
    1.94 +        }
    1.95 +    }
    1.96 +
    1.97 +    return *this;
    1.98 +}
    1.99 +
   1.100 +StringEnumeration* PluralRules::getAvailableLocales(UErrorCode &status) {
   1.101 +    StringEnumeration *result = new PluralAvailableLocalesEnumeration(status);
   1.102 +    if (result == NULL && U_SUCCESS(status)) {
   1.103 +        status = U_MEMORY_ALLOCATION_ERROR;
   1.104 +    }
   1.105 +    if (U_FAILURE(status)) {
   1.106 +        delete result;
   1.107 +        result = NULL;
   1.108 +    }
   1.109 +    return result;
   1.110 +}
   1.111 +
   1.112 +
   1.113 +PluralRules* U_EXPORT2
   1.114 +PluralRules::createRules(const UnicodeString& description, UErrorCode& status) {
   1.115 +    if (U_FAILURE(status)) {
   1.116 +        return NULL;
   1.117 +    }
   1.118 +
   1.119 +    PluralRuleParser parser;
   1.120 +    PluralRules *newRules = new PluralRules(status);
   1.121 +    if (U_SUCCESS(status) && newRules == NULL) {
   1.122 +        status = U_MEMORY_ALLOCATION_ERROR;
   1.123 +    }
   1.124 +    parser.parse(description, newRules, status);
   1.125 +    if (U_FAILURE(status)) {
   1.126 +        delete newRules;
   1.127 +        newRules = NULL;
   1.128 +    }
   1.129 +    return newRules;
   1.130 +}
   1.131 +
   1.132 +
   1.133 +PluralRules* U_EXPORT2
   1.134 +PluralRules::createDefaultRules(UErrorCode& status) {
   1.135 +    return createRules(UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1), status);
   1.136 +}
   1.137 +
   1.138 +PluralRules* U_EXPORT2
   1.139 +PluralRules::forLocale(const Locale& locale, UErrorCode& status) {
   1.140 +    return forLocale(locale, UPLURAL_TYPE_CARDINAL, status);
   1.141 +}
   1.142 +
   1.143 +PluralRules* U_EXPORT2
   1.144 +PluralRules::forLocale(const Locale& locale, UPluralType type, UErrorCode& status) {
   1.145 +    if (U_FAILURE(status)) {
   1.146 +        return NULL;
   1.147 +    }
   1.148 +    if (type >= UPLURAL_TYPE_COUNT) {
   1.149 +        status = U_ILLEGAL_ARGUMENT_ERROR;
   1.150 +        return NULL;
   1.151 +    }
   1.152 +    PluralRules *newObj = new PluralRules(status);
   1.153 +    if (newObj==NULL || U_FAILURE(status)) {
   1.154 +        delete newObj;
   1.155 +        return NULL;
   1.156 +    }
   1.157 +    UnicodeString locRule = newObj->getRuleFromResource(locale, type, status);
   1.158 +    // TODO: which errors, if any, should be returned?
   1.159 +    if (locRule.length() == 0) {
   1.160 +        // Locales with no specific rules (all numbers have the "other" category
   1.161 +        //   will return a U_MISSING_RESOURCE_ERROR at this point. This is not
   1.162 +        //   an error.
   1.163 +        locRule =  UnicodeString(PLURAL_DEFAULT_RULE);
   1.164 +        status = U_ZERO_ERROR;
   1.165 +    }
   1.166 +    PluralRuleParser parser;
   1.167 +    parser.parse(locRule, newObj, status);
   1.168 +        //  TODO: should rule parse errors be returned, or
   1.169 +        //        should we silently use default rules?
   1.170 +        //        Original impl used default rules.
   1.171 +        //        Ask the question to ICU Core.
   1.172 +
   1.173 +    return newObj;
   1.174 +}
   1.175 +
   1.176 +UnicodeString
   1.177 +PluralRules::select(int32_t number) const {
   1.178 +    return select(FixedDecimal(number));
   1.179 +}
   1.180 +
   1.181 +UnicodeString
   1.182 +PluralRules::select(double number) const {
   1.183 +    return select(FixedDecimal(number));
   1.184 +}
   1.185 +
   1.186 +UnicodeString
   1.187 +PluralRules::select(const FixedDecimal &number) const {
   1.188 +    if (mRules == NULL) {
   1.189 +        return UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1);
   1.190 +    }
   1.191 +    else {
   1.192 +        return mRules->select(number);
   1.193 +    }
   1.194 +}
   1.195 +
   1.196 +StringEnumeration*
   1.197 +PluralRules::getKeywords(UErrorCode& status) const {
   1.198 +    if (U_FAILURE(status))  return NULL;
   1.199 +    StringEnumeration* nameEnumerator = new PluralKeywordEnumeration(mRules, status);
   1.200 +    if (U_FAILURE(status)) {
   1.201 +      delete nameEnumerator;
   1.202 +      return NULL;
   1.203 +    }
   1.204 +
   1.205 +    return nameEnumerator;
   1.206 +}
   1.207 +
   1.208 +double
   1.209 +PluralRules::getUniqueKeywordValue(const UnicodeString& /* keyword */) {
   1.210 +  // Not Implemented.
   1.211 +  return UPLRULES_NO_UNIQUE_VALUE;
   1.212 +}
   1.213 +
   1.214 +int32_t
   1.215 +PluralRules::getAllKeywordValues(const UnicodeString & /* keyword */, double * /* dest */,
   1.216 +                                 int32_t /* destCapacity */, UErrorCode& error) {
   1.217 +    error = U_UNSUPPORTED_ERROR;
   1.218 +    return 0;
   1.219 +}
   1.220 +
   1.221 +    
   1.222 +static double scaleForInt(double d) {
   1.223 +    double scale = 1.0;
   1.224 +    while (d != floor(d)) {
   1.225 +        d = d * 10.0;
   1.226 +        scale = scale * 10.0;
   1.227 +    }
   1.228 +    return scale;
   1.229 +}
   1.230 +
   1.231 +static int32_t
   1.232 +getSamplesFromString(const UnicodeString &samples, double *dest,
   1.233 +                        int32_t destCapacity, UErrorCode& status) {
   1.234 +    int32_t sampleCount = 0;
   1.235 +    int32_t sampleStartIdx = 0;
   1.236 +    int32_t sampleEndIdx = 0;
   1.237 +
   1.238 +    //std::string ss;  // TODO: debugging.
   1.239 +    // std::cout << "PluralRules::getSamples(), samples = \"" << samples.toUTF8String(ss) << "\"\n";
   1.240 +    for (sampleCount = 0; sampleCount < destCapacity && sampleStartIdx < samples.length(); ) {
   1.241 +        sampleEndIdx = samples.indexOf(COMMA, sampleStartIdx);
   1.242 +        if (sampleEndIdx == -1) {
   1.243 +            sampleEndIdx = samples.length();
   1.244 +        }
   1.245 +        const UnicodeString &sampleRange = samples.tempSubStringBetween(sampleStartIdx, sampleEndIdx);
   1.246 +        // ss.erase();
   1.247 +        // std::cout << "PluralRules::getSamples(), samplesRange = \"" << sampleRange.toUTF8String(ss) << "\"\n";
   1.248 +        int32_t tildeIndex = sampleRange.indexOf(TILDE);
   1.249 +        if (tildeIndex < 0) {
   1.250 +            FixedDecimal fixed(sampleRange, status);
   1.251 +            double sampleValue = fixed.source;
   1.252 +            if (fixed.visibleDecimalDigitCount == 0 || sampleValue != floor(sampleValue)) {
   1.253 +                dest[sampleCount++] = sampleValue;
   1.254 +            }
   1.255 +        } else {
   1.256 +            
   1.257 +            FixedDecimal fixedLo(sampleRange.tempSubStringBetween(0, tildeIndex), status);
   1.258 +            FixedDecimal fixedHi(sampleRange.tempSubStringBetween(tildeIndex+1), status);
   1.259 +            double rangeLo = fixedLo.source;
   1.260 +            double rangeHi = fixedHi.source;
   1.261 +            if (U_FAILURE(status)) {
   1.262 +                break;
   1.263 +            }
   1.264 +            if (rangeHi < rangeLo) {
   1.265 +                status = U_INVALID_FORMAT_ERROR;
   1.266 +                break;
   1.267 +            }
   1.268 +
   1.269 +            // For ranges of samples with fraction decimal digits, scale the number up so that we
   1.270 +            //   are adding one in the units place. Avoids roundoffs from repetitive adds of tenths.
   1.271 +
   1.272 +            double scale = scaleForInt(rangeLo); 
   1.273 +            double t = scaleForInt(rangeHi);
   1.274 +            if (t > scale) {
   1.275 +                scale = t;
   1.276 +            }
   1.277 +            rangeLo *= scale;
   1.278 +            rangeHi *= scale;
   1.279 +            for (double n=rangeLo; n<=rangeHi; n+=1) {
   1.280 +                // Hack Alert: don't return any decimal samples with integer values that
   1.281 +                //    originated from a format with trailing decimals.
   1.282 +                //    This API is returning doubles, which can't distinguish having displayed
   1.283 +                //    zeros to the right of the decimal.
   1.284 +                //    This results in test failures with values mapping back to a different keyword.
   1.285 +                double sampleValue = n/scale;
   1.286 +                if (!(sampleValue == floor(sampleValue) && fixedLo.visibleDecimalDigitCount > 0)) {
   1.287 +                    dest[sampleCount++] = sampleValue;
   1.288 +                }
   1.289 +                if (sampleCount >= destCapacity) {
   1.290 +                    break;
   1.291 +                }
   1.292 +            }
   1.293 +        }
   1.294 +        sampleStartIdx = sampleEndIdx + 1;
   1.295 +    }
   1.296 +    return sampleCount;
   1.297 +}
   1.298 +
   1.299 +
   1.300 +int32_t
   1.301 +PluralRules::getSamples(const UnicodeString &keyword, double *dest,
   1.302 +                        int32_t destCapacity, UErrorCode& status) {
   1.303 +    RuleChain *rc = rulesForKeyword(keyword);
   1.304 +    if (rc == NULL || destCapacity == 0 || U_FAILURE(status)) {
   1.305 +        return 0;
   1.306 +    }
   1.307 +    int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, dest, destCapacity, status);
   1.308 +    if (numSamples == 0) { 
   1.309 +        numSamples = getSamplesFromString(rc->fDecimalSamples, dest, destCapacity, status);
   1.310 +    }
   1.311 +    return numSamples;
   1.312 +}
   1.313 +    
   1.314 +
   1.315 +RuleChain *PluralRules::rulesForKeyword(const UnicodeString &keyword) const {
   1.316 +    RuleChain *rc;
   1.317 +    for (rc = mRules; rc != NULL; rc = rc->fNext) {
   1.318 +        if (rc->fKeyword == keyword) {
   1.319 +            break;
   1.320 +        }
   1.321 +    }
   1.322 +    return rc;
   1.323 +}
   1.324 +
   1.325 +
   1.326 +UBool
   1.327 +PluralRules::isKeyword(const UnicodeString& keyword) const {
   1.328 +    if (0 == keyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
   1.329 +        return true;
   1.330 +    }
   1.331 +    return rulesForKeyword(keyword) != NULL;
   1.332 +}
   1.333 +
   1.334 +UnicodeString
   1.335 +PluralRules::getKeywordOther() const {
   1.336 +    return UnicodeString(TRUE, PLURAL_KEYWORD_OTHER, 5);
   1.337 +}
   1.338 +
   1.339 +UBool
   1.340 +PluralRules::operator==(const PluralRules& other) const  {
   1.341 +    const UnicodeString *ptrKeyword;
   1.342 +    UErrorCode status= U_ZERO_ERROR;
   1.343 +
   1.344 +    if ( this == &other ) {
   1.345 +        return TRUE;
   1.346 +    }
   1.347 +    LocalPointer<StringEnumeration> myKeywordList(getKeywords(status));
   1.348 +    LocalPointer<StringEnumeration> otherKeywordList(other.getKeywords(status));
   1.349 +    if (U_FAILURE(status)) {
   1.350 +        return FALSE;
   1.351 +    }
   1.352 +
   1.353 +    if (myKeywordList->count(status)!=otherKeywordList->count(status)) {
   1.354 +        return FALSE;
   1.355 +    }
   1.356 +    myKeywordList->reset(status);
   1.357 +    while ((ptrKeyword=myKeywordList->snext(status))!=NULL) {
   1.358 +        if (!other.isKeyword(*ptrKeyword)) {
   1.359 +            return FALSE;
   1.360 +        }
   1.361 +    }
   1.362 +    otherKeywordList->reset(status);
   1.363 +    while ((ptrKeyword=otherKeywordList->snext(status))!=NULL) {
   1.364 +        if (!this->isKeyword(*ptrKeyword)) {
   1.365 +            return FALSE;
   1.366 +        }
   1.367 +    }
   1.368 +    if (U_FAILURE(status)) {
   1.369 +        return FALSE;
   1.370 +    }
   1.371 +
   1.372 +    return TRUE;
   1.373 +}
   1.374 +
   1.375 +
   1.376 +void
   1.377 +PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErrorCode &status)
   1.378 +{
   1.379 +    if (U_FAILURE(status)) {
   1.380 +        return;
   1.381 +    }
   1.382 +    U_ASSERT(ruleIndex == 0);    // Parsers are good for a single use only!
   1.383 +    ruleSrc = &ruleData;
   1.384 +
   1.385 +    while (ruleIndex< ruleSrc->length()) {
   1.386 +        getNextToken(status);
   1.387 +        if (U_FAILURE(status)) {
   1.388 +            return;
   1.389 +        }
   1.390 +        checkSyntax(status);
   1.391 +        if (U_FAILURE(status)) {
   1.392 +            return;
   1.393 +        }
   1.394 +        switch (type) {
   1.395 +        case tAnd:
   1.396 +            U_ASSERT(curAndConstraint != NULL);
   1.397 +            curAndConstraint = curAndConstraint->add();
   1.398 +            break;
   1.399 +        case tOr:
   1.400 +            {
   1.401 +                U_ASSERT(currentChain != NULL);
   1.402 +                OrConstraint *orNode=currentChain->ruleHeader;
   1.403 +                while (orNode->next != NULL) {
   1.404 +                    orNode = orNode->next;
   1.405 +                }
   1.406 +                orNode->next= new OrConstraint();
   1.407 +                orNode=orNode->next;
   1.408 +                orNode->next=NULL;
   1.409 +                curAndConstraint = orNode->add();
   1.410 +            }
   1.411 +            break;
   1.412 +        case tIs:
   1.413 +            U_ASSERT(curAndConstraint != NULL);
   1.414 +            U_ASSERT(curAndConstraint->value == -1);
   1.415 +            U_ASSERT(curAndConstraint->rangeList == NULL);
   1.416 +            break;
   1.417 +        case tNot:
   1.418 +            U_ASSERT(curAndConstraint != NULL);
   1.419 +            curAndConstraint->negated=TRUE;
   1.420 +            break;
   1.421 +
   1.422 +        case tNotEqual:
   1.423 +            curAndConstraint->negated=TRUE;
   1.424 +        case tIn:
   1.425 +        case tWithin:
   1.426 +        case tEqual:
   1.427 +            U_ASSERT(curAndConstraint != NULL);
   1.428 +            curAndConstraint->rangeList = new UVector32(status);
   1.429 +            curAndConstraint->rangeList->addElement(-1, status);  // range Low
   1.430 +            curAndConstraint->rangeList->addElement(-1, status);  // range Hi
   1.431 +            rangeLowIdx = 0;
   1.432 +            rangeHiIdx  = 1;
   1.433 +            curAndConstraint->value=PLURAL_RANGE_HIGH;
   1.434 +            curAndConstraint->integerOnly = (type != tWithin);
   1.435 +            break;
   1.436 +        case tNumber:
   1.437 +            U_ASSERT(curAndConstraint != NULL);
   1.438 +            if ( (curAndConstraint->op==AndConstraint::MOD)&&
   1.439 +                 (curAndConstraint->opNum == -1 ) ) {
   1.440 +                curAndConstraint->opNum=getNumberValue(token);
   1.441 +            }
   1.442 +            else {
   1.443 +                if (curAndConstraint->rangeList == NULL) {
   1.444 +                    // this is for an 'is' rule
   1.445 +                    curAndConstraint->value = getNumberValue(token);
   1.446 +                } else {
   1.447 +                    // this is for an 'in' or 'within' rule
   1.448 +                    if (curAndConstraint->rangeList->elementAti(rangeLowIdx) == -1) {
   1.449 +                        curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeLowIdx);
   1.450 +                        curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeHiIdx);
   1.451 +                    }
   1.452 +                    else {
   1.453 +                        curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeHiIdx);
   1.454 +                        if (curAndConstraint->rangeList->elementAti(rangeLowIdx) > 
   1.455 +                                curAndConstraint->rangeList->elementAti(rangeHiIdx)) {
   1.456 +                            // Range Lower bound > Range Upper bound.
   1.457 +                            // U_UNEXPECTED_TOKEN seems a little funny, but it is consistently
   1.458 +                            // used for all plural rule parse errors.
   1.459 +                            status = U_UNEXPECTED_TOKEN;
   1.460 +                            break;
   1.461 +                        }
   1.462 +                    }
   1.463 +                }
   1.464 +            }
   1.465 +            break;
   1.466 +        case tComma:
   1.467 +            // TODO: rule syntax checking is inadequate, can happen with badly formed rules.
   1.468 +            //       Catch cases like "n mod 10, is 1" here instead.
   1.469 +            if (curAndConstraint == NULL || curAndConstraint->rangeList == NULL) {
   1.470 +                status = U_UNEXPECTED_TOKEN;
   1.471 +                break;
   1.472 +            }
   1.473 +            U_ASSERT(curAndConstraint->rangeList->size() >= 2);
   1.474 +            rangeLowIdx = curAndConstraint->rangeList->size();
   1.475 +            curAndConstraint->rangeList->addElement(-1, status);  // range Low
   1.476 +            rangeHiIdx = curAndConstraint->rangeList->size();
   1.477 +            curAndConstraint->rangeList->addElement(-1, status);  // range Hi
   1.478 +            break;
   1.479 +        case tMod:
   1.480 +            U_ASSERT(curAndConstraint != NULL);
   1.481 +            curAndConstraint->op=AndConstraint::MOD;
   1.482 +            break;
   1.483 +        case tVariableN:
   1.484 +        case tVariableI:
   1.485 +        case tVariableF:
   1.486 +        case tVariableT:
   1.487 +        case tVariableV:
   1.488 +            U_ASSERT(curAndConstraint != NULL);
   1.489 +            curAndConstraint->digitsType = type;
   1.490 +            break;
   1.491 +        case tKeyword:
   1.492 +            {
   1.493 +            RuleChain *newChain = new RuleChain;
   1.494 +            if (newChain == NULL) {
   1.495 +                status = U_MEMORY_ALLOCATION_ERROR;
   1.496 +                break;
   1.497 +            }
   1.498 +            newChain->fKeyword = token;
   1.499 +            if (prules->mRules == NULL) {
   1.500 +                prules->mRules = newChain;
   1.501 +            } else {
   1.502 +                // The new rule chain goes at the end of the linked list of rule chains,
   1.503 +                //   unless there is an "other" keyword & chain. "other" must remain last.
   1.504 +                RuleChain *insertAfter = prules->mRules;
   1.505 +                while (insertAfter->fNext!=NULL && 
   1.506 +                       insertAfter->fNext->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5) != 0 ){
   1.507 +                    insertAfter=insertAfter->fNext;
   1.508 +                }
   1.509 +                newChain->fNext = insertAfter->fNext;
   1.510 +                insertAfter->fNext = newChain;
   1.511 +            }
   1.512 +            OrConstraint *orNode = new OrConstraint();
   1.513 +            newChain->ruleHeader = orNode;
   1.514 +            curAndConstraint = orNode->add();
   1.515 +            currentChain = newChain;
   1.516 +            }
   1.517 +            break;
   1.518 +
   1.519 +        case tInteger:
   1.520 +            for (;;) {
   1.521 +                getNextToken(status);
   1.522 +                if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) {
   1.523 +                    break;
   1.524 +                }
   1.525 +                if (type == tEllipsis) {
   1.526 +                    currentChain->fIntegerSamplesUnbounded = TRUE;
   1.527 +                    continue;
   1.528 +                }
   1.529 +                currentChain->fIntegerSamples.append(token);
   1.530 +            }
   1.531 +            break;
   1.532 +
   1.533 +        case tDecimal:
   1.534 +            for (;;) {
   1.535 +                getNextToken(status);
   1.536 +                if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) {
   1.537 +                    break;
   1.538 +                }
   1.539 +                if (type == tEllipsis) {
   1.540 +                    currentChain->fDecimalSamplesUnbounded = TRUE;
   1.541 +                    continue;
   1.542 +                }
   1.543 +                currentChain->fDecimalSamples.append(token);
   1.544 +            }
   1.545 +            break;
   1.546 +                
   1.547 +        default:
   1.548 +            break;
   1.549 +        }
   1.550 +        prevType=type;
   1.551 +        if (U_FAILURE(status)) {
   1.552 +            break;
   1.553 +        }
   1.554 +    }
   1.555 +}
   1.556 +
   1.557 +UnicodeString
   1.558 +PluralRules::getRuleFromResource(const Locale& locale, UPluralType type, UErrorCode& errCode) {
   1.559 +    UnicodeString emptyStr;
   1.560 +
   1.561 +    if (U_FAILURE(errCode)) {
   1.562 +        return emptyStr;
   1.563 +    }
   1.564 +    LocalUResourceBundlePointer rb(ures_openDirect(NULL, "plurals", &errCode));
   1.565 +    if(U_FAILURE(errCode)) {
   1.566 +        return emptyStr;
   1.567 +    }
   1.568 +    const char *typeKey;
   1.569 +    switch (type) {
   1.570 +    case UPLURAL_TYPE_CARDINAL:
   1.571 +        typeKey = "locales";
   1.572 +        break;
   1.573 +    case UPLURAL_TYPE_ORDINAL:
   1.574 +        typeKey = "locales_ordinals";
   1.575 +        break;
   1.576 +    default:
   1.577 +        // Must not occur: The caller should have checked for valid types.
   1.578 +        errCode = U_ILLEGAL_ARGUMENT_ERROR;
   1.579 +        return emptyStr;
   1.580 +    }
   1.581 +    LocalUResourceBundlePointer locRes(ures_getByKey(rb.getAlias(), typeKey, NULL, &errCode));
   1.582 +    if(U_FAILURE(errCode)) {
   1.583 +        return emptyStr;
   1.584 +    }
   1.585 +    int32_t resLen=0;
   1.586 +    const char *curLocaleName=locale.getName();
   1.587 +    const UChar* s = ures_getStringByKey(locRes.getAlias(), curLocaleName, &resLen, &errCode);
   1.588 +
   1.589 +    if (s == NULL) {
   1.590 +        // Check parent locales.
   1.591 +        UErrorCode status = U_ZERO_ERROR;
   1.592 +        char parentLocaleName[ULOC_FULLNAME_CAPACITY];
   1.593 +        const char *curLocaleName=locale.getName();
   1.594 +        uprv_strcpy(parentLocaleName, curLocaleName);
   1.595 +
   1.596 +        while (uloc_getParent(parentLocaleName, parentLocaleName,
   1.597 +                                       ULOC_FULLNAME_CAPACITY, &status) > 0) {
   1.598 +            resLen=0;
   1.599 +            s = ures_getStringByKey(locRes.getAlias(), parentLocaleName, &resLen, &status);
   1.600 +            if (s != NULL) {
   1.601 +                errCode = U_ZERO_ERROR;
   1.602 +                break;
   1.603 +            }
   1.604 +            status = U_ZERO_ERROR;
   1.605 +        }
   1.606 +    }
   1.607 +    if (s==NULL) {
   1.608 +        return emptyStr;
   1.609 +    }
   1.610 +
   1.611 +    char setKey[256];
   1.612 +    u_UCharsToChars(s, setKey, resLen + 1);
   1.613 +    // printf("\n PluralRule: %s\n", setKey);
   1.614 +
   1.615 +    LocalUResourceBundlePointer ruleRes(ures_getByKey(rb.getAlias(), "rules", NULL, &errCode));
   1.616 +    if(U_FAILURE(errCode)) {
   1.617 +        return emptyStr;
   1.618 +    }
   1.619 +    LocalUResourceBundlePointer setRes(ures_getByKey(ruleRes.getAlias(), setKey, NULL, &errCode));
   1.620 +    if (U_FAILURE(errCode)) {
   1.621 +        return emptyStr;
   1.622 +    }
   1.623 +
   1.624 +    int32_t numberKeys = ures_getSize(setRes.getAlias());
   1.625 +    UnicodeString result;
   1.626 +    const char *key=NULL;
   1.627 +    for(int32_t i=0; i<numberKeys; ++i) {   // Keys are zero, one, few, ...
   1.628 +        UnicodeString rules = ures_getNextUnicodeString(setRes.getAlias(), &key, &errCode);
   1.629 +        UnicodeString uKey(key, -1, US_INV);
   1.630 +        result.append(uKey);
   1.631 +        result.append(COLON);
   1.632 +        result.append(rules);
   1.633 +        result.append(SEMI_COLON);
   1.634 +    }
   1.635 +    return result;
   1.636 +}
   1.637 +
   1.638 +
   1.639 +UnicodeString
   1.640 +PluralRules::getRules() const {
   1.641 +    UnicodeString rules;
   1.642 +    if (mRules != NULL) {
   1.643 +        mRules->dumpRules(rules);
   1.644 +    }
   1.645 +    return rules;
   1.646 +}
   1.647 +
   1.648 +
   1.649 +AndConstraint::AndConstraint() {
   1.650 +    op = AndConstraint::NONE;
   1.651 +    opNum=-1;
   1.652 +    value = -1;
   1.653 +    rangeList = NULL;
   1.654 +    negated = FALSE;
   1.655 +    integerOnly = FALSE;
   1.656 +    digitsType = none;
   1.657 +    next=NULL;
   1.658 +}
   1.659 +
   1.660 +
   1.661 +AndConstraint::AndConstraint(const AndConstraint& other) {
   1.662 +    this->op = other.op;
   1.663 +    this->opNum=other.opNum;
   1.664 +    this->value=other.value;
   1.665 +    this->rangeList=NULL;
   1.666 +    if (other.rangeList != NULL) {
   1.667 +        UErrorCode status = U_ZERO_ERROR;
   1.668 +        this->rangeList = new UVector32(status);
   1.669 +        this->rangeList->assign(*other.rangeList, status);
   1.670 +    }
   1.671 +    this->integerOnly=other.integerOnly;
   1.672 +    this->negated=other.negated;
   1.673 +    this->digitsType = other.digitsType;
   1.674 +    if (other.next==NULL) {
   1.675 +        this->next=NULL;
   1.676 +    }
   1.677 +    else {
   1.678 +        this->next = new AndConstraint(*other.next);
   1.679 +    }
   1.680 +}
   1.681 +
   1.682 +AndConstraint::~AndConstraint() {
   1.683 +    delete rangeList;
   1.684 +    if (next!=NULL) {
   1.685 +        delete next;
   1.686 +    }
   1.687 +}
   1.688 +
   1.689 +
   1.690 +UBool
   1.691 +AndConstraint::isFulfilled(const FixedDecimal &number) {
   1.692 +    UBool result = TRUE;
   1.693 +    if (digitsType == none) {
   1.694 +        // An empty AndConstraint, created by a rule with a keyword but no following expression.
   1.695 +        return TRUE;
   1.696 +    }
   1.697 +    double n = number.get(digitsType);  // pulls n | i | v | f value for the number.
   1.698 +                                        // Will always be positive.
   1.699 +                                        // May be non-integer (n option only)
   1.700 +    do {
   1.701 +        if (integerOnly && n != uprv_floor(n)) {
   1.702 +            result = FALSE;
   1.703 +            break;
   1.704 +        }
   1.705 +
   1.706 +        if (op == MOD) {
   1.707 +            n = fmod(n, opNum);
   1.708 +        }
   1.709 +        if (rangeList == NULL) {
   1.710 +            result = value == -1 ||    // empty rule
   1.711 +                     n == value;       //  'is' rule
   1.712 +            break;
   1.713 +        }
   1.714 +        result = FALSE;                // 'in' or 'within' rule
   1.715 +        for (int32_t r=0; r<rangeList->size(); r+=2) {
   1.716 +            if (rangeList->elementAti(r) <= n && n <= rangeList->elementAti(r+1)) {
   1.717 +                result = TRUE;
   1.718 +                break;
   1.719 +            }
   1.720 +        }
   1.721 +    } while (FALSE);
   1.722 +
   1.723 +    if (negated) {
   1.724 +        result = !result;
   1.725 +    }
   1.726 +    return result;
   1.727 +}
   1.728 +
   1.729 +
   1.730 +AndConstraint*
   1.731 +AndConstraint::add()
   1.732 +{
   1.733 +    this->next = new AndConstraint();
   1.734 +    return this->next;
   1.735 +}
   1.736 +
   1.737 +OrConstraint::OrConstraint() {
   1.738 +    childNode=NULL;
   1.739 +    next=NULL;
   1.740 +}
   1.741 +
   1.742 +OrConstraint::OrConstraint(const OrConstraint& other) {
   1.743 +    if ( other.childNode == NULL ) {
   1.744 +        this->childNode = NULL;
   1.745 +    }
   1.746 +    else {
   1.747 +        this->childNode = new AndConstraint(*(other.childNode));
   1.748 +    }
   1.749 +    if (other.next == NULL ) {
   1.750 +        this->next = NULL;
   1.751 +    }
   1.752 +    else {
   1.753 +        this->next = new OrConstraint(*(other.next));
   1.754 +    }
   1.755 +}
   1.756 +
   1.757 +OrConstraint::~OrConstraint() {
   1.758 +    if (childNode!=NULL) {
   1.759 +        delete childNode;
   1.760 +    }
   1.761 +    if (next!=NULL) {
   1.762 +        delete next;
   1.763 +    }
   1.764 +}
   1.765 +
   1.766 +AndConstraint*
   1.767 +OrConstraint::add()
   1.768 +{
   1.769 +    OrConstraint *curOrConstraint=this;
   1.770 +    {
   1.771 +        while (curOrConstraint->next!=NULL) {
   1.772 +            curOrConstraint = curOrConstraint->next;
   1.773 +        }
   1.774 +        U_ASSERT(curOrConstraint->childNode == NULL);
   1.775 +        curOrConstraint->childNode = new AndConstraint();
   1.776 +    }
   1.777 +    return curOrConstraint->childNode;
   1.778 +}
   1.779 +
   1.780 +UBool
   1.781 +OrConstraint::isFulfilled(const FixedDecimal &number) {
   1.782 +    OrConstraint* orRule=this;
   1.783 +    UBool result=FALSE;
   1.784 +
   1.785 +    while (orRule!=NULL && !result) {
   1.786 +        result=TRUE;
   1.787 +        AndConstraint* andRule = orRule->childNode;
   1.788 +        while (andRule!=NULL && result) {
   1.789 +            result = andRule->isFulfilled(number);
   1.790 +            andRule=andRule->next;
   1.791 +        }
   1.792 +        orRule = orRule->next;
   1.793 +    }
   1.794 +
   1.795 +    return result;
   1.796 +}
   1.797 +
   1.798 +
   1.799 +RuleChain::RuleChain(): fKeyword(), fNext(NULL), ruleHeader(NULL), fDecimalSamples(), fIntegerSamples(), 
   1.800 +                        fDecimalSamplesUnbounded(FALSE), fIntegerSamplesUnbounded(FALSE) {
   1.801 +}
   1.802 +
   1.803 +RuleChain::RuleChain(const RuleChain& other) : 
   1.804 +        fKeyword(other.fKeyword), fNext(NULL), ruleHeader(NULL), fDecimalSamples(other.fDecimalSamples),
   1.805 +        fIntegerSamples(other.fIntegerSamples), fDecimalSamplesUnbounded(other.fDecimalSamplesUnbounded), 
   1.806 +        fIntegerSamplesUnbounded(other.fIntegerSamplesUnbounded) {
   1.807 +    if (other.ruleHeader != NULL) {
   1.808 +        this->ruleHeader = new OrConstraint(*(other.ruleHeader));
   1.809 +    }
   1.810 +    if (other.fNext != NULL ) {
   1.811 +        this->fNext = new RuleChain(*other.fNext);
   1.812 +    }
   1.813 +}
   1.814 +
   1.815 +RuleChain::~RuleChain() {
   1.816 +    delete fNext;
   1.817 +    delete ruleHeader;
   1.818 +}
   1.819 +
   1.820 +
   1.821 +UnicodeString
   1.822 +RuleChain::select(const FixedDecimal &number) const {
   1.823 +    if (!number.isNanOrInfinity) {
   1.824 +        for (const RuleChain *rules = this; rules != NULL; rules = rules->fNext) {
   1.825 +             if (rules->ruleHeader->isFulfilled(number)) {
   1.826 +                 return rules->fKeyword;
   1.827 +             }
   1.828 +        }
   1.829 +    }
   1.830 +    return UnicodeString(TRUE, PLURAL_KEYWORD_OTHER, 5);
   1.831 +}
   1.832 +
   1.833 +static UnicodeString tokenString(tokenType tok) {
   1.834 +    UnicodeString s;
   1.835 +    switch (tok) {
   1.836 +      case tVariableN:
   1.837 +        s.append(LOW_N); break;
   1.838 +      case tVariableI:
   1.839 +        s.append(LOW_I); break;
   1.840 +      case tVariableF:
   1.841 +        s.append(LOW_F); break;
   1.842 +      case tVariableV:
   1.843 +        s.append(LOW_V); break;
   1.844 +      case tVariableT:
   1.845 +        s.append(LOW_T); break;
   1.846 +      default:
   1.847 +        s.append(TILDE);
   1.848 +    }
   1.849 +    return s;
   1.850 +}
   1.851 +
   1.852 +void
   1.853 +RuleChain::dumpRules(UnicodeString& result) {
   1.854 +    UChar digitString[16];
   1.855 +
   1.856 +    if ( ruleHeader != NULL ) {
   1.857 +        result +=  fKeyword;
   1.858 +        result += COLON;
   1.859 +        result += SPACE;
   1.860 +        OrConstraint* orRule=ruleHeader;
   1.861 +        while ( orRule != NULL ) {
   1.862 +            AndConstraint* andRule=orRule->childNode;
   1.863 +            while ( andRule != NULL ) {
   1.864 +                if ((andRule->op==AndConstraint::NONE) &&  (andRule->rangeList==NULL) && (andRule->value == -1)) {
   1.865 +                    // Empty Rules.
   1.866 +                } else if ( (andRule->op==AndConstraint::NONE) && (andRule->rangeList==NULL) ) {
   1.867 +                    result += tokenString(andRule->digitsType);
   1.868 +                    result += UNICODE_STRING_SIMPLE(" is ");
   1.869 +                    if (andRule->negated) {
   1.870 +                        result += UNICODE_STRING_SIMPLE("not ");
   1.871 +                    }
   1.872 +                    uprv_itou(digitString,16, andRule->value,10,0);
   1.873 +                    result += UnicodeString(digitString);
   1.874 +                }
   1.875 +                else {
   1.876 +                    result += tokenString(andRule->digitsType);
   1.877 +                    result += SPACE;
   1.878 +                    if (andRule->op==AndConstraint::MOD) {
   1.879 +                        result += UNICODE_STRING_SIMPLE("mod ");
   1.880 +                        uprv_itou(digitString,16, andRule->opNum,10,0);
   1.881 +                        result += UnicodeString(digitString);
   1.882 +                    }
   1.883 +                    if (andRule->rangeList==NULL) {
   1.884 +                        if (andRule->negated) {
   1.885 +                            result += UNICODE_STRING_SIMPLE(" is not ");
   1.886 +                            uprv_itou(digitString,16, andRule->value,10,0);
   1.887 +                            result += UnicodeString(digitString);
   1.888 +                        }
   1.889 +                        else {
   1.890 +                            result += UNICODE_STRING_SIMPLE(" is ");
   1.891 +                            uprv_itou(digitString,16, andRule->value,10,0);
   1.892 +                            result += UnicodeString(digitString);
   1.893 +                        }
   1.894 +                    }
   1.895 +                    else {
   1.896 +                        if (andRule->negated) {
   1.897 +                            if ( andRule->integerOnly ) {
   1.898 +                                result += UNICODE_STRING_SIMPLE(" not in ");
   1.899 +                            }
   1.900 +                            else {
   1.901 +                                result += UNICODE_STRING_SIMPLE(" not within ");
   1.902 +                            }
   1.903 +                        }
   1.904 +                        else {
   1.905 +                            if ( andRule->integerOnly ) {
   1.906 +                                result += UNICODE_STRING_SIMPLE(" in ");
   1.907 +                            }
   1.908 +                            else {
   1.909 +                                result += UNICODE_STRING_SIMPLE(" within ");
   1.910 +                            }
   1.911 +                        }
   1.912 +                        for (int32_t r=0; r<andRule->rangeList->size(); r+=2) {
   1.913 +                            int32_t rangeLo = andRule->rangeList->elementAti(r);
   1.914 +                            int32_t rangeHi = andRule->rangeList->elementAti(r+1);
   1.915 +                            uprv_itou(digitString,16, rangeLo, 10, 0);
   1.916 +                            result += UnicodeString(digitString);
   1.917 +                            result += UNICODE_STRING_SIMPLE("..");
   1.918 +                            uprv_itou(digitString,16, rangeHi, 10,0);
   1.919 +                            result += UnicodeString(digitString);
   1.920 +                            if (r+2 < andRule->rangeList->size()) {
   1.921 +                                result += UNICODE_STRING_SIMPLE(", ");
   1.922 +                            }
   1.923 +                        }
   1.924 +                    }
   1.925 +                }
   1.926 +                if ( (andRule=andRule->next) != NULL) {
   1.927 +                    result += UNICODE_STRING_SIMPLE(" and ");
   1.928 +                }
   1.929 +            }
   1.930 +            if ( (orRule = orRule->next) != NULL ) {
   1.931 +                result += UNICODE_STRING_SIMPLE(" or ");
   1.932 +            }
   1.933 +        }
   1.934 +    }
   1.935 +    if ( fNext != NULL ) {
   1.936 +        result += UNICODE_STRING_SIMPLE("; ");
   1.937 +        fNext->dumpRules(result);
   1.938 +    }
   1.939 +}
   1.940 +
   1.941 +
   1.942 +UErrorCode
   1.943 +RuleChain::getKeywords(int32_t capacityOfKeywords, UnicodeString* keywords, int32_t& arraySize) const {
   1.944 +    if ( arraySize < capacityOfKeywords-1 ) {
   1.945 +        keywords[arraySize++]=fKeyword;
   1.946 +    }
   1.947 +    else {
   1.948 +        return U_BUFFER_OVERFLOW_ERROR;
   1.949 +    }
   1.950 +
   1.951 +    if ( fNext != NULL ) {
   1.952 +        return fNext->getKeywords(capacityOfKeywords, keywords, arraySize);
   1.953 +    }
   1.954 +    else {
   1.955 +        return U_ZERO_ERROR;
   1.956 +    }
   1.957 +}
   1.958 +
   1.959 +UBool
   1.960 +RuleChain::isKeyword(const UnicodeString& keywordParam) const {
   1.961 +    if ( fKeyword == keywordParam ) {
   1.962 +        return TRUE;
   1.963 +    }
   1.964 +
   1.965 +    if ( fNext != NULL ) {
   1.966 +        return fNext->isKeyword(keywordParam);
   1.967 +    }
   1.968 +    else {
   1.969 +        return FALSE;
   1.970 +    }
   1.971 +}
   1.972 +
   1.973 +
   1.974 +PluralRuleParser::PluralRuleParser() : 
   1.975 +        ruleIndex(0), token(), type(none), prevType(none), 
   1.976 +        curAndConstraint(NULL), currentChain(NULL), rangeLowIdx(-1), rangeHiIdx(-1)  
   1.977 +{
   1.978 +}
   1.979 +
   1.980 +PluralRuleParser::~PluralRuleParser() {
   1.981 +}
   1.982 +
   1.983 +
   1.984 +int32_t
   1.985 +PluralRuleParser::getNumberValue(const UnicodeString& token) {
   1.986 +    int32_t i;
   1.987 +    char digits[128];
   1.988 +
   1.989 +    i = token.extract(0, token.length(), digits, ARRAY_SIZE(digits), US_INV);
   1.990 +    digits[i]='\0';
   1.991 +
   1.992 +    return((int32_t)atoi(digits));
   1.993 +}
   1.994 +
   1.995 +
   1.996 +void
   1.997 +PluralRuleParser::checkSyntax(UErrorCode &status)
   1.998 +{
   1.999 +    if (U_FAILURE(status)) {
  1.1000 +        return;
  1.1001 +    }
  1.1002 +    if (!(prevType==none || prevType==tSemiColon)) {
  1.1003 +        type = getKeyType(token, type);  // Switch token type from tKeyword if we scanned a reserved word,
  1.1004 +                                               //   and we are not at the start of a rule, where a
  1.1005 +                                               //   keyword is expected.
  1.1006 +    }
  1.1007 +
  1.1008 +    switch(prevType) {
  1.1009 +    case none:
  1.1010 +    case tSemiColon:
  1.1011 +        if (type!=tKeyword && type != tEOF) {
  1.1012 +            status = U_UNEXPECTED_TOKEN;
  1.1013 +        }
  1.1014 +        break;
  1.1015 +    case tVariableN:
  1.1016 +    case tVariableI:
  1.1017 +    case tVariableF:
  1.1018 +    case tVariableT:
  1.1019 +    case tVariableV:
  1.1020 +        if (type != tIs && type != tMod && type != tIn &&
  1.1021 +            type != tNot && type != tWithin && type != tEqual && type != tNotEqual) {
  1.1022 +            status = U_UNEXPECTED_TOKEN;
  1.1023 +        }
  1.1024 +        break;
  1.1025 +    case tKeyword:
  1.1026 +        if (type != tColon) {
  1.1027 +            status = U_UNEXPECTED_TOKEN;
  1.1028 +        }
  1.1029 +        break;
  1.1030 +    case tColon:
  1.1031 +        if (!(type == tVariableN ||
  1.1032 +              type == tVariableI ||
  1.1033 +              type == tVariableF ||
  1.1034 +              type == tVariableT ||
  1.1035 +              type == tVariableV ||
  1.1036 +              type == tAt)) {
  1.1037 +            status = U_UNEXPECTED_TOKEN;
  1.1038 +        }
  1.1039 +        break;
  1.1040 +    case tIs:
  1.1041 +        if ( type != tNumber && type != tNot) {
  1.1042 +            status = U_UNEXPECTED_TOKEN;
  1.1043 +        }
  1.1044 +        break;
  1.1045 +    case tNot:
  1.1046 +        if (type != tNumber && type != tIn && type != tWithin) {
  1.1047 +            status = U_UNEXPECTED_TOKEN;
  1.1048 +        }
  1.1049 +        break;
  1.1050 +    case tMod:
  1.1051 +    case tDot2:
  1.1052 +    case tIn:
  1.1053 +    case tWithin:
  1.1054 +    case tEqual:
  1.1055 +    case tNotEqual:
  1.1056 +        if (type != tNumber) {
  1.1057 +            status = U_UNEXPECTED_TOKEN;
  1.1058 +        }
  1.1059 +        break;
  1.1060 +    case tAnd:
  1.1061 +    case tOr:
  1.1062 +        if ( type != tVariableN &&
  1.1063 +             type != tVariableI &&
  1.1064 +             type != tVariableF &&
  1.1065 +             type != tVariableT &&
  1.1066 +             type != tVariableV) {
  1.1067 +            status = U_UNEXPECTED_TOKEN;
  1.1068 +        }
  1.1069 +        break;
  1.1070 +    case tComma:
  1.1071 +        if (type != tNumber) {
  1.1072 +            status = U_UNEXPECTED_TOKEN;
  1.1073 +        }
  1.1074 +        break;
  1.1075 +    case tNumber:
  1.1076 +        if (type != tDot2  && type != tSemiColon && type != tIs       && type != tNot    &&
  1.1077 +            type != tIn    && type != tEqual     && type != tNotEqual && type != tWithin && 
  1.1078 +            type != tAnd   && type != tOr        && type != tComma    && type != tAt     && 
  1.1079 +            type != tEOF)
  1.1080 +        {
  1.1081 +            status = U_UNEXPECTED_TOKEN;
  1.1082 +        }
  1.1083 +        // TODO: a comma following a number that is not part of a range will be allowed.
  1.1084 +        //       It's not the only case of this sort of thing. Parser needs a re-write.
  1.1085 +        break;
  1.1086 +    case tAt:
  1.1087 +        if (type != tDecimal && type != tInteger) {
  1.1088 +            status = U_UNEXPECTED_TOKEN;
  1.1089 +        }
  1.1090 +        break;
  1.1091 +    default:
  1.1092 +        status = U_UNEXPECTED_TOKEN;
  1.1093 +        break;
  1.1094 +    }
  1.1095 +}
  1.1096 +
  1.1097 +
  1.1098 +/*
  1.1099 + *  Scan the next token from the input rules.
  1.1100 + *     rules and returned token type are in the parser state variables.
  1.1101 + */
  1.1102 +void
  1.1103 +PluralRuleParser::getNextToken(UErrorCode &status)
  1.1104 +{
  1.1105 +    if (U_FAILURE(status)) {
  1.1106 +        return;
  1.1107 +    }
  1.1108 +
  1.1109 +    UChar ch;
  1.1110 +    while (ruleIndex < ruleSrc->length()) {
  1.1111 +        ch = ruleSrc->charAt(ruleIndex);
  1.1112 +        type = charType(ch);
  1.1113 +        if (type != tSpace) {
  1.1114 +            break;
  1.1115 +        }
  1.1116 +        ++(ruleIndex);
  1.1117 +    }
  1.1118 +    if (ruleIndex >= ruleSrc->length()) {
  1.1119 +        type = tEOF;
  1.1120 +        return;
  1.1121 +    }
  1.1122 +    int32_t curIndex= ruleIndex;
  1.1123 +        
  1.1124 +    switch (type) {
  1.1125 +      case tColon:
  1.1126 +      case tSemiColon:
  1.1127 +      case tComma:
  1.1128 +      case tEllipsis:
  1.1129 +      case tTilde:   // scanned '~'
  1.1130 +      case tAt:      // scanned '@'
  1.1131 +      case tEqual:   // scanned '='
  1.1132 +      case tMod:     // scanned '%'
  1.1133 +        // Single character tokens.
  1.1134 +        ++curIndex;
  1.1135 +        break;
  1.1136 +
  1.1137 +      case tNotEqual:  // scanned '!'
  1.1138 +        if (ruleSrc->charAt(curIndex+1) == EQUALS) {
  1.1139 +            curIndex += 2;
  1.1140 +        } else {
  1.1141 +            type = none;
  1.1142 +            curIndex += 1;
  1.1143 +        }
  1.1144 +        break;
  1.1145 +
  1.1146 +      case tKeyword:
  1.1147 +         while (type == tKeyword && ++curIndex < ruleSrc->length()) {
  1.1148 +             ch = ruleSrc->charAt(curIndex);
  1.1149 +             type = charType(ch);
  1.1150 +         }
  1.1151 +         type = tKeyword;
  1.1152 +         break;
  1.1153 +
  1.1154 +      case tNumber:
  1.1155 +         while (type == tNumber && ++curIndex < ruleSrc->length()) {
  1.1156 +             ch = ruleSrc->charAt(curIndex);
  1.1157 +             type = charType(ch);
  1.1158 +         }
  1.1159 +         type = tNumber;
  1.1160 +         break;
  1.1161 +
  1.1162 +       case tDot:
  1.1163 +         // We could be looking at either ".." in a range, or "..." at the end of a sample.
  1.1164 +         if (curIndex+1 >= ruleSrc->length() || ruleSrc->charAt(curIndex+1) != DOT) {
  1.1165 +             ++curIndex;
  1.1166 +             break; // Single dot
  1.1167 +         }
  1.1168 +         if (curIndex+2 >= ruleSrc->length() || ruleSrc->charAt(curIndex+2) != DOT) {
  1.1169 +             curIndex += 2;
  1.1170 +             type = tDot2;
  1.1171 +             break; // double dot
  1.1172 +         }
  1.1173 +         type = tEllipsis;
  1.1174 +         curIndex += 3;
  1.1175 +         break;     // triple dot
  1.1176 +
  1.1177 +       default:
  1.1178 +         status = U_UNEXPECTED_TOKEN;
  1.1179 +         ++curIndex;
  1.1180 +         break;
  1.1181 +    }
  1.1182 +
  1.1183 +    U_ASSERT(ruleIndex <= ruleSrc->length());
  1.1184 +    U_ASSERT(curIndex <= ruleSrc->length());
  1.1185 +    token=UnicodeString(*ruleSrc, ruleIndex, curIndex-ruleIndex);
  1.1186 +    ruleIndex = curIndex;
  1.1187 +}
  1.1188 +
  1.1189 +tokenType
  1.1190 +PluralRuleParser::charType(UChar ch) {
  1.1191 +    if ((ch>=U_ZERO) && (ch<=U_NINE)) {
  1.1192 +        return tNumber;
  1.1193 +    }
  1.1194 +    if (ch>=LOW_A && ch<=LOW_Z) {
  1.1195 +        return tKeyword;
  1.1196 +    }
  1.1197 +    switch (ch) {
  1.1198 +    case COLON:
  1.1199 +        return tColon;
  1.1200 +    case SPACE:
  1.1201 +        return tSpace;
  1.1202 +    case SEMI_COLON:
  1.1203 +        return tSemiColon;
  1.1204 +    case DOT:
  1.1205 +        return tDot;
  1.1206 +    case COMMA:
  1.1207 +        return tComma;
  1.1208 +    case EXCLAMATION:
  1.1209 +        return tNotEqual;
  1.1210 +    case EQUALS:
  1.1211 +        return tEqual;
  1.1212 +    case PERCENT_SIGN:
  1.1213 +        return tMod;
  1.1214 +    case AT:
  1.1215 +        return tAt;
  1.1216 +    case ELLIPSIS:
  1.1217 +        return tEllipsis;
  1.1218 +    case TILDE:
  1.1219 +        return tTilde;
  1.1220 +    default :
  1.1221 +        return none;
  1.1222 +    }
  1.1223 +}
  1.1224 +
  1.1225 +
  1.1226 +//  Set token type for reserved words in the Plural Rule syntax.
  1.1227 +
  1.1228 +tokenType 
  1.1229 +PluralRuleParser::getKeyType(const UnicodeString &token, tokenType keyType)
  1.1230 +{
  1.1231 +    if (keyType != tKeyword) {
  1.1232 +        return keyType;
  1.1233 +    }
  1.1234 +
  1.1235 +    if (0 == token.compare(PK_VAR_N, 1)) {
  1.1236 +        keyType = tVariableN;
  1.1237 +    } else if (0 == token.compare(PK_VAR_I, 1)) {
  1.1238 +        keyType = tVariableI;
  1.1239 +    } else if (0 == token.compare(PK_VAR_F, 1)) {
  1.1240 +        keyType = tVariableF;
  1.1241 +    } else if (0 == token.compare(PK_VAR_T, 1)) {
  1.1242 +        keyType = tVariableT;
  1.1243 +    } else if (0 == token.compare(PK_VAR_V, 1)) {
  1.1244 +        keyType = tVariableV;
  1.1245 +    } else if (0 == token.compare(PK_IS, 2)) {
  1.1246 +        keyType = tIs;
  1.1247 +    } else if (0 == token.compare(PK_AND, 3)) {
  1.1248 +        keyType = tAnd;
  1.1249 +    } else if (0 == token.compare(PK_IN, 2)) {
  1.1250 +        keyType = tIn;
  1.1251 +    } else if (0 == token.compare(PK_WITHIN, 6)) {
  1.1252 +        keyType = tWithin;
  1.1253 +    } else if (0 == token.compare(PK_NOT, 3)) {
  1.1254 +        keyType = tNot;
  1.1255 +    } else if (0 == token.compare(PK_MOD, 3)) {
  1.1256 +        keyType = tMod;
  1.1257 +    } else if (0 == token.compare(PK_OR, 2)) {
  1.1258 +        keyType = tOr;
  1.1259 +    } else if (0 == token.compare(PK_DECIMAL, 7)) {
  1.1260 +        keyType = tDecimal;
  1.1261 +    } else if (0 == token.compare(PK_INTEGER, 7)) {
  1.1262 +        keyType = tInteger;
  1.1263 +    }
  1.1264 +    return keyType;
  1.1265 +}
  1.1266 +
  1.1267 +
  1.1268 +PluralKeywordEnumeration::PluralKeywordEnumeration(RuleChain *header, UErrorCode& status)
  1.1269 +        : pos(0), fKeywordNames(status) {
  1.1270 +    if (U_FAILURE(status)) {
  1.1271 +        return;
  1.1272 +    }
  1.1273 +    fKeywordNames.setDeleter(uprv_deleteUObject);
  1.1274 +    UBool  addKeywordOther=TRUE;
  1.1275 +    RuleChain *node=header;
  1.1276 +    while(node!=NULL) {
  1.1277 +        fKeywordNames.addElement(new UnicodeString(node->fKeyword), status);
  1.1278 +        if (U_FAILURE(status)) {
  1.1279 +            return;
  1.1280 +        }
  1.1281 +        if (0 == node->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
  1.1282 +            addKeywordOther= FALSE;
  1.1283 +        }
  1.1284 +        node=node->fNext;
  1.1285 +    }
  1.1286 +
  1.1287 +    if (addKeywordOther) {
  1.1288 +        fKeywordNames.addElement(new UnicodeString(PLURAL_KEYWORD_OTHER), status);
  1.1289 +    }
  1.1290 +}
  1.1291 +
  1.1292 +const UnicodeString*
  1.1293 +PluralKeywordEnumeration::snext(UErrorCode& status) {
  1.1294 +    if (U_SUCCESS(status) && pos < fKeywordNames.size()) {
  1.1295 +        return (const UnicodeString*)fKeywordNames.elementAt(pos++);
  1.1296 +    }
  1.1297 +    return NULL;
  1.1298 +}
  1.1299 +
  1.1300 +void
  1.1301 +PluralKeywordEnumeration::reset(UErrorCode& /*status*/) {
  1.1302 +    pos=0;
  1.1303 +}
  1.1304 +
  1.1305 +int32_t
  1.1306 +PluralKeywordEnumeration::count(UErrorCode& /*status*/) const {
  1.1307 +       return fKeywordNames.size();
  1.1308 +}
  1.1309 +
  1.1310 +PluralKeywordEnumeration::~PluralKeywordEnumeration() {
  1.1311 +}
  1.1312 +
  1.1313 +
  1.1314 +
  1.1315 +FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) {
  1.1316 +    init(n, v, f);
  1.1317 +    // check values. TODO make into unit test.
  1.1318 +    //            
  1.1319 +    //            long visiblePower = (int) Math.pow(10, v);
  1.1320 +    //            if (decimalDigits > visiblePower) {
  1.1321 +    //                throw new IllegalArgumentException();
  1.1322 +    //            }
  1.1323 +    //            double fraction = intValue + (decimalDigits / (double) visiblePower);
  1.1324 +    //            if (fraction != source) {
  1.1325 +    //                double diff = Math.abs(fraction - source)/(Math.abs(fraction) + Math.abs(source));
  1.1326 +    //                if (diff > 0.00000001d) {
  1.1327 +    //                    throw new IllegalArgumentException();
  1.1328 +    //                }
  1.1329 +    //            }
  1.1330 +}
  1.1331 +
  1.1332 +FixedDecimal::FixedDecimal(double n, int32_t v) {
  1.1333 +    // Ugly, but for samples we don't care.
  1.1334 +    init(n, v, getFractionalDigits(n, v));
  1.1335 +}
  1.1336 +
  1.1337 +FixedDecimal::FixedDecimal(double n) {
  1.1338 +    init(n);
  1.1339 +}
  1.1340 +
  1.1341 +FixedDecimal::FixedDecimal() {
  1.1342 +    init(0, 0, 0);
  1.1343 +}
  1.1344 +
  1.1345 +
  1.1346 +// Create a FixedDecimal from a UnicodeString containing a number.
  1.1347 +//    Inefficient, but only used for samples, so simplicity trumps efficiency.
  1.1348 +
  1.1349 +FixedDecimal::FixedDecimal(const UnicodeString &num, UErrorCode &status) {
  1.1350 +    CharString cs;
  1.1351 +    cs.appendInvariantChars(num, status);
  1.1352 +    DigitList dl;
  1.1353 +    dl.set(cs.toStringPiece(), status);
  1.1354 +    if (U_FAILURE(status)) {
  1.1355 +        init(0, 0, 0);
  1.1356 +        return;
  1.1357 +    }
  1.1358 +    int32_t decimalPoint = num.indexOf(DOT);
  1.1359 +    double n = dl.getDouble();
  1.1360 +    if (decimalPoint == -1) {
  1.1361 +        init(n, 0, 0);
  1.1362 +    } else {
  1.1363 +        int32_t v = num.length() - decimalPoint - 1;
  1.1364 +        init(n, v, getFractionalDigits(n, v));
  1.1365 +    }
  1.1366 +}
  1.1367 +
  1.1368 +
  1.1369 +FixedDecimal::FixedDecimal(const FixedDecimal &other) {
  1.1370 +    source = other.source;
  1.1371 +    visibleDecimalDigitCount = other.visibleDecimalDigitCount;
  1.1372 +    decimalDigits = other.decimalDigits;
  1.1373 +    decimalDigitsWithoutTrailingZeros = other.decimalDigitsWithoutTrailingZeros;
  1.1374 +    intValue = other.intValue;
  1.1375 +    hasIntegerValue = other.hasIntegerValue;
  1.1376 +    isNegative = other.isNegative;
  1.1377 +    isNanOrInfinity = other.isNanOrInfinity;
  1.1378 +}
  1.1379 +
  1.1380 +
  1.1381 +void FixedDecimal::init(double n) {
  1.1382 +    int32_t numFractionDigits = decimals(n);
  1.1383 +    init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits));
  1.1384 +}
  1.1385 +
  1.1386 +
  1.1387 +void FixedDecimal::init(double n, int32_t v, int64_t f) {
  1.1388 +    isNegative = n < 0.0;
  1.1389 +    source = fabs(n);
  1.1390 +    isNanOrInfinity = uprv_isNaN(source) || uprv_isPositiveInfinity(source);
  1.1391 +    if (isNanOrInfinity) {
  1.1392 +        v = 0;
  1.1393 +        f = 0;
  1.1394 +        intValue = 0;
  1.1395 +        hasIntegerValue = FALSE;
  1.1396 +    } else {
  1.1397 +        intValue = (int64_t)source;
  1.1398 +        hasIntegerValue = (source == intValue);
  1.1399 +    }
  1.1400 +
  1.1401 +    visibleDecimalDigitCount = v;
  1.1402 +    decimalDigits = f;
  1.1403 +    if (f == 0) {
  1.1404 +         decimalDigitsWithoutTrailingZeros = 0;
  1.1405 +    } else {
  1.1406 +        int64_t fdwtz = f;
  1.1407 +        while ((fdwtz%10) == 0) {
  1.1408 +            fdwtz /= 10;
  1.1409 +        }
  1.1410 +        decimalDigitsWithoutTrailingZeros = fdwtz;
  1.1411 +    }
  1.1412 +}
  1.1413 +
  1.1414 +
  1.1415 +//  Fast path only exact initialization. Return true if successful.
  1.1416 +//     Note: Do not multiply by 10 each time through loop, rounding cruft can build
  1.1417 +//           up that makes the check for an integer result fail.
  1.1418 +//           A single multiply of the original number works more reliably.
  1.1419 +static int32_t p10[] = {1, 10, 100, 1000, 10000};
  1.1420 +UBool FixedDecimal::quickInit(double n) {
  1.1421 +    UBool success = FALSE;
  1.1422 +    n = fabs(n);
  1.1423 +    int32_t numFractionDigits;
  1.1424 +    for (numFractionDigits = 0; numFractionDigits <= 3; numFractionDigits++) {
  1.1425 +        double scaledN = n * p10[numFractionDigits];
  1.1426 +        if (scaledN == floor(scaledN)) {
  1.1427 +            success = TRUE;
  1.1428 +            break;
  1.1429 +        }
  1.1430 +    }
  1.1431 +    if (success) {
  1.1432 +        init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits));
  1.1433 +    }
  1.1434 +    return success;
  1.1435 +}
  1.1436 +
  1.1437 +
  1.1438 +
  1.1439 +int32_t FixedDecimal::decimals(double n) {
  1.1440 +    // Count the number of decimal digits in the fraction part of the number, excluding trailing zeros.
  1.1441 +    // fastpath the common cases, integers or fractions with 3 or fewer digits
  1.1442 +    n = fabs(n);
  1.1443 +    for (int ndigits=0; ndigits<=3; ndigits++) {
  1.1444 +        double scaledN = n * p10[ndigits];
  1.1445 +        if (scaledN == floor(scaledN)) {
  1.1446 +            return ndigits;
  1.1447 +        }
  1.1448 +    }
  1.1449 +
  1.1450 +    // Slow path, convert with sprintf, parse converted output.
  1.1451 +    char  buf[30] = {0};
  1.1452 +    sprintf(buf, "%1.15e", n);
  1.1453 +    // formatted number looks like this: 1.234567890123457e-01
  1.1454 +    int exponent = atoi(buf+18);
  1.1455 +    int numFractionDigits = 15;
  1.1456 +    for (int i=16; ; --i) {
  1.1457 +        if (buf[i] != '0') {
  1.1458 +            break;
  1.1459 +        }
  1.1460 +        --numFractionDigits; 
  1.1461 +    }
  1.1462 +    numFractionDigits -= exponent;   // Fraction part of fixed point representation.
  1.1463 +    return numFractionDigits;
  1.1464 +}
  1.1465 +
  1.1466 +
  1.1467 +// Get the fraction digits of a double, represented as an integer.
  1.1468 +//    v is the number of visible fraction digits in the displayed form of the number.
  1.1469 +//       Example: n = 1001.234, v = 6, result = 234000
  1.1470 +//    TODO: need to think through how this is used in the plural rule context.
  1.1471 +//          This function can easily encounter integer overflow, 
  1.1472 +//          and can easily return noise digits when the precision of a double is exceeded.
  1.1473 +
  1.1474 +int64_t FixedDecimal::getFractionalDigits(double n, int32_t v) {
  1.1475 +    if (v == 0 || n == floor(n) || uprv_isNaN(n) || uprv_isPositiveInfinity(n)) {
  1.1476 +        return 0;
  1.1477 +    }
  1.1478 +    n = fabs(n);
  1.1479 +    double fract = n - floor(n);
  1.1480 +    switch (v) {
  1.1481 +      case 1: return (int64_t)(fract*10.0 + 0.5);
  1.1482 +      case 2: return (int64_t)(fract*100.0 + 0.5);
  1.1483 +      case 3: return (int64_t)(fract*1000.0 + 0.5);
  1.1484 +      default:
  1.1485 +          double scaled = floor(fract * pow(10.0, (double)v) + 0.5);
  1.1486 +          if (scaled > U_INT64_MAX) {
  1.1487 +              return U_INT64_MAX;
  1.1488 +          } else {
  1.1489 +              return (int64_t)scaled;
  1.1490 +          }
  1.1491 +      }
  1.1492 +}
  1.1493 +
  1.1494 +
  1.1495 +void FixedDecimal::adjustForMinFractionDigits(int32_t minFractionDigits) {
  1.1496 +    int32_t numTrailingFractionZeros = minFractionDigits - visibleDecimalDigitCount;
  1.1497 +    if (numTrailingFractionZeros > 0) {
  1.1498 +        for (int32_t i=0; i<numTrailingFractionZeros; i++) {
  1.1499 +            // Do not let the decimalDigits value overflow if there are many trailing zeros.
  1.1500 +            // Limit the value to 18 digits, the most that a 64 bit int can fully represent.
  1.1501 +            if (decimalDigits >= 100000000000000000LL) {
  1.1502 +                break;
  1.1503 +            }
  1.1504 +            decimalDigits *= 10;
  1.1505 +        }
  1.1506 +        visibleDecimalDigitCount += numTrailingFractionZeros;
  1.1507 +    }
  1.1508 +}
  1.1509 +        
  1.1510 +
  1.1511 +double FixedDecimal::get(tokenType operand) const {
  1.1512 +    switch(operand) {
  1.1513 +        case tVariableN: return source;
  1.1514 +        case tVariableI: return (double)intValue;
  1.1515 +        case tVariableF: return (double)decimalDigits;
  1.1516 +        case tVariableT: return (double)decimalDigitsWithoutTrailingZeros; 
  1.1517 +        case tVariableV: return visibleDecimalDigitCount;
  1.1518 +        default:
  1.1519 +             U_ASSERT(FALSE);  // unexpected.
  1.1520 +             return source;
  1.1521 +    }
  1.1522 +}
  1.1523 +
  1.1524 +int32_t FixedDecimal::getVisibleFractionDigitCount() const {
  1.1525 +    return visibleDecimalDigitCount;
  1.1526 +}
  1.1527 +
  1.1528 +
  1.1529 +
  1.1530 +PluralAvailableLocalesEnumeration::PluralAvailableLocalesEnumeration(UErrorCode &status) {
  1.1531 +    fLocales = NULL;
  1.1532 +    fRes = NULL;
  1.1533 +    fOpenStatus = status;
  1.1534 +    if (U_FAILURE(status)) {
  1.1535 +        return;
  1.1536 +    }
  1.1537 +    fOpenStatus = U_ZERO_ERROR;
  1.1538 +    LocalUResourceBundlePointer rb(ures_openDirect(NULL, "plurals", &fOpenStatus));
  1.1539 +    fLocales = ures_getByKey(rb.getAlias(), "locales", NULL, &fOpenStatus);
  1.1540 +}
  1.1541 +
  1.1542 +PluralAvailableLocalesEnumeration::~PluralAvailableLocalesEnumeration() {
  1.1543 +    ures_close(fLocales);
  1.1544 +    ures_close(fRes);
  1.1545 +    fLocales = NULL;
  1.1546 +    fRes = NULL;
  1.1547 +}
  1.1548 +
  1.1549 +const char *PluralAvailableLocalesEnumeration::next(int32_t *resultLength, UErrorCode &status) {
  1.1550 +    if (U_FAILURE(status)) {
  1.1551 +        return NULL;
  1.1552 +    }
  1.1553 +    if (U_FAILURE(fOpenStatus)) {
  1.1554 +        status = fOpenStatus;
  1.1555 +        return NULL;
  1.1556 +    }
  1.1557 +    fRes = ures_getNextResource(fLocales, fRes, &status);
  1.1558 +    if (fRes == NULL || U_FAILURE(status)) {
  1.1559 +        if (status == U_INDEX_OUTOFBOUNDS_ERROR) {
  1.1560 +            status = U_ZERO_ERROR;
  1.1561 +        }
  1.1562 +        return NULL;
  1.1563 +    }
  1.1564 +    const char *result = ures_getKey(fRes);
  1.1565 +    if (resultLength != NULL) {
  1.1566 +        *resultLength = uprv_strlen(result);
  1.1567 +    }
  1.1568 +    return result;
  1.1569 +}
  1.1570 +
  1.1571 +
  1.1572 +void PluralAvailableLocalesEnumeration::reset(UErrorCode &status) {
  1.1573 +    if (U_FAILURE(status)) {
  1.1574 +       return;
  1.1575 +    }
  1.1576 +    if (U_FAILURE(fOpenStatus)) {
  1.1577 +        status = fOpenStatus;
  1.1578 +        return;
  1.1579 +    }
  1.1580 +    ures_resetIterator(fLocales);
  1.1581 +}
  1.1582 +
  1.1583 +int32_t PluralAvailableLocalesEnumeration::count(UErrorCode &status) const {
  1.1584 +    if (U_FAILURE(status)) {
  1.1585 +        return 0;
  1.1586 +    }
  1.1587 +    if (U_FAILURE(fOpenStatus)) {
  1.1588 +        status = fOpenStatus;
  1.1589 +        return 0;
  1.1590 +    }
  1.1591 +    return ures_getSize(fLocales);
  1.1592 +}
  1.1593 +
  1.1594 +U_NAMESPACE_END
  1.1595 +
  1.1596 +
  1.1597 +#endif /* #if !UCONFIG_NO_FORMATTING */
  1.1598 +
  1.1599 +//eof

mercurial