plurrule.cpp (65700B)
1 // © 2016 and later: Unicode, Inc. and others. 2 // License & terms of use: http://www.unicode.org/copyright.html 3 /* 4 ******************************************************************************* 5 * Copyright (C) 2007-2016, International Business Machines Corporation and 6 * others. All Rights Reserved. 7 ******************************************************************************* 8 * 9 * File plurrule.cpp 10 */ 11 12 #include <math.h> 13 #include <stdio.h> 14 15 #include <utility> 16 17 #include "unicode/utypes.h" 18 #include "unicode/localpointer.h" 19 #include "unicode/plurrule.h" 20 #include "unicode/upluralrules.h" 21 #include "unicode/ures.h" 22 #include "unicode/numfmt.h" 23 #include "unicode/decimfmt.h" 24 #include "unicode/numberrangeformatter.h" 25 #include "charstr.h" 26 #include "cmemory.h" 27 #include "cstring.h" 28 #include "hash.h" 29 #include "locutil.h" 30 #include "mutex.h" 31 #include "number_decnum.h" 32 #include "patternprops.h" 33 #include "plurrule_impl.h" 34 #include "putilimp.h" 35 #include "ucln_in.h" 36 #include "ustrfmt.h" 37 #include "uassert.h" 38 #include "uvectr32.h" 39 #include "sharedpluralrules.h" 40 #include "unifiedcache.h" 41 #include "number_decimalquantity.h" 42 #include "util.h" 43 #include "pluralranges.h" 44 #include "numrange_impl.h" 45 #include "ulocimp.h" 46 47 #if !UCONFIG_NO_FORMATTING 48 49 U_NAMESPACE_BEGIN 50 51 using namespace icu::pluralimpl; 52 using icu::number::impl::DecNum; 53 using icu::number::impl::DecimalQuantity; 54 using icu::number::impl::RoundingMode; 55 56 static const char16_t PLURAL_KEYWORD_OTHER[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,0}; 57 static const char16_t PLURAL_DEFAULT_RULE[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,COLON,SPACE,LOW_N,0}; 58 static const char16_t PK_IN[]={LOW_I,LOW_N,0}; 59 static const char16_t PK_NOT[]={LOW_N,LOW_O,LOW_T,0}; 60 static const char16_t PK_IS[]={LOW_I,LOW_S,0}; 61 static const char16_t PK_MOD[]={LOW_M,LOW_O,LOW_D,0}; 62 static const char16_t PK_AND[]={LOW_A,LOW_N,LOW_D,0}; 63 static const char16_t PK_OR[]={LOW_O,LOW_R,0}; 64 static const char16_t PK_VAR_N[]={LOW_N,0}; 65 static const char16_t PK_VAR_I[]={LOW_I,0}; 66 static const char16_t PK_VAR_F[]={LOW_F,0}; 67 static const char16_t PK_VAR_T[]={LOW_T,0}; 68 static const char16_t PK_VAR_E[]={LOW_E,0}; 69 static const char16_t PK_VAR_C[]={LOW_C,0}; 70 static const char16_t PK_VAR_V[]={LOW_V,0}; 71 static const char16_t PK_WITHIN[]={LOW_W,LOW_I,LOW_T,LOW_H,LOW_I,LOW_N,0}; 72 static const char16_t PK_DECIMAL[]={LOW_D,LOW_E,LOW_C,LOW_I,LOW_M,LOW_A,LOW_L,0}; 73 static const char16_t PK_INTEGER[]={LOW_I,LOW_N,LOW_T,LOW_E,LOW_G,LOW_E,LOW_R,0}; 74 75 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralRules) 76 UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralKeywordEnumeration) 77 78 PluralRules::PluralRules(UErrorCode& /*status*/) 79 : UObject(), 80 mRules(nullptr), 81 mStandardPluralRanges(nullptr), 82 mInternalStatus(U_ZERO_ERROR) 83 { 84 } 85 86 PluralRules::PluralRules(const PluralRules& other) 87 : UObject(other), 88 mRules(nullptr), 89 mStandardPluralRanges(nullptr), 90 mInternalStatus(U_ZERO_ERROR) 91 { 92 *this=other; 93 } 94 95 PluralRules::~PluralRules() { 96 delete mRules; 97 delete mStandardPluralRanges; 98 } 99 100 SharedPluralRules::~SharedPluralRules() { 101 delete ptr; 102 } 103 104 PluralRules* 105 PluralRules::clone() const { 106 // Since clone doesn't have a 'status' parameter, the best we can do is return nullptr if 107 // the newly created object was not fully constructed properly (an error occurred). 108 UErrorCode localStatus = U_ZERO_ERROR; 109 return clone(localStatus); 110 } 111 112 PluralRules* 113 PluralRules::clone(UErrorCode& status) const { 114 LocalPointer<PluralRules> newObj(new PluralRules(*this), status); 115 if (U_SUCCESS(status) && U_FAILURE(newObj->mInternalStatus)) { 116 status = newObj->mInternalStatus; 117 newObj.adoptInstead(nullptr); 118 } 119 return newObj.orphan(); 120 } 121 122 PluralRules& 123 PluralRules::operator=(const PluralRules& other) { 124 if (this != &other) { 125 delete mRules; 126 mRules = nullptr; 127 delete mStandardPluralRanges; 128 mStandardPluralRanges = nullptr; 129 mInternalStatus = other.mInternalStatus; 130 if (U_FAILURE(mInternalStatus)) { 131 // bail out early if the object we were copying from was already 'invalid'. 132 return *this; 133 } 134 if (other.mRules != nullptr) { 135 mRules = new RuleChain(*other.mRules); 136 if (mRules == nullptr) { 137 mInternalStatus = U_MEMORY_ALLOCATION_ERROR; 138 } 139 else if (U_FAILURE(mRules->fInternalStatus)) { 140 // If the RuleChain wasn't fully copied, then set our status to failure as well. 141 mInternalStatus = mRules->fInternalStatus; 142 } 143 } 144 if (other.mStandardPluralRanges != nullptr) { 145 mStandardPluralRanges = other.mStandardPluralRanges->copy(mInternalStatus) 146 .toPointer(mInternalStatus) 147 .orphan(); 148 } 149 } 150 return *this; 151 } 152 153 StringEnumeration* PluralRules::getAvailableLocales(UErrorCode &status) { 154 if (U_FAILURE(status)) { 155 return nullptr; 156 } 157 LocalPointer<StringEnumeration> result(new PluralAvailableLocalesEnumeration(status), status); 158 if (U_FAILURE(status)) { 159 return nullptr; 160 } 161 return result.orphan(); 162 } 163 164 165 PluralRules* U_EXPORT2 166 PluralRules::createRules(const UnicodeString& description, UErrorCode& status) { 167 if (U_FAILURE(status)) { 168 return nullptr; 169 } 170 PluralRuleParser parser; 171 LocalPointer<PluralRules> newRules(new PluralRules(status), status); 172 if (U_FAILURE(status)) { 173 return nullptr; 174 } 175 parser.parse(description, newRules.getAlias(), status); 176 if (U_FAILURE(status)) { 177 newRules.adoptInstead(nullptr); 178 } 179 return newRules.orphan(); 180 } 181 182 183 PluralRules* U_EXPORT2 184 PluralRules::createDefaultRules(UErrorCode& status) { 185 return createRules(UnicodeString(true, PLURAL_DEFAULT_RULE, -1), status); 186 } 187 188 /******************************************************************************/ 189 /* Create PluralRules cache */ 190 191 template<> U_I18N_API 192 const SharedPluralRules *LocaleCacheKey<SharedPluralRules>::createObject( 193 const void * /*unused*/, UErrorCode &status) const { 194 const char *localeId = fLoc.getName(); 195 LocalPointer<PluralRules> pr(PluralRules::internalForLocale(localeId, UPLURAL_TYPE_CARDINAL, status), status); 196 if (U_FAILURE(status)) { 197 return nullptr; 198 } 199 LocalPointer<SharedPluralRules> result(new SharedPluralRules(pr.getAlias()), status); 200 if (U_FAILURE(status)) { 201 return nullptr; 202 } 203 pr.orphan(); // result was successfully created so it nows pr. 204 result->addRef(); 205 return result.orphan(); 206 } 207 208 /* end plural rules cache */ 209 /******************************************************************************/ 210 211 const SharedPluralRules* U_EXPORT2 212 PluralRules::createSharedInstance( 213 const Locale& locale, UPluralType type, UErrorCode& status) { 214 if (U_FAILURE(status)) { 215 return nullptr; 216 } 217 if (type != UPLURAL_TYPE_CARDINAL) { 218 status = U_UNSUPPORTED_ERROR; 219 return nullptr; 220 } 221 const SharedPluralRules *result = nullptr; 222 UnifiedCache::getByLocale(locale, result, status); 223 return result; 224 } 225 226 PluralRules* U_EXPORT2 227 PluralRules::forLocale(const Locale& locale, UErrorCode& status) { 228 return forLocale(locale, UPLURAL_TYPE_CARDINAL, status); 229 } 230 231 PluralRules* U_EXPORT2 232 PluralRules::forLocale(const Locale& locale, UPluralType type, UErrorCode& status) { 233 if (type != UPLURAL_TYPE_CARDINAL) { 234 return internalForLocale(locale, type, status); 235 } 236 const SharedPluralRules *shared = createSharedInstance( 237 locale, type, status); 238 if (U_FAILURE(status)) { 239 return nullptr; 240 } 241 PluralRules *result = (*shared)->clone(status); 242 shared->removeRef(); 243 return result; 244 } 245 246 PluralRules* U_EXPORT2 247 PluralRules::internalForLocale(const Locale& locale, UPluralType type, UErrorCode& status) { 248 if (U_FAILURE(status)) { 249 return nullptr; 250 } 251 if (type >= UPLURAL_TYPE_COUNT) { 252 status = U_ILLEGAL_ARGUMENT_ERROR; 253 return nullptr; 254 } 255 LocalPointer<PluralRules> newObj(new PluralRules(status), status); 256 if (U_FAILURE(status)) { 257 return nullptr; 258 } 259 UnicodeString locRule = newObj->getRuleFromResource(locale, type, status); 260 // TODO: which other errors, if any, should be returned? 261 if (locRule.length() == 0) { 262 // If an out-of-memory error occurred, then stop and report the failure. 263 if (status == U_MEMORY_ALLOCATION_ERROR) { 264 return nullptr; 265 } 266 // Locales with no specific rules (all numbers have the "other" category 267 // will return a U_MISSING_RESOURCE_ERROR at this point. This is not 268 // an error. 269 locRule = UnicodeString(PLURAL_DEFAULT_RULE); 270 status = U_ZERO_ERROR; 271 } 272 PluralRuleParser parser; 273 parser.parse(locRule, newObj.getAlias(), status); 274 // TODO: should rule parse errors be returned, or 275 // should we silently use default rules? 276 // Original impl used default rules. 277 // Ask the question to ICU Core. 278 279 newObj->mStandardPluralRanges = StandardPluralRanges::forLocale(locale, status) 280 .toPointer(status) 281 .orphan(); 282 283 return newObj.orphan(); 284 } 285 286 UnicodeString 287 PluralRules::select(int32_t number) const { 288 return select(FixedDecimal(number)); 289 } 290 291 UnicodeString 292 PluralRules::select(double number) const { 293 return select(FixedDecimal(number)); 294 } 295 296 UnicodeString 297 PluralRules::select(const number::FormattedNumber& number, UErrorCode& status) const { 298 DecimalQuantity dq; 299 number.getDecimalQuantity(dq, status); 300 if (U_FAILURE(status)) { 301 return ICU_Utility::makeBogusString(); 302 } 303 if (U_FAILURE(mInternalStatus)) { 304 status = mInternalStatus; 305 return ICU_Utility::makeBogusString(); 306 } 307 return select(dq); 308 } 309 310 UnicodeString 311 PluralRules::select(const IFixedDecimal &number) const { 312 if (mRules == nullptr) { 313 return UnicodeString(true, PLURAL_DEFAULT_RULE, -1); 314 } 315 else { 316 return mRules->select(number); 317 } 318 } 319 320 UnicodeString 321 PluralRules::select(const number::FormattedNumberRange& range, UErrorCode& status) const { 322 return select(range.getData(status), status); 323 } 324 325 UnicodeString 326 PluralRules::select(const number::impl::UFormattedNumberRangeData* impl, UErrorCode& status) const { 327 if (U_FAILURE(status)) { 328 return ICU_Utility::makeBogusString(); 329 } 330 if (U_FAILURE(mInternalStatus)) { 331 status = mInternalStatus; 332 return ICU_Utility::makeBogusString(); 333 } 334 if (mStandardPluralRanges == nullptr) { 335 // Happens if PluralRules was constructed via createRules() 336 status = U_UNSUPPORTED_ERROR; 337 return ICU_Utility::makeBogusString(); 338 } 339 auto form1 = StandardPlural::fromString(select(impl->quantity1), status); 340 auto form2 = StandardPlural::fromString(select(impl->quantity2), status); 341 if (U_FAILURE(status)) { 342 return ICU_Utility::makeBogusString(); 343 } 344 auto result = mStandardPluralRanges->resolve(form1, form2); 345 return UnicodeString(StandardPlural::getKeyword(result), -1, US_INV); 346 } 347 348 349 StringEnumeration* 350 PluralRules::getKeywords(UErrorCode& status) const { 351 if (U_FAILURE(status)) { 352 return nullptr; 353 } 354 if (U_FAILURE(mInternalStatus)) { 355 status = mInternalStatus; 356 return nullptr; 357 } 358 LocalPointer<StringEnumeration> nameEnumerator(new PluralKeywordEnumeration(mRules, status), status); 359 if (U_FAILURE(status)) { 360 return nullptr; 361 } 362 return nameEnumerator.orphan(); 363 } 364 365 double 366 PluralRules::getUniqueKeywordValue(const UnicodeString& /* keyword */) { 367 // Not Implemented. 368 return UPLRULES_NO_UNIQUE_VALUE; 369 } 370 371 int32_t 372 PluralRules::getAllKeywordValues(const UnicodeString & /* keyword */, double * /* dest */, 373 int32_t /* destCapacity */, UErrorCode& error) { 374 error = U_UNSUPPORTED_ERROR; 375 return 0; 376 } 377 378 /** 379 * Helper method for the overrides of getSamples() for double and DecimalQuantity 380 * return value types. Provide only one of an allocated array of double or 381 * DecimalQuantity, and a nullptr for the other. 382 */ 383 static int32_t 384 getSamplesFromString(const UnicodeString &samples, double *destDbl, 385 DecimalQuantity* destDq, int32_t destCapacity, 386 UErrorCode& status) { 387 388 if ((destDbl == nullptr && destDq == nullptr) 389 || (destDbl != nullptr && destDq != nullptr)) { 390 status = U_INTERNAL_PROGRAM_ERROR; 391 return 0; 392 } 393 394 bool isDouble = destDbl != nullptr; 395 int32_t sampleCount = 0; 396 int32_t sampleStartIdx = 0; 397 int32_t sampleEndIdx = 0; 398 399 //std::string ss; // TODO: debugging. 400 // std::cout << "PluralRules::getSamples(), samples = \"" << samples.toUTF8String(ss) << "\"\n"; 401 for (sampleCount = 0; sampleCount < destCapacity && sampleStartIdx < samples.length(); ) { 402 sampleEndIdx = samples.indexOf(COMMA, sampleStartIdx); 403 if (sampleEndIdx == -1) { 404 sampleEndIdx = samples.length(); 405 } 406 const UnicodeString &sampleRange = samples.tempSubStringBetween(sampleStartIdx, sampleEndIdx); 407 // ss.erase(); 408 // std::cout << "PluralRules::getSamples(), samplesRange = \"" << sampleRange.toUTF8String(ss) << "\"\n"; 409 int32_t tildeIndex = sampleRange.indexOf(TILDE); 410 if (tildeIndex < 0) { 411 DecimalQuantity dq = DecimalQuantity::fromExponentString(sampleRange, status); 412 if (isDouble) { 413 // See warning note below about lack of precision for floating point samples for numbers with 414 // trailing zeroes in the decimal fraction representation. 415 double dblValue = dq.toDouble(); 416 if (!(dblValue == floor(dblValue) && dq.fractionCount() > 0)) { 417 destDbl[sampleCount++] = dblValue; 418 } 419 } else { 420 destDq[sampleCount++] = dq; 421 } 422 } else { 423 DecimalQuantity rangeLo = 424 DecimalQuantity::fromExponentString(sampleRange.tempSubStringBetween(0, tildeIndex), status); 425 DecimalQuantity rangeHi = DecimalQuantity::fromExponentString(sampleRange.tempSubStringBetween(tildeIndex+1), status); 426 if (U_FAILURE(status)) { 427 break; 428 } 429 if (rangeHi.toDouble() < rangeLo.toDouble()) { 430 status = U_INVALID_FORMAT_ERROR; 431 break; 432 } 433 434 DecimalQuantity incrementDq; 435 incrementDq.setToInt(1); 436 int32_t lowerDispMag = rangeLo.getLowerDisplayMagnitude(); 437 int32_t exponent = rangeLo.getExponent(); 438 int32_t incrementScale = lowerDispMag + exponent; 439 incrementDq.adjustMagnitude(incrementScale); 440 double incrementVal = incrementDq.toDouble(); // 10 ^ incrementScale 441 442 443 DecimalQuantity dq(rangeLo); 444 double dblValue = dq.toDouble(); 445 double end = rangeHi.toDouble(); 446 447 while (dblValue <= end) { 448 if (isDouble) { 449 // Hack Alert: don't return any decimal samples with integer values that 450 // originated from a format with trailing decimals. 451 // This API is returning doubles, which can't distinguish having displayed 452 // zeros to the right of the decimal. 453 // This results in test failures with values mapping back to a different keyword. 454 if (!(dblValue == floor(dblValue) && dq.fractionCount() > 0)) { 455 destDbl[sampleCount++] = dblValue; 456 } 457 } else { 458 destDq[sampleCount++] = dq; 459 } 460 if (sampleCount >= destCapacity) { 461 break; 462 } 463 464 // Increment dq for next iteration 465 466 // Because DecNum and DecimalQuantity do not support 467 // add operations, we need to convert to/from double, 468 // despite precision lossiness for decimal fractions like 0.1. 469 dblValue += incrementVal; 470 DecNum newDqDecNum; 471 newDqDecNum.setTo(dblValue, status); 472 DecimalQuantity newDq; 473 newDq.setToDecNum(newDqDecNum, status); 474 newDq.setMinFraction(-lowerDispMag); 475 newDq.roundToMagnitude(lowerDispMag, RoundingMode::UNUM_ROUND_HALFEVEN, status); 476 newDq.adjustMagnitude(-exponent); 477 newDq.adjustExponent(exponent); 478 dblValue = newDq.toDouble(); 479 dq = newDq; 480 } 481 } 482 sampleStartIdx = sampleEndIdx + 1; 483 } 484 return sampleCount; 485 } 486 487 int32_t 488 PluralRules::getSamples(const UnicodeString &keyword, double *dest, 489 int32_t destCapacity, UErrorCode& status) { 490 if (U_FAILURE(status)) { 491 return 0; 492 } 493 if (U_FAILURE(mInternalStatus)) { 494 status = mInternalStatus; 495 return 0; 496 } 497 if (dest != nullptr ? destCapacity < 0 : destCapacity != 0) { 498 status = U_ILLEGAL_ARGUMENT_ERROR; 499 return 0; 500 } 501 RuleChain *rc = rulesForKeyword(keyword); 502 if (rc == nullptr) { 503 return 0; 504 } 505 int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, dest, nullptr, destCapacity, status); 506 if (numSamples == 0) { 507 numSamples = getSamplesFromString(rc->fDecimalSamples, dest, nullptr, destCapacity, status); 508 } 509 return numSamples; 510 } 511 512 int32_t 513 PluralRules::getSamples(const UnicodeString &keyword, DecimalQuantity *dest, 514 int32_t destCapacity, UErrorCode& status) { 515 if (U_FAILURE(status)) { 516 return 0; 517 } 518 if (U_FAILURE(mInternalStatus)) { 519 status = mInternalStatus; 520 return 0; 521 } 522 if (dest != nullptr ? destCapacity < 0 : destCapacity != 0) { 523 status = U_ILLEGAL_ARGUMENT_ERROR; 524 return 0; 525 } 526 RuleChain *rc = rulesForKeyword(keyword); 527 if (rc == nullptr) { 528 return 0; 529 } 530 531 int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, nullptr, dest, destCapacity, status); 532 if (numSamples == 0) { 533 numSamples = getSamplesFromString(rc->fDecimalSamples, nullptr, dest, destCapacity, status); 534 } 535 return numSamples; 536 } 537 538 539 RuleChain *PluralRules::rulesForKeyword(const UnicodeString &keyword) const { 540 RuleChain *rc; 541 for (rc = mRules; rc != nullptr; rc = rc->fNext) { 542 if (rc->fKeyword == keyword) { 543 break; 544 } 545 } 546 return rc; 547 } 548 549 550 UBool 551 PluralRules::isKeyword(const UnicodeString& keyword) const { 552 if (0 == keyword.compare(PLURAL_KEYWORD_OTHER, 5)) { 553 return true; 554 } 555 return rulesForKeyword(keyword) != nullptr; 556 } 557 558 UnicodeString 559 PluralRules::getKeywordOther() const { 560 return UnicodeString(true, PLURAL_KEYWORD_OTHER, 5); 561 } 562 563 bool 564 PluralRules::operator==(const PluralRules& other) const { 565 const UnicodeString *ptrKeyword; 566 UErrorCode status= U_ZERO_ERROR; 567 568 if ( this == &other ) { 569 return true; 570 } 571 LocalPointer<StringEnumeration> myKeywordList(getKeywords(status)); 572 LocalPointer<StringEnumeration> otherKeywordList(other.getKeywords(status)); 573 if (U_FAILURE(status)) { 574 return false; 575 } 576 577 if (myKeywordList->count(status)!=otherKeywordList->count(status)) { 578 return false; 579 } 580 myKeywordList->reset(status); 581 while ((ptrKeyword=myKeywordList->snext(status))!=nullptr) { 582 if (!other.isKeyword(*ptrKeyword)) { 583 return false; 584 } 585 } 586 otherKeywordList->reset(status); 587 while ((ptrKeyword=otherKeywordList->snext(status))!=nullptr) { 588 if (!this->isKeyword(*ptrKeyword)) { 589 return false; 590 } 591 } 592 if (U_FAILURE(status)) { 593 return false; 594 } 595 596 return true; 597 } 598 599 600 void 601 PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErrorCode &status) 602 { 603 if (U_FAILURE(status)) { 604 return; 605 } 606 U_ASSERT(ruleIndex == 0); // Parsers are good for a single use only! 607 ruleSrc = &ruleData; 608 609 while (ruleIndex< ruleSrc->length()) { 610 getNextToken(status); 611 if (U_FAILURE(status)) { 612 return; 613 } 614 checkSyntax(status); 615 if (U_FAILURE(status)) { 616 return; 617 } 618 switch (type) { 619 case tAnd: 620 U_ASSERT(curAndConstraint != nullptr); 621 curAndConstraint = curAndConstraint->add(status); 622 break; 623 case tOr: 624 { 625 U_ASSERT(currentChain != nullptr); 626 OrConstraint *orNode=currentChain->ruleHeader; 627 while (orNode->next != nullptr) { 628 orNode = orNode->next; 629 } 630 orNode->next= new OrConstraint(); 631 if (orNode->next == nullptr) { 632 status = U_MEMORY_ALLOCATION_ERROR; 633 break; 634 } 635 orNode=orNode->next; 636 orNode->next=nullptr; 637 curAndConstraint = orNode->add(status); 638 } 639 break; 640 case tIs: 641 U_ASSERT(curAndConstraint != nullptr); 642 U_ASSERT(curAndConstraint->value == -1); 643 U_ASSERT(curAndConstraint->rangeList == nullptr); 644 break; 645 case tNot: 646 U_ASSERT(curAndConstraint != nullptr); 647 curAndConstraint->negated=true; 648 break; 649 650 case tNotEqual: 651 curAndConstraint->negated=true; 652 U_FALLTHROUGH; 653 case tIn: 654 case tWithin: 655 case tEqual: 656 { 657 U_ASSERT(curAndConstraint != nullptr); 658 if (curAndConstraint->rangeList != nullptr) { 659 // Already get a '='. 660 status = U_UNEXPECTED_TOKEN; 661 break; 662 } 663 LocalPointer<UVector32> newRangeList(new UVector32(status), status); 664 if (U_FAILURE(status)) { 665 break; 666 } 667 curAndConstraint->rangeList = newRangeList.orphan(); 668 curAndConstraint->rangeList->addElement(-1, status); // range Low 669 curAndConstraint->rangeList->addElement(-1, status); // range Hi 670 rangeLowIdx = 0; 671 rangeHiIdx = 1; 672 curAndConstraint->value=PLURAL_RANGE_HIGH; 673 curAndConstraint->integerOnly = (type != tWithin); 674 } 675 break; 676 case tNumber: 677 U_ASSERT(curAndConstraint != nullptr); 678 if ( (curAndConstraint->op==AndConstraint::MOD)&& 679 (curAndConstraint->opNum == -1 ) ) { 680 int32_t num = getNumberValue(token); 681 if (num == -1) { 682 status = U_UNEXPECTED_TOKEN; 683 break; 684 } 685 curAndConstraint->opNum=num; 686 } 687 else { 688 if (curAndConstraint->rangeList == nullptr) { 689 // this is for an 'is' rule 690 int32_t num = getNumberValue(token); 691 if (num == -1) { 692 status = U_UNEXPECTED_TOKEN; 693 break; 694 } 695 curAndConstraint->value = num; 696 } else { 697 // this is for an 'in' or 'within' rule 698 if (curAndConstraint->rangeList->elementAti(rangeLowIdx) == -1) { 699 int32_t num = getNumberValue(token); 700 if (num == -1) { 701 status = U_UNEXPECTED_TOKEN; 702 break; 703 } 704 curAndConstraint->rangeList->setElementAt(num, rangeLowIdx); 705 curAndConstraint->rangeList->setElementAt(num, rangeHiIdx); 706 } 707 else { 708 int32_t num = getNumberValue(token); 709 if (num == -1) { 710 status = U_UNEXPECTED_TOKEN; 711 break; 712 } 713 curAndConstraint->rangeList->setElementAt(num, rangeHiIdx); 714 if (curAndConstraint->rangeList->elementAti(rangeLowIdx) > 715 curAndConstraint->rangeList->elementAti(rangeHiIdx)) { 716 // Range Lower bound > Range Upper bound. 717 // U_UNEXPECTED_TOKEN seems a little funny, but it is consistently 718 // used for all plural rule parse errors. 719 status = U_UNEXPECTED_TOKEN; 720 break; 721 } 722 } 723 } 724 } 725 break; 726 case tComma: 727 // TODO: rule syntax checking is inadequate, can happen with badly formed rules. 728 // Catch cases like "n mod 10, is 1" here instead. 729 if (curAndConstraint == nullptr || curAndConstraint->rangeList == nullptr) { 730 status = U_UNEXPECTED_TOKEN; 731 break; 732 } 733 U_ASSERT(curAndConstraint->rangeList->size() >= 2); 734 rangeLowIdx = curAndConstraint->rangeList->size(); 735 curAndConstraint->rangeList->addElement(-1, status); // range Low 736 rangeHiIdx = curAndConstraint->rangeList->size(); 737 curAndConstraint->rangeList->addElement(-1, status); // range Hi 738 break; 739 case tMod: 740 U_ASSERT(curAndConstraint != nullptr); 741 curAndConstraint->op=AndConstraint::MOD; 742 break; 743 case tVariableN: 744 case tVariableI: 745 case tVariableF: 746 case tVariableT: 747 case tVariableE: 748 case tVariableC: 749 case tVariableV: 750 U_ASSERT(curAndConstraint != nullptr); 751 curAndConstraint->digitsType = type; 752 break; 753 case tKeyword: 754 { 755 RuleChain *newChain = new RuleChain; 756 if (newChain == nullptr) { 757 status = U_MEMORY_ALLOCATION_ERROR; 758 break; 759 } 760 newChain->fKeyword = token; 761 if (prules->mRules == nullptr) { 762 prules->mRules = newChain; 763 } else { 764 // The new rule chain goes at the end of the linked list of rule chains, 765 // unless there is an "other" keyword & chain. "other" must remain last. 766 RuleChain *insertAfter = prules->mRules; 767 while (insertAfter->fNext!=nullptr && 768 insertAfter->fNext->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5) != 0 ){ 769 insertAfter=insertAfter->fNext; 770 } 771 newChain->fNext = insertAfter->fNext; 772 insertAfter->fNext = newChain; 773 } 774 OrConstraint *orNode = new OrConstraint(); 775 if (orNode == nullptr) { 776 status = U_MEMORY_ALLOCATION_ERROR; 777 break; 778 } 779 newChain->ruleHeader = orNode; 780 curAndConstraint = orNode->add(status); 781 currentChain = newChain; 782 } 783 break; 784 785 case tInteger: 786 for (;;) { 787 getNextToken(status); 788 if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) { 789 break; 790 } 791 if (type == tEllipsis) { 792 currentChain->fIntegerSamplesUnbounded = true; 793 continue; 794 } 795 currentChain->fIntegerSamples.append(token); 796 } 797 break; 798 799 case tDecimal: 800 for (;;) { 801 getNextToken(status); 802 if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) { 803 break; 804 } 805 if (type == tEllipsis) { 806 currentChain->fDecimalSamplesUnbounded = true; 807 continue; 808 } 809 currentChain->fDecimalSamples.append(token); 810 } 811 break; 812 813 default: 814 break; 815 } 816 prevType=type; 817 if (U_FAILURE(status)) { 818 break; 819 } 820 } 821 } 822 823 UnicodeString 824 PluralRules::getRuleFromResource(const Locale& locale, UPluralType type, UErrorCode& errCode) { 825 UnicodeString emptyStr; 826 827 if (U_FAILURE(errCode)) { 828 return emptyStr; 829 } 830 LocalUResourceBundlePointer rb(ures_openDirect(nullptr, "plurals", &errCode)); 831 if(U_FAILURE(errCode)) { 832 return emptyStr; 833 } 834 const char *typeKey; 835 switch (type) { 836 case UPLURAL_TYPE_CARDINAL: 837 typeKey = "locales"; 838 break; 839 case UPLURAL_TYPE_ORDINAL: 840 typeKey = "locales_ordinals"; 841 break; 842 default: 843 // Must not occur: The caller should have checked for valid types. 844 errCode = U_ILLEGAL_ARGUMENT_ERROR; 845 return emptyStr; 846 } 847 LocalUResourceBundlePointer locRes(ures_getByKey(rb.getAlias(), typeKey, nullptr, &errCode)); 848 if(U_FAILURE(errCode)) { 849 return emptyStr; 850 } 851 int32_t resLen=0; 852 const char *curLocaleName=locale.getBaseName(); 853 const char16_t* s = ures_getStringByKey(locRes.getAlias(), curLocaleName, &resLen, &errCode); 854 855 if (s == nullptr) { 856 // Check parent locales. 857 UErrorCode status = U_ZERO_ERROR; 858 const char *curLocaleName2=locale.getBaseName(); 859 CharString parentLocaleName(curLocaleName2, status); 860 861 for (;;) { 862 { 863 CharString tmp = ulocimp_getParent(parentLocaleName.data(), status); 864 if (tmp.isEmpty()) break; 865 parentLocaleName = std::move(tmp); 866 } 867 resLen=0; 868 s = ures_getStringByKey(locRes.getAlias(), parentLocaleName.data(), &resLen, &status); 869 if (s != nullptr) { 870 errCode = U_ZERO_ERROR; 871 break; 872 } 873 status = U_ZERO_ERROR; 874 } 875 } 876 if (s==nullptr) { 877 return emptyStr; 878 } 879 880 char setKey[256]; 881 u_UCharsToChars(s, setKey, resLen + 1); 882 // printf("\n PluralRule: %s\n", setKey); 883 884 LocalUResourceBundlePointer ruleRes(ures_getByKey(rb.getAlias(), "rules", nullptr, &errCode)); 885 if(U_FAILURE(errCode)) { 886 return emptyStr; 887 } 888 LocalUResourceBundlePointer setRes(ures_getByKey(ruleRes.getAlias(), setKey, nullptr, &errCode)); 889 if (U_FAILURE(errCode)) { 890 return emptyStr; 891 } 892 893 int32_t numberKeys = ures_getSize(setRes.getAlias()); 894 UnicodeString result; 895 const char *key=nullptr; 896 for(int32_t i=0; i<numberKeys; ++i) { // Keys are zero, one, few, ... 897 UnicodeString rules = ures_getNextUnicodeString(setRes.getAlias(), &key, &errCode); 898 UnicodeString uKey(key, -1, US_INV); 899 result.append(uKey); 900 result.append(COLON); 901 result.append(rules); 902 result.append(SEMI_COLON); 903 } 904 return result; 905 } 906 907 908 UnicodeString 909 PluralRules::getRules() const { 910 UnicodeString rules; 911 if (mRules != nullptr) { 912 mRules->dumpRules(rules); 913 } 914 return rules; 915 } 916 917 AndConstraint::AndConstraint(const AndConstraint& other) { 918 this->fInternalStatus = other.fInternalStatus; 919 if (U_FAILURE(fInternalStatus)) { 920 return; // stop early if the object we are copying from is invalid. 921 } 922 this->op = other.op; 923 this->opNum=other.opNum; 924 this->value=other.value; 925 if (other.rangeList != nullptr) { 926 LocalPointer<UVector32> newRangeList(new UVector32(fInternalStatus), fInternalStatus); 927 if (U_FAILURE(fInternalStatus)) { 928 return; 929 } 930 this->rangeList = newRangeList.orphan(); 931 this->rangeList->assign(*other.rangeList, fInternalStatus); 932 } 933 this->integerOnly=other.integerOnly; 934 this->negated=other.negated; 935 this->digitsType = other.digitsType; 936 if (other.next != nullptr) { 937 this->next = new AndConstraint(*other.next); 938 if (this->next == nullptr) { 939 fInternalStatus = U_MEMORY_ALLOCATION_ERROR; 940 } 941 } 942 } 943 944 AndConstraint::~AndConstraint() { 945 delete rangeList; 946 rangeList = nullptr; 947 delete next; 948 next = nullptr; 949 } 950 951 UBool 952 AndConstraint::isFulfilled(const IFixedDecimal &number) { 953 UBool result = true; 954 if (digitsType == none) { 955 // An empty AndConstraint, created by a rule with a keyword but no following expression. 956 return true; 957 } 958 959 PluralOperand operand = tokenTypeToPluralOperand(digitsType); 960 double n = number.getPluralOperand(operand); // pulls n | i | v | f value for the number. 961 // Will always be positive. 962 // May be non-integer (n option only) 963 do { 964 if (integerOnly && n != uprv_floor(n)) { 965 result = false; 966 break; 967 } 968 969 if (op == MOD) { 970 n = fmod(n, opNum); 971 } 972 if (rangeList == nullptr) { 973 result = value == -1 || // empty rule 974 n == value; // 'is' rule 975 break; 976 } 977 result = false; // 'in' or 'within' rule 978 for (int32_t r=0; r<rangeList->size(); r+=2) { 979 if (rangeList->elementAti(r) <= n && n <= rangeList->elementAti(r+1)) { 980 result = true; 981 break; 982 } 983 } 984 } while (false); 985 986 if (negated) { 987 result = !result; 988 } 989 return result; 990 } 991 992 AndConstraint* 993 AndConstraint::add(UErrorCode& status) { 994 if (U_FAILURE(fInternalStatus)) { 995 status = fInternalStatus; 996 return nullptr; 997 } 998 this->next = new AndConstraint(); 999 if (this->next == nullptr) { 1000 status = U_MEMORY_ALLOCATION_ERROR; 1001 } 1002 return this->next; 1003 } 1004 1005 1006 OrConstraint::OrConstraint(const OrConstraint& other) { 1007 this->fInternalStatus = other.fInternalStatus; 1008 if (U_FAILURE(fInternalStatus)) { 1009 return; // stop early if the object we are copying from is invalid. 1010 } 1011 if ( other.childNode != nullptr ) { 1012 this->childNode = new AndConstraint(*(other.childNode)); 1013 if (this->childNode == nullptr) { 1014 fInternalStatus = U_MEMORY_ALLOCATION_ERROR; 1015 return; 1016 } 1017 } 1018 if (other.next != nullptr ) { 1019 this->next = new OrConstraint(*(other.next)); 1020 if (this->next == nullptr) { 1021 fInternalStatus = U_MEMORY_ALLOCATION_ERROR; 1022 return; 1023 } 1024 if (U_FAILURE(this->next->fInternalStatus)) { 1025 this->fInternalStatus = this->next->fInternalStatus; 1026 } 1027 } 1028 } 1029 1030 OrConstraint::~OrConstraint() { 1031 delete childNode; 1032 childNode = nullptr; 1033 delete next; 1034 next = nullptr; 1035 } 1036 1037 AndConstraint* 1038 OrConstraint::add(UErrorCode& status) { 1039 if (U_FAILURE(fInternalStatus)) { 1040 status = fInternalStatus; 1041 return nullptr; 1042 } 1043 OrConstraint *curOrConstraint=this; 1044 { 1045 while (curOrConstraint->next!=nullptr) { 1046 curOrConstraint = curOrConstraint->next; 1047 } 1048 U_ASSERT(curOrConstraint->childNode == nullptr); 1049 curOrConstraint->childNode = new AndConstraint(); 1050 if (curOrConstraint->childNode == nullptr) { 1051 status = U_MEMORY_ALLOCATION_ERROR; 1052 } 1053 } 1054 return curOrConstraint->childNode; 1055 } 1056 1057 UBool 1058 OrConstraint::isFulfilled(const IFixedDecimal &number) { 1059 OrConstraint* orRule=this; 1060 UBool result=false; 1061 1062 while (orRule!=nullptr && !result) { 1063 result=true; 1064 AndConstraint* andRule = orRule->childNode; 1065 while (andRule!=nullptr && result) { 1066 result = andRule->isFulfilled(number); 1067 andRule=andRule->next; 1068 } 1069 orRule = orRule->next; 1070 } 1071 1072 return result; 1073 } 1074 1075 1076 RuleChain::RuleChain(const RuleChain& other) : 1077 fKeyword(other.fKeyword), fDecimalSamples(other.fDecimalSamples), 1078 fIntegerSamples(other.fIntegerSamples), fDecimalSamplesUnbounded(other.fDecimalSamplesUnbounded), 1079 fIntegerSamplesUnbounded(other.fIntegerSamplesUnbounded), fInternalStatus(other.fInternalStatus) { 1080 if (U_FAILURE(this->fInternalStatus)) { 1081 return; // stop early if the object we are copying from is invalid. 1082 } 1083 if (other.ruleHeader != nullptr) { 1084 this->ruleHeader = new OrConstraint(*(other.ruleHeader)); 1085 if (this->ruleHeader == nullptr) { 1086 this->fInternalStatus = U_MEMORY_ALLOCATION_ERROR; 1087 } 1088 else if (U_FAILURE(this->ruleHeader->fInternalStatus)) { 1089 // If the OrConstraint wasn't fully copied, then set our status to failure as well. 1090 this->fInternalStatus = this->ruleHeader->fInternalStatus; 1091 return; // exit early. 1092 } 1093 } 1094 if (other.fNext != nullptr ) { 1095 this->fNext = new RuleChain(*other.fNext); 1096 if (this->fNext == nullptr) { 1097 this->fInternalStatus = U_MEMORY_ALLOCATION_ERROR; 1098 } 1099 else if (U_FAILURE(this->fNext->fInternalStatus)) { 1100 // If the RuleChain wasn't fully copied, then set our status to failure as well. 1101 this->fInternalStatus = this->fNext->fInternalStatus; 1102 } 1103 } 1104 } 1105 1106 RuleChain::~RuleChain() { 1107 delete fNext; 1108 delete ruleHeader; 1109 } 1110 1111 UnicodeString 1112 RuleChain::select(const IFixedDecimal &number) const { 1113 if (!number.isNaN() && !number.isInfinite()) { 1114 for (const RuleChain *rules = this; rules != nullptr; rules = rules->fNext) { 1115 if (rules->ruleHeader->isFulfilled(number)) { 1116 return rules->fKeyword; 1117 } 1118 } 1119 } 1120 return UnicodeString(true, PLURAL_KEYWORD_OTHER, 5); 1121 } 1122 1123 static UnicodeString tokenString(tokenType tok) { 1124 UnicodeString s; 1125 switch (tok) { 1126 case tVariableN: 1127 s.append(LOW_N); break; 1128 case tVariableI: 1129 s.append(LOW_I); break; 1130 case tVariableF: 1131 s.append(LOW_F); break; 1132 case tVariableV: 1133 s.append(LOW_V); break; 1134 case tVariableT: 1135 s.append(LOW_T); break; 1136 case tVariableE: 1137 s.append(LOW_E); break; 1138 case tVariableC: 1139 s.append(LOW_C); break; 1140 default: 1141 s.append(TILDE); 1142 } 1143 return s; 1144 } 1145 1146 void 1147 RuleChain::dumpRules(UnicodeString& result) { 1148 char16_t digitString[16]; 1149 1150 if ( ruleHeader != nullptr ) { 1151 result += fKeyword; 1152 result += COLON; 1153 result += SPACE; 1154 OrConstraint* orRule=ruleHeader; 1155 while ( orRule != nullptr ) { 1156 AndConstraint* andRule=orRule->childNode; 1157 while ( andRule != nullptr ) { 1158 if ((andRule->op==AndConstraint::NONE) && (andRule->rangeList==nullptr) && (andRule->value == -1)) { 1159 // Empty Rules. 1160 } else if ( (andRule->op==AndConstraint::NONE) && (andRule->rangeList==nullptr) ) { 1161 result += tokenString(andRule->digitsType); 1162 result += UNICODE_STRING_SIMPLE(" is "); 1163 if (andRule->negated) { 1164 result += UNICODE_STRING_SIMPLE("not "); 1165 } 1166 uprv_itou(digitString,16, andRule->value,10,0); 1167 result += UnicodeString(digitString); 1168 } 1169 else { 1170 result += tokenString(andRule->digitsType); 1171 result += SPACE; 1172 if (andRule->op==AndConstraint::MOD) { 1173 result += UNICODE_STRING_SIMPLE("mod "); 1174 uprv_itou(digitString,16, andRule->opNum,10,0); 1175 result += UnicodeString(digitString); 1176 } 1177 if (andRule->rangeList==nullptr) { 1178 if (andRule->negated) { 1179 result += UNICODE_STRING_SIMPLE(" is not "); 1180 uprv_itou(digitString,16, andRule->value,10,0); 1181 result += UnicodeString(digitString); 1182 } 1183 else { 1184 result += UNICODE_STRING_SIMPLE(" is "); 1185 uprv_itou(digitString,16, andRule->value,10,0); 1186 result += UnicodeString(digitString); 1187 } 1188 } 1189 else { 1190 if (andRule->negated) { 1191 if ( andRule->integerOnly ) { 1192 result += UNICODE_STRING_SIMPLE(" not in "); 1193 } 1194 else { 1195 result += UNICODE_STRING_SIMPLE(" not within "); 1196 } 1197 } 1198 else { 1199 if ( andRule->integerOnly ) { 1200 result += UNICODE_STRING_SIMPLE(" in "); 1201 } 1202 else { 1203 result += UNICODE_STRING_SIMPLE(" within "); 1204 } 1205 } 1206 for (int32_t r=0; r<andRule->rangeList->size(); r+=2) { 1207 int32_t rangeLo = andRule->rangeList->elementAti(r); 1208 int32_t rangeHi = andRule->rangeList->elementAti(r+1); 1209 uprv_itou(digitString,16, rangeLo, 10, 0); 1210 result += UnicodeString(digitString); 1211 result += UNICODE_STRING_SIMPLE(".."); 1212 uprv_itou(digitString,16, rangeHi, 10,0); 1213 result += UnicodeString(digitString); 1214 if (r+2 < andRule->rangeList->size()) { 1215 result += UNICODE_STRING_SIMPLE(", "); 1216 } 1217 } 1218 } 1219 } 1220 if ( (andRule=andRule->next) != nullptr) { 1221 result += UNICODE_STRING_SIMPLE(" and "); 1222 } 1223 } 1224 if ( (orRule = orRule->next) != nullptr ) { 1225 result += UNICODE_STRING_SIMPLE(" or "); 1226 } 1227 } 1228 } 1229 if ( fNext != nullptr ) { 1230 result += UNICODE_STRING_SIMPLE("; "); 1231 fNext->dumpRules(result); 1232 } 1233 } 1234 1235 1236 UErrorCode 1237 RuleChain::getKeywords(int32_t capacityOfKeywords, UnicodeString* keywords, int32_t& arraySize) const { 1238 if (U_FAILURE(fInternalStatus)) { 1239 return fInternalStatus; 1240 } 1241 if ( arraySize < capacityOfKeywords-1 ) { 1242 keywords[arraySize++]=fKeyword; 1243 } 1244 else { 1245 return U_BUFFER_OVERFLOW_ERROR; 1246 } 1247 1248 if ( fNext != nullptr ) { 1249 return fNext->getKeywords(capacityOfKeywords, keywords, arraySize); 1250 } 1251 else { 1252 return U_ZERO_ERROR; 1253 } 1254 } 1255 1256 UBool 1257 RuleChain::isKeyword(const UnicodeString& keywordParam) const { 1258 if ( fKeyword == keywordParam ) { 1259 return true; 1260 } 1261 1262 if ( fNext != nullptr ) { 1263 return fNext->isKeyword(keywordParam); 1264 } 1265 else { 1266 return false; 1267 } 1268 } 1269 1270 1271 PluralRuleParser::PluralRuleParser() : 1272 ruleIndex(0), token(), type(none), prevType(none), 1273 curAndConstraint(nullptr), currentChain(nullptr), rangeLowIdx(-1), rangeHiIdx(-1) 1274 { 1275 } 1276 1277 PluralRuleParser::~PluralRuleParser() { 1278 } 1279 1280 1281 int32_t 1282 PluralRuleParser::getNumberValue(const UnicodeString& token) { 1283 int32_t pos = 0; 1284 return ICU_Utility::parseNumber(token, pos, 10); 1285 } 1286 1287 1288 void 1289 PluralRuleParser::checkSyntax(UErrorCode &status) 1290 { 1291 if (U_FAILURE(status)) { 1292 return; 1293 } 1294 if (!(prevType==none || prevType==tSemiColon)) { 1295 type = getKeyType(token, type); // Switch token type from tKeyword if we scanned a reserved word, 1296 // and we are not at the start of a rule, where a 1297 // keyword is expected. 1298 } 1299 1300 switch(prevType) { 1301 case none: 1302 case tSemiColon: 1303 if (type!=tKeyword && type != tEOF) { 1304 status = U_UNEXPECTED_TOKEN; 1305 } 1306 break; 1307 case tVariableN: 1308 case tVariableI: 1309 case tVariableF: 1310 case tVariableT: 1311 case tVariableE: 1312 case tVariableC: 1313 case tVariableV: 1314 if (type != tIs && type != tMod && type != tIn && 1315 type != tNot && type != tWithin && type != tEqual && type != tNotEqual) { 1316 status = U_UNEXPECTED_TOKEN; 1317 } 1318 break; 1319 case tKeyword: 1320 if (type != tColon) { 1321 status = U_UNEXPECTED_TOKEN; 1322 } 1323 break; 1324 case tColon: 1325 if (!(type == tVariableN || 1326 type == tVariableI || 1327 type == tVariableF || 1328 type == tVariableT || 1329 type == tVariableE || 1330 type == tVariableC || 1331 type == tVariableV || 1332 type == tAt)) { 1333 status = U_UNEXPECTED_TOKEN; 1334 } 1335 break; 1336 case tIs: 1337 if ( type != tNumber && type != tNot) { 1338 status = U_UNEXPECTED_TOKEN; 1339 } 1340 break; 1341 case tNot: 1342 if (type != tNumber && type != tIn && type != tWithin) { 1343 status = U_UNEXPECTED_TOKEN; 1344 } 1345 break; 1346 case tMod: 1347 case tDot2: 1348 case tIn: 1349 case tWithin: 1350 case tEqual: 1351 case tNotEqual: 1352 if (type != tNumber) { 1353 status = U_UNEXPECTED_TOKEN; 1354 } 1355 break; 1356 case tAnd: 1357 case tOr: 1358 if ( type != tVariableN && 1359 type != tVariableI && 1360 type != tVariableF && 1361 type != tVariableT && 1362 type != tVariableE && 1363 type != tVariableC && 1364 type != tVariableV) { 1365 status = U_UNEXPECTED_TOKEN; 1366 } 1367 break; 1368 case tComma: 1369 if (type != tNumber) { 1370 status = U_UNEXPECTED_TOKEN; 1371 } 1372 break; 1373 case tNumber: 1374 if (type != tDot2 && type != tSemiColon && type != tIs && type != tNot && 1375 type != tIn && type != tEqual && type != tNotEqual && type != tWithin && 1376 type != tAnd && type != tOr && type != tComma && type != tAt && 1377 type != tEOF) 1378 { 1379 status = U_UNEXPECTED_TOKEN; 1380 } 1381 // TODO: a comma following a number that is not part of a range will be allowed. 1382 // It's not the only case of this sort of thing. Parser needs a re-write. 1383 break; 1384 case tAt: 1385 if (type != tDecimal && type != tInteger) { 1386 status = U_UNEXPECTED_TOKEN; 1387 } 1388 break; 1389 default: 1390 status = U_UNEXPECTED_TOKEN; 1391 break; 1392 } 1393 } 1394 1395 1396 /* 1397 * Scan the next token from the input rules. 1398 * rules and returned token type are in the parser state variables. 1399 */ 1400 void 1401 PluralRuleParser::getNextToken(UErrorCode &status) 1402 { 1403 if (U_FAILURE(status)) { 1404 return; 1405 } 1406 1407 char16_t ch; 1408 while (ruleIndex < ruleSrc->length()) { 1409 ch = ruleSrc->charAt(ruleIndex); 1410 type = charType(ch); 1411 if (type != tSpace) { 1412 break; 1413 } 1414 ++(ruleIndex); 1415 } 1416 if (ruleIndex >= ruleSrc->length()) { 1417 type = tEOF; 1418 return; 1419 } 1420 int32_t curIndex= ruleIndex; 1421 1422 switch (type) { 1423 case tColon: 1424 case tSemiColon: 1425 case tComma: 1426 case tEllipsis: 1427 case tTilde: // scanned '~' 1428 case tAt: // scanned '@' 1429 case tEqual: // scanned '=' 1430 case tMod: // scanned '%' 1431 // Single character tokens. 1432 ++curIndex; 1433 break; 1434 1435 case tNotEqual: // scanned '!' 1436 if (ruleSrc->charAt(curIndex+1) == EQUALS) { 1437 curIndex += 2; 1438 } else { 1439 type = none; 1440 curIndex += 1; 1441 } 1442 break; 1443 1444 case tKeyword: 1445 while (type == tKeyword && ++curIndex < ruleSrc->length()) { 1446 ch = ruleSrc->charAt(curIndex); 1447 type = charType(ch); 1448 } 1449 type = tKeyword; 1450 break; 1451 1452 case tNumber: 1453 while (type == tNumber && ++curIndex < ruleSrc->length()) { 1454 ch = ruleSrc->charAt(curIndex); 1455 type = charType(ch); 1456 } 1457 type = tNumber; 1458 break; 1459 1460 case tDot: 1461 // We could be looking at either ".." in a range, or "..." at the end of a sample. 1462 if (curIndex+1 >= ruleSrc->length() || ruleSrc->charAt(curIndex+1) != DOT) { 1463 ++curIndex; 1464 break; // Single dot 1465 } 1466 if (curIndex+2 >= ruleSrc->length() || ruleSrc->charAt(curIndex+2) != DOT) { 1467 curIndex += 2; 1468 type = tDot2; 1469 break; // double dot 1470 } 1471 type = tEllipsis; 1472 curIndex += 3; 1473 break; // triple dot 1474 1475 default: 1476 status = U_UNEXPECTED_TOKEN; 1477 ++curIndex; 1478 break; 1479 } 1480 1481 U_ASSERT(ruleIndex <= ruleSrc->length()); 1482 U_ASSERT(curIndex <= ruleSrc->length()); 1483 token=UnicodeString(*ruleSrc, ruleIndex, curIndex-ruleIndex); 1484 ruleIndex = curIndex; 1485 } 1486 1487 tokenType 1488 PluralRuleParser::charType(char16_t ch) { 1489 if ((ch>=U_ZERO) && (ch<=U_NINE)) { 1490 return tNumber; 1491 } 1492 if (ch>=LOW_A && ch<=LOW_Z) { 1493 return tKeyword; 1494 } 1495 switch (ch) { 1496 case COLON: 1497 return tColon; 1498 case SPACE: 1499 return tSpace; 1500 case SEMI_COLON: 1501 return tSemiColon; 1502 case DOT: 1503 return tDot; 1504 case COMMA: 1505 return tComma; 1506 case EXCLAMATION: 1507 return tNotEqual; 1508 case EQUALS: 1509 return tEqual; 1510 case PERCENT_SIGN: 1511 return tMod; 1512 case AT: 1513 return tAt; 1514 case ELLIPSIS: 1515 return tEllipsis; 1516 case TILDE: 1517 return tTilde; 1518 default : 1519 return none; 1520 } 1521 } 1522 1523 1524 // Set token type for reserved words in the Plural Rule syntax. 1525 1526 tokenType 1527 PluralRuleParser::getKeyType(const UnicodeString &token, tokenType keyType) 1528 { 1529 if (keyType != tKeyword) { 1530 return keyType; 1531 } 1532 1533 if (0 == token.compare(PK_VAR_N, 1)) { 1534 keyType = tVariableN; 1535 } else if (0 == token.compare(PK_VAR_I, 1)) { 1536 keyType = tVariableI; 1537 } else if (0 == token.compare(PK_VAR_F, 1)) { 1538 keyType = tVariableF; 1539 } else if (0 == token.compare(PK_VAR_T, 1)) { 1540 keyType = tVariableT; 1541 } else if (0 == token.compare(PK_VAR_E, 1)) { 1542 keyType = tVariableE; 1543 } else if (0 == token.compare(PK_VAR_C, 1)) { 1544 keyType = tVariableC; 1545 } else if (0 == token.compare(PK_VAR_V, 1)) { 1546 keyType = tVariableV; 1547 } else if (0 == token.compare(PK_IS, 2)) { 1548 keyType = tIs; 1549 } else if (0 == token.compare(PK_AND, 3)) { 1550 keyType = tAnd; 1551 } else if (0 == token.compare(PK_IN, 2)) { 1552 keyType = tIn; 1553 } else if (0 == token.compare(PK_WITHIN, 6)) { 1554 keyType = tWithin; 1555 } else if (0 == token.compare(PK_NOT, 3)) { 1556 keyType = tNot; 1557 } else if (0 == token.compare(PK_MOD, 3)) { 1558 keyType = tMod; 1559 } else if (0 == token.compare(PK_OR, 2)) { 1560 keyType = tOr; 1561 } else if (0 == token.compare(PK_DECIMAL, 7)) { 1562 keyType = tDecimal; 1563 } else if (0 == token.compare(PK_INTEGER, 7)) { 1564 keyType = tInteger; 1565 } 1566 return keyType; 1567 } 1568 1569 1570 PluralKeywordEnumeration::PluralKeywordEnumeration(RuleChain *header, UErrorCode& status) 1571 : pos(0), fKeywordNames(status) { 1572 if (U_FAILURE(status)) { 1573 return; 1574 } 1575 fKeywordNames.setDeleter(uprv_deleteUObject); 1576 UBool addKeywordOther = true; 1577 RuleChain *node = header; 1578 while (node != nullptr) { 1579 LocalPointer<UnicodeString> newElem(node->fKeyword.clone(), status); 1580 fKeywordNames.adoptElement(newElem.orphan(), status); 1581 if (U_FAILURE(status)) { 1582 return; 1583 } 1584 if (0 == node->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5)) { 1585 addKeywordOther = false; 1586 } 1587 node = node->fNext; 1588 } 1589 1590 if (addKeywordOther) { 1591 LocalPointer<UnicodeString> newElem(new UnicodeString(PLURAL_KEYWORD_OTHER), status); 1592 fKeywordNames.adoptElement(newElem.orphan(), status); 1593 if (U_FAILURE(status)) { 1594 return; 1595 } 1596 } 1597 } 1598 1599 const UnicodeString* 1600 PluralKeywordEnumeration::snext(UErrorCode& status) { 1601 if (U_SUCCESS(status) && pos < fKeywordNames.size()) { 1602 return static_cast<const UnicodeString*>(fKeywordNames.elementAt(pos++)); 1603 } 1604 return nullptr; 1605 } 1606 1607 void 1608 PluralKeywordEnumeration::reset(UErrorCode& /*status*/) { 1609 pos=0; 1610 } 1611 1612 int32_t 1613 PluralKeywordEnumeration::count(UErrorCode& /*status*/) const { 1614 return fKeywordNames.size(); 1615 } 1616 1617 PluralKeywordEnumeration::~PluralKeywordEnumeration() { 1618 } 1619 1620 PluralOperand tokenTypeToPluralOperand(tokenType tt) { 1621 switch(tt) { 1622 case tVariableN: 1623 return PLURAL_OPERAND_N; 1624 case tVariableI: 1625 return PLURAL_OPERAND_I; 1626 case tVariableF: 1627 return PLURAL_OPERAND_F; 1628 case tVariableV: 1629 return PLURAL_OPERAND_V; 1630 case tVariableT: 1631 return PLURAL_OPERAND_T; 1632 case tVariableE: 1633 return PLURAL_OPERAND_E; 1634 case tVariableC: 1635 return PLURAL_OPERAND_E; 1636 default: 1637 UPRV_UNREACHABLE_EXIT; // unexpected. 1638 } 1639 } 1640 1641 FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f, int32_t e, int32_t c) { 1642 init(n, v, f, e, c); 1643 } 1644 1645 FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f, int32_t e) { 1646 init(n, v, f, e); 1647 // check values. TODO make into unit test. 1648 // 1649 // long visiblePower = (int) Math.pow(10.0, v); 1650 // if (decimalDigits > visiblePower) { 1651 // throw new IllegalArgumentException(); 1652 // } 1653 // double fraction = intValue + (decimalDigits / (double) visiblePower); 1654 // if (fraction != source) { 1655 // double diff = Math.abs(fraction - source)/(Math.abs(fraction) + Math.abs(source)); 1656 // if (diff > 0.00000001d) { 1657 // throw new IllegalArgumentException(); 1658 // } 1659 // } 1660 } 1661 1662 FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) { 1663 init(n, v, f); 1664 } 1665 1666 FixedDecimal::FixedDecimal(double n, int32_t v) { 1667 // Ugly, but for samples we don't care. 1668 init(n, v, getFractionalDigits(n, v)); 1669 } 1670 1671 FixedDecimal::FixedDecimal(double n) { 1672 init(n); 1673 } 1674 1675 FixedDecimal::FixedDecimal() { 1676 init(0, 0, 0); 1677 } 1678 1679 1680 // Create a FixedDecimal from a UnicodeString containing a number. 1681 // Inefficient, but only used for samples, so simplicity trumps efficiency. 1682 1683 FixedDecimal::FixedDecimal(const UnicodeString &num, UErrorCode &status) { 1684 CharString cs; 1685 int32_t parsedExponent = 0; 1686 int32_t parsedCompactExponent = 0; 1687 1688 int32_t exponentIdx = num.indexOf(u'e'); 1689 if (exponentIdx < 0) { 1690 exponentIdx = num.indexOf(u'E'); 1691 } 1692 int32_t compactExponentIdx = num.indexOf(u'c'); 1693 if (compactExponentIdx < 0) { 1694 compactExponentIdx = num.indexOf(u'C'); 1695 } 1696 1697 if (exponentIdx >= 0) { 1698 cs.appendInvariantChars(num.tempSubString(0, exponentIdx), status); 1699 int32_t expSubstrStart = exponentIdx + 1; 1700 parsedExponent = ICU_Utility::parseAsciiInteger(num, expSubstrStart); 1701 } 1702 else if (compactExponentIdx >= 0) { 1703 cs.appendInvariantChars(num.tempSubString(0, compactExponentIdx), status); 1704 int32_t expSubstrStart = compactExponentIdx + 1; 1705 parsedCompactExponent = ICU_Utility::parseAsciiInteger(num, expSubstrStart); 1706 1707 parsedExponent = parsedCompactExponent; 1708 exponentIdx = compactExponentIdx; 1709 } 1710 else { 1711 cs.appendInvariantChars(num, status); 1712 } 1713 1714 DecimalQuantity dl; 1715 dl.setToDecNumber(cs.toStringPiece(), status); 1716 if (U_FAILURE(status)) { 1717 init(0, 0, 0); 1718 return; 1719 } 1720 1721 int32_t decimalPoint = num.indexOf(DOT); 1722 double n = dl.toDouble(); 1723 if (decimalPoint == -1) { 1724 init(n, 0, 0, parsedExponent); 1725 } else { 1726 int32_t fractionNumLength = exponentIdx < 0 ? num.length() : cs.length(); 1727 int32_t v = fractionNumLength - decimalPoint - 1; 1728 init(n, v, getFractionalDigits(n, v), parsedExponent); 1729 } 1730 } 1731 1732 1733 FixedDecimal::FixedDecimal(const FixedDecimal &other) { 1734 source = other.source; 1735 visibleDecimalDigitCount = other.visibleDecimalDigitCount; 1736 decimalDigits = other.decimalDigits; 1737 decimalDigitsWithoutTrailingZeros = other.decimalDigitsWithoutTrailingZeros; 1738 intValue = other.intValue; 1739 exponent = other.exponent; 1740 _hasIntegerValue = other._hasIntegerValue; 1741 isNegative = other.isNegative; 1742 _isNaN = other._isNaN; 1743 _isInfinite = other._isInfinite; 1744 } 1745 1746 FixedDecimal::~FixedDecimal() = default; 1747 1748 FixedDecimal FixedDecimal::createWithExponent(double n, int32_t v, int32_t e) { 1749 return FixedDecimal(n, v, getFractionalDigits(n, v), e); 1750 } 1751 1752 1753 void FixedDecimal::init(double n) { 1754 int32_t numFractionDigits = decimals(n); 1755 init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits)); 1756 } 1757 1758 1759 void FixedDecimal::init(double n, int32_t v, int64_t f) { 1760 int32_t exponent = 0; 1761 init(n, v, f, exponent); 1762 } 1763 1764 void FixedDecimal::init(double n, int32_t v, int64_t f, int32_t e) { 1765 // Currently, `c` is an alias for `e` 1766 init(n, v, f, e, e); 1767 } 1768 1769 void FixedDecimal::init(double n, int32_t v, int64_t f, int32_t e, int32_t c) { 1770 isNegative = n < 0.0; 1771 source = fabs(n); 1772 _isNaN = uprv_isNaN(source); 1773 _isInfinite = uprv_isInfinite(source); 1774 exponent = e; 1775 if (exponent == 0) { 1776 exponent = c; 1777 } 1778 if (_isNaN || _isInfinite || 1779 source > static_cast<double>(U_INT64_MAX) || 1780 source < static_cast<double>(U_INT64_MIN)) { 1781 v = 0; 1782 f = 0; 1783 intValue = 0; 1784 _hasIntegerValue = false; 1785 } else { 1786 intValue = static_cast<int64_t>(source); 1787 _hasIntegerValue = (source == intValue); 1788 } 1789 1790 visibleDecimalDigitCount = v; 1791 decimalDigits = f; 1792 if (f == 0) { 1793 decimalDigitsWithoutTrailingZeros = 0; 1794 } else { 1795 int64_t fdwtz = f; 1796 while ((fdwtz%10) == 0) { 1797 fdwtz /= 10; 1798 } 1799 decimalDigitsWithoutTrailingZeros = fdwtz; 1800 } 1801 } 1802 1803 1804 // Fast path only exact initialization. Return true if successful. 1805 // Note: Do not multiply by 10 each time through loop, rounding cruft can build 1806 // up that makes the check for an integer result fail. 1807 // A single multiply of the original number works more reliably. 1808 static int32_t p10[] = {1, 10, 100, 1000, 10000}; 1809 UBool FixedDecimal::quickInit(double n) { 1810 UBool success = false; 1811 n = fabs(n); 1812 int32_t numFractionDigits; 1813 for (numFractionDigits = 0; numFractionDigits <= 3; numFractionDigits++) { 1814 double scaledN = n * p10[numFractionDigits]; 1815 if (scaledN == floor(scaledN)) { 1816 success = true; 1817 break; 1818 } 1819 } 1820 if (success) { 1821 init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits)); 1822 } 1823 return success; 1824 } 1825 1826 1827 1828 int32_t FixedDecimal::decimals(double n) { 1829 // Count the number of decimal digits in the fraction part of the number, excluding trailing zeros. 1830 // fastpath the common cases, integers or fractions with 3 or fewer digits 1831 n = fabs(n); 1832 for (int ndigits=0; ndigits<=3; ndigits++) { 1833 double scaledN = n * p10[ndigits]; 1834 if (scaledN == floor(scaledN)) { 1835 return ndigits; 1836 } 1837 } 1838 1839 // Slow path, convert with snprintf, parse converted output. 1840 char buf[30] = {0}; 1841 snprintf(buf, sizeof(buf), "%1.15e", n); 1842 // formatted number looks like this: 1.234567890123457e-01 1843 int exponent = atoi(buf+18); 1844 int numFractionDigits = 15; 1845 for (int i=16; ; --i) { 1846 if (buf[i] != '0') { 1847 break; 1848 } 1849 --numFractionDigits; 1850 } 1851 numFractionDigits -= exponent; // Fraction part of fixed point representation. 1852 return numFractionDigits; 1853 } 1854 1855 1856 // Get the fraction digits of a double, represented as an integer. 1857 // v is the number of visible fraction digits in the displayed form of the number. 1858 // Example: n = 1001.234, v = 6, result = 234000 1859 // TODO: need to think through how this is used in the plural rule context. 1860 // This function can easily encounter integer overflow, 1861 // and can easily return noise digits when the precision of a double is exceeded. 1862 1863 int64_t FixedDecimal::getFractionalDigits(double n, int32_t v) { 1864 if (v == 0 || n == floor(n) || uprv_isNaN(n) || uprv_isPositiveInfinity(n)) { 1865 return 0; 1866 } 1867 n = fabs(n); 1868 double fract = n - floor(n); 1869 switch (v) { 1870 case 1: return static_cast<int64_t>(fract * 10.0 + 0.5); 1871 case 2: return static_cast<int64_t>(fract * 100.0 + 0.5); 1872 case 3: return static_cast<int64_t>(fract * 1000.0 + 0.5); 1873 default: 1874 double scaled = floor(fract * pow(10.0, static_cast<double>(v)) + 0.5); 1875 if (scaled >= static_cast<double>(U_INT64_MAX)) { 1876 // Note: a double cannot accurately represent U_INT64_MAX. Casting it to double 1877 // will round up to the next representable value, which is U_INT64_MAX + 1. 1878 return U_INT64_MAX; 1879 } else { 1880 return static_cast<int64_t>(scaled); 1881 } 1882 } 1883 } 1884 1885 1886 void FixedDecimal::adjustForMinFractionDigits(int32_t minFractionDigits) { 1887 int32_t numTrailingFractionZeros = minFractionDigits - visibleDecimalDigitCount; 1888 if (numTrailingFractionZeros > 0) { 1889 for (int32_t i=0; i<numTrailingFractionZeros; i++) { 1890 // Do not let the decimalDigits value overflow if there are many trailing zeros. 1891 // Limit the value to 18 digits, the most that a 64 bit int can fully represent. 1892 if (decimalDigits >= 100000000000000000LL) { 1893 break; 1894 } 1895 decimalDigits *= 10; 1896 } 1897 visibleDecimalDigitCount += numTrailingFractionZeros; 1898 } 1899 } 1900 1901 1902 double FixedDecimal::getPluralOperand(PluralOperand operand) const { 1903 switch(operand) { 1904 case PLURAL_OPERAND_N: return (exponent == 0 ? source : source * pow(10.0, exponent)); 1905 case PLURAL_OPERAND_I: return static_cast<double>(longValue()); 1906 case PLURAL_OPERAND_F: return static_cast<double>(decimalDigits); 1907 case PLURAL_OPERAND_T: return static_cast<double>(decimalDigitsWithoutTrailingZeros); 1908 case PLURAL_OPERAND_V: return visibleDecimalDigitCount; 1909 case PLURAL_OPERAND_E: return exponent; 1910 case PLURAL_OPERAND_C: return exponent; 1911 default: 1912 UPRV_UNREACHABLE_EXIT; // unexpected. 1913 } 1914 } 1915 1916 bool FixedDecimal::isNaN() const { 1917 return _isNaN; 1918 } 1919 1920 bool FixedDecimal::isInfinite() const { 1921 return _isInfinite; 1922 } 1923 1924 bool FixedDecimal::hasIntegerValue() const { 1925 return _hasIntegerValue; 1926 } 1927 1928 bool FixedDecimal::isNanOrInfinity() const { 1929 return _isNaN || _isInfinite; 1930 } 1931 1932 int32_t FixedDecimal::getVisibleFractionDigitCount() const { 1933 return visibleDecimalDigitCount; 1934 } 1935 1936 bool FixedDecimal::operator==(const FixedDecimal &other) const { 1937 return source == other.source && visibleDecimalDigitCount == other.visibleDecimalDigitCount 1938 && decimalDigits == other.decimalDigits && exponent == other.exponent; 1939 } 1940 1941 UnicodeString FixedDecimal::toString() const { 1942 char pattern[15]; 1943 char buffer[20]; 1944 if (exponent != 0) { 1945 snprintf(pattern, sizeof(pattern), "%%.%dfe%%d", visibleDecimalDigitCount); 1946 snprintf(buffer, sizeof(buffer), pattern, source, exponent); 1947 } else { 1948 snprintf(pattern, sizeof(pattern), "%%.%df", visibleDecimalDigitCount); 1949 snprintf(buffer, sizeof(buffer), pattern, source); 1950 } 1951 return UnicodeString(buffer, -1, US_INV); 1952 } 1953 1954 double FixedDecimal::doubleValue() const { 1955 return (isNegative ? -source : source) * pow(10.0, exponent); 1956 } 1957 1958 int64_t FixedDecimal::longValue() const { 1959 if (exponent == 0) { 1960 return intValue; 1961 } else { 1962 return static_cast<long>(pow(10.0, exponent) * intValue); 1963 } 1964 } 1965 1966 1967 PluralAvailableLocalesEnumeration::PluralAvailableLocalesEnumeration(UErrorCode &status) { 1968 fOpenStatus = status; 1969 if (U_FAILURE(status)) { 1970 return; 1971 } 1972 fOpenStatus = U_ZERO_ERROR; // clear any warnings. 1973 LocalUResourceBundlePointer rb(ures_openDirect(nullptr, "plurals", &fOpenStatus)); 1974 fLocales = ures_getByKey(rb.getAlias(), "locales", nullptr, &fOpenStatus); 1975 } 1976 1977 PluralAvailableLocalesEnumeration::~PluralAvailableLocalesEnumeration() { 1978 ures_close(fLocales); 1979 ures_close(fRes); 1980 fLocales = nullptr; 1981 fRes = nullptr; 1982 } 1983 1984 const char *PluralAvailableLocalesEnumeration::next(int32_t *resultLength, UErrorCode &status) { 1985 if (U_FAILURE(status)) { 1986 return nullptr; 1987 } 1988 if (U_FAILURE(fOpenStatus)) { 1989 status = fOpenStatus; 1990 return nullptr; 1991 } 1992 fRes = ures_getNextResource(fLocales, fRes, &status); 1993 if (fRes == nullptr || U_FAILURE(status)) { 1994 if (status == U_INDEX_OUTOFBOUNDS_ERROR) { 1995 status = U_ZERO_ERROR; 1996 } 1997 return nullptr; 1998 } 1999 const char *result = ures_getKey(fRes); 2000 if (resultLength != nullptr) { 2001 *resultLength = static_cast<int32_t>(uprv_strlen(result)); 2002 } 2003 return result; 2004 } 2005 2006 2007 void PluralAvailableLocalesEnumeration::reset(UErrorCode &status) { 2008 if (U_FAILURE(status)) { 2009 return; 2010 } 2011 if (U_FAILURE(fOpenStatus)) { 2012 status = fOpenStatus; 2013 return; 2014 } 2015 ures_resetIterator(fLocales); 2016 } 2017 2018 int32_t PluralAvailableLocalesEnumeration::count(UErrorCode &status) const { 2019 if (U_FAILURE(status)) { 2020 return 0; 2021 } 2022 if (U_FAILURE(fOpenStatus)) { 2023 status = fOpenStatus; 2024 return 0; 2025 } 2026 return ures_getSize(fLocales); 2027 } 2028 2029 U_NAMESPACE_END 2030 2031 2032 #endif /* #if !UCONFIG_NO_FORMATTING */ 2033 2034 //eof