intl/icu/source/i18n/plurrule.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /*
michael@0 2 *******************************************************************************
michael@0 3 * Copyright (C) 2007-2013, International Business Machines Corporation and
michael@0 4 * others. All Rights Reserved.
michael@0 5 *******************************************************************************
michael@0 6 *
michael@0 7 * File plurrule.cpp
michael@0 8 */
michael@0 9
michael@0 10 #include <math.h>
michael@0 11 #include <stdio.h>
michael@0 12
michael@0 13 #include "unicode/utypes.h"
michael@0 14 #include "unicode/localpointer.h"
michael@0 15 #include "unicode/plurrule.h"
michael@0 16 #include "unicode/upluralrules.h"
michael@0 17 #include "unicode/ures.h"
michael@0 18 #include "charstr.h"
michael@0 19 #include "cmemory.h"
michael@0 20 #include "cstring.h"
michael@0 21 #include "digitlst.h"
michael@0 22 #include "hash.h"
michael@0 23 #include "locutil.h"
michael@0 24 #include "mutex.h"
michael@0 25 #include "patternprops.h"
michael@0 26 #include "plurrule_impl.h"
michael@0 27 #include "putilimp.h"
michael@0 28 #include "ucln_in.h"
michael@0 29 #include "ustrfmt.h"
michael@0 30 #include "uassert.h"
michael@0 31 #include "uvectr32.h"
michael@0 32
michael@0 33 #if !UCONFIG_NO_FORMATTING
michael@0 34
michael@0 35 U_NAMESPACE_BEGIN
michael@0 36
michael@0 37 #define ARRAY_SIZE(array) (int32_t)(sizeof array / sizeof array[0])
michael@0 38
michael@0 39 static const UChar PLURAL_KEYWORD_OTHER[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,0};
michael@0 40 static const UChar PLURAL_DEFAULT_RULE[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,COLON,SPACE,LOW_N,0};
michael@0 41 static const UChar PK_IN[]={LOW_I,LOW_N,0};
michael@0 42 static const UChar PK_NOT[]={LOW_N,LOW_O,LOW_T,0};
michael@0 43 static const UChar PK_IS[]={LOW_I,LOW_S,0};
michael@0 44 static const UChar PK_MOD[]={LOW_M,LOW_O,LOW_D,0};
michael@0 45 static const UChar PK_AND[]={LOW_A,LOW_N,LOW_D,0};
michael@0 46 static const UChar PK_OR[]={LOW_O,LOW_R,0};
michael@0 47 static const UChar PK_VAR_N[]={LOW_N,0};
michael@0 48 static const UChar PK_VAR_I[]={LOW_I,0};
michael@0 49 static const UChar PK_VAR_F[]={LOW_F,0};
michael@0 50 static const UChar PK_VAR_T[]={LOW_T,0};
michael@0 51 static const UChar PK_VAR_V[]={LOW_V,0};
michael@0 52 static const UChar PK_VAR_J[]={LOW_J,0};
michael@0 53 static const UChar PK_WITHIN[]={LOW_W,LOW_I,LOW_T,LOW_H,LOW_I,LOW_N,0};
michael@0 54 static const UChar PK_DECIMAL[]={LOW_D,LOW_E,LOW_C,LOW_I,LOW_M,LOW_A,LOW_L,0};
michael@0 55 static const UChar PK_INTEGER[]={LOW_I,LOW_N,LOW_T,LOW_E,LOW_G,LOW_E,LOW_R,0};
michael@0 56
michael@0 57 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralRules)
michael@0 58 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralKeywordEnumeration)
michael@0 59
michael@0 60 PluralRules::PluralRules(UErrorCode& /*status*/)
michael@0 61 : UObject(),
michael@0 62 mRules(NULL)
michael@0 63 {
michael@0 64 }
michael@0 65
michael@0 66 PluralRules::PluralRules(const PluralRules& other)
michael@0 67 : UObject(other),
michael@0 68 mRules(NULL)
michael@0 69 {
michael@0 70 *this=other;
michael@0 71 }
michael@0 72
michael@0 73 PluralRules::~PluralRules() {
michael@0 74 delete mRules;
michael@0 75 }
michael@0 76
michael@0 77 PluralRules*
michael@0 78 PluralRules::clone() const {
michael@0 79 return new PluralRules(*this);
michael@0 80 }
michael@0 81
michael@0 82 PluralRules&
michael@0 83 PluralRules::operator=(const PluralRules& other) {
michael@0 84 if (this != &other) {
michael@0 85 delete mRules;
michael@0 86 if (other.mRules==NULL) {
michael@0 87 mRules = NULL;
michael@0 88 }
michael@0 89 else {
michael@0 90 mRules = new RuleChain(*other.mRules);
michael@0 91 }
michael@0 92 }
michael@0 93
michael@0 94 return *this;
michael@0 95 }
michael@0 96
michael@0 97 StringEnumeration* PluralRules::getAvailableLocales(UErrorCode &status) {
michael@0 98 StringEnumeration *result = new PluralAvailableLocalesEnumeration(status);
michael@0 99 if (result == NULL && U_SUCCESS(status)) {
michael@0 100 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 101 }
michael@0 102 if (U_FAILURE(status)) {
michael@0 103 delete result;
michael@0 104 result = NULL;
michael@0 105 }
michael@0 106 return result;
michael@0 107 }
michael@0 108
michael@0 109
michael@0 110 PluralRules* U_EXPORT2
michael@0 111 PluralRules::createRules(const UnicodeString& description, UErrorCode& status) {
michael@0 112 if (U_FAILURE(status)) {
michael@0 113 return NULL;
michael@0 114 }
michael@0 115
michael@0 116 PluralRuleParser parser;
michael@0 117 PluralRules *newRules = new PluralRules(status);
michael@0 118 if (U_SUCCESS(status) && newRules == NULL) {
michael@0 119 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 120 }
michael@0 121 parser.parse(description, newRules, status);
michael@0 122 if (U_FAILURE(status)) {
michael@0 123 delete newRules;
michael@0 124 newRules = NULL;
michael@0 125 }
michael@0 126 return newRules;
michael@0 127 }
michael@0 128
michael@0 129
michael@0 130 PluralRules* U_EXPORT2
michael@0 131 PluralRules::createDefaultRules(UErrorCode& status) {
michael@0 132 return createRules(UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1), status);
michael@0 133 }
michael@0 134
michael@0 135 PluralRules* U_EXPORT2
michael@0 136 PluralRules::forLocale(const Locale& locale, UErrorCode& status) {
michael@0 137 return forLocale(locale, UPLURAL_TYPE_CARDINAL, status);
michael@0 138 }
michael@0 139
michael@0 140 PluralRules* U_EXPORT2
michael@0 141 PluralRules::forLocale(const Locale& locale, UPluralType type, UErrorCode& status) {
michael@0 142 if (U_FAILURE(status)) {
michael@0 143 return NULL;
michael@0 144 }
michael@0 145 if (type >= UPLURAL_TYPE_COUNT) {
michael@0 146 status = U_ILLEGAL_ARGUMENT_ERROR;
michael@0 147 return NULL;
michael@0 148 }
michael@0 149 PluralRules *newObj = new PluralRules(status);
michael@0 150 if (newObj==NULL || U_FAILURE(status)) {
michael@0 151 delete newObj;
michael@0 152 return NULL;
michael@0 153 }
michael@0 154 UnicodeString locRule = newObj->getRuleFromResource(locale, type, status);
michael@0 155 // TODO: which errors, if any, should be returned?
michael@0 156 if (locRule.length() == 0) {
michael@0 157 // Locales with no specific rules (all numbers have the "other" category
michael@0 158 // will return a U_MISSING_RESOURCE_ERROR at this point. This is not
michael@0 159 // an error.
michael@0 160 locRule = UnicodeString(PLURAL_DEFAULT_RULE);
michael@0 161 status = U_ZERO_ERROR;
michael@0 162 }
michael@0 163 PluralRuleParser parser;
michael@0 164 parser.parse(locRule, newObj, status);
michael@0 165 // TODO: should rule parse errors be returned, or
michael@0 166 // should we silently use default rules?
michael@0 167 // Original impl used default rules.
michael@0 168 // Ask the question to ICU Core.
michael@0 169
michael@0 170 return newObj;
michael@0 171 }
michael@0 172
michael@0 173 UnicodeString
michael@0 174 PluralRules::select(int32_t number) const {
michael@0 175 return select(FixedDecimal(number));
michael@0 176 }
michael@0 177
michael@0 178 UnicodeString
michael@0 179 PluralRules::select(double number) const {
michael@0 180 return select(FixedDecimal(number));
michael@0 181 }
michael@0 182
michael@0 183 UnicodeString
michael@0 184 PluralRules::select(const FixedDecimal &number) const {
michael@0 185 if (mRules == NULL) {
michael@0 186 return UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1);
michael@0 187 }
michael@0 188 else {
michael@0 189 return mRules->select(number);
michael@0 190 }
michael@0 191 }
michael@0 192
michael@0 193 StringEnumeration*
michael@0 194 PluralRules::getKeywords(UErrorCode& status) const {
michael@0 195 if (U_FAILURE(status)) return NULL;
michael@0 196 StringEnumeration* nameEnumerator = new PluralKeywordEnumeration(mRules, status);
michael@0 197 if (U_FAILURE(status)) {
michael@0 198 delete nameEnumerator;
michael@0 199 return NULL;
michael@0 200 }
michael@0 201
michael@0 202 return nameEnumerator;
michael@0 203 }
michael@0 204
michael@0 205 double
michael@0 206 PluralRules::getUniqueKeywordValue(const UnicodeString& /* keyword */) {
michael@0 207 // Not Implemented.
michael@0 208 return UPLRULES_NO_UNIQUE_VALUE;
michael@0 209 }
michael@0 210
michael@0 211 int32_t
michael@0 212 PluralRules::getAllKeywordValues(const UnicodeString & /* keyword */, double * /* dest */,
michael@0 213 int32_t /* destCapacity */, UErrorCode& error) {
michael@0 214 error = U_UNSUPPORTED_ERROR;
michael@0 215 return 0;
michael@0 216 }
michael@0 217
michael@0 218
michael@0 219 static double scaleForInt(double d) {
michael@0 220 double scale = 1.0;
michael@0 221 while (d != floor(d)) {
michael@0 222 d = d * 10.0;
michael@0 223 scale = scale * 10.0;
michael@0 224 }
michael@0 225 return scale;
michael@0 226 }
michael@0 227
michael@0 228 static int32_t
michael@0 229 getSamplesFromString(const UnicodeString &samples, double *dest,
michael@0 230 int32_t destCapacity, UErrorCode& status) {
michael@0 231 int32_t sampleCount = 0;
michael@0 232 int32_t sampleStartIdx = 0;
michael@0 233 int32_t sampleEndIdx = 0;
michael@0 234
michael@0 235 //std::string ss; // TODO: debugging.
michael@0 236 // std::cout << "PluralRules::getSamples(), samples = \"" << samples.toUTF8String(ss) << "\"\n";
michael@0 237 for (sampleCount = 0; sampleCount < destCapacity && sampleStartIdx < samples.length(); ) {
michael@0 238 sampleEndIdx = samples.indexOf(COMMA, sampleStartIdx);
michael@0 239 if (sampleEndIdx == -1) {
michael@0 240 sampleEndIdx = samples.length();
michael@0 241 }
michael@0 242 const UnicodeString &sampleRange = samples.tempSubStringBetween(sampleStartIdx, sampleEndIdx);
michael@0 243 // ss.erase();
michael@0 244 // std::cout << "PluralRules::getSamples(), samplesRange = \"" << sampleRange.toUTF8String(ss) << "\"\n";
michael@0 245 int32_t tildeIndex = sampleRange.indexOf(TILDE);
michael@0 246 if (tildeIndex < 0) {
michael@0 247 FixedDecimal fixed(sampleRange, status);
michael@0 248 double sampleValue = fixed.source;
michael@0 249 if (fixed.visibleDecimalDigitCount == 0 || sampleValue != floor(sampleValue)) {
michael@0 250 dest[sampleCount++] = sampleValue;
michael@0 251 }
michael@0 252 } else {
michael@0 253
michael@0 254 FixedDecimal fixedLo(sampleRange.tempSubStringBetween(0, tildeIndex), status);
michael@0 255 FixedDecimal fixedHi(sampleRange.tempSubStringBetween(tildeIndex+1), status);
michael@0 256 double rangeLo = fixedLo.source;
michael@0 257 double rangeHi = fixedHi.source;
michael@0 258 if (U_FAILURE(status)) {
michael@0 259 break;
michael@0 260 }
michael@0 261 if (rangeHi < rangeLo) {
michael@0 262 status = U_INVALID_FORMAT_ERROR;
michael@0 263 break;
michael@0 264 }
michael@0 265
michael@0 266 // For ranges of samples with fraction decimal digits, scale the number up so that we
michael@0 267 // are adding one in the units place. Avoids roundoffs from repetitive adds of tenths.
michael@0 268
michael@0 269 double scale = scaleForInt(rangeLo);
michael@0 270 double t = scaleForInt(rangeHi);
michael@0 271 if (t > scale) {
michael@0 272 scale = t;
michael@0 273 }
michael@0 274 rangeLo *= scale;
michael@0 275 rangeHi *= scale;
michael@0 276 for (double n=rangeLo; n<=rangeHi; n+=1) {
michael@0 277 // Hack Alert: don't return any decimal samples with integer values that
michael@0 278 // originated from a format with trailing decimals.
michael@0 279 // This API is returning doubles, which can't distinguish having displayed
michael@0 280 // zeros to the right of the decimal.
michael@0 281 // This results in test failures with values mapping back to a different keyword.
michael@0 282 double sampleValue = n/scale;
michael@0 283 if (!(sampleValue == floor(sampleValue) && fixedLo.visibleDecimalDigitCount > 0)) {
michael@0 284 dest[sampleCount++] = sampleValue;
michael@0 285 }
michael@0 286 if (sampleCount >= destCapacity) {
michael@0 287 break;
michael@0 288 }
michael@0 289 }
michael@0 290 }
michael@0 291 sampleStartIdx = sampleEndIdx + 1;
michael@0 292 }
michael@0 293 return sampleCount;
michael@0 294 }
michael@0 295
michael@0 296
michael@0 297 int32_t
michael@0 298 PluralRules::getSamples(const UnicodeString &keyword, double *dest,
michael@0 299 int32_t destCapacity, UErrorCode& status) {
michael@0 300 RuleChain *rc = rulesForKeyword(keyword);
michael@0 301 if (rc == NULL || destCapacity == 0 || U_FAILURE(status)) {
michael@0 302 return 0;
michael@0 303 }
michael@0 304 int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, dest, destCapacity, status);
michael@0 305 if (numSamples == 0) {
michael@0 306 numSamples = getSamplesFromString(rc->fDecimalSamples, dest, destCapacity, status);
michael@0 307 }
michael@0 308 return numSamples;
michael@0 309 }
michael@0 310
michael@0 311
michael@0 312 RuleChain *PluralRules::rulesForKeyword(const UnicodeString &keyword) const {
michael@0 313 RuleChain *rc;
michael@0 314 for (rc = mRules; rc != NULL; rc = rc->fNext) {
michael@0 315 if (rc->fKeyword == keyword) {
michael@0 316 break;
michael@0 317 }
michael@0 318 }
michael@0 319 return rc;
michael@0 320 }
michael@0 321
michael@0 322
michael@0 323 UBool
michael@0 324 PluralRules::isKeyword(const UnicodeString& keyword) const {
michael@0 325 if (0 == keyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
michael@0 326 return true;
michael@0 327 }
michael@0 328 return rulesForKeyword(keyword) != NULL;
michael@0 329 }
michael@0 330
michael@0 331 UnicodeString
michael@0 332 PluralRules::getKeywordOther() const {
michael@0 333 return UnicodeString(TRUE, PLURAL_KEYWORD_OTHER, 5);
michael@0 334 }
michael@0 335
michael@0 336 UBool
michael@0 337 PluralRules::operator==(const PluralRules& other) const {
michael@0 338 const UnicodeString *ptrKeyword;
michael@0 339 UErrorCode status= U_ZERO_ERROR;
michael@0 340
michael@0 341 if ( this == &other ) {
michael@0 342 return TRUE;
michael@0 343 }
michael@0 344 LocalPointer<StringEnumeration> myKeywordList(getKeywords(status));
michael@0 345 LocalPointer<StringEnumeration> otherKeywordList(other.getKeywords(status));
michael@0 346 if (U_FAILURE(status)) {
michael@0 347 return FALSE;
michael@0 348 }
michael@0 349
michael@0 350 if (myKeywordList->count(status)!=otherKeywordList->count(status)) {
michael@0 351 return FALSE;
michael@0 352 }
michael@0 353 myKeywordList->reset(status);
michael@0 354 while ((ptrKeyword=myKeywordList->snext(status))!=NULL) {
michael@0 355 if (!other.isKeyword(*ptrKeyword)) {
michael@0 356 return FALSE;
michael@0 357 }
michael@0 358 }
michael@0 359 otherKeywordList->reset(status);
michael@0 360 while ((ptrKeyword=otherKeywordList->snext(status))!=NULL) {
michael@0 361 if (!this->isKeyword(*ptrKeyword)) {
michael@0 362 return FALSE;
michael@0 363 }
michael@0 364 }
michael@0 365 if (U_FAILURE(status)) {
michael@0 366 return FALSE;
michael@0 367 }
michael@0 368
michael@0 369 return TRUE;
michael@0 370 }
michael@0 371
michael@0 372
michael@0 373 void
michael@0 374 PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErrorCode &status)
michael@0 375 {
michael@0 376 if (U_FAILURE(status)) {
michael@0 377 return;
michael@0 378 }
michael@0 379 U_ASSERT(ruleIndex == 0); // Parsers are good for a single use only!
michael@0 380 ruleSrc = &ruleData;
michael@0 381
michael@0 382 while (ruleIndex< ruleSrc->length()) {
michael@0 383 getNextToken(status);
michael@0 384 if (U_FAILURE(status)) {
michael@0 385 return;
michael@0 386 }
michael@0 387 checkSyntax(status);
michael@0 388 if (U_FAILURE(status)) {
michael@0 389 return;
michael@0 390 }
michael@0 391 switch (type) {
michael@0 392 case tAnd:
michael@0 393 U_ASSERT(curAndConstraint != NULL);
michael@0 394 curAndConstraint = curAndConstraint->add();
michael@0 395 break;
michael@0 396 case tOr:
michael@0 397 {
michael@0 398 U_ASSERT(currentChain != NULL);
michael@0 399 OrConstraint *orNode=currentChain->ruleHeader;
michael@0 400 while (orNode->next != NULL) {
michael@0 401 orNode = orNode->next;
michael@0 402 }
michael@0 403 orNode->next= new OrConstraint();
michael@0 404 orNode=orNode->next;
michael@0 405 orNode->next=NULL;
michael@0 406 curAndConstraint = orNode->add();
michael@0 407 }
michael@0 408 break;
michael@0 409 case tIs:
michael@0 410 U_ASSERT(curAndConstraint != NULL);
michael@0 411 U_ASSERT(curAndConstraint->value == -1);
michael@0 412 U_ASSERT(curAndConstraint->rangeList == NULL);
michael@0 413 break;
michael@0 414 case tNot:
michael@0 415 U_ASSERT(curAndConstraint != NULL);
michael@0 416 curAndConstraint->negated=TRUE;
michael@0 417 break;
michael@0 418
michael@0 419 case tNotEqual:
michael@0 420 curAndConstraint->negated=TRUE;
michael@0 421 case tIn:
michael@0 422 case tWithin:
michael@0 423 case tEqual:
michael@0 424 U_ASSERT(curAndConstraint != NULL);
michael@0 425 curAndConstraint->rangeList = new UVector32(status);
michael@0 426 curAndConstraint->rangeList->addElement(-1, status); // range Low
michael@0 427 curAndConstraint->rangeList->addElement(-1, status); // range Hi
michael@0 428 rangeLowIdx = 0;
michael@0 429 rangeHiIdx = 1;
michael@0 430 curAndConstraint->value=PLURAL_RANGE_HIGH;
michael@0 431 curAndConstraint->integerOnly = (type != tWithin);
michael@0 432 break;
michael@0 433 case tNumber:
michael@0 434 U_ASSERT(curAndConstraint != NULL);
michael@0 435 if ( (curAndConstraint->op==AndConstraint::MOD)&&
michael@0 436 (curAndConstraint->opNum == -1 ) ) {
michael@0 437 curAndConstraint->opNum=getNumberValue(token);
michael@0 438 }
michael@0 439 else {
michael@0 440 if (curAndConstraint->rangeList == NULL) {
michael@0 441 // this is for an 'is' rule
michael@0 442 curAndConstraint->value = getNumberValue(token);
michael@0 443 } else {
michael@0 444 // this is for an 'in' or 'within' rule
michael@0 445 if (curAndConstraint->rangeList->elementAti(rangeLowIdx) == -1) {
michael@0 446 curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeLowIdx);
michael@0 447 curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeHiIdx);
michael@0 448 }
michael@0 449 else {
michael@0 450 curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeHiIdx);
michael@0 451 if (curAndConstraint->rangeList->elementAti(rangeLowIdx) >
michael@0 452 curAndConstraint->rangeList->elementAti(rangeHiIdx)) {
michael@0 453 // Range Lower bound > Range Upper bound.
michael@0 454 // U_UNEXPECTED_TOKEN seems a little funny, but it is consistently
michael@0 455 // used for all plural rule parse errors.
michael@0 456 status = U_UNEXPECTED_TOKEN;
michael@0 457 break;
michael@0 458 }
michael@0 459 }
michael@0 460 }
michael@0 461 }
michael@0 462 break;
michael@0 463 case tComma:
michael@0 464 // TODO: rule syntax checking is inadequate, can happen with badly formed rules.
michael@0 465 // Catch cases like "n mod 10, is 1" here instead.
michael@0 466 if (curAndConstraint == NULL || curAndConstraint->rangeList == NULL) {
michael@0 467 status = U_UNEXPECTED_TOKEN;
michael@0 468 break;
michael@0 469 }
michael@0 470 U_ASSERT(curAndConstraint->rangeList->size() >= 2);
michael@0 471 rangeLowIdx = curAndConstraint->rangeList->size();
michael@0 472 curAndConstraint->rangeList->addElement(-1, status); // range Low
michael@0 473 rangeHiIdx = curAndConstraint->rangeList->size();
michael@0 474 curAndConstraint->rangeList->addElement(-1, status); // range Hi
michael@0 475 break;
michael@0 476 case tMod:
michael@0 477 U_ASSERT(curAndConstraint != NULL);
michael@0 478 curAndConstraint->op=AndConstraint::MOD;
michael@0 479 break;
michael@0 480 case tVariableN:
michael@0 481 case tVariableI:
michael@0 482 case tVariableF:
michael@0 483 case tVariableT:
michael@0 484 case tVariableV:
michael@0 485 U_ASSERT(curAndConstraint != NULL);
michael@0 486 curAndConstraint->digitsType = type;
michael@0 487 break;
michael@0 488 case tKeyword:
michael@0 489 {
michael@0 490 RuleChain *newChain = new RuleChain;
michael@0 491 if (newChain == NULL) {
michael@0 492 status = U_MEMORY_ALLOCATION_ERROR;
michael@0 493 break;
michael@0 494 }
michael@0 495 newChain->fKeyword = token;
michael@0 496 if (prules->mRules == NULL) {
michael@0 497 prules->mRules = newChain;
michael@0 498 } else {
michael@0 499 // The new rule chain goes at the end of the linked list of rule chains,
michael@0 500 // unless there is an "other" keyword & chain. "other" must remain last.
michael@0 501 RuleChain *insertAfter = prules->mRules;
michael@0 502 while (insertAfter->fNext!=NULL &&
michael@0 503 insertAfter->fNext->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5) != 0 ){
michael@0 504 insertAfter=insertAfter->fNext;
michael@0 505 }
michael@0 506 newChain->fNext = insertAfter->fNext;
michael@0 507 insertAfter->fNext = newChain;
michael@0 508 }
michael@0 509 OrConstraint *orNode = new OrConstraint();
michael@0 510 newChain->ruleHeader = orNode;
michael@0 511 curAndConstraint = orNode->add();
michael@0 512 currentChain = newChain;
michael@0 513 }
michael@0 514 break;
michael@0 515
michael@0 516 case tInteger:
michael@0 517 for (;;) {
michael@0 518 getNextToken(status);
michael@0 519 if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) {
michael@0 520 break;
michael@0 521 }
michael@0 522 if (type == tEllipsis) {
michael@0 523 currentChain->fIntegerSamplesUnbounded = TRUE;
michael@0 524 continue;
michael@0 525 }
michael@0 526 currentChain->fIntegerSamples.append(token);
michael@0 527 }
michael@0 528 break;
michael@0 529
michael@0 530 case tDecimal:
michael@0 531 for (;;) {
michael@0 532 getNextToken(status);
michael@0 533 if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) {
michael@0 534 break;
michael@0 535 }
michael@0 536 if (type == tEllipsis) {
michael@0 537 currentChain->fDecimalSamplesUnbounded = TRUE;
michael@0 538 continue;
michael@0 539 }
michael@0 540 currentChain->fDecimalSamples.append(token);
michael@0 541 }
michael@0 542 break;
michael@0 543
michael@0 544 default:
michael@0 545 break;
michael@0 546 }
michael@0 547 prevType=type;
michael@0 548 if (U_FAILURE(status)) {
michael@0 549 break;
michael@0 550 }
michael@0 551 }
michael@0 552 }
michael@0 553
michael@0 554 UnicodeString
michael@0 555 PluralRules::getRuleFromResource(const Locale& locale, UPluralType type, UErrorCode& errCode) {
michael@0 556 UnicodeString emptyStr;
michael@0 557
michael@0 558 if (U_FAILURE(errCode)) {
michael@0 559 return emptyStr;
michael@0 560 }
michael@0 561 LocalUResourceBundlePointer rb(ures_openDirect(NULL, "plurals", &errCode));
michael@0 562 if(U_FAILURE(errCode)) {
michael@0 563 return emptyStr;
michael@0 564 }
michael@0 565 const char *typeKey;
michael@0 566 switch (type) {
michael@0 567 case UPLURAL_TYPE_CARDINAL:
michael@0 568 typeKey = "locales";
michael@0 569 break;
michael@0 570 case UPLURAL_TYPE_ORDINAL:
michael@0 571 typeKey = "locales_ordinals";
michael@0 572 break;
michael@0 573 default:
michael@0 574 // Must not occur: The caller should have checked for valid types.
michael@0 575 errCode = U_ILLEGAL_ARGUMENT_ERROR;
michael@0 576 return emptyStr;
michael@0 577 }
michael@0 578 LocalUResourceBundlePointer locRes(ures_getByKey(rb.getAlias(), typeKey, NULL, &errCode));
michael@0 579 if(U_FAILURE(errCode)) {
michael@0 580 return emptyStr;
michael@0 581 }
michael@0 582 int32_t resLen=0;
michael@0 583 const char *curLocaleName=locale.getName();
michael@0 584 const UChar* s = ures_getStringByKey(locRes.getAlias(), curLocaleName, &resLen, &errCode);
michael@0 585
michael@0 586 if (s == NULL) {
michael@0 587 // Check parent locales.
michael@0 588 UErrorCode status = U_ZERO_ERROR;
michael@0 589 char parentLocaleName[ULOC_FULLNAME_CAPACITY];
michael@0 590 const char *curLocaleName=locale.getName();
michael@0 591 uprv_strcpy(parentLocaleName, curLocaleName);
michael@0 592
michael@0 593 while (uloc_getParent(parentLocaleName, parentLocaleName,
michael@0 594 ULOC_FULLNAME_CAPACITY, &status) > 0) {
michael@0 595 resLen=0;
michael@0 596 s = ures_getStringByKey(locRes.getAlias(), parentLocaleName, &resLen, &status);
michael@0 597 if (s != NULL) {
michael@0 598 errCode = U_ZERO_ERROR;
michael@0 599 break;
michael@0 600 }
michael@0 601 status = U_ZERO_ERROR;
michael@0 602 }
michael@0 603 }
michael@0 604 if (s==NULL) {
michael@0 605 return emptyStr;
michael@0 606 }
michael@0 607
michael@0 608 char setKey[256];
michael@0 609 u_UCharsToChars(s, setKey, resLen + 1);
michael@0 610 // printf("\n PluralRule: %s\n", setKey);
michael@0 611
michael@0 612 LocalUResourceBundlePointer ruleRes(ures_getByKey(rb.getAlias(), "rules", NULL, &errCode));
michael@0 613 if(U_FAILURE(errCode)) {
michael@0 614 return emptyStr;
michael@0 615 }
michael@0 616 LocalUResourceBundlePointer setRes(ures_getByKey(ruleRes.getAlias(), setKey, NULL, &errCode));
michael@0 617 if (U_FAILURE(errCode)) {
michael@0 618 return emptyStr;
michael@0 619 }
michael@0 620
michael@0 621 int32_t numberKeys = ures_getSize(setRes.getAlias());
michael@0 622 UnicodeString result;
michael@0 623 const char *key=NULL;
michael@0 624 for(int32_t i=0; i<numberKeys; ++i) { // Keys are zero, one, few, ...
michael@0 625 UnicodeString rules = ures_getNextUnicodeString(setRes.getAlias(), &key, &errCode);
michael@0 626 UnicodeString uKey(key, -1, US_INV);
michael@0 627 result.append(uKey);
michael@0 628 result.append(COLON);
michael@0 629 result.append(rules);
michael@0 630 result.append(SEMI_COLON);
michael@0 631 }
michael@0 632 return result;
michael@0 633 }
michael@0 634
michael@0 635
michael@0 636 UnicodeString
michael@0 637 PluralRules::getRules() const {
michael@0 638 UnicodeString rules;
michael@0 639 if (mRules != NULL) {
michael@0 640 mRules->dumpRules(rules);
michael@0 641 }
michael@0 642 return rules;
michael@0 643 }
michael@0 644
michael@0 645
michael@0 646 AndConstraint::AndConstraint() {
michael@0 647 op = AndConstraint::NONE;
michael@0 648 opNum=-1;
michael@0 649 value = -1;
michael@0 650 rangeList = NULL;
michael@0 651 negated = FALSE;
michael@0 652 integerOnly = FALSE;
michael@0 653 digitsType = none;
michael@0 654 next=NULL;
michael@0 655 }
michael@0 656
michael@0 657
michael@0 658 AndConstraint::AndConstraint(const AndConstraint& other) {
michael@0 659 this->op = other.op;
michael@0 660 this->opNum=other.opNum;
michael@0 661 this->value=other.value;
michael@0 662 this->rangeList=NULL;
michael@0 663 if (other.rangeList != NULL) {
michael@0 664 UErrorCode status = U_ZERO_ERROR;
michael@0 665 this->rangeList = new UVector32(status);
michael@0 666 this->rangeList->assign(*other.rangeList, status);
michael@0 667 }
michael@0 668 this->integerOnly=other.integerOnly;
michael@0 669 this->negated=other.negated;
michael@0 670 this->digitsType = other.digitsType;
michael@0 671 if (other.next==NULL) {
michael@0 672 this->next=NULL;
michael@0 673 }
michael@0 674 else {
michael@0 675 this->next = new AndConstraint(*other.next);
michael@0 676 }
michael@0 677 }
michael@0 678
michael@0 679 AndConstraint::~AndConstraint() {
michael@0 680 delete rangeList;
michael@0 681 if (next!=NULL) {
michael@0 682 delete next;
michael@0 683 }
michael@0 684 }
michael@0 685
michael@0 686
michael@0 687 UBool
michael@0 688 AndConstraint::isFulfilled(const FixedDecimal &number) {
michael@0 689 UBool result = TRUE;
michael@0 690 if (digitsType == none) {
michael@0 691 // An empty AndConstraint, created by a rule with a keyword but no following expression.
michael@0 692 return TRUE;
michael@0 693 }
michael@0 694 double n = number.get(digitsType); // pulls n | i | v | f value for the number.
michael@0 695 // Will always be positive.
michael@0 696 // May be non-integer (n option only)
michael@0 697 do {
michael@0 698 if (integerOnly && n != uprv_floor(n)) {
michael@0 699 result = FALSE;
michael@0 700 break;
michael@0 701 }
michael@0 702
michael@0 703 if (op == MOD) {
michael@0 704 n = fmod(n, opNum);
michael@0 705 }
michael@0 706 if (rangeList == NULL) {
michael@0 707 result = value == -1 || // empty rule
michael@0 708 n == value; // 'is' rule
michael@0 709 break;
michael@0 710 }
michael@0 711 result = FALSE; // 'in' or 'within' rule
michael@0 712 for (int32_t r=0; r<rangeList->size(); r+=2) {
michael@0 713 if (rangeList->elementAti(r) <= n && n <= rangeList->elementAti(r+1)) {
michael@0 714 result = TRUE;
michael@0 715 break;
michael@0 716 }
michael@0 717 }
michael@0 718 } while (FALSE);
michael@0 719
michael@0 720 if (negated) {
michael@0 721 result = !result;
michael@0 722 }
michael@0 723 return result;
michael@0 724 }
michael@0 725
michael@0 726
michael@0 727 AndConstraint*
michael@0 728 AndConstraint::add()
michael@0 729 {
michael@0 730 this->next = new AndConstraint();
michael@0 731 return this->next;
michael@0 732 }
michael@0 733
michael@0 734 OrConstraint::OrConstraint() {
michael@0 735 childNode=NULL;
michael@0 736 next=NULL;
michael@0 737 }
michael@0 738
michael@0 739 OrConstraint::OrConstraint(const OrConstraint& other) {
michael@0 740 if ( other.childNode == NULL ) {
michael@0 741 this->childNode = NULL;
michael@0 742 }
michael@0 743 else {
michael@0 744 this->childNode = new AndConstraint(*(other.childNode));
michael@0 745 }
michael@0 746 if (other.next == NULL ) {
michael@0 747 this->next = NULL;
michael@0 748 }
michael@0 749 else {
michael@0 750 this->next = new OrConstraint(*(other.next));
michael@0 751 }
michael@0 752 }
michael@0 753
michael@0 754 OrConstraint::~OrConstraint() {
michael@0 755 if (childNode!=NULL) {
michael@0 756 delete childNode;
michael@0 757 }
michael@0 758 if (next!=NULL) {
michael@0 759 delete next;
michael@0 760 }
michael@0 761 }
michael@0 762
michael@0 763 AndConstraint*
michael@0 764 OrConstraint::add()
michael@0 765 {
michael@0 766 OrConstraint *curOrConstraint=this;
michael@0 767 {
michael@0 768 while (curOrConstraint->next!=NULL) {
michael@0 769 curOrConstraint = curOrConstraint->next;
michael@0 770 }
michael@0 771 U_ASSERT(curOrConstraint->childNode == NULL);
michael@0 772 curOrConstraint->childNode = new AndConstraint();
michael@0 773 }
michael@0 774 return curOrConstraint->childNode;
michael@0 775 }
michael@0 776
michael@0 777 UBool
michael@0 778 OrConstraint::isFulfilled(const FixedDecimal &number) {
michael@0 779 OrConstraint* orRule=this;
michael@0 780 UBool result=FALSE;
michael@0 781
michael@0 782 while (orRule!=NULL && !result) {
michael@0 783 result=TRUE;
michael@0 784 AndConstraint* andRule = orRule->childNode;
michael@0 785 while (andRule!=NULL && result) {
michael@0 786 result = andRule->isFulfilled(number);
michael@0 787 andRule=andRule->next;
michael@0 788 }
michael@0 789 orRule = orRule->next;
michael@0 790 }
michael@0 791
michael@0 792 return result;
michael@0 793 }
michael@0 794
michael@0 795
michael@0 796 RuleChain::RuleChain(): fKeyword(), fNext(NULL), ruleHeader(NULL), fDecimalSamples(), fIntegerSamples(),
michael@0 797 fDecimalSamplesUnbounded(FALSE), fIntegerSamplesUnbounded(FALSE) {
michael@0 798 }
michael@0 799
michael@0 800 RuleChain::RuleChain(const RuleChain& other) :
michael@0 801 fKeyword(other.fKeyword), fNext(NULL), ruleHeader(NULL), fDecimalSamples(other.fDecimalSamples),
michael@0 802 fIntegerSamples(other.fIntegerSamples), fDecimalSamplesUnbounded(other.fDecimalSamplesUnbounded),
michael@0 803 fIntegerSamplesUnbounded(other.fIntegerSamplesUnbounded) {
michael@0 804 if (other.ruleHeader != NULL) {
michael@0 805 this->ruleHeader = new OrConstraint(*(other.ruleHeader));
michael@0 806 }
michael@0 807 if (other.fNext != NULL ) {
michael@0 808 this->fNext = new RuleChain(*other.fNext);
michael@0 809 }
michael@0 810 }
michael@0 811
michael@0 812 RuleChain::~RuleChain() {
michael@0 813 delete fNext;
michael@0 814 delete ruleHeader;
michael@0 815 }
michael@0 816
michael@0 817
michael@0 818 UnicodeString
michael@0 819 RuleChain::select(const FixedDecimal &number) const {
michael@0 820 if (!number.isNanOrInfinity) {
michael@0 821 for (const RuleChain *rules = this; rules != NULL; rules = rules->fNext) {
michael@0 822 if (rules->ruleHeader->isFulfilled(number)) {
michael@0 823 return rules->fKeyword;
michael@0 824 }
michael@0 825 }
michael@0 826 }
michael@0 827 return UnicodeString(TRUE, PLURAL_KEYWORD_OTHER, 5);
michael@0 828 }
michael@0 829
michael@0 830 static UnicodeString tokenString(tokenType tok) {
michael@0 831 UnicodeString s;
michael@0 832 switch (tok) {
michael@0 833 case tVariableN:
michael@0 834 s.append(LOW_N); break;
michael@0 835 case tVariableI:
michael@0 836 s.append(LOW_I); break;
michael@0 837 case tVariableF:
michael@0 838 s.append(LOW_F); break;
michael@0 839 case tVariableV:
michael@0 840 s.append(LOW_V); break;
michael@0 841 case tVariableT:
michael@0 842 s.append(LOW_T); break;
michael@0 843 default:
michael@0 844 s.append(TILDE);
michael@0 845 }
michael@0 846 return s;
michael@0 847 }
michael@0 848
michael@0 849 void
michael@0 850 RuleChain::dumpRules(UnicodeString& result) {
michael@0 851 UChar digitString[16];
michael@0 852
michael@0 853 if ( ruleHeader != NULL ) {
michael@0 854 result += fKeyword;
michael@0 855 result += COLON;
michael@0 856 result += SPACE;
michael@0 857 OrConstraint* orRule=ruleHeader;
michael@0 858 while ( orRule != NULL ) {
michael@0 859 AndConstraint* andRule=orRule->childNode;
michael@0 860 while ( andRule != NULL ) {
michael@0 861 if ((andRule->op==AndConstraint::NONE) && (andRule->rangeList==NULL) && (andRule->value == -1)) {
michael@0 862 // Empty Rules.
michael@0 863 } else if ( (andRule->op==AndConstraint::NONE) && (andRule->rangeList==NULL) ) {
michael@0 864 result += tokenString(andRule->digitsType);
michael@0 865 result += UNICODE_STRING_SIMPLE(" is ");
michael@0 866 if (andRule->negated) {
michael@0 867 result += UNICODE_STRING_SIMPLE("not ");
michael@0 868 }
michael@0 869 uprv_itou(digitString,16, andRule->value,10,0);
michael@0 870 result += UnicodeString(digitString);
michael@0 871 }
michael@0 872 else {
michael@0 873 result += tokenString(andRule->digitsType);
michael@0 874 result += SPACE;
michael@0 875 if (andRule->op==AndConstraint::MOD) {
michael@0 876 result += UNICODE_STRING_SIMPLE("mod ");
michael@0 877 uprv_itou(digitString,16, andRule->opNum,10,0);
michael@0 878 result += UnicodeString(digitString);
michael@0 879 }
michael@0 880 if (andRule->rangeList==NULL) {
michael@0 881 if (andRule->negated) {
michael@0 882 result += UNICODE_STRING_SIMPLE(" is not ");
michael@0 883 uprv_itou(digitString,16, andRule->value,10,0);
michael@0 884 result += UnicodeString(digitString);
michael@0 885 }
michael@0 886 else {
michael@0 887 result += UNICODE_STRING_SIMPLE(" is ");
michael@0 888 uprv_itou(digitString,16, andRule->value,10,0);
michael@0 889 result += UnicodeString(digitString);
michael@0 890 }
michael@0 891 }
michael@0 892 else {
michael@0 893 if (andRule->negated) {
michael@0 894 if ( andRule->integerOnly ) {
michael@0 895 result += UNICODE_STRING_SIMPLE(" not in ");
michael@0 896 }
michael@0 897 else {
michael@0 898 result += UNICODE_STRING_SIMPLE(" not within ");
michael@0 899 }
michael@0 900 }
michael@0 901 else {
michael@0 902 if ( andRule->integerOnly ) {
michael@0 903 result += UNICODE_STRING_SIMPLE(" in ");
michael@0 904 }
michael@0 905 else {
michael@0 906 result += UNICODE_STRING_SIMPLE(" within ");
michael@0 907 }
michael@0 908 }
michael@0 909 for (int32_t r=0; r<andRule->rangeList->size(); r+=2) {
michael@0 910 int32_t rangeLo = andRule->rangeList->elementAti(r);
michael@0 911 int32_t rangeHi = andRule->rangeList->elementAti(r+1);
michael@0 912 uprv_itou(digitString,16, rangeLo, 10, 0);
michael@0 913 result += UnicodeString(digitString);
michael@0 914 result += UNICODE_STRING_SIMPLE("..");
michael@0 915 uprv_itou(digitString,16, rangeHi, 10,0);
michael@0 916 result += UnicodeString(digitString);
michael@0 917 if (r+2 < andRule->rangeList->size()) {
michael@0 918 result += UNICODE_STRING_SIMPLE(", ");
michael@0 919 }
michael@0 920 }
michael@0 921 }
michael@0 922 }
michael@0 923 if ( (andRule=andRule->next) != NULL) {
michael@0 924 result += UNICODE_STRING_SIMPLE(" and ");
michael@0 925 }
michael@0 926 }
michael@0 927 if ( (orRule = orRule->next) != NULL ) {
michael@0 928 result += UNICODE_STRING_SIMPLE(" or ");
michael@0 929 }
michael@0 930 }
michael@0 931 }
michael@0 932 if ( fNext != NULL ) {
michael@0 933 result += UNICODE_STRING_SIMPLE("; ");
michael@0 934 fNext->dumpRules(result);
michael@0 935 }
michael@0 936 }
michael@0 937
michael@0 938
michael@0 939 UErrorCode
michael@0 940 RuleChain::getKeywords(int32_t capacityOfKeywords, UnicodeString* keywords, int32_t& arraySize) const {
michael@0 941 if ( arraySize < capacityOfKeywords-1 ) {
michael@0 942 keywords[arraySize++]=fKeyword;
michael@0 943 }
michael@0 944 else {
michael@0 945 return U_BUFFER_OVERFLOW_ERROR;
michael@0 946 }
michael@0 947
michael@0 948 if ( fNext != NULL ) {
michael@0 949 return fNext->getKeywords(capacityOfKeywords, keywords, arraySize);
michael@0 950 }
michael@0 951 else {
michael@0 952 return U_ZERO_ERROR;
michael@0 953 }
michael@0 954 }
michael@0 955
michael@0 956 UBool
michael@0 957 RuleChain::isKeyword(const UnicodeString& keywordParam) const {
michael@0 958 if ( fKeyword == keywordParam ) {
michael@0 959 return TRUE;
michael@0 960 }
michael@0 961
michael@0 962 if ( fNext != NULL ) {
michael@0 963 return fNext->isKeyword(keywordParam);
michael@0 964 }
michael@0 965 else {
michael@0 966 return FALSE;
michael@0 967 }
michael@0 968 }
michael@0 969
michael@0 970
michael@0 971 PluralRuleParser::PluralRuleParser() :
michael@0 972 ruleIndex(0), token(), type(none), prevType(none),
michael@0 973 curAndConstraint(NULL), currentChain(NULL), rangeLowIdx(-1), rangeHiIdx(-1)
michael@0 974 {
michael@0 975 }
michael@0 976
michael@0 977 PluralRuleParser::~PluralRuleParser() {
michael@0 978 }
michael@0 979
michael@0 980
michael@0 981 int32_t
michael@0 982 PluralRuleParser::getNumberValue(const UnicodeString& token) {
michael@0 983 int32_t i;
michael@0 984 char digits[128];
michael@0 985
michael@0 986 i = token.extract(0, token.length(), digits, ARRAY_SIZE(digits), US_INV);
michael@0 987 digits[i]='\0';
michael@0 988
michael@0 989 return((int32_t)atoi(digits));
michael@0 990 }
michael@0 991
michael@0 992
michael@0 993 void
michael@0 994 PluralRuleParser::checkSyntax(UErrorCode &status)
michael@0 995 {
michael@0 996 if (U_FAILURE(status)) {
michael@0 997 return;
michael@0 998 }
michael@0 999 if (!(prevType==none || prevType==tSemiColon)) {
michael@0 1000 type = getKeyType(token, type); // Switch token type from tKeyword if we scanned a reserved word,
michael@0 1001 // and we are not at the start of a rule, where a
michael@0 1002 // keyword is expected.
michael@0 1003 }
michael@0 1004
michael@0 1005 switch(prevType) {
michael@0 1006 case none:
michael@0 1007 case tSemiColon:
michael@0 1008 if (type!=tKeyword && type != tEOF) {
michael@0 1009 status = U_UNEXPECTED_TOKEN;
michael@0 1010 }
michael@0 1011 break;
michael@0 1012 case tVariableN:
michael@0 1013 case tVariableI:
michael@0 1014 case tVariableF:
michael@0 1015 case tVariableT:
michael@0 1016 case tVariableV:
michael@0 1017 if (type != tIs && type != tMod && type != tIn &&
michael@0 1018 type != tNot && type != tWithin && type != tEqual && type != tNotEqual) {
michael@0 1019 status = U_UNEXPECTED_TOKEN;
michael@0 1020 }
michael@0 1021 break;
michael@0 1022 case tKeyword:
michael@0 1023 if (type != tColon) {
michael@0 1024 status = U_UNEXPECTED_TOKEN;
michael@0 1025 }
michael@0 1026 break;
michael@0 1027 case tColon:
michael@0 1028 if (!(type == tVariableN ||
michael@0 1029 type == tVariableI ||
michael@0 1030 type == tVariableF ||
michael@0 1031 type == tVariableT ||
michael@0 1032 type == tVariableV ||
michael@0 1033 type == tAt)) {
michael@0 1034 status = U_UNEXPECTED_TOKEN;
michael@0 1035 }
michael@0 1036 break;
michael@0 1037 case tIs:
michael@0 1038 if ( type != tNumber && type != tNot) {
michael@0 1039 status = U_UNEXPECTED_TOKEN;
michael@0 1040 }
michael@0 1041 break;
michael@0 1042 case tNot:
michael@0 1043 if (type != tNumber && type != tIn && type != tWithin) {
michael@0 1044 status = U_UNEXPECTED_TOKEN;
michael@0 1045 }
michael@0 1046 break;
michael@0 1047 case tMod:
michael@0 1048 case tDot2:
michael@0 1049 case tIn:
michael@0 1050 case tWithin:
michael@0 1051 case tEqual:
michael@0 1052 case tNotEqual:
michael@0 1053 if (type != tNumber) {
michael@0 1054 status = U_UNEXPECTED_TOKEN;
michael@0 1055 }
michael@0 1056 break;
michael@0 1057 case tAnd:
michael@0 1058 case tOr:
michael@0 1059 if ( type != tVariableN &&
michael@0 1060 type != tVariableI &&
michael@0 1061 type != tVariableF &&
michael@0 1062 type != tVariableT &&
michael@0 1063 type != tVariableV) {
michael@0 1064 status = U_UNEXPECTED_TOKEN;
michael@0 1065 }
michael@0 1066 break;
michael@0 1067 case tComma:
michael@0 1068 if (type != tNumber) {
michael@0 1069 status = U_UNEXPECTED_TOKEN;
michael@0 1070 }
michael@0 1071 break;
michael@0 1072 case tNumber:
michael@0 1073 if (type != tDot2 && type != tSemiColon && type != tIs && type != tNot &&
michael@0 1074 type != tIn && type != tEqual && type != tNotEqual && type != tWithin &&
michael@0 1075 type != tAnd && type != tOr && type != tComma && type != tAt &&
michael@0 1076 type != tEOF)
michael@0 1077 {
michael@0 1078 status = U_UNEXPECTED_TOKEN;
michael@0 1079 }
michael@0 1080 // TODO: a comma following a number that is not part of a range will be allowed.
michael@0 1081 // It's not the only case of this sort of thing. Parser needs a re-write.
michael@0 1082 break;
michael@0 1083 case tAt:
michael@0 1084 if (type != tDecimal && type != tInteger) {
michael@0 1085 status = U_UNEXPECTED_TOKEN;
michael@0 1086 }
michael@0 1087 break;
michael@0 1088 default:
michael@0 1089 status = U_UNEXPECTED_TOKEN;
michael@0 1090 break;
michael@0 1091 }
michael@0 1092 }
michael@0 1093
michael@0 1094
michael@0 1095 /*
michael@0 1096 * Scan the next token from the input rules.
michael@0 1097 * rules and returned token type are in the parser state variables.
michael@0 1098 */
michael@0 1099 void
michael@0 1100 PluralRuleParser::getNextToken(UErrorCode &status)
michael@0 1101 {
michael@0 1102 if (U_FAILURE(status)) {
michael@0 1103 return;
michael@0 1104 }
michael@0 1105
michael@0 1106 UChar ch;
michael@0 1107 while (ruleIndex < ruleSrc->length()) {
michael@0 1108 ch = ruleSrc->charAt(ruleIndex);
michael@0 1109 type = charType(ch);
michael@0 1110 if (type != tSpace) {
michael@0 1111 break;
michael@0 1112 }
michael@0 1113 ++(ruleIndex);
michael@0 1114 }
michael@0 1115 if (ruleIndex >= ruleSrc->length()) {
michael@0 1116 type = tEOF;
michael@0 1117 return;
michael@0 1118 }
michael@0 1119 int32_t curIndex= ruleIndex;
michael@0 1120
michael@0 1121 switch (type) {
michael@0 1122 case tColon:
michael@0 1123 case tSemiColon:
michael@0 1124 case tComma:
michael@0 1125 case tEllipsis:
michael@0 1126 case tTilde: // scanned '~'
michael@0 1127 case tAt: // scanned '@'
michael@0 1128 case tEqual: // scanned '='
michael@0 1129 case tMod: // scanned '%'
michael@0 1130 // Single character tokens.
michael@0 1131 ++curIndex;
michael@0 1132 break;
michael@0 1133
michael@0 1134 case tNotEqual: // scanned '!'
michael@0 1135 if (ruleSrc->charAt(curIndex+1) == EQUALS) {
michael@0 1136 curIndex += 2;
michael@0 1137 } else {
michael@0 1138 type = none;
michael@0 1139 curIndex += 1;
michael@0 1140 }
michael@0 1141 break;
michael@0 1142
michael@0 1143 case tKeyword:
michael@0 1144 while (type == tKeyword && ++curIndex < ruleSrc->length()) {
michael@0 1145 ch = ruleSrc->charAt(curIndex);
michael@0 1146 type = charType(ch);
michael@0 1147 }
michael@0 1148 type = tKeyword;
michael@0 1149 break;
michael@0 1150
michael@0 1151 case tNumber:
michael@0 1152 while (type == tNumber && ++curIndex < ruleSrc->length()) {
michael@0 1153 ch = ruleSrc->charAt(curIndex);
michael@0 1154 type = charType(ch);
michael@0 1155 }
michael@0 1156 type = tNumber;
michael@0 1157 break;
michael@0 1158
michael@0 1159 case tDot:
michael@0 1160 // We could be looking at either ".." in a range, or "..." at the end of a sample.
michael@0 1161 if (curIndex+1 >= ruleSrc->length() || ruleSrc->charAt(curIndex+1) != DOT) {
michael@0 1162 ++curIndex;
michael@0 1163 break; // Single dot
michael@0 1164 }
michael@0 1165 if (curIndex+2 >= ruleSrc->length() || ruleSrc->charAt(curIndex+2) != DOT) {
michael@0 1166 curIndex += 2;
michael@0 1167 type = tDot2;
michael@0 1168 break; // double dot
michael@0 1169 }
michael@0 1170 type = tEllipsis;
michael@0 1171 curIndex += 3;
michael@0 1172 break; // triple dot
michael@0 1173
michael@0 1174 default:
michael@0 1175 status = U_UNEXPECTED_TOKEN;
michael@0 1176 ++curIndex;
michael@0 1177 break;
michael@0 1178 }
michael@0 1179
michael@0 1180 U_ASSERT(ruleIndex <= ruleSrc->length());
michael@0 1181 U_ASSERT(curIndex <= ruleSrc->length());
michael@0 1182 token=UnicodeString(*ruleSrc, ruleIndex, curIndex-ruleIndex);
michael@0 1183 ruleIndex = curIndex;
michael@0 1184 }
michael@0 1185
michael@0 1186 tokenType
michael@0 1187 PluralRuleParser::charType(UChar ch) {
michael@0 1188 if ((ch>=U_ZERO) && (ch<=U_NINE)) {
michael@0 1189 return tNumber;
michael@0 1190 }
michael@0 1191 if (ch>=LOW_A && ch<=LOW_Z) {
michael@0 1192 return tKeyword;
michael@0 1193 }
michael@0 1194 switch (ch) {
michael@0 1195 case COLON:
michael@0 1196 return tColon;
michael@0 1197 case SPACE:
michael@0 1198 return tSpace;
michael@0 1199 case SEMI_COLON:
michael@0 1200 return tSemiColon;
michael@0 1201 case DOT:
michael@0 1202 return tDot;
michael@0 1203 case COMMA:
michael@0 1204 return tComma;
michael@0 1205 case EXCLAMATION:
michael@0 1206 return tNotEqual;
michael@0 1207 case EQUALS:
michael@0 1208 return tEqual;
michael@0 1209 case PERCENT_SIGN:
michael@0 1210 return tMod;
michael@0 1211 case AT:
michael@0 1212 return tAt;
michael@0 1213 case ELLIPSIS:
michael@0 1214 return tEllipsis;
michael@0 1215 case TILDE:
michael@0 1216 return tTilde;
michael@0 1217 default :
michael@0 1218 return none;
michael@0 1219 }
michael@0 1220 }
michael@0 1221
michael@0 1222
michael@0 1223 // Set token type for reserved words in the Plural Rule syntax.
michael@0 1224
michael@0 1225 tokenType
michael@0 1226 PluralRuleParser::getKeyType(const UnicodeString &token, tokenType keyType)
michael@0 1227 {
michael@0 1228 if (keyType != tKeyword) {
michael@0 1229 return keyType;
michael@0 1230 }
michael@0 1231
michael@0 1232 if (0 == token.compare(PK_VAR_N, 1)) {
michael@0 1233 keyType = tVariableN;
michael@0 1234 } else if (0 == token.compare(PK_VAR_I, 1)) {
michael@0 1235 keyType = tVariableI;
michael@0 1236 } else if (0 == token.compare(PK_VAR_F, 1)) {
michael@0 1237 keyType = tVariableF;
michael@0 1238 } else if (0 == token.compare(PK_VAR_T, 1)) {
michael@0 1239 keyType = tVariableT;
michael@0 1240 } else if (0 == token.compare(PK_VAR_V, 1)) {
michael@0 1241 keyType = tVariableV;
michael@0 1242 } else if (0 == token.compare(PK_IS, 2)) {
michael@0 1243 keyType = tIs;
michael@0 1244 } else if (0 == token.compare(PK_AND, 3)) {
michael@0 1245 keyType = tAnd;
michael@0 1246 } else if (0 == token.compare(PK_IN, 2)) {
michael@0 1247 keyType = tIn;
michael@0 1248 } else if (0 == token.compare(PK_WITHIN, 6)) {
michael@0 1249 keyType = tWithin;
michael@0 1250 } else if (0 == token.compare(PK_NOT, 3)) {
michael@0 1251 keyType = tNot;
michael@0 1252 } else if (0 == token.compare(PK_MOD, 3)) {
michael@0 1253 keyType = tMod;
michael@0 1254 } else if (0 == token.compare(PK_OR, 2)) {
michael@0 1255 keyType = tOr;
michael@0 1256 } else if (0 == token.compare(PK_DECIMAL, 7)) {
michael@0 1257 keyType = tDecimal;
michael@0 1258 } else if (0 == token.compare(PK_INTEGER, 7)) {
michael@0 1259 keyType = tInteger;
michael@0 1260 }
michael@0 1261 return keyType;
michael@0 1262 }
michael@0 1263
michael@0 1264
michael@0 1265 PluralKeywordEnumeration::PluralKeywordEnumeration(RuleChain *header, UErrorCode& status)
michael@0 1266 : pos(0), fKeywordNames(status) {
michael@0 1267 if (U_FAILURE(status)) {
michael@0 1268 return;
michael@0 1269 }
michael@0 1270 fKeywordNames.setDeleter(uprv_deleteUObject);
michael@0 1271 UBool addKeywordOther=TRUE;
michael@0 1272 RuleChain *node=header;
michael@0 1273 while(node!=NULL) {
michael@0 1274 fKeywordNames.addElement(new UnicodeString(node->fKeyword), status);
michael@0 1275 if (U_FAILURE(status)) {
michael@0 1276 return;
michael@0 1277 }
michael@0 1278 if (0 == node->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
michael@0 1279 addKeywordOther= FALSE;
michael@0 1280 }
michael@0 1281 node=node->fNext;
michael@0 1282 }
michael@0 1283
michael@0 1284 if (addKeywordOther) {
michael@0 1285 fKeywordNames.addElement(new UnicodeString(PLURAL_KEYWORD_OTHER), status);
michael@0 1286 }
michael@0 1287 }
michael@0 1288
michael@0 1289 const UnicodeString*
michael@0 1290 PluralKeywordEnumeration::snext(UErrorCode& status) {
michael@0 1291 if (U_SUCCESS(status) && pos < fKeywordNames.size()) {
michael@0 1292 return (const UnicodeString*)fKeywordNames.elementAt(pos++);
michael@0 1293 }
michael@0 1294 return NULL;
michael@0 1295 }
michael@0 1296
michael@0 1297 void
michael@0 1298 PluralKeywordEnumeration::reset(UErrorCode& /*status*/) {
michael@0 1299 pos=0;
michael@0 1300 }
michael@0 1301
michael@0 1302 int32_t
michael@0 1303 PluralKeywordEnumeration::count(UErrorCode& /*status*/) const {
michael@0 1304 return fKeywordNames.size();
michael@0 1305 }
michael@0 1306
michael@0 1307 PluralKeywordEnumeration::~PluralKeywordEnumeration() {
michael@0 1308 }
michael@0 1309
michael@0 1310
michael@0 1311
michael@0 1312 FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) {
michael@0 1313 init(n, v, f);
michael@0 1314 // check values. TODO make into unit test.
michael@0 1315 //
michael@0 1316 // long visiblePower = (int) Math.pow(10, v);
michael@0 1317 // if (decimalDigits > visiblePower) {
michael@0 1318 // throw new IllegalArgumentException();
michael@0 1319 // }
michael@0 1320 // double fraction = intValue + (decimalDigits / (double) visiblePower);
michael@0 1321 // if (fraction != source) {
michael@0 1322 // double diff = Math.abs(fraction - source)/(Math.abs(fraction) + Math.abs(source));
michael@0 1323 // if (diff > 0.00000001d) {
michael@0 1324 // throw new IllegalArgumentException();
michael@0 1325 // }
michael@0 1326 // }
michael@0 1327 }
michael@0 1328
michael@0 1329 FixedDecimal::FixedDecimal(double n, int32_t v) {
michael@0 1330 // Ugly, but for samples we don't care.
michael@0 1331 init(n, v, getFractionalDigits(n, v));
michael@0 1332 }
michael@0 1333
michael@0 1334 FixedDecimal::FixedDecimal(double n) {
michael@0 1335 init(n);
michael@0 1336 }
michael@0 1337
michael@0 1338 FixedDecimal::FixedDecimal() {
michael@0 1339 init(0, 0, 0);
michael@0 1340 }
michael@0 1341
michael@0 1342
michael@0 1343 // Create a FixedDecimal from a UnicodeString containing a number.
michael@0 1344 // Inefficient, but only used for samples, so simplicity trumps efficiency.
michael@0 1345
michael@0 1346 FixedDecimal::FixedDecimal(const UnicodeString &num, UErrorCode &status) {
michael@0 1347 CharString cs;
michael@0 1348 cs.appendInvariantChars(num, status);
michael@0 1349 DigitList dl;
michael@0 1350 dl.set(cs.toStringPiece(), status);
michael@0 1351 if (U_FAILURE(status)) {
michael@0 1352 init(0, 0, 0);
michael@0 1353 return;
michael@0 1354 }
michael@0 1355 int32_t decimalPoint = num.indexOf(DOT);
michael@0 1356 double n = dl.getDouble();
michael@0 1357 if (decimalPoint == -1) {
michael@0 1358 init(n, 0, 0);
michael@0 1359 } else {
michael@0 1360 int32_t v = num.length() - decimalPoint - 1;
michael@0 1361 init(n, v, getFractionalDigits(n, v));
michael@0 1362 }
michael@0 1363 }
michael@0 1364
michael@0 1365
michael@0 1366 FixedDecimal::FixedDecimal(const FixedDecimal &other) {
michael@0 1367 source = other.source;
michael@0 1368 visibleDecimalDigitCount = other.visibleDecimalDigitCount;
michael@0 1369 decimalDigits = other.decimalDigits;
michael@0 1370 decimalDigitsWithoutTrailingZeros = other.decimalDigitsWithoutTrailingZeros;
michael@0 1371 intValue = other.intValue;
michael@0 1372 hasIntegerValue = other.hasIntegerValue;
michael@0 1373 isNegative = other.isNegative;
michael@0 1374 isNanOrInfinity = other.isNanOrInfinity;
michael@0 1375 }
michael@0 1376
michael@0 1377
michael@0 1378 void FixedDecimal::init(double n) {
michael@0 1379 int32_t numFractionDigits = decimals(n);
michael@0 1380 init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits));
michael@0 1381 }
michael@0 1382
michael@0 1383
michael@0 1384 void FixedDecimal::init(double n, int32_t v, int64_t f) {
michael@0 1385 isNegative = n < 0.0;
michael@0 1386 source = fabs(n);
michael@0 1387 isNanOrInfinity = uprv_isNaN(source) || uprv_isPositiveInfinity(source);
michael@0 1388 if (isNanOrInfinity) {
michael@0 1389 v = 0;
michael@0 1390 f = 0;
michael@0 1391 intValue = 0;
michael@0 1392 hasIntegerValue = FALSE;
michael@0 1393 } else {
michael@0 1394 intValue = (int64_t)source;
michael@0 1395 hasIntegerValue = (source == intValue);
michael@0 1396 }
michael@0 1397
michael@0 1398 visibleDecimalDigitCount = v;
michael@0 1399 decimalDigits = f;
michael@0 1400 if (f == 0) {
michael@0 1401 decimalDigitsWithoutTrailingZeros = 0;
michael@0 1402 } else {
michael@0 1403 int64_t fdwtz = f;
michael@0 1404 while ((fdwtz%10) == 0) {
michael@0 1405 fdwtz /= 10;
michael@0 1406 }
michael@0 1407 decimalDigitsWithoutTrailingZeros = fdwtz;
michael@0 1408 }
michael@0 1409 }
michael@0 1410
michael@0 1411
michael@0 1412 // Fast path only exact initialization. Return true if successful.
michael@0 1413 // Note: Do not multiply by 10 each time through loop, rounding cruft can build
michael@0 1414 // up that makes the check for an integer result fail.
michael@0 1415 // A single multiply of the original number works more reliably.
michael@0 1416 static int32_t p10[] = {1, 10, 100, 1000, 10000};
michael@0 1417 UBool FixedDecimal::quickInit(double n) {
michael@0 1418 UBool success = FALSE;
michael@0 1419 n = fabs(n);
michael@0 1420 int32_t numFractionDigits;
michael@0 1421 for (numFractionDigits = 0; numFractionDigits <= 3; numFractionDigits++) {
michael@0 1422 double scaledN = n * p10[numFractionDigits];
michael@0 1423 if (scaledN == floor(scaledN)) {
michael@0 1424 success = TRUE;
michael@0 1425 break;
michael@0 1426 }
michael@0 1427 }
michael@0 1428 if (success) {
michael@0 1429 init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits));
michael@0 1430 }
michael@0 1431 return success;
michael@0 1432 }
michael@0 1433
michael@0 1434
michael@0 1435
michael@0 1436 int32_t FixedDecimal::decimals(double n) {
michael@0 1437 // Count the number of decimal digits in the fraction part of the number, excluding trailing zeros.
michael@0 1438 // fastpath the common cases, integers or fractions with 3 or fewer digits
michael@0 1439 n = fabs(n);
michael@0 1440 for (int ndigits=0; ndigits<=3; ndigits++) {
michael@0 1441 double scaledN = n * p10[ndigits];
michael@0 1442 if (scaledN == floor(scaledN)) {
michael@0 1443 return ndigits;
michael@0 1444 }
michael@0 1445 }
michael@0 1446
michael@0 1447 // Slow path, convert with sprintf, parse converted output.
michael@0 1448 char buf[30] = {0};
michael@0 1449 sprintf(buf, "%1.15e", n);
michael@0 1450 // formatted number looks like this: 1.234567890123457e-01
michael@0 1451 int exponent = atoi(buf+18);
michael@0 1452 int numFractionDigits = 15;
michael@0 1453 for (int i=16; ; --i) {
michael@0 1454 if (buf[i] != '0') {
michael@0 1455 break;
michael@0 1456 }
michael@0 1457 --numFractionDigits;
michael@0 1458 }
michael@0 1459 numFractionDigits -= exponent; // Fraction part of fixed point representation.
michael@0 1460 return numFractionDigits;
michael@0 1461 }
michael@0 1462
michael@0 1463
michael@0 1464 // Get the fraction digits of a double, represented as an integer.
michael@0 1465 // v is the number of visible fraction digits in the displayed form of the number.
michael@0 1466 // Example: n = 1001.234, v = 6, result = 234000
michael@0 1467 // TODO: need to think through how this is used in the plural rule context.
michael@0 1468 // This function can easily encounter integer overflow,
michael@0 1469 // and can easily return noise digits when the precision of a double is exceeded.
michael@0 1470
michael@0 1471 int64_t FixedDecimal::getFractionalDigits(double n, int32_t v) {
michael@0 1472 if (v == 0 || n == floor(n) || uprv_isNaN(n) || uprv_isPositiveInfinity(n)) {
michael@0 1473 return 0;
michael@0 1474 }
michael@0 1475 n = fabs(n);
michael@0 1476 double fract = n - floor(n);
michael@0 1477 switch (v) {
michael@0 1478 case 1: return (int64_t)(fract*10.0 + 0.5);
michael@0 1479 case 2: return (int64_t)(fract*100.0 + 0.5);
michael@0 1480 case 3: return (int64_t)(fract*1000.0 + 0.5);
michael@0 1481 default:
michael@0 1482 double scaled = floor(fract * pow(10.0, (double)v) + 0.5);
michael@0 1483 if (scaled > U_INT64_MAX) {
michael@0 1484 return U_INT64_MAX;
michael@0 1485 } else {
michael@0 1486 return (int64_t)scaled;
michael@0 1487 }
michael@0 1488 }
michael@0 1489 }
michael@0 1490
michael@0 1491
michael@0 1492 void FixedDecimal::adjustForMinFractionDigits(int32_t minFractionDigits) {
michael@0 1493 int32_t numTrailingFractionZeros = minFractionDigits - visibleDecimalDigitCount;
michael@0 1494 if (numTrailingFractionZeros > 0) {
michael@0 1495 for (int32_t i=0; i<numTrailingFractionZeros; i++) {
michael@0 1496 // Do not let the decimalDigits value overflow if there are many trailing zeros.
michael@0 1497 // Limit the value to 18 digits, the most that a 64 bit int can fully represent.
michael@0 1498 if (decimalDigits >= 100000000000000000LL) {
michael@0 1499 break;
michael@0 1500 }
michael@0 1501 decimalDigits *= 10;
michael@0 1502 }
michael@0 1503 visibleDecimalDigitCount += numTrailingFractionZeros;
michael@0 1504 }
michael@0 1505 }
michael@0 1506
michael@0 1507
michael@0 1508 double FixedDecimal::get(tokenType operand) const {
michael@0 1509 switch(operand) {
michael@0 1510 case tVariableN: return source;
michael@0 1511 case tVariableI: return (double)intValue;
michael@0 1512 case tVariableF: return (double)decimalDigits;
michael@0 1513 case tVariableT: return (double)decimalDigitsWithoutTrailingZeros;
michael@0 1514 case tVariableV: return visibleDecimalDigitCount;
michael@0 1515 default:
michael@0 1516 U_ASSERT(FALSE); // unexpected.
michael@0 1517 return source;
michael@0 1518 }
michael@0 1519 }
michael@0 1520
michael@0 1521 int32_t FixedDecimal::getVisibleFractionDigitCount() const {
michael@0 1522 return visibleDecimalDigitCount;
michael@0 1523 }
michael@0 1524
michael@0 1525
michael@0 1526
michael@0 1527 PluralAvailableLocalesEnumeration::PluralAvailableLocalesEnumeration(UErrorCode &status) {
michael@0 1528 fLocales = NULL;
michael@0 1529 fRes = NULL;
michael@0 1530 fOpenStatus = status;
michael@0 1531 if (U_FAILURE(status)) {
michael@0 1532 return;
michael@0 1533 }
michael@0 1534 fOpenStatus = U_ZERO_ERROR;
michael@0 1535 LocalUResourceBundlePointer rb(ures_openDirect(NULL, "plurals", &fOpenStatus));
michael@0 1536 fLocales = ures_getByKey(rb.getAlias(), "locales", NULL, &fOpenStatus);
michael@0 1537 }
michael@0 1538
michael@0 1539 PluralAvailableLocalesEnumeration::~PluralAvailableLocalesEnumeration() {
michael@0 1540 ures_close(fLocales);
michael@0 1541 ures_close(fRes);
michael@0 1542 fLocales = NULL;
michael@0 1543 fRes = NULL;
michael@0 1544 }
michael@0 1545
michael@0 1546 const char *PluralAvailableLocalesEnumeration::next(int32_t *resultLength, UErrorCode &status) {
michael@0 1547 if (U_FAILURE(status)) {
michael@0 1548 return NULL;
michael@0 1549 }
michael@0 1550 if (U_FAILURE(fOpenStatus)) {
michael@0 1551 status = fOpenStatus;
michael@0 1552 return NULL;
michael@0 1553 }
michael@0 1554 fRes = ures_getNextResource(fLocales, fRes, &status);
michael@0 1555 if (fRes == NULL || U_FAILURE(status)) {
michael@0 1556 if (status == U_INDEX_OUTOFBOUNDS_ERROR) {
michael@0 1557 status = U_ZERO_ERROR;
michael@0 1558 }
michael@0 1559 return NULL;
michael@0 1560 }
michael@0 1561 const char *result = ures_getKey(fRes);
michael@0 1562 if (resultLength != NULL) {
michael@0 1563 *resultLength = uprv_strlen(result);
michael@0 1564 }
michael@0 1565 return result;
michael@0 1566 }
michael@0 1567
michael@0 1568
michael@0 1569 void PluralAvailableLocalesEnumeration::reset(UErrorCode &status) {
michael@0 1570 if (U_FAILURE(status)) {
michael@0 1571 return;
michael@0 1572 }
michael@0 1573 if (U_FAILURE(fOpenStatus)) {
michael@0 1574 status = fOpenStatus;
michael@0 1575 return;
michael@0 1576 }
michael@0 1577 ures_resetIterator(fLocales);
michael@0 1578 }
michael@0 1579
michael@0 1580 int32_t PluralAvailableLocalesEnumeration::count(UErrorCode &status) const {
michael@0 1581 if (U_FAILURE(status)) {
michael@0 1582 return 0;
michael@0 1583 }
michael@0 1584 if (U_FAILURE(fOpenStatus)) {
michael@0 1585 status = fOpenStatus;
michael@0 1586 return 0;
michael@0 1587 }
michael@0 1588 return ures_getSize(fLocales);
michael@0 1589 }
michael@0 1590
michael@0 1591 U_NAMESPACE_END
michael@0 1592
michael@0 1593
michael@0 1594 #endif /* #if !UCONFIG_NO_FORMATTING */
michael@0 1595
michael@0 1596 //eof

mercurial