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