blob: e1e1667a6ea9a249473901ffa822f621b69c1cb4 [file] [log] [blame]
// © 2016 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
/*
*******************************************************************************
* Copyright (C) 2007-2016, International Business Machines Corporation and
* others. All Rights Reserved.
*******************************************************************************
*
* File plurrule.cpp
*/
#include <math.h>
#include <stdio.h>
#include "unicode/utypes.h"
#include "unicode/localpointer.h"
#include "unicode/plurrule.h"
#include "unicode/upluralrules.h"
#include "unicode/ures.h"
#include "unicode/numfmt.h"
#include "unicode/decimfmt.h"
#include "unicode/numberrangeformatter.h"
#include "charstr.h"
#include "cmemory.h"
#include "cstring.h"
#include "hash.h"
#include "locutil.h"
#include "mutex.h"
#include "patternprops.h"
#include "plurrule_impl.h"
#include "putilimp.h"
#include "ucln_in.h"
#include "ustrfmt.h"
#include "uassert.h"
#include "uvectr32.h"
#include "sharedpluralrules.h"
#include "unifiedcache.h"
#include "number_decimalquantity.h"
#include "util.h"
#include "pluralranges.h"
#include "numrange_impl.h"
#if !UCONFIG_NO_FORMATTING
U_NAMESPACE_BEGIN
using namespace icu::pluralimpl;
using icu::number::impl::DecimalQuantity;
static const UChar PLURAL_KEYWORD_OTHER[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,0};
static const UChar PLURAL_DEFAULT_RULE[]={LOW_O,LOW_T,LOW_H,LOW_E,LOW_R,COLON,SPACE,LOW_N,0};
static const UChar PK_IN[]={LOW_I,LOW_N,0};
static const UChar PK_NOT[]={LOW_N,LOW_O,LOW_T,0};
static const UChar PK_IS[]={LOW_I,LOW_S,0};
static const UChar PK_MOD[]={LOW_M,LOW_O,LOW_D,0};
static const UChar PK_AND[]={LOW_A,LOW_N,LOW_D,0};
static const UChar PK_OR[]={LOW_O,LOW_R,0};
static const UChar PK_VAR_N[]={LOW_N,0};
static const UChar PK_VAR_I[]={LOW_I,0};
static const UChar PK_VAR_F[]={LOW_F,0};
static const UChar PK_VAR_T[]={LOW_T,0};
static const UChar PK_VAR_E[]={LOW_E,0};
static const UChar PK_VAR_V[]={LOW_V,0};
static const UChar PK_WITHIN[]={LOW_W,LOW_I,LOW_T,LOW_H,LOW_I,LOW_N,0};
static const UChar PK_DECIMAL[]={LOW_D,LOW_E,LOW_C,LOW_I,LOW_M,LOW_A,LOW_L,0};
static const UChar PK_INTEGER[]={LOW_I,LOW_N,LOW_T,LOW_E,LOW_G,LOW_E,LOW_R,0};
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralRules)
UOBJECT_DEFINE_RTTI_IMPLEMENTATION(PluralKeywordEnumeration)
PluralRules::PluralRules(UErrorCode& /*status*/)
: UObject(),
mRules(nullptr),
mStandardPluralRanges(nullptr),
mInternalStatus(U_ZERO_ERROR)
{
}
PluralRules::PluralRules(const PluralRules& other)
: UObject(other),
mRules(nullptr),
mStandardPluralRanges(nullptr),
mInternalStatus(U_ZERO_ERROR)
{
*this=other;
}
PluralRules::~PluralRules() {
delete mRules;
delete mStandardPluralRanges;
}
SharedPluralRules::~SharedPluralRules() {
delete ptr;
}
PluralRules*
PluralRules::clone() const {
// Since clone doesn't have a 'status' parameter, the best we can do is return nullptr if
// the newly created object was not fully constructed properly (an error occurred).
UErrorCode localStatus = U_ZERO_ERROR;
return clone(localStatus);
}
PluralRules*
PluralRules::clone(UErrorCode& status) const {
LocalPointer<PluralRules> newObj(new PluralRules(*this), status);
if (U_SUCCESS(status) && U_FAILURE(newObj->mInternalStatus)) {
status = newObj->mInternalStatus;
newObj.adoptInstead(nullptr);
}
return newObj.orphan();
}
PluralRules&
PluralRules::operator=(const PluralRules& other) {
if (this != &other) {
delete mRules;
mRules = nullptr;
delete mStandardPluralRanges;
mStandardPluralRanges = nullptr;
mInternalStatus = other.mInternalStatus;
if (U_FAILURE(mInternalStatus)) {
// bail out early if the object we were copying from was already 'invalid'.
return *this;
}
if (other.mRules != nullptr) {
mRules = new RuleChain(*other.mRules);
if (mRules == nullptr) {
mInternalStatus = U_MEMORY_ALLOCATION_ERROR;
}
else if (U_FAILURE(mRules->fInternalStatus)) {
// If the RuleChain wasn't fully copied, then set our status to failure as well.
mInternalStatus = mRules->fInternalStatus;
}
}
if (other.mStandardPluralRanges != nullptr) {
mStandardPluralRanges = other.mStandardPluralRanges->copy(mInternalStatus)
.toPointer(mInternalStatus)
.orphan();
}
}
return *this;
}
StringEnumeration* PluralRules::getAvailableLocales(UErrorCode &status) {
if (U_FAILURE(status)) {
return nullptr;
}
LocalPointer<StringEnumeration> result(new PluralAvailableLocalesEnumeration(status), status);
if (U_FAILURE(status)) {
return nullptr;
}
return result.orphan();
}
PluralRules* U_EXPORT2
PluralRules::createRules(const UnicodeString& description, UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
PluralRuleParser parser;
LocalPointer<PluralRules> newRules(new PluralRules(status), status);
if (U_FAILURE(status)) {
return nullptr;
}
parser.parse(description, newRules.getAlias(), status);
if (U_FAILURE(status)) {
newRules.adoptInstead(nullptr);
}
return newRules.orphan();
}
PluralRules* U_EXPORT2
PluralRules::createDefaultRules(UErrorCode& status) {
return createRules(UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1), status);
}
/******************************************************************************/
/* Create PluralRules cache */
template<> U_I18N_API
const SharedPluralRules *LocaleCacheKey<SharedPluralRules>::createObject(
const void * /*unused*/, UErrorCode &status) const {
const char *localeId = fLoc.getName();
LocalPointer<PluralRules> pr(PluralRules::internalForLocale(localeId, UPLURAL_TYPE_CARDINAL, status), status);
if (U_FAILURE(status)) {
return nullptr;
}
LocalPointer<SharedPluralRules> result(new SharedPluralRules(pr.getAlias()), status);
if (U_FAILURE(status)) {
return nullptr;
}
pr.orphan(); // result was successfully created so it nows pr.
result->addRef();
return result.orphan();
}
/* end plural rules cache */
/******************************************************************************/
const SharedPluralRules* U_EXPORT2
PluralRules::createSharedInstance(
const Locale& locale, UPluralType type, UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
if (type != UPLURAL_TYPE_CARDINAL) {
status = U_UNSUPPORTED_ERROR;
return nullptr;
}
const SharedPluralRules *result = nullptr;
UnifiedCache::getByLocale(locale, result, status);
return result;
}
PluralRules* U_EXPORT2
PluralRules::forLocale(const Locale& locale, UErrorCode& status) {
return forLocale(locale, UPLURAL_TYPE_CARDINAL, status);
}
PluralRules* U_EXPORT2
PluralRules::forLocale(const Locale& locale, UPluralType type, UErrorCode& status) {
if (type != UPLURAL_TYPE_CARDINAL) {
return internalForLocale(locale, type, status);
}
const SharedPluralRules *shared = createSharedInstance(
locale, type, status);
if (U_FAILURE(status)) {
return nullptr;
}
PluralRules *result = (*shared)->clone(status);
shared->removeRef();
return result;
}
PluralRules* U_EXPORT2
PluralRules::internalForLocale(const Locale& locale, UPluralType type, UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
if (type >= UPLURAL_TYPE_COUNT) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return nullptr;
}
LocalPointer<PluralRules> newObj(new PluralRules(status), status);
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeString locRule = newObj->getRuleFromResource(locale, type, status);
// TODO: which other errors, if any, should be returned?
if (locRule.length() == 0) {
// If an out-of-memory error occurred, then stop and report the failure.
if (status == U_MEMORY_ALLOCATION_ERROR) {
return nullptr;
}
// Locales with no specific rules (all numbers have the "other" category
// will return a U_MISSING_RESOURCE_ERROR at this point. This is not
// an error.
locRule = UnicodeString(PLURAL_DEFAULT_RULE);
status = U_ZERO_ERROR;
}
PluralRuleParser parser;
parser.parse(locRule, newObj.getAlias(), status);
// TODO: should rule parse errors be returned, or
// should we silently use default rules?
// Original impl used default rules.
// Ask the question to ICU Core.
newObj->mStandardPluralRanges = StandardPluralRanges::forLocale(locale, status)
.toPointer(status)
.orphan();
return newObj.orphan();
}
UnicodeString
PluralRules::select(int32_t number) const {
return select(FixedDecimal(number));
}
UnicodeString
PluralRules::select(double number) const {
return select(FixedDecimal(number));
}
UnicodeString
PluralRules::select(const number::FormattedNumber& number, UErrorCode& status) const {
DecimalQuantity dq;
number.getDecimalQuantity(dq, status);
if (U_FAILURE(status)) {
return ICU_Utility::makeBogusString();
}
if (U_FAILURE(mInternalStatus)) {
status = mInternalStatus;
return ICU_Utility::makeBogusString();
}
return select(dq);
}
UnicodeString
PluralRules::select(const IFixedDecimal &number) const {
if (mRules == nullptr) {
return UnicodeString(TRUE, PLURAL_DEFAULT_RULE, -1);
}
else {
return mRules->select(number);
}
}
UnicodeString
PluralRules::select(const number::FormattedNumberRange& range, UErrorCode& status) const {
return select(range.getData(status), status);
}
UnicodeString
PluralRules::select(const number::impl::UFormattedNumberRangeData* impl, UErrorCode& status) const {
if (U_FAILURE(status)) {
return ICU_Utility::makeBogusString();
}
if (U_FAILURE(mInternalStatus)) {
status = mInternalStatus;
return ICU_Utility::makeBogusString();
}
if (mStandardPluralRanges == nullptr) {
// Happens if PluralRules was constructed via createRules()
status = U_UNSUPPORTED_ERROR;
return ICU_Utility::makeBogusString();
}
auto form1 = StandardPlural::fromString(select(impl->quantity1), status);
auto form2 = StandardPlural::fromString(select(impl->quantity2), status);
if (U_FAILURE(status)) {
return ICU_Utility::makeBogusString();
}
auto result = mStandardPluralRanges->resolve(form1, form2);
return UnicodeString(StandardPlural::getKeyword(result), -1, US_INV);
}
StringEnumeration*
PluralRules::getKeywords(UErrorCode& status) const {
if (U_FAILURE(status)) {
return nullptr;
}
if (U_FAILURE(mInternalStatus)) {
status = mInternalStatus;
return nullptr;
}
LocalPointer<StringEnumeration> nameEnumerator(new PluralKeywordEnumeration(mRules, status), status);
if (U_FAILURE(status)) {
return nullptr;
}
return nameEnumerator.orphan();
}
double
PluralRules::getUniqueKeywordValue(const UnicodeString& /* keyword */) {
// Not Implemented.
return UPLRULES_NO_UNIQUE_VALUE;
}
int32_t
PluralRules::getAllKeywordValues(const UnicodeString & /* keyword */, double * /* dest */,
int32_t /* destCapacity */, UErrorCode& error) {
error = U_UNSUPPORTED_ERROR;
return 0;
}
static double scaleForInt(double d) {
double scale = 1.0;
while (d != floor(d)) {
d = d * 10.0;
scale = scale * 10.0;
}
return scale;
}
/**
* Helper method for the overrides of getSamples() for double and FixedDecimal
* return value types. Provide only one of an allocated array of doubles or
* FixedDecimals, and a nullptr for the other.
*/
static int32_t
getSamplesFromString(const UnicodeString &samples, double *destDbl,
FixedDecimal* destFd, int32_t destCapacity,
UErrorCode& status) {
if ((destDbl == nullptr && destFd == nullptr)
|| (destDbl != nullptr && destFd != nullptr)) {
status = U_INTERNAL_PROGRAM_ERROR;
return 0;
}
bool isDouble = destDbl != nullptr;
int32_t sampleCount = 0;
int32_t sampleStartIdx = 0;
int32_t sampleEndIdx = 0;
//std::string ss; // TODO: debugging.
// std::cout << "PluralRules::getSamples(), samples = \"" << samples.toUTF8String(ss) << "\"\n";
for (sampleCount = 0; sampleCount < destCapacity && sampleStartIdx < samples.length(); ) {
sampleEndIdx = samples.indexOf(COMMA, sampleStartIdx);
if (sampleEndIdx == -1) {
sampleEndIdx = samples.length();
}
const UnicodeString &sampleRange = samples.tempSubStringBetween(sampleStartIdx, sampleEndIdx);
// ss.erase();
// std::cout << "PluralRules::getSamples(), samplesRange = \"" << sampleRange.toUTF8String(ss) << "\"\n";
int32_t tildeIndex = sampleRange.indexOf(TILDE);
if (tildeIndex < 0) {
FixedDecimal fixed(sampleRange, status);
if (isDouble) {
double sampleValue = fixed.source;
if (fixed.visibleDecimalDigitCount == 0 || sampleValue != floor(sampleValue)) {
destDbl[sampleCount++] = sampleValue;
}
} else {
destFd[sampleCount++] = fixed;
}
} else {
FixedDecimal fixedLo(sampleRange.tempSubStringBetween(0, tildeIndex), status);
FixedDecimal fixedHi(sampleRange.tempSubStringBetween(tildeIndex+1), status);
double rangeLo = fixedLo.source;
double rangeHi = fixedHi.source;
if (U_FAILURE(status)) {
break;
}
if (rangeHi < rangeLo) {
status = U_INVALID_FORMAT_ERROR;
break;
}
// For ranges of samples with fraction decimal digits, scale the number up so that we
// are adding one in the units place. Avoids roundoffs from repetitive adds of tenths.
double scale = scaleForInt(rangeLo);
double t = scaleForInt(rangeHi);
if (t > scale) {
scale = t;
}
rangeLo *= scale;
rangeHi *= scale;
for (double n=rangeLo; n<=rangeHi; n+=1) {
double sampleValue = n/scale;
if (isDouble) {
// Hack Alert: don't return any decimal samples with integer values that
// originated from a format with trailing decimals.
// This API is returning doubles, which can't distinguish having displayed
// zeros to the right of the decimal.
// This results in test failures with values mapping back to a different keyword.
if (!(sampleValue == floor(sampleValue) && fixedLo.visibleDecimalDigitCount > 0)) {
destDbl[sampleCount++] = sampleValue;
}
} else {
int32_t v = (int32_t) fixedLo.getPluralOperand(PluralOperand::PLURAL_OPERAND_V);
int32_t e = (int32_t) fixedLo.getPluralOperand(PluralOperand::PLURAL_OPERAND_E);
FixedDecimal newSample = FixedDecimal::createWithExponent(sampleValue, v, e);
destFd[sampleCount++] = newSample;
}
if (sampleCount >= destCapacity) {
break;
}
}
}
sampleStartIdx = sampleEndIdx + 1;
}
return sampleCount;
}
int32_t
PluralRules::getSamples(const UnicodeString &keyword, double *dest,
int32_t destCapacity, UErrorCode& status) {
if (U_FAILURE(status)) {
return 0;
}
if (U_FAILURE(mInternalStatus)) {
status = mInternalStatus;
return 0;
}
if (dest != nullptr ? destCapacity < 0 : destCapacity != 0) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
RuleChain *rc = rulesForKeyword(keyword);
if (rc == nullptr) {
return 0;
}
int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, dest, nullptr, destCapacity, status);
if (numSamples == 0) {
numSamples = getSamplesFromString(rc->fDecimalSamples, dest, nullptr, destCapacity, status);
}
return numSamples;
}
int32_t
PluralRules::getSamples(const UnicodeString &keyword, FixedDecimal *dest,
int32_t destCapacity, UErrorCode& status) {
if (U_FAILURE(status)) {
return 0;
}
if (U_FAILURE(mInternalStatus)) {
status = mInternalStatus;
return 0;
}
if (dest != nullptr ? destCapacity < 0 : destCapacity != 0) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
RuleChain *rc = rulesForKeyword(keyword);
if (rc == nullptr) {
return 0;
}
int32_t numSamples = getSamplesFromString(rc->fIntegerSamples, nullptr, dest, destCapacity, status);
if (numSamples == 0) {
numSamples = getSamplesFromString(rc->fDecimalSamples, nullptr, dest, destCapacity, status);
}
return numSamples;
}
RuleChain *PluralRules::rulesForKeyword(const UnicodeString &keyword) const {
RuleChain *rc;
for (rc = mRules; rc != nullptr; rc = rc->fNext) {
if (rc->fKeyword == keyword) {
break;
}
}
return rc;
}
UBool
PluralRules::isKeyword(const UnicodeString& keyword) const {
if (0 == keyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
return true;
}
return rulesForKeyword(keyword) != nullptr;
}
UnicodeString
PluralRules::getKeywordOther() const {
return UnicodeString(TRUE, PLURAL_KEYWORD_OTHER, 5);
}
UBool
PluralRules::operator==(const PluralRules& other) const {
const UnicodeString *ptrKeyword;
UErrorCode status= U_ZERO_ERROR;
if ( this == &other ) {
return TRUE;
}
LocalPointer<StringEnumeration> myKeywordList(getKeywords(status));
LocalPointer<StringEnumeration> otherKeywordList(other.getKeywords(status));
if (U_FAILURE(status)) {
return FALSE;
}
if (myKeywordList->count(status)!=otherKeywordList->count(status)) {
return FALSE;
}
myKeywordList->reset(status);
while ((ptrKeyword=myKeywordList->snext(status))!=nullptr) {
if (!other.isKeyword(*ptrKeyword)) {
return FALSE;
}
}
otherKeywordList->reset(status);
while ((ptrKeyword=otherKeywordList->snext(status))!=nullptr) {
if (!this->isKeyword(*ptrKeyword)) {
return FALSE;
}
}
if (U_FAILURE(status)) {
return FALSE;
}
return TRUE;
}
void
PluralRuleParser::parse(const UnicodeString& ruleData, PluralRules *prules, UErrorCode &status)
{
if (U_FAILURE(status)) {
return;
}
U_ASSERT(ruleIndex == 0); // Parsers are good for a single use only!
ruleSrc = &ruleData;
while (ruleIndex< ruleSrc->length()) {
getNextToken(status);
if (U_FAILURE(status)) {
return;
}
checkSyntax(status);
if (U_FAILURE(status)) {
return;
}
switch (type) {
case tAnd:
U_ASSERT(curAndConstraint != nullptr);
curAndConstraint = curAndConstraint->add(status);
break;
case tOr:
{
U_ASSERT(currentChain != nullptr);
OrConstraint *orNode=currentChain->ruleHeader;
while (orNode->next != nullptr) {
orNode = orNode->next;
}
orNode->next= new OrConstraint();
if (orNode->next == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
break;
}
orNode=orNode->next;
orNode->next=nullptr;
curAndConstraint = orNode->add(status);
}
break;
case tIs:
U_ASSERT(curAndConstraint != nullptr);
U_ASSERT(curAndConstraint->value == -1);
U_ASSERT(curAndConstraint->rangeList == nullptr);
break;
case tNot:
U_ASSERT(curAndConstraint != nullptr);
curAndConstraint->negated=TRUE;
break;
case tNotEqual:
curAndConstraint->negated=TRUE;
U_FALLTHROUGH;
case tIn:
case tWithin:
case tEqual:
{
U_ASSERT(curAndConstraint != nullptr);
LocalPointer<UVector32> newRangeList(new UVector32(status), status);
if (U_FAILURE(status)) {
break;
}
curAndConstraint->rangeList = newRangeList.orphan();
curAndConstraint->rangeList->addElement(-1, status); // range Low
curAndConstraint->rangeList->addElement(-1, status); // range Hi
rangeLowIdx = 0;
rangeHiIdx = 1;
curAndConstraint->value=PLURAL_RANGE_HIGH;
curAndConstraint->integerOnly = (type != tWithin);
}
break;
case tNumber:
U_ASSERT(curAndConstraint != nullptr);
if ( (curAndConstraint->op==AndConstraint::MOD)&&
(curAndConstraint->opNum == -1 ) ) {
curAndConstraint->opNum=getNumberValue(token);
}
else {
if (curAndConstraint->rangeList == nullptr) {
// this is for an 'is' rule
curAndConstraint->value = getNumberValue(token);
} else {
// this is for an 'in' or 'within' rule
if (curAndConstraint->rangeList->elementAti(rangeLowIdx) == -1) {
curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeLowIdx);
curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeHiIdx);
}
else {
curAndConstraint->rangeList->setElementAt(getNumberValue(token), rangeHiIdx);
if (curAndConstraint->rangeList->elementAti(rangeLowIdx) >
curAndConstraint->rangeList->elementAti(rangeHiIdx)) {
// Range Lower bound > Range Upper bound.
// U_UNEXPECTED_TOKEN seems a little funny, but it is consistently
// used for all plural rule parse errors.
status = U_UNEXPECTED_TOKEN;
break;
}
}
}
}
break;
case tComma:
// TODO: rule syntax checking is inadequate, can happen with badly formed rules.
// Catch cases like "n mod 10, is 1" here instead.
if (curAndConstraint == nullptr || curAndConstraint->rangeList == nullptr) {
status = U_UNEXPECTED_TOKEN;
break;
}
U_ASSERT(curAndConstraint->rangeList->size() >= 2);
rangeLowIdx = curAndConstraint->rangeList->size();
curAndConstraint->rangeList->addElement(-1, status); // range Low
rangeHiIdx = curAndConstraint->rangeList->size();
curAndConstraint->rangeList->addElement(-1, status); // range Hi
break;
case tMod:
U_ASSERT(curAndConstraint != nullptr);
curAndConstraint->op=AndConstraint::MOD;
break;
case tVariableN:
case tVariableI:
case tVariableF:
case tVariableT:
case tVariableE:
case tVariableV:
U_ASSERT(curAndConstraint != nullptr);
curAndConstraint->digitsType = type;
break;
case tKeyword:
{
RuleChain *newChain = new RuleChain;
if (newChain == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
break;
}
newChain->fKeyword = token;
if (prules->mRules == nullptr) {
prules->mRules = newChain;
} else {
// The new rule chain goes at the end of the linked list of rule chains,
// unless there is an "other" keyword & chain. "other" must remain last.
RuleChain *insertAfter = prules->mRules;
while (insertAfter->fNext!=nullptr &&
insertAfter->fNext->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5) != 0 ){
insertAfter=insertAfter->fNext;
}
newChain->fNext = insertAfter->fNext;
insertAfter->fNext = newChain;
}
OrConstraint *orNode = new OrConstraint();
if (orNode == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
break;
}
newChain->ruleHeader = orNode;
curAndConstraint = orNode->add(status);
currentChain = newChain;
}
break;
case tInteger:
for (;;) {
getNextToken(status);
if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) {
break;
}
if (type == tEllipsis) {
currentChain->fIntegerSamplesUnbounded = TRUE;
continue;
}
currentChain->fIntegerSamples.append(token);
}
break;
case tDecimal:
for (;;) {
getNextToken(status);
if (U_FAILURE(status) || type == tSemiColon || type == tEOF || type == tAt) {
break;
}
if (type == tEllipsis) {
currentChain->fDecimalSamplesUnbounded = TRUE;
continue;
}
currentChain->fDecimalSamples.append(token);
}
break;
default:
break;
}
prevType=type;
if (U_FAILURE(status)) {
break;
}
}
}
UnicodeString
PluralRules::getRuleFromResource(const Locale& locale, UPluralType type, UErrorCode& errCode) {
UnicodeString emptyStr;
if (U_FAILURE(errCode)) {
return emptyStr;
}
LocalUResourceBundlePointer rb(ures_openDirect(nullptr, "plurals", &errCode));
if(U_FAILURE(errCode)) {
return emptyStr;
}
const char *typeKey;
switch (type) {
case UPLURAL_TYPE_CARDINAL:
typeKey = "locales";
break;
case UPLURAL_TYPE_ORDINAL:
typeKey = "locales_ordinals";
break;
default:
// Must not occur: The caller should have checked for valid types.
errCode = U_ILLEGAL_ARGUMENT_ERROR;
return emptyStr;
}
LocalUResourceBundlePointer locRes(ures_getByKey(rb.getAlias(), typeKey, nullptr, &errCode));
if(U_FAILURE(errCode)) {
return emptyStr;
}
int32_t resLen=0;
const char *curLocaleName=locale.getBaseName();
const UChar* s = ures_getStringByKey(locRes.getAlias(), curLocaleName, &resLen, &errCode);
if (s == nullptr) {
// Check parent locales.
UErrorCode status = U_ZERO_ERROR;
char parentLocaleName[ULOC_FULLNAME_CAPACITY];
const char *curLocaleName2=locale.getBaseName();
uprv_strcpy(parentLocaleName, curLocaleName2);
while (uloc_getParent(parentLocaleName, parentLocaleName,
ULOC_FULLNAME_CAPACITY, &status) > 0) {
resLen=0;
s = ures_getStringByKey(locRes.getAlias(), parentLocaleName, &resLen, &status);
if (s != nullptr) {
errCode = U_ZERO_ERROR;
break;
}
status = U_ZERO_ERROR;
}
}
if (s==nullptr) {
return emptyStr;
}
char setKey[256];
u_UCharsToChars(s, setKey, resLen + 1);
// printf("\n PluralRule: %s\n", setKey);
LocalUResourceBundlePointer ruleRes(ures_getByKey(rb.getAlias(), "rules", nullptr, &errCode));
if(U_FAILURE(errCode)) {
return emptyStr;
}
LocalUResourceBundlePointer setRes(ures_getByKey(ruleRes.getAlias(), setKey, nullptr, &errCode));
if (U_FAILURE(errCode)) {
return emptyStr;
}
int32_t numberKeys = ures_getSize(setRes.getAlias());
UnicodeString result;
const char *key=nullptr;
for(int32_t i=0; i<numberKeys; ++i) { // Keys are zero, one, few, ...
UnicodeString rules = ures_getNextUnicodeString(setRes.getAlias(), &key, &errCode);
UnicodeString uKey(key, -1, US_INV);
result.append(uKey);
result.append(COLON);
result.append(rules);
result.append(SEMI_COLON);
}
return result;
}
UnicodeString
PluralRules::getRules() const {
UnicodeString rules;
if (mRules != nullptr) {
mRules->dumpRules(rules);
}
return rules;
}
AndConstraint::AndConstraint(const AndConstraint& other) {
this->fInternalStatus = other.fInternalStatus;
if (U_FAILURE(fInternalStatus)) {
return; // stop early if the object we are copying from is invalid.
}
this->op = other.op;
this->opNum=other.opNum;
this->value=other.value;
if (other.rangeList != nullptr) {
LocalPointer<UVector32> newRangeList(new UVector32(fInternalStatus), fInternalStatus);
if (U_FAILURE(fInternalStatus)) {
return;
}
this->rangeList = newRangeList.orphan();
this->rangeList->assign(*other.rangeList, fInternalStatus);
}
this->integerOnly=other.integerOnly;
this->negated=other.negated;
this->digitsType = other.digitsType;
if (other.next != nullptr) {
this->next = new AndConstraint(*other.next);
if (this->next == nullptr) {
fInternalStatus = U_MEMORY_ALLOCATION_ERROR;
}
}
}
AndConstraint::~AndConstraint() {
delete rangeList;
rangeList = nullptr;
delete next;
next = nullptr;
}
UBool
AndConstraint::isFulfilled(const IFixedDecimal &number) {
UBool result = TRUE;
if (digitsType == none) {
// An empty AndConstraint, created by a rule with a keyword but no following expression.
return TRUE;
}
PluralOperand operand = tokenTypeToPluralOperand(digitsType);
double n = number.getPluralOperand(operand); // pulls n | i | v | f value for the number.
// Will always be positive.
// May be non-integer (n option only)
do {
if (integerOnly && n != uprv_floor(n)) {
result = FALSE;
break;
}
if (op == MOD) {
n = fmod(n, opNum);
}
if (rangeList == nullptr) {
result = value == -1 || // empty rule
n == value; // 'is' rule
break;
}
result = FALSE; // 'in' or 'within' rule
for (int32_t r=0; r<rangeList->size(); r+=2) {
if (rangeList->elementAti(r) <= n && n <= rangeList->elementAti(r+1)) {
result = TRUE;
break;
}
}
} while (FALSE);
if (negated) {
result = !result;
}
return result;
}
AndConstraint*
AndConstraint::add(UErrorCode& status) {
if (U_FAILURE(fInternalStatus)) {
status = fInternalStatus;
return nullptr;
}
this->next = new AndConstraint();
if (this->next == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
}
return this->next;
}
OrConstraint::OrConstraint(const OrConstraint& other) {
this->fInternalStatus = other.fInternalStatus;
if (U_FAILURE(fInternalStatus)) {
return; // stop early if the object we are copying from is invalid.
}
if ( other.childNode != nullptr ) {
this->childNode = new AndConstraint(*(other.childNode));
if (this->childNode == nullptr) {
fInternalStatus = U_MEMORY_ALLOCATION_ERROR;
return;
}
}
if (other.next != nullptr ) {
this->next = new OrConstraint(*(other.next));
if (this->next == nullptr) {
fInternalStatus = U_MEMORY_ALLOCATION_ERROR;
return;
}
if (U_FAILURE(this->next->fInternalStatus)) {
this->fInternalStatus = this->next->fInternalStatus;
}
}
}
OrConstraint::~OrConstraint() {
delete childNode;
childNode = nullptr;
delete next;
next = nullptr;
}
AndConstraint*
OrConstraint::add(UErrorCode& status) {
if (U_FAILURE(fInternalStatus)) {
status = fInternalStatus;
return nullptr;
}
OrConstraint *curOrConstraint=this;
{
while (curOrConstraint->next!=nullptr) {
curOrConstraint = curOrConstraint->next;
}
U_ASSERT(curOrConstraint->childNode == nullptr);
curOrConstraint->childNode = new AndConstraint();
if (curOrConstraint->childNode == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
}
}
return curOrConstraint->childNode;
}
UBool
OrConstraint::isFulfilled(const IFixedDecimal &number) {
OrConstraint* orRule=this;
UBool result=FALSE;
while (orRule!=nullptr && !result) {
result=TRUE;
AndConstraint* andRule = orRule->childNode;
while (andRule!=nullptr && result) {
result = andRule->isFulfilled(number);
andRule=andRule->next;
}
orRule = orRule->next;
}
return result;
}
RuleChain::RuleChain(const RuleChain& other) :
fKeyword(other.fKeyword), fDecimalSamples(other.fDecimalSamples),
fIntegerSamples(other.fIntegerSamples), fDecimalSamplesUnbounded(other.fDecimalSamplesUnbounded),
fIntegerSamplesUnbounded(other.fIntegerSamplesUnbounded), fInternalStatus(other.fInternalStatus) {
if (U_FAILURE(this->fInternalStatus)) {
return; // stop early if the object we are copying from is invalid.
}
if (other.ruleHeader != nullptr) {
this->ruleHeader = new OrConstraint(*(other.ruleHeader));
if (this->ruleHeader == nullptr) {
this->fInternalStatus = U_MEMORY_ALLOCATION_ERROR;
}
else if (U_FAILURE(this->ruleHeader->fInternalStatus)) {
// If the OrConstraint wasn't fully copied, then set our status to failure as well.
this->fInternalStatus = this->ruleHeader->fInternalStatus;
return; // exit early.
}
}
if (other.fNext != nullptr ) {
this->fNext = new RuleChain(*other.fNext);
if (this->fNext == nullptr) {
this->fInternalStatus = U_MEMORY_ALLOCATION_ERROR;
}
else if (U_FAILURE(this->fNext->fInternalStatus)) {
// If the RuleChain wasn't fully copied, then set our status to failure as well.
this->fInternalStatus = this->fNext->fInternalStatus;
}
}
}
RuleChain::~RuleChain() {
delete fNext;
delete ruleHeader;
}
UnicodeString
RuleChain::select(const IFixedDecimal &number) const {
if (!number.isNaN() && !number.isInfinite()) {
for (const RuleChain *rules = this; rules != nullptr; rules = rules->fNext) {
if (rules->ruleHeader->isFulfilled(number)) {
return rules->fKeyword;
}
}
}
return UnicodeString(TRUE, PLURAL_KEYWORD_OTHER, 5);
}
static UnicodeString tokenString(tokenType tok) {
UnicodeString s;
switch (tok) {
case tVariableN:
s.append(LOW_N); break;
case tVariableI:
s.append(LOW_I); break;
case tVariableF:
s.append(LOW_F); break;
case tVariableV:
s.append(LOW_V); break;
case tVariableT:
s.append(LOW_T); break;
case tVariableE:
s.append(LOW_E); break;
default:
s.append(TILDE);
}
return s;
}
void
RuleChain::dumpRules(UnicodeString& result) {
UChar digitString[16];
if ( ruleHeader != nullptr ) {
result += fKeyword;
result += COLON;
result += SPACE;
OrConstraint* orRule=ruleHeader;
while ( orRule != nullptr ) {
AndConstraint* andRule=orRule->childNode;
while ( andRule != nullptr ) {
if ((andRule->op==AndConstraint::NONE) && (andRule->rangeList==nullptr) && (andRule->value == -1)) {
// Empty Rules.
} else if ( (andRule->op==AndConstraint::NONE) && (andRule->rangeList==nullptr) ) {
result += tokenString(andRule->digitsType);
result += UNICODE_STRING_SIMPLE(" is ");
if (andRule->negated) {
result += UNICODE_STRING_SIMPLE("not ");
}
uprv_itou(digitString,16, andRule->value,10,0);
result += UnicodeString(digitString);
}
else {
result += tokenString(andRule->digitsType);
result += SPACE;
if (andRule->op==AndConstraint::MOD) {
result += UNICODE_STRING_SIMPLE("mod ");
uprv_itou(digitString,16, andRule->opNum,10,0);
result += UnicodeString(digitString);
}
if (andRule->rangeList==nullptr) {
if (andRule->negated) {
result += UNICODE_STRING_SIMPLE(" is not ");
uprv_itou(digitString,16, andRule->value,10,0);
result += UnicodeString(digitString);
}
else {
result += UNICODE_STRING_SIMPLE(" is ");
uprv_itou(digitString,16, andRule->value,10,0);
result += UnicodeString(digitString);
}
}
else {
if (andRule->negated) {
if ( andRule->integerOnly ) {
result += UNICODE_STRING_SIMPLE(" not in ");
}
else {
result += UNICODE_STRING_SIMPLE(" not within ");
}
}
else {
if ( andRule->integerOnly ) {
result += UNICODE_STRING_SIMPLE(" in ");
}
else {
result += UNICODE_STRING_SIMPLE(" within ");
}
}
for (int32_t r=0; r<andRule->rangeList->size(); r+=2) {
int32_t rangeLo = andRule->rangeList->elementAti(r);
int32_t rangeHi = andRule->rangeList->elementAti(r+1);
uprv_itou(digitString,16, rangeLo, 10, 0);
result += UnicodeString(digitString);
result += UNICODE_STRING_SIMPLE("..");
uprv_itou(digitString,16, rangeHi, 10,0);
result += UnicodeString(digitString);
if (r+2 < andRule->rangeList->size()) {
result += UNICODE_STRING_SIMPLE(", ");
}
}
}
}
if ( (andRule=andRule->next) != nullptr) {
result += UNICODE_STRING_SIMPLE(" and ");
}
}
if ( (orRule = orRule->next) != nullptr ) {
result += UNICODE_STRING_SIMPLE(" or ");
}
}
}
if ( fNext != nullptr ) {
result += UNICODE_STRING_SIMPLE("; ");
fNext->dumpRules(result);
}
}
UErrorCode
RuleChain::getKeywords(int32_t capacityOfKeywords, UnicodeString* keywords, int32_t& arraySize) const {
if (U_FAILURE(fInternalStatus)) {
return fInternalStatus;
}
if ( arraySize < capacityOfKeywords-1 ) {
keywords[arraySize++]=fKeyword;
}
else {
return U_BUFFER_OVERFLOW_ERROR;
}
if ( fNext != nullptr ) {
return fNext->getKeywords(capacityOfKeywords, keywords, arraySize);
}
else {
return U_ZERO_ERROR;
}
}
UBool
RuleChain::isKeyword(const UnicodeString& keywordParam) const {
if ( fKeyword == keywordParam ) {
return TRUE;
}
if ( fNext != nullptr ) {
return fNext->isKeyword(keywordParam);
}
else {
return FALSE;
}
}
PluralRuleParser::PluralRuleParser() :
ruleIndex(0), token(), type(none), prevType(none),
curAndConstraint(nullptr), currentChain(nullptr), rangeLowIdx(-1), rangeHiIdx(-1)
{
}
PluralRuleParser::~PluralRuleParser() {
}
int32_t
PluralRuleParser::getNumberValue(const UnicodeString& token) {
int32_t i;
char digits[128];
i = token.extract(0, token.length(), digits, UPRV_LENGTHOF(digits), US_INV);
digits[i]='\0';
return((int32_t)atoi(digits));
}
void
PluralRuleParser::checkSyntax(UErrorCode &status)
{
if (U_FAILURE(status)) {
return;
}
if (!(prevType==none || prevType==tSemiColon)) {
type = getKeyType(token, type); // Switch token type from tKeyword if we scanned a reserved word,
// and we are not at the start of a rule, where a
// keyword is expected.
}
switch(prevType) {
case none:
case tSemiColon:
if (type!=tKeyword && type != tEOF) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tVariableN:
case tVariableI:
case tVariableF:
case tVariableT:
case tVariableE:
case tVariableV:
if (type != tIs && type != tMod && type != tIn &&
type != tNot && type != tWithin && type != tEqual && type != tNotEqual) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tKeyword:
if (type != tColon) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tColon:
if (!(type == tVariableN ||
type == tVariableI ||
type == tVariableF ||
type == tVariableT ||
type == tVariableE ||
type == tVariableV ||
type == tAt)) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tIs:
if ( type != tNumber && type != tNot) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tNot:
if (type != tNumber && type != tIn && type != tWithin) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tMod:
case tDot2:
case tIn:
case tWithin:
case tEqual:
case tNotEqual:
if (type != tNumber) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tAnd:
case tOr:
if ( type != tVariableN &&
type != tVariableI &&
type != tVariableF &&
type != tVariableT &&
type != tVariableE &&
type != tVariableV) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tComma:
if (type != tNumber) {
status = U_UNEXPECTED_TOKEN;
}
break;
case tNumber:
if (type != tDot2 && type != tSemiColon && type != tIs && type != tNot &&
type != tIn && type != tEqual && type != tNotEqual && type != tWithin &&
type != tAnd && type != tOr && type != tComma && type != tAt &&
type != tEOF)
{
status = U_UNEXPECTED_TOKEN;
}
// TODO: a comma following a number that is not part of a range will be allowed.
// It's not the only case of this sort of thing. Parser needs a re-write.
break;
case tAt:
if (type != tDecimal && type != tInteger) {
status = U_UNEXPECTED_TOKEN;
}
break;
default:
status = U_UNEXPECTED_TOKEN;
break;
}
}
/*
* Scan the next token from the input rules.
* rules and returned token type are in the parser state variables.
*/
void
PluralRuleParser::getNextToken(UErrorCode &status)
{
if (U_FAILURE(status)) {
return;
}
UChar ch;
while (ruleIndex < ruleSrc->length()) {
ch = ruleSrc->charAt(ruleIndex);
type = charType(ch);
if (type != tSpace) {
break;
}
++(ruleIndex);
}
if (ruleIndex >= ruleSrc->length()) {
type = tEOF;
return;
}
int32_t curIndex= ruleIndex;
switch (type) {
case tColon:
case tSemiColon:
case tComma:
case tEllipsis:
case tTilde: // scanned '~'
case tAt: // scanned '@'
case tEqual: // scanned '='
case tMod: // scanned '%'
// Single character tokens.
++curIndex;
break;
case tNotEqual: // scanned '!'
if (ruleSrc->charAt(curIndex+1) == EQUALS) {
curIndex += 2;
} else {
type = none;
curIndex += 1;
}
break;
case tKeyword:
while (type == tKeyword && ++curIndex < ruleSrc->length()) {
ch = ruleSrc->charAt(curIndex);
type = charType(ch);
}
type = tKeyword;
break;
case tNumber:
while (type == tNumber && ++curIndex < ruleSrc->length()) {
ch = ruleSrc->charAt(curIndex);
type = charType(ch);
}
type = tNumber;
break;
case tDot:
// We could be looking at either ".." in a range, or "..." at the end of a sample.
if (curIndex+1 >= ruleSrc->length() || ruleSrc->charAt(curIndex+1) != DOT) {
++curIndex;
break; // Single dot
}
if (curIndex+2 >= ruleSrc->length() || ruleSrc->charAt(curIndex+2) != DOT) {
curIndex += 2;
type = tDot2;
break; // double dot
}
type = tEllipsis;
curIndex += 3;
break; // triple dot
default:
status = U_UNEXPECTED_TOKEN;
++curIndex;
break;
}
U_ASSERT(ruleIndex <= ruleSrc->length());
U_ASSERT(curIndex <= ruleSrc->length());
token=UnicodeString(*ruleSrc, ruleIndex, curIndex-ruleIndex);
ruleIndex = curIndex;
}
tokenType
PluralRuleParser::charType(UChar ch) {
if ((ch>=U_ZERO) && (ch<=U_NINE)) {
return tNumber;
}
if (ch>=LOW_A && ch<=LOW_Z) {
return tKeyword;
}
switch (ch) {
case COLON:
return tColon;
case SPACE:
return tSpace;
case SEMI_COLON:
return tSemiColon;
case DOT:
return tDot;
case COMMA:
return tComma;
case EXCLAMATION:
return tNotEqual;
case EQUALS:
return tEqual;
case PERCENT_SIGN:
return tMod;
case AT:
return tAt;
case ELLIPSIS:
return tEllipsis;
case TILDE:
return tTilde;
default :
return none;
}
}
// Set token type for reserved words in the Plural Rule syntax.
tokenType
PluralRuleParser::getKeyType(const UnicodeString &token, tokenType keyType)
{
if (keyType != tKeyword) {
return keyType;
}
if (0 == token.compare(PK_VAR_N, 1)) {
keyType = tVariableN;
} else if (0 == token.compare(PK_VAR_I, 1)) {
keyType = tVariableI;
} else if (0 == token.compare(PK_VAR_F, 1)) {
keyType = tVariableF;
} else if (0 == token.compare(PK_VAR_T, 1)) {
keyType = tVariableT;
} else if (0 == token.compare(PK_VAR_E, 1)) {
keyType = tVariableE;
} else if (0 == token.compare(PK_VAR_V, 1)) {
keyType = tVariableV;
} else if (0 == token.compare(PK_IS, 2)) {
keyType = tIs;
} else if (0 == token.compare(PK_AND, 3)) {
keyType = tAnd;
} else if (0 == token.compare(PK_IN, 2)) {
keyType = tIn;
} else if (0 == token.compare(PK_WITHIN, 6)) {
keyType = tWithin;
} else if (0 == token.compare(PK_NOT, 3)) {
keyType = tNot;
} else if (0 == token.compare(PK_MOD, 3)) {
keyType = tMod;
} else if (0 == token.compare(PK_OR, 2)) {
keyType = tOr;
} else if (0 == token.compare(PK_DECIMAL, 7)) {
keyType = tDecimal;
} else if (0 == token.compare(PK_INTEGER, 7)) {
keyType = tInteger;
}
return keyType;
}
PluralKeywordEnumeration::PluralKeywordEnumeration(RuleChain *header, UErrorCode& status)
: pos(0), fKeywordNames(status) {
if (U_FAILURE(status)) {
return;
}
fKeywordNames.setDeleter(uprv_deleteUObject);
UBool addKeywordOther = TRUE;
RuleChain *node = header;
while (node != nullptr) {
auto newElem = new UnicodeString(node->fKeyword);
if (newElem == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
fKeywordNames.addElement(newElem, status);
if (U_FAILURE(status)) {
delete newElem;
return;
}
if (0 == node->fKeyword.compare(PLURAL_KEYWORD_OTHER, 5)) {
addKeywordOther = FALSE;
}
node = node->fNext;
}
if (addKeywordOther) {
auto newElem = new UnicodeString(PLURAL_KEYWORD_OTHER);
if (newElem == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
fKeywordNames.addElement(newElem, status);
if (U_FAILURE(status)) {
delete newElem;
return;
}
}
}
const UnicodeString*
PluralKeywordEnumeration::snext(UErrorCode& status) {
if (U_SUCCESS(status) && pos < fKeywordNames.size()) {
return (const UnicodeString*)fKeywordNames.elementAt(pos++);
}
return nullptr;
}
void
PluralKeywordEnumeration::reset(UErrorCode& /*status*/) {
pos=0;
}
int32_t
PluralKeywordEnumeration::count(UErrorCode& /*status*/) const {
return fKeywordNames.size();
}
PluralKeywordEnumeration::~PluralKeywordEnumeration() {
}
PluralOperand tokenTypeToPluralOperand(tokenType tt) {
switch(tt) {
case tVariableN:
return PLURAL_OPERAND_N;
case tVariableI:
return PLURAL_OPERAND_I;
case tVariableF:
return PLURAL_OPERAND_F;
case tVariableV:
return PLURAL_OPERAND_V;
case tVariableT:
return PLURAL_OPERAND_T;
case tVariableE:
return PLURAL_OPERAND_E;
default:
UPRV_UNREACHABLE; // unexpected.
}
}
FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f, int32_t e) {
init(n, v, f, e);
// check values. TODO make into unit test.
//
// long visiblePower = (int) Math.pow(10, v);
// if (decimalDigits > visiblePower) {
// throw new IllegalArgumentException();
// }
// double fraction = intValue + (decimalDigits / (double) visiblePower);
// if (fraction != source) {
// double diff = Math.abs(fraction - source)/(Math.abs(fraction) + Math.abs(source));
// if (diff > 0.00000001d) {
// throw new IllegalArgumentException();
// }
// }
}
FixedDecimal::FixedDecimal(double n, int32_t v, int64_t f) {
init(n, v, f);
}
FixedDecimal::FixedDecimal(double n, int32_t v) {
// Ugly, but for samples we don't care.
init(n, v, getFractionalDigits(n, v));
}
FixedDecimal::FixedDecimal(double n) {
init(n);
}
FixedDecimal::FixedDecimal() {
init(0, 0, 0);
}
// Create a FixedDecimal from a UnicodeString containing a number.
// Inefficient, but only used for samples, so simplicity trumps efficiency.
FixedDecimal::FixedDecimal(const UnicodeString &num, UErrorCode &status) {
CharString cs;
int32_t parsedExponent = 0;
int32_t exponentIdx = num.indexOf(u'e');
if (exponentIdx < 0) {
exponentIdx = num.indexOf(u'E');
}
if (exponentIdx >= 0) {
cs.appendInvariantChars(num.tempSubString(0, exponentIdx), status);
int32_t expSubstrStart = exponentIdx + 1;
parsedExponent = ICU_Utility::parseAsciiInteger(num, expSubstrStart);
}
else {
cs.appendInvariantChars(num, status);
}
DecimalQuantity dl;
dl.setToDecNumber(cs.toStringPiece(), status);
if (U_FAILURE(status)) {
init(0, 0, 0);
return;
}
int32_t decimalPoint = num.indexOf(DOT);
double n = dl.toDouble();
if (decimalPoint == -1) {
init(n, 0, 0, parsedExponent);
} else {
int32_t fractionNumLength = exponentIdx < 0 ? num.length() : cs.length();
int32_t v = fractionNumLength - decimalPoint - 1;
init(n, v, getFractionalDigits(n, v), parsedExponent);
}
}
FixedDecimal::FixedDecimal(const FixedDecimal &other) {
source = other.source;
visibleDecimalDigitCount = other.visibleDecimalDigitCount;
decimalDigits = other.decimalDigits;
decimalDigitsWithoutTrailingZeros = other.decimalDigitsWithoutTrailingZeros;
intValue = other.intValue;
exponent = other.exponent;
_hasIntegerValue = other._hasIntegerValue;
isNegative = other.isNegative;
_isNaN = other._isNaN;
_isInfinite = other._isInfinite;
}
FixedDecimal::~FixedDecimal() = default;
FixedDecimal FixedDecimal::createWithExponent(double n, int32_t v, int32_t e) {
return FixedDecimal(n, v, getFractionalDigits(n, v), e);
}
void FixedDecimal::init(double n) {
int32_t numFractionDigits = decimals(n);
init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits));
}
void FixedDecimal::init(double n, int32_t v, int64_t f) {
int32_t exponent = 0;
init(n, v, f, exponent);
}
void FixedDecimal::init(double n, int32_t v, int64_t f, int32_t e) {
isNegative = n < 0.0;
source = fabs(n);
_isNaN = uprv_isNaN(source);
_isInfinite = uprv_isInfinite(source);
exponent = e;
if (_isNaN || _isInfinite) {
v = 0;
f = 0;
intValue = 0;
_hasIntegerValue = FALSE;
} else {
intValue = (int64_t)source;
_hasIntegerValue = (source == intValue);
}
visibleDecimalDigitCount = v;
decimalDigits = f;
if (f == 0) {
decimalDigitsWithoutTrailingZeros = 0;
} else {
int64_t fdwtz = f;
while ((fdwtz%10) == 0) {
fdwtz /= 10;
}
decimalDigitsWithoutTrailingZeros = fdwtz;
}
}
// Fast path only exact initialization. Return true if successful.
// Note: Do not multiply by 10 each time through loop, rounding cruft can build
// up that makes the check for an integer result fail.
// A single multiply of the original number works more reliably.
static int32_t p10[] = {1, 10, 100, 1000, 10000};
UBool FixedDecimal::quickInit(double n) {
UBool success = FALSE;
n = fabs(n);
int32_t numFractionDigits;
for (numFractionDigits = 0; numFractionDigits <= 3; numFractionDigits++) {
double scaledN = n * p10[numFractionDigits];
if (scaledN == floor(scaledN)) {
success = TRUE;
break;
}
}
if (success) {
init(n, numFractionDigits, getFractionalDigits(n, numFractionDigits));
}
return success;
}
int32_t FixedDecimal::decimals(double n) {
// Count the number of decimal digits in the fraction part of the number, excluding trailing zeros.
// fastpath the common cases, integers or fractions with 3 or fewer digits
n = fabs(n);
for (int ndigits=0; ndigits<=3; ndigits++) {
double scaledN = n * p10[ndigits];
if (scaledN == floor(scaledN)) {
return ndigits;
}
}
// Slow path, convert with sprintf, parse converted output.
char buf[30] = {0};
sprintf(buf, "%1.15e", n);
// formatted number looks like this: 1.234567890123457e-01
int exponent = atoi(buf+18);
int numFractionDigits = 15;
for (int i=16; ; --i) {
if (buf[i] != '0') {
break;
}
--numFractionDigits;
}
numFractionDigits -= exponent; // Fraction part of fixed point representation.
return numFractionDigits;
}
// Get the fraction digits of a double, represented as an integer.
// v is the number of visible fraction digits in the displayed form of the number.
// Example: n = 1001.234, v = 6, result = 234000
// TODO: need to think through how this is used in the plural rule context.
// This function can easily encounter integer overflow,
// and can easily return noise digits when the precision of a double is exceeded.
int64_t FixedDecimal::getFractionalDigits(double n, int32_t v) {
if (v == 0 || n == floor(n) || uprv_isNaN(n) || uprv_isPositiveInfinity(n)) {
return 0;
}
n = fabs(n);
double fract = n - floor(n);
switch (v) {
case 1: return (int64_t)(fract*10.0 + 0.5);
case 2: return (int64_t)(fract*100.0 + 0.5);
case 3: return (int64_t)(fract*1000.0 + 0.5);
default:
double scaled = floor(fract * pow(10.0, (double)v) + 0.5);
if (scaled >= static_cast<double>(U_INT64_MAX)) {
// Note: a double cannot accurately represent U_INT64_MAX. Casting it to double
// will round up to the next representable value, which is U_INT64_MAX + 1.
return U_INT64_MAX;
} else {
return (int64_t)scaled;
}
}
}
void FixedDecimal::adjustForMinFractionDigits(int32_t minFractionDigits) {
int32_t numTrailingFractionZeros = minFractionDigits - visibleDecimalDigitCount;
if (numTrailingFractionZeros > 0) {
for (int32_t i=0; i<numTrailingFractionZeros; i++) {
// Do not let the decimalDigits value overflow if there are many trailing zeros.
// Limit the value to 18 digits, the most that a 64 bit int can fully represent.
if (decimalDigits >= 100000000000000000LL) {
break;
}
decimalDigits *= 10;
}
visibleDecimalDigitCount += numTrailingFractionZeros;
}
}
double FixedDecimal::getPluralOperand(PluralOperand operand) const {
switch(operand) {
case PLURAL_OPERAND_N: return source;
case PLURAL_OPERAND_I: return static_cast<double>(intValue);
case PLURAL_OPERAND_F: return static_cast<double>(decimalDigits);
case PLURAL_OPERAND_T: return static_cast<double>(decimalDigitsWithoutTrailingZeros);
case PLURAL_OPERAND_V: return visibleDecimalDigitCount;
case PLURAL_OPERAND_E: return exponent;
default:
UPRV_UNREACHABLE; // unexpected.
}
}
bool FixedDecimal::isNaN() const {
return _isNaN;
}
bool FixedDecimal::isInfinite() const {
return _isInfinite;
}
bool FixedDecimal::hasIntegerValue() const {
return _hasIntegerValue;
}
bool FixedDecimal::isNanOrInfinity() const {
return _isNaN || _isInfinite;
}
int32_t FixedDecimal::getVisibleFractionDigitCount() const {
return visibleDecimalDigitCount;
}
bool FixedDecimal::operator==(const FixedDecimal &other) const {
return source == other.source && visibleDecimalDigitCount == other.visibleDecimalDigitCount
&& decimalDigits == other.decimalDigits && exponent == other.exponent;
}
UnicodeString FixedDecimal::toString() const {
char pattern[15];
char buffer[20];
if (exponent == 0) {
snprintf(pattern, sizeof(pattern), "%%.%df", visibleDecimalDigitCount);
snprintf(buffer, sizeof(buffer), pattern, source);
} else {
snprintf(pattern, sizeof(pattern), "%%.%dfe%%d", visibleDecimalDigitCount);
snprintf(buffer, sizeof(buffer), pattern, source, exponent);
}
return UnicodeString(buffer, -1, US_INV);
}
PluralAvailableLocalesEnumeration::PluralAvailableLocalesEnumeration(UErrorCode &status) {
fOpenStatus = status;
if (U_FAILURE(status)) {
return;
}
fOpenStatus = U_ZERO_ERROR; // clear any warnings.
LocalUResourceBundlePointer rb(ures_openDirect(nullptr, "plurals", &fOpenStatus));
fLocales = ures_getByKey(rb.getAlias(), "locales", nullptr, &fOpenStatus);
}
PluralAvailableLocalesEnumeration::~PluralAvailableLocalesEnumeration() {
ures_close(fLocales);
ures_close(fRes);
fLocales = nullptr;
fRes = nullptr;
}
const char *PluralAvailableLocalesEnumeration::next(int32_t *resultLength, UErrorCode &status) {
if (U_FAILURE(status)) {
return nullptr;
}
if (U_FAILURE(fOpenStatus)) {
status = fOpenStatus;
return nullptr;
}
fRes = ures_getNextResource(fLocales, fRes, &status);
if (fRes == nullptr || U_FAILURE(status)) {
if (status == U_INDEX_OUTOFBOUNDS_ERROR) {
status = U_ZERO_ERROR;
}
return nullptr;
}
const char *result = ures_getKey(fRes);
if (resultLength != nullptr) {
*resultLength = static_cast<int32_t>(uprv_strlen(result));
}
return result;
}
void PluralAvailableLocalesEnumeration::reset(UErrorCode &status) {
if (U_FAILURE(status)) {
return;
}
if (U_FAILURE(fOpenStatus)) {
status = fOpenStatus;
return;
}
ures_resetIterator(fLocales);
}
int32_t PluralAvailableLocalesEnumeration::count(UErrorCode &status) const {
if (U_FAILURE(status)) {
return 0;
}
if (U_FAILURE(fOpenStatus)) {
status = fOpenStatus;
return 0;
}
return ures_getSize(fLocales);
}
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
//eof