deps: update icu to 77.1
Some checks failed
Coverage Linux (without intl) / coverage-linux-without-intl (push) Waiting to run
Coverage Linux / coverage-linux (push) Waiting to run
Coverage Windows / coverage-windows (push) Waiting to run
Test and upload documentation to artifacts / build-docs (push) Waiting to run
Linters / lint-addon-docs (push) Waiting to run
Linters / lint-cpp (push) Waiting to run
Linters / format-cpp (push) Waiting to run
Linters / lint-js-and-md (push) Waiting to run
Linters / lint-py (push) Waiting to run
Linters / lint-yaml (push) Waiting to run
Linters / lint-sh (push) Waiting to run
Linters / lint-codeowners (push) Waiting to run
Linters / lint-pr-url (push) Waiting to run
Linters / lint-readme (push) Waiting to run
Notify on Push / Notify on Force Push on `main` (push) Waiting to run
Notify on Push / Notify on Push on `main` that lacks metadata (push) Waiting to run
Scorecard supply-chain security / Scorecard analysis (push) Waiting to run
License update / update_license (push) Has been cancelled
Find inactive collaborators / find (push) Has been cancelled

PR-URL: https://github.com/nodejs/node/pull/57455
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
Reviewed-By: LiviaMedeiros <livia@cirno.name>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Steven R Loomis <srl295@gmail.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Reviewed-By: Richard Lau <rlau@redhat.com>
This commit is contained in:
Node.js GitHub Bot 2025-03-16 16:10:32 -04:00 committed by GitHub
parent 8ccbfb656a
commit ab9660b55a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
145 changed files with 5248 additions and 3341 deletions

View File

@ -2,7 +2,7 @@ UNICODE LICENSE V3
COPYRIGHT AND PERMISSION NOTICE COPYRIGHT AND PERMISSION NOTICE
Copyright © 2016-2024 Unicode, Inc. Copyright © 2016-2025 Unicode, Inc.
NOTICE TO USER: Carefully read the following legal agreement. BY NOTICE TO USER: Carefully read the following legal agreement. BY
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR

View File

@ -1,8 +1,8 @@
ICU sources - auto generated by shrink-icu-src.py ICU sources - auto generated by shrink-icu-src.py
This directory contains the ICU subset used by --with-intl=full-icu This directory contains the ICU subset used by --with-intl=full-icu
It is a strict subset of ICU 76 source files with the following exception(s): It is a strict subset of ICU 77 source files with the following exception(s):
* deps/icu-small/source/data/in/icudt76l.dat.bz2 : compressed data file * deps/icu-small/source/data/in/icudt77l.dat.bz2 : compressed data file
To rebuild this directory, see ../../tools/icu/README.md To rebuild this directory, see ../../tools/icu/README.md

View File

@ -59,7 +59,7 @@ BreakIterator::buildInstance(const Locale& loc, const char *type, UErrorCode &st
{ {
char fnbuff[256]; char fnbuff[256];
char ext[4]={'\0'}; char ext[4]={'\0'};
CharString actualLocale; CharString actual;
int32_t size; int32_t size;
const char16_t* brkfname = nullptr; const char16_t* brkfname = nullptr;
UResourceBundle brkRulesStack; UResourceBundle brkRulesStack;
@ -94,7 +94,7 @@ BreakIterator::buildInstance(const Locale& loc, const char *type, UErrorCode &st
// Use the string if we found it // Use the string if we found it
if (U_SUCCESS(status) && brkfname) { if (U_SUCCESS(status) && brkfname) {
actualLocale.append(ures_getLocaleInternal(brkName, &status), -1, status); actual.append(ures_getLocaleInternal(brkName, &status), -1, status);
char16_t* extStart=u_strchr(brkfname, 0x002e); char16_t* extStart=u_strchr(brkfname, 0x002e);
int len = 0; int len = 0;
@ -124,9 +124,8 @@ BreakIterator::buildInstance(const Locale& loc, const char *type, UErrorCode &st
U_LOCALE_BASED(locBased, *(BreakIterator*)result); U_LOCALE_BASED(locBased, *(BreakIterator*)result);
locBased.setLocaleIDs(ures_getLocaleByType(b, ULOC_VALID_LOCALE, &status), locBased.setLocaleIDs(ures_getLocaleByType(b, ULOC_VALID_LOCALE, &status),
actualLocale.data()); actual.data(), status);
uprv_strncpy(result->requestLocale, loc.getName(), ULOC_FULLNAME_CAPACITY); LocaleBased::setLocaleID(loc.getName(), result->requestLocale, status);
result->requestLocale[ULOC_FULLNAME_CAPACITY-1] = 0; // always terminate
} }
ures_close(b); ures_close(b);
@ -206,26 +205,32 @@ BreakIterator::getAvailableLocales(int32_t& count)
BreakIterator::BreakIterator() BreakIterator::BreakIterator()
{ {
*validLocale = *actualLocale = *requestLocale = 0;
} }
BreakIterator::BreakIterator(const BreakIterator &other) : UObject(other) { BreakIterator::BreakIterator(const BreakIterator &other) : UObject(other) {
uprv_strncpy(actualLocale, other.actualLocale, sizeof(actualLocale)); UErrorCode status = U_ZERO_ERROR;
uprv_strncpy(validLocale, other.validLocale, sizeof(validLocale)); U_LOCALE_BASED(locBased, *this);
uprv_strncpy(requestLocale, other.requestLocale, sizeof(requestLocale)); locBased.setLocaleIDs(other.validLocale, other.actualLocale, status);
LocaleBased::setLocaleID(other.requestLocale, requestLocale, status);
U_ASSERT(U_SUCCESS(status));
} }
BreakIterator &BreakIterator::operator =(const BreakIterator &other) { BreakIterator &BreakIterator::operator =(const BreakIterator &other) {
if (this != &other) { if (this != &other) {
uprv_strncpy(actualLocale, other.actualLocale, sizeof(actualLocale)); UErrorCode status = U_ZERO_ERROR;
uprv_strncpy(validLocale, other.validLocale, sizeof(validLocale)); U_LOCALE_BASED(locBased, *this);
uprv_strncpy(requestLocale, other.requestLocale, sizeof(requestLocale)); locBased.setLocaleIDs(other.validLocale, other.actualLocale, status);
LocaleBased::setLocaleID(other.requestLocale, requestLocale, status);
U_ASSERT(U_SUCCESS(status));
} }
return *this; return *this;
} }
BreakIterator::~BreakIterator() BreakIterator::~BreakIterator()
{ {
delete validLocale;
delete actualLocale;
delete requestLocale;
} }
// ------------------------------------------ // ------------------------------------------
@ -394,7 +399,7 @@ BreakIterator::createInstance(const Locale& loc, int32_t kind, UErrorCode& statu
// revisit this in ICU 3.0 and clean it up/fix it/remove it. // revisit this in ICU 3.0 and clean it up/fix it/remove it.
if (U_SUCCESS(status) && (result != nullptr) && *actualLoc.getName() != 0) { if (U_SUCCESS(status) && (result != nullptr) && *actualLoc.getName() != 0) {
U_LOCALE_BASED(locBased, *result); U_LOCALE_BASED(locBased, *result);
locBased.setLocaleIDs(actualLoc.getName(), actualLoc.getName()); locBased.setLocaleIDs(actualLoc.getName(), actualLoc.getName(), status);
} }
return result; return result;
} }
@ -488,6 +493,7 @@ BreakIterator::makeInstance(const Locale& loc, int32_t kind, UErrorCode& status)
} }
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
delete result;
return nullptr; return nullptr;
} }
@ -496,20 +502,25 @@ BreakIterator::makeInstance(const Locale& loc, int32_t kind, UErrorCode& status)
Locale Locale
BreakIterator::getLocale(ULocDataLocaleType type, UErrorCode& status) const { BreakIterator::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
if (type == ULOC_REQUESTED_LOCALE) { if (U_FAILURE(status)) {
return {requestLocale}; return Locale::getRoot();
} }
U_LOCALE_BASED(locBased, *this); if (type == ULOC_REQUESTED_LOCALE) {
return locBased.getLocale(type, status); return requestLocale == nullptr ?
Locale::getRoot() : Locale(requestLocale->data());
}
return LocaleBased::getLocale(validLocale, actualLocale, type, status);
} }
const char * const char *
BreakIterator::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const { BreakIterator::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const {
if (type == ULOC_REQUESTED_LOCALE) { if (U_FAILURE(status)) {
return requestLocale; return nullptr;
} }
U_LOCALE_BASED(locBased, *this); if (type == ULOC_REQUESTED_LOCALE) {
return locBased.getLocaleID(type, status); return requestLocale == nullptr ? "" : requestLocale->data();
}
return LocaleBased::getLocaleID(validLocale, actualLocale, type, status);
} }
@ -536,8 +547,10 @@ int32_t BreakIterator::getRuleStatusVec(int32_t *fillInVec, int32_t capacity, UE
} }
BreakIterator::BreakIterator (const Locale& valid, const Locale& actual) { BreakIterator::BreakIterator (const Locale& valid, const Locale& actual) {
UErrorCode status = U_ZERO_ERROR;
U_LOCALE_BASED(locBased, (*this)); U_LOCALE_BASED(locBased, (*this));
locBased.setLocaleIDs(valid, actual); locBased.setLocaleIDs(valid.getName(), actual.getName(), status);
U_ASSERT(U_SUCCESS(status));
} }
U_NAMESPACE_END U_NAMESPACE_END

View File

@ -70,6 +70,15 @@ CharString &CharString::copyFrom(const CharString &s, UErrorCode &errorCode) {
return *this; return *this;
} }
CharString &CharString::copyFrom(StringPiece s, UErrorCode &errorCode) {
if (U_FAILURE(errorCode)) {
return *this;
}
len = 0;
append(s, errorCode);
return *this;
}
int32_t CharString::lastIndexOf(char c) const { int32_t CharString::lastIndexOf(char c) const {
for(int32_t i=len; i>0;) { for(int32_t i=len; i>0;) {
if(buffer[--i]==c) { if(buffer[--i]==c) {
@ -143,7 +152,7 @@ CharString &CharString::append(const char *s, int32_t sLength, UErrorCode &error
return *this; return *this;
} }
CharString &CharString::appendNumber(int32_t number, UErrorCode &status) { CharString &CharString::appendNumber(int64_t number, UErrorCode &status) {
if (number < 0) { if (number < 0) {
this->append('-', status); this->append('-', status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {

View File

@ -74,6 +74,7 @@ public:
* use a UErrorCode where memory allocations might be needed. * use a UErrorCode where memory allocations might be needed.
*/ */
CharString &copyFrom(const CharString &other, UErrorCode &errorCode); CharString &copyFrom(const CharString &other, UErrorCode &errorCode);
CharString &copyFrom(StringPiece s, UErrorCode &errorCode);
UBool isEmpty() const { return len==0; } UBool isEmpty() const { return len==0; }
int32_t length() const { return len; } int32_t length() const { return len; }
@ -135,7 +136,7 @@ public:
} }
CharString &append(const char *s, int32_t sLength, UErrorCode &status); CharString &append(const char *s, int32_t sLength, UErrorCode &status);
CharString &appendNumber(int32_t number, UErrorCode &status); CharString &appendNumber(int64_t number, UErrorCode &status);
/** /**
* Returns a writable buffer for appending and writes the buffer's capacity to * Returns a writable buffer for appending and writes the buffer's capacity to

File diff suppressed because it is too large Load Diff

View File

@ -12,44 +12,84 @@
*/ */
#include "locbased.h" #include "locbased.h"
#include "cstring.h" #include "cstring.h"
#include "charstr.h"
U_NAMESPACE_BEGIN U_NAMESPACE_BEGIN
Locale LocaleBased::getLocale(ULocDataLocaleType type, UErrorCode& status) const { Locale LocaleBased::getLocale(const CharString* valid, const CharString* actual,
const char* id = getLocaleID(type, status); ULocDataLocaleType type, UErrorCode& status) {
const char* id = getLocaleID(valid, actual, type, status);
return Locale(id != nullptr ? id : ""); return Locale(id != nullptr ? id : "");
} }
const char* LocaleBased::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const { const char* LocaleBased::getLocaleID(const CharString* valid, const CharString* actual,
ULocDataLocaleType type, UErrorCode& status) {
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return nullptr; return nullptr;
} }
switch(type) { switch(type) {
case ULOC_VALID_LOCALE: case ULOC_VALID_LOCALE:
return valid; return valid == nullptr ? "" : valid->data();
case ULOC_ACTUAL_LOCALE: case ULOC_ACTUAL_LOCALE:
return actual; return actual == nullptr ? "" : actual->data();
default: default:
status = U_ILLEGAL_ARGUMENT_ERROR; status = U_ILLEGAL_ARGUMENT_ERROR;
return nullptr; return nullptr;
} }
} }
void LocaleBased::setLocaleIDs(const char* validID, const char* actualID) { void LocaleBased::setLocaleIDs(const CharString* validID, const CharString* actualID, UErrorCode& status) {
if (validID != nullptr) { setValidLocaleID(validID, status);
uprv_strncpy(valid, validID, ULOC_FULLNAME_CAPACITY); setActualLocaleID(actualID,status);
valid[ULOC_FULLNAME_CAPACITY-1] = 0; // always terminate }
void LocaleBased::setLocaleIDs(const char* validID, const char* actualID, UErrorCode& status) {
setValidLocaleID(validID, status);
setActualLocaleID(actualID,status);
}
void LocaleBased::setLocaleID(const char* id, CharString*& dest, UErrorCode& status) {
if (U_FAILURE(status)) { return; }
if (id == nullptr || *id == 0) {
delete dest;
dest = nullptr;
} else {
if (dest == nullptr) {
dest = new CharString(id, status);
if (dest == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
} else {
dest->copyFrom(id, status);
} }
if (actualID != nullptr) {
uprv_strncpy(actual, actualID, ULOC_FULLNAME_CAPACITY);
actual[ULOC_FULLNAME_CAPACITY-1] = 0; // always terminate
} }
} }
void LocaleBased::setLocaleIDs(const Locale& validID, const Locale& actualID) { void LocaleBased::setLocaleID(const CharString* id, CharString*& dest, UErrorCode& status) {
uprv_strcpy(valid, validID.getName()); if (U_FAILURE(status)) { return; }
uprv_strcpy(actual, actualID.getName()); if (id == nullptr || id->isEmpty()) {
delete dest;
dest = nullptr;
} else {
if (dest == nullptr) {
dest = new CharString(*id, status);
if (dest == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
} else {
dest->copyFrom(*id, status);
}
}
}
bool LocaleBased::equalIDs(const CharString* left, const CharString* right) {
// true if both are nullptr
if (left == nullptr && right == nullptr) return true;
// false if only one is nullptr
if (left == nullptr || right == nullptr) return false;
return *left == *right;
} }
U_NAMESPACE_END U_NAMESPACE_END

View File

@ -19,13 +19,14 @@
/** /**
* Macro to declare a locale LocaleBased wrapper object for the given * Macro to declare a locale LocaleBased wrapper object for the given
* object, which must have two members named `validLocale' and * object, which must have two members named `validLocale' and
* `actualLocale' of size ULOC_FULLNAME_CAPACITY * `actualLocale' of which are pointers to the internal icu::CharString.
*/ */
#define U_LOCALE_BASED(varname, objname) \ #define U_LOCALE_BASED(varname, objname) \
LocaleBased varname((objname).validLocale, (objname).actualLocale) LocaleBased varname((objname).validLocale, (objname).actualLocale)
U_NAMESPACE_BEGIN U_NAMESPACE_BEGIN
class CharString;
/** /**
* A utility class that unifies the implementation of getLocale() by * A utility class that unifies the implementation of getLocale() by
* various ICU services. This class is likely to be removed in the * various ICU services. This class is likely to be removed in the
@ -41,33 +42,35 @@ class U_COMMON_API LocaleBased : public UMemory {
* Construct a LocaleBased wrapper around the two pointers. These * Construct a LocaleBased wrapper around the two pointers. These
* will be aliased for the lifetime of this object. * will be aliased for the lifetime of this object.
*/ */
inline LocaleBased(char* validAlias, char* actualAlias); inline LocaleBased(CharString*& validAlias, CharString*& actualAlias);
/**
* Construct a LocaleBased wrapper around the two const pointers.
* These will be aliased for the lifetime of this object.
*/
inline LocaleBased(const char* validAlias, const char* actualAlias);
/** /**
* Return locale meta-data for the service object wrapped by this * Return locale meta-data for the service object wrapped by this
* object. Either the valid or the actual locale may be * object. Either the valid or the actual locale may be
* retrieved. * retrieved.
* @param valid The valid locale.
* @param actual The actual locale.
* @param type either ULOC_VALID_LOCALE or ULOC_ACTUAL_LOCALE * @param type either ULOC_VALID_LOCALE or ULOC_ACTUAL_LOCALE
* @param status input-output error code * @param status input-output error code
* @return the indicated locale * @return the indicated locale
*/ */
Locale getLocale(ULocDataLocaleType type, UErrorCode& status) const; static Locale getLocale(
const CharString* valid, const CharString* actual,
ULocDataLocaleType type, UErrorCode& status);
/** /**
* Return the locale ID for the service object wrapped by this * Return the locale ID for the service object wrapped by this
* object. Either the valid or the actual locale may be * object. Either the valid or the actual locale may be
* retrieved. * retrieved.
* @param valid The valid locale.
* @param actual The actual locale.
* @param type either ULOC_VALID_LOCALE or ULOC_ACTUAL_LOCALE * @param type either ULOC_VALID_LOCALE or ULOC_ACTUAL_LOCALE
* @param status input-output error code * @param status input-output error code
* @return the indicated locale ID * @return the indicated locale ID
*/ */
const char* getLocaleID(ULocDataLocaleType type, UErrorCode& status) const; static const char* getLocaleID(
const CharString* valid, const CharString* actual,
ULocDataLocaleType type, UErrorCode& status);
/** /**
* Set the locale meta-data for the service object wrapped by this * Set the locale meta-data for the service object wrapped by this
@ -75,31 +78,40 @@ class U_COMMON_API LocaleBased : public UMemory {
* @param valid the ID of the valid locale * @param valid the ID of the valid locale
* @param actual the ID of the actual locale * @param actual the ID of the actual locale
*/ */
void setLocaleIDs(const char* valid, const char* actual); void setLocaleIDs(const char* valid, const char* actual, UErrorCode& status);
void setLocaleIDs(const CharString* valid, const CharString* actual, UErrorCode& status);
/** static void setLocaleID(const char* id, CharString*& dest, UErrorCode& status);
* Set the locale meta-data for the service object wrapped by this static void setLocaleID(const CharString* id, CharString*& dest, UErrorCode& status);
* object.
* @param valid the ID of the valid locale static bool equalIDs(const CharString* left, const CharString* right);
* @param actual the ID of the actual locale
*/
void setLocaleIDs(const Locale& valid, const Locale& actual);
private: private:
char* valid; void setValidLocaleID(const CharString* id, UErrorCode& status);
void setActualLocaleID(const CharString* id, UErrorCode& status);
void setValidLocaleID(const char* id, UErrorCode& status);
void setActualLocaleID(const char* id, UErrorCode& status);
char* actual; CharString*& valid;
CharString*& actual;
}; };
inline LocaleBased::LocaleBased(char* validAlias, char* actualAlias) : inline LocaleBased::LocaleBased(CharString*& validAlias, CharString*& actualAlias) :
valid(validAlias), actual(actualAlias) { valid(validAlias), actual(actualAlias) {
} }
inline LocaleBased::LocaleBased(const char* validAlias, inline void LocaleBased::setValidLocaleID(const CharString* id, UErrorCode& status) {
const char* actualAlias) : setLocaleID(id, valid, status);
// ugh: cast away const }
valid(const_cast<char*>(validAlias)), actual(const_cast<char*>(actualAlias)) { inline void LocaleBased::setActualLocaleID(const CharString* id, UErrorCode& status) {
setLocaleID(id, actual, status);
}
inline void LocaleBased::setValidLocaleID(const char* id, UErrorCode& status) {
setLocaleID(id, valid, status);
}
inline void LocaleBased::setActualLocaleID(const char* id, UErrorCode& status) {
setLocaleID(id, actual, status);
} }
U_NAMESPACE_END U_NAMESPACE_END

View File

@ -19,6 +19,8 @@
* that then do not depend on resource bundle code and display name data. * that then do not depend on resource bundle code and display name data.
*/ */
#include <string_view>
#include "unicode/utypes.h" #include "unicode/utypes.h"
#include "unicode/brkiter.h" #include "unicode/brkiter.h"
#include "unicode/locid.h" #include "unicode/locid.h"
@ -359,7 +361,7 @@ _getStringOrCopyKey(const char *path, const char *locale,
return u_terminateUChars(dest, destCapacity, length, &errorCode); return u_terminateUChars(dest, destCapacity, length, &errorCode);
} }
using UDisplayNameGetter = icu::CharString(const char*, UErrorCode&); using UDisplayNameGetter = icu::CharString(std::string_view, UErrorCode&);
int32_t int32_t
_getDisplayNameForComponent(const char *locale, _getDisplayNameForComponent(const char *locale,
@ -377,6 +379,10 @@ _getDisplayNameForComponent(const char *locale,
return 0; return 0;
} }
if (locale == nullptr) {
locale = uloc_getDefault();
}
localStatus = U_ZERO_ERROR; localStatus = U_ZERO_ERROR;
icu::CharString localeBuffer = (*getter)(locale, localStatus); icu::CharString localeBuffer = (*getter)(locale, localStatus);
if (U_FAILURE(localStatus)) { if (U_FAILURE(localStatus)) {

View File

@ -1828,8 +1828,13 @@ ulocimp_isCanonicalizedLocaleForTest(const char* localeName)
U_NAMESPACE_BEGIN U_NAMESPACE_BEGIN
/*This function initializes a Locale from a C locale ID*/
Locale& Locale::init(const char* localeID, UBool canonicalize) Locale& Locale::init(const char* localeID, UBool canonicalize)
{
return localeID == nullptr ? *this = getDefault() : init(StringPiece{localeID}, canonicalize);
}
/*This function initializes a Locale from a C locale ID*/
Locale& Locale::init(StringPiece localeID, UBool canonicalize)
{ {
fIsBogus = false; fIsBogus = false;
/* Free our current storage */ /* Free our current storage */
@ -1854,19 +1859,28 @@ Locale& Locale::init(const char* localeID, UBool canonicalize)
int32_t length; int32_t length;
UErrorCode err; UErrorCode err;
if(localeID == nullptr) {
// not an error, just set the default locale
return *this = getDefault();
}
/* preset all fields to empty */ /* preset all fields to empty */
language[0] = script[0] = country[0] = 0; language[0] = script[0] = country[0] = 0;
const auto parse = [canonicalize](std::string_view localeID,
char* name,
int32_t nameCapacity,
UErrorCode& status) {
return ByteSinkUtil::viaByteSinkToTerminatedChars(
name, nameCapacity,
[&](ByteSink& sink, UErrorCode& status) {
if (canonicalize) {
ulocimp_canonicalize(localeID, sink, status);
} else {
ulocimp_getName(localeID, sink, status);
}
},
status);
};
// "canonicalize" the locale ID to ICU/Java format // "canonicalize" the locale ID to ICU/Java format
err = U_ZERO_ERROR; err = U_ZERO_ERROR;
length = canonicalize ? length = parse(localeID, fullName, sizeof fullNameBuffer, err);
uloc_canonicalize(localeID, fullName, sizeof(fullNameBuffer), &err) :
uloc_getName(localeID, fullName, sizeof(fullNameBuffer), &err);
if (err == U_BUFFER_OVERFLOW_ERROR || length >= static_cast<int32_t>(sizeof(fullNameBuffer))) { if (err == U_BUFFER_OVERFLOW_ERROR || length >= static_cast<int32_t>(sizeof(fullNameBuffer))) {
U_ASSERT(baseName == nullptr); U_ASSERT(baseName == nullptr);
@ -1877,9 +1891,7 @@ Locale& Locale::init(const char* localeID, UBool canonicalize)
} }
fullName = newFullName; fullName = newFullName;
err = U_ZERO_ERROR; err = U_ZERO_ERROR;
length = canonicalize ? length = parse(localeID, fullName, length + 1, err);
uloc_canonicalize(localeID, fullName, length+1, &err) :
uloc_getName(localeID, fullName, length+1, &err);
} }
if(U_FAILURE(err) || err == U_STRING_NOT_TERMINATED_WARNING) { if(U_FAILURE(err) || err == U_STRING_NOT_TERMINATED_WARNING) {
/* should never occur */ /* should never occur */
@ -2200,6 +2212,13 @@ Locale::createFromName (const char *name)
} }
} }
Locale U_EXPORT2
Locale::createFromName(StringPiece name) {
Locale loc("");
loc.init(name, false);
return loc;
}
Locale U_EXPORT2 Locale U_EXPORT2
Locale::createCanonical(const char* name) { Locale::createCanonical(const char* name) {
Locale loc(""); Locale loc("");

View File

@ -300,6 +300,9 @@ ulocimp_addLikelySubtags(const char* localeID,
icu::ByteSink& sink, icu::ByteSink& sink,
UErrorCode& status) { UErrorCode& status) {
if (U_FAILURE(status)) { return; } if (U_FAILURE(status)) { return; }
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
icu::CharString localeBuffer = ulocimp_canonicalize(localeID, status); icu::CharString localeBuffer = ulocimp_canonicalize(localeID, status);
_uloc_addLikelySubtags(localeBuffer.data(), sink, status); _uloc_addLikelySubtags(localeBuffer.data(), sink, status);
} }
@ -334,6 +337,9 @@ ulocimp_minimizeSubtags(const char* localeID,
bool favorScript, bool favorScript,
UErrorCode& status) { UErrorCode& status) {
if (U_FAILURE(status)) { return; } if (U_FAILURE(status)) { return; }
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
icu::CharString localeBuffer = ulocimp_canonicalize(localeID, status); icu::CharString localeBuffer = ulocimp_canonicalize(localeID, status);
_uloc_minimizeSubtags(localeBuffer.data(), sink, favorScript, status); _uloc_minimizeSubtags(localeBuffer.data(), sink, favorScript, status);
} }
@ -349,7 +355,9 @@ uloc_isRightToLeft(const char *locale) {
UErrorCode errorCode = U_ZERO_ERROR; UErrorCode errorCode = U_ZERO_ERROR;
icu::CharString lang; icu::CharString lang;
icu::CharString script; icu::CharString script;
ulocimp_getSubtags(locale, &lang, &script, nullptr, nullptr, nullptr, errorCode); ulocimp_getSubtags(
locale == nullptr ? uloc_getDefault() : locale,
&lang, &script, nullptr, nullptr, nullptr, errorCode);
if (U_FAILURE(errorCode) || script.isEmpty()) { if (U_FAILURE(errorCode) || script.isEmpty()) {
// Fastpath: We know the likely scripts and their writing direction // Fastpath: We know the likely scripts and their writing direction
// for some common languages. // for some common languages.
@ -369,7 +377,7 @@ uloc_isRightToLeft(const char *locale) {
if (U_FAILURE(errorCode)) { if (U_FAILURE(errorCode)) {
return false; return false;
} }
ulocimp_getSubtags(likely.data(), nullptr, &script, nullptr, nullptr, nullptr, errorCode); ulocimp_getSubtags(likely.toStringPiece(), nullptr, &script, nullptr, nullptr, nullptr, errorCode);
if (U_FAILURE(errorCode) || script.isEmpty()) { if (U_FAILURE(errorCode) || script.isEmpty()) {
return false; return false;
} }
@ -430,7 +438,7 @@ ulocimp_getRegionForSupplementalData(const char *localeID, bool inferRegion,
icu::CharString rgBuf = GetRegionFromKey(localeID, "rg", status); icu::CharString rgBuf = GetRegionFromKey(localeID, "rg", status);
if (U_SUCCESS(status) && rgBuf.isEmpty()) { if (U_SUCCESS(status) && rgBuf.isEmpty()) {
// No valid rg keyword value, try for unicode_region_subtag // No valid rg keyword value, try for unicode_region_subtag
rgBuf = ulocimp_getRegion(localeID, status); rgBuf = ulocimp_getRegion(localeID == nullptr ? uloc_getDefault() : localeID, status);
if (U_SUCCESS(status) && rgBuf.isEmpty() && inferRegion) { if (U_SUCCESS(status) && rgBuf.isEmpty() && inferRegion) {
// Second check for sd keyword value // Second check for sd keyword value
rgBuf = GetRegionFromKey(localeID, "sd", status); rgBuf = GetRegionFromKey(localeID, "sd", status);
@ -439,7 +447,7 @@ ulocimp_getRegionForSupplementalData(const char *localeID, bool inferRegion,
UErrorCode rgStatus = U_ZERO_ERROR; UErrorCode rgStatus = U_ZERO_ERROR;
icu::CharString locBuf = ulocimp_addLikelySubtags(localeID, rgStatus); icu::CharString locBuf = ulocimp_addLikelySubtags(localeID, rgStatus);
if (U_SUCCESS(rgStatus)) { if (U_SUCCESS(rgStatus)) {
rgBuf = ulocimp_getRegion(locBuf.data(), status); rgBuf = ulocimp_getRegion(locBuf.toStringPiece(), status);
} }
} }
} }

View File

@ -527,7 +527,7 @@ LSR LikelySubtags::makeMaximizedLsrFrom(const Locale &locale,
return {}; return {};
} }
const char *name = locale.getName(); const char *name = locale.getName();
if (uprv_isAtSign(name[0]) && name[1] == 'x' && name[2] == '=') { // name.startsWith("@x=") if (!returnInputIfUnmatch && uprv_isAtSign(name[0]) && name[1] == 'x' && name[2] == '=') { // name.startsWith("@x=")
// Private use language tag x-subtag-subtag... which CLDR changes to // Private use language tag x-subtag-subtag... which CLDR changes to
// und-x-subtag-subtag... // und-x-subtag-subtag...
return LSR(name, "", "", LSR::EXPLICIT_LSR); return LSR(name, "", "", LSR::EXPLICIT_LSR);

View File

@ -161,6 +161,9 @@ _uloc_getOrientationHelper(const char* localeId,
if (U_FAILURE(status)) { return result; } if (U_FAILURE(status)) { return result; }
if (localeId == nullptr) {
localeId = uloc_getDefault();
}
icu::CharString localeBuffer = ulocimp_canonicalize(localeId, status); icu::CharString localeBuffer = ulocimp_canonicalize(localeId, status);
if (U_FAILURE(status)) { return result; } if (U_FAILURE(status)) { return result; }

View File

@ -193,7 +193,7 @@ u_strToPunycode(const char16_t *src, int32_t srcLength,
return 0; return 0;
} }
if(src==nullptr || srcLength<-1 || (dest==nullptr && destCapacity!=0)) { if(src==nullptr || srcLength<-1 || destCapacity<0 || (dest==nullptr && destCapacity!=0)) {
*pErrorCode=U_ILLEGAL_ARGUMENT_ERROR; *pErrorCode=U_ILLEGAL_ARGUMENT_ERROR;
return 0; return 0;
} }

View File

@ -76,7 +76,7 @@
#include <float.h> #include <float.h>
#ifndef U_COMMON_IMPLEMENTATION #ifndef U_COMMON_IMPLEMENTATION
#error U_COMMON_IMPLEMENTATION not set - must be set for all ICU source files in common/ - see https://unicode-org.github.io/icu/userguide/howtouseicu #error U_COMMON_IMPLEMENTATION not set - must be set for all ICU source files in common/ - see https://unicode-org.github.io/icu/userguide/icu/howtouseicu.html
#endif #endif

View File

@ -47,7 +47,10 @@ static int gLastSerial = 0;
// Constructor. Just set the fields to reasonable default values. // Constructor. Just set the fields to reasonable default values.
// //
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
RBBINode::RBBINode(NodeType t) : UMemory() { RBBINode::RBBINode(NodeType t, UErrorCode& status) : UMemory() {
if (U_FAILURE(status)) {
return;
}
#ifdef RBBI_DEBUG #ifdef RBBI_DEBUG
fSerialNum = ++gLastSerial; fSerialNum = ++gLastSerial;
#endif #endif
@ -65,10 +68,13 @@ RBBINode::RBBINode(NodeType t) : UMemory() {
fVal = 0; fVal = 0;
fPrecedence = precZero; fPrecedence = precZero;
UErrorCode status = U_ZERO_ERROR; fFirstPosSet = new UVector(status);
fFirstPosSet = new UVector(status); // TODO - get a real status from somewhere
fLastPosSet = new UVector(status); fLastPosSet = new UVector(status);
fFollowPos = new UVector(status); fFollowPos = new UVector(status);
if (U_SUCCESS(status) &&
(fFirstPosSet == nullptr || fLastPosSet == nullptr || fFollowPos == nullptr)) {
status = U_MEMORY_ALLOCATION_ERROR;
}
if (t==opCat) {fPrecedence = precOpCat;} if (t==opCat) {fPrecedence = precOpCat;}
else if (t==opOr) {fPrecedence = precOpOr;} else if (t==opOr) {fPrecedence = precOpOr;}
else if (t==opStart) {fPrecedence = precStart;} else if (t==opStart) {fPrecedence = precStart;}
@ -77,7 +83,10 @@ RBBINode::RBBINode(NodeType t) : UMemory() {
} }
RBBINode::RBBINode(const RBBINode &other) : UMemory(other) { RBBINode::RBBINode(const RBBINode &other, UErrorCode& status) : UMemory(other) {
if (U_FAILURE(status)) {
return;
}
#ifdef RBBI_DEBUG #ifdef RBBI_DEBUG
fSerialNum = ++gLastSerial; fSerialNum = ++gLastSerial;
#endif #endif
@ -94,10 +103,13 @@ RBBINode::RBBINode(const RBBINode &other) : UMemory(other) {
fVal = other.fVal; fVal = other.fVal;
fRuleRoot = false; fRuleRoot = false;
fChainIn = other.fChainIn; fChainIn = other.fChainIn;
UErrorCode status = U_ZERO_ERROR;
fFirstPosSet = new UVector(status); // TODO - get a real status from somewhere fFirstPosSet = new UVector(status); // TODO - get a real status from somewhere
fLastPosSet = new UVector(status); fLastPosSet = new UVector(status);
fFollowPos = new UVector(status); fFollowPos = new UVector(status);
if (U_SUCCESS(status) &&
(fFirstPosSet == nullptr || fLastPosSet == nullptr || fFollowPos == nullptr)) {
status = U_MEMORY_ALLOCATION_ERROR;
}
} }
@ -193,27 +205,54 @@ void RBBINode::NRDeleteNode(RBBINode *node) {
// references in preparation for generating the DFA tables. // references in preparation for generating the DFA tables.
// //
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
RBBINode *RBBINode::cloneTree() { constexpr int kRecursiveDepthLimit = 3500;
RBBINode *RBBINode::cloneTree(UErrorCode &status, int depth) {
if (U_FAILURE(status)) {
return nullptr;
}
// If the depth of the stack is too deep, we return U_INPUT_TOO_LONG_ERROR
// to avoid stack overflow crash.
if (depth > kRecursiveDepthLimit) {
status = U_INPUT_TOO_LONG_ERROR;
return nullptr;
}
RBBINode *n; RBBINode *n;
if (fType == RBBINode::varRef) { if (fType == RBBINode::varRef) {
// If the current node is a variable reference, skip over it // If the current node is a variable reference, skip over it
// and clone the definition of the variable instead. // and clone the definition of the variable instead.
n = fLeftChild->cloneTree(); n = fLeftChild->cloneTree(status, depth+1);
if (U_FAILURE(status)) {
return nullptr;
}
} else if (fType == RBBINode::uset) { } else if (fType == RBBINode::uset) {
n = this; n = this;
} else { } else {
n = new RBBINode(*this); n = new RBBINode(*this, status);
if (U_FAILURE(status)) {
delete n;
return nullptr;
}
// Check for null pointer. // Check for null pointer.
if (n != nullptr) { if (n == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
if (fLeftChild != nullptr) { if (fLeftChild != nullptr) {
n->fLeftChild = fLeftChild->cloneTree(); n->fLeftChild = fLeftChild->cloneTree(status, depth+1);
if (U_FAILURE(status)) {
delete n;
return nullptr;
}
n->fLeftChild->fParent = n; n->fLeftChild->fParent = n;
} }
if (fRightChild != nullptr) { if (fRightChild != nullptr) {
n->fRightChild = fRightChild->cloneTree(); n->fRightChild = fRightChild->cloneTree(status, depth+1);
n->fRightChild->fParent = n; if (U_FAILURE(status)) {
delete n;
return nullptr;
} }
n->fRightChild->fParent = n;
} }
} }
return n; return n;
@ -239,7 +278,6 @@ RBBINode *RBBINode::cloneTree() {
// nested references are handled by cloneTree(), not here. // nested references are handled by cloneTree(), not here.
// //
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
constexpr int kRecursiveDepthLimit = 3500;
RBBINode *RBBINode::flattenVariables(UErrorCode& status, int depth) { RBBINode *RBBINode::flattenVariables(UErrorCode& status, int depth) {
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return this; return this;
@ -251,21 +289,34 @@ RBBINode *RBBINode::flattenVariables(UErrorCode& status, int depth) {
return this; return this;
} }
if (fType == varRef) { if (fType == varRef) {
RBBINode *retNode = fLeftChild->cloneTree(); RBBINode *retNode = fLeftChild->cloneTree(status, depth+1);
if (retNode != nullptr) { if (U_FAILURE(status)) {
return this;
}
retNode->fRuleRoot = this->fRuleRoot; retNode->fRuleRoot = this->fRuleRoot;
retNode->fChainIn = this->fChainIn; retNode->fChainIn = this->fChainIn;
}
delete this; // TODO: undefined behavior. Fix. delete this; // TODO: undefined behavior. Fix.
return retNode; return retNode;
} }
if (fLeftChild != nullptr) { if (fLeftChild != nullptr) {
fLeftChild = fLeftChild->flattenVariables(status, depth+1); fLeftChild = fLeftChild->flattenVariables(status, depth+1);
if (fLeftChild == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(status)) {
return this;
}
fLeftChild->fParent = this; fLeftChild->fParent = this;
} }
if (fRightChild != nullptr) { if (fRightChild != nullptr) {
fRightChild = fRightChild->flattenVariables(status, depth+1); fRightChild = fRightChild->flattenVariables(status, depth+1);
if (fRightChild == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(status)) {
return this;
}
fRightChild->fParent = this; fRightChild->fParent = this;
} }
return this; return this;
@ -280,7 +331,16 @@ RBBINode *RBBINode::flattenVariables(UErrorCode& status, int depth) {
// the left child of the uset node. // the left child of the uset node.
// //
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
void RBBINode::flattenSets() { void RBBINode::flattenSets(UErrorCode &status, int depth) {
if (U_FAILURE(status)) {
return;
}
// If the depth of the stack is too deep, we return U_INPUT_TOO_LONG_ERROR
// to avoid stack overflow crash.
if (depth > kRecursiveDepthLimit) {
status = U_INPUT_TOO_LONG_ERROR;
return;
}
U_ASSERT(fType != setRef); U_ASSERT(fType != setRef);
if (fLeftChild != nullptr) { if (fLeftChild != nullptr) {
@ -288,11 +348,15 @@ void RBBINode::flattenSets() {
RBBINode *setRefNode = fLeftChild; RBBINode *setRefNode = fLeftChild;
RBBINode *usetNode = setRefNode->fLeftChild; RBBINode *usetNode = setRefNode->fLeftChild;
RBBINode *replTree = usetNode->fLeftChild; RBBINode *replTree = usetNode->fLeftChild;
fLeftChild = replTree->cloneTree(); fLeftChild = replTree->cloneTree(status, depth+1);
if (U_FAILURE(status)) {
delete setRefNode;
return;
}
fLeftChild->fParent = this; fLeftChild->fParent = this;
delete setRefNode; delete setRefNode;
} else { } else {
fLeftChild->flattenSets(); fLeftChild->flattenSets(status, depth+1);
} }
} }
@ -301,11 +365,15 @@ void RBBINode::flattenSets() {
RBBINode *setRefNode = fRightChild; RBBINode *setRefNode = fRightChild;
RBBINode *usetNode = setRefNode->fLeftChild; RBBINode *usetNode = setRefNode->fLeftChild;
RBBINode *replTree = usetNode->fLeftChild; RBBINode *replTree = usetNode->fLeftChild;
fRightChild = replTree->cloneTree(); fRightChild = replTree->cloneTree(status, depth+1);
if (U_FAILURE(status)) {
delete setRefNode;
return;
}
fRightChild->fParent = this; fRightChild->fParent = this;
delete setRefNode; delete setRefNode;
} else { } else {
fRightChild->flattenSets(); fRightChild->flattenSets(status, depth+1);
} }
} }
} }

View File

@ -91,14 +91,14 @@ class RBBINode : public UMemory {
UVector *fFollowPos; UVector *fFollowPos;
RBBINode(NodeType t); RBBINode(NodeType t, UErrorCode& status);
RBBINode(const RBBINode &other); RBBINode(const RBBINode &other, UErrorCode& status);
~RBBINode(); ~RBBINode();
static void NRDeleteNode(RBBINode *node); static void NRDeleteNode(RBBINode *node);
RBBINode *cloneTree(); RBBINode *cloneTree(UErrorCode &status, int depth=0);
RBBINode *flattenVariables(UErrorCode &status, int depth=0); RBBINode *flattenVariables(UErrorCode &status, int depth=0);
void flattenSets(); void flattenSets(UErrorCode &status, int depth=0);
void findNodes(UVector *dest, RBBINode::NodeType kind, UErrorCode &status); void findNodes(UVector *dest, RBBINode::NodeType kind, UErrorCode &status);
#ifdef RBBI_DEBUG #ifdef RBBI_DEBUG

View File

@ -767,15 +767,24 @@ void RBBIRuleScanner::findSetFor(const UnicodeString &s, RBBINode *node, Unicode
c = s.char32At(0); c = s.char32At(0);
setToAdopt = new UnicodeSet(c, c); setToAdopt = new UnicodeSet(c, c);
} }
if (setToAdopt == nullptr) {
error(U_MEMORY_ALLOCATION_ERROR);
return;
}
} }
// //
// Make a new uset node to refer to this UnicodeSet // Make a new uset node to refer to this UnicodeSet
// This new uset node becomes the child of the caller's setReference node. // This new uset node becomes the child of the caller's setReference node.
// //
RBBINode *usetNode = new RBBINode(RBBINode::uset); UErrorCode localStatus = U_ZERO_ERROR;
RBBINode *usetNode = new RBBINode(RBBINode::uset, localStatus);
if (usetNode == nullptr) { if (usetNode == nullptr) {
error(U_MEMORY_ALLOCATION_ERROR); localStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(localStatus)) {
delete usetNode;
error(localStatus);
delete setToAdopt; delete setToAdopt;
return; return;
} }
@ -1191,7 +1200,7 @@ RBBINode *RBBIRuleScanner::pushNewNode(RBBINode::NodeType t) {
return nullptr; return nullptr;
} }
fNodeStackPtr++; fNodeStackPtr++;
fNodeStack[fNodeStackPtr] = new RBBINode(t); fNodeStack[fNodeStackPtr] = new RBBINode(t, *fRB->fStatus);
if (fNodeStack[fNodeStackPtr] == nullptr) { if (fNodeStack[fNodeStackPtr] == nullptr) {
*fRB->fStatus = U_MEMORY_ALLOCATION_ERROR; *fRB->fStatus = U_MEMORY_ALLOCATION_ERROR;
} }

View File

@ -375,7 +375,11 @@ void RBBISetBuilder::addValToSets(UVector *sets, uint32_t val) {
} }
void RBBISetBuilder::addValToSet(RBBINode *usetNode, uint32_t val) { void RBBISetBuilder::addValToSet(RBBINode *usetNode, uint32_t val) {
RBBINode *leafNode = new RBBINode(RBBINode::leafChar); RBBINode *leafNode = new RBBINode(RBBINode::leafChar, *fStatus);
if (U_FAILURE(*fStatus)) {
delete leafNode;
return;
}
if (leafNode == nullptr) { if (leafNode == nullptr) {
*fStatus = U_MEMORY_ALLOCATION_ERROR; *fStatus = U_MEMORY_ALLOCATION_ERROR;
return; return;
@ -388,9 +392,13 @@ void RBBISetBuilder::addValToSet(RBBINode *usetNode, uint32_t val) {
// There are already input symbols present for this set. // There are already input symbols present for this set.
// Set up an OR node, with the previous stuff as the left child // Set up an OR node, with the previous stuff as the left child
// and the new value as the right child. // and the new value as the right child.
RBBINode *orNode = new RBBINode(RBBINode::opOr); RBBINode *orNode = new RBBINode(RBBINode::opOr, *fStatus);
if (orNode == nullptr) { if (orNode == nullptr) {
*fStatus = U_MEMORY_ALLOCATION_ERROR; *fStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(*fStatus)) {
delete orNode;
delete leafNode;
return; return;
} }
orNode->fLeftChild = usetNode->fLeftChild; orNode->fLeftChild = usetNode->fLeftChild;

View File

@ -99,13 +99,22 @@ void RBBITableBuilder::buildForwardTable() {
// {bof} fake character. // {bof} fake character.
// //
if (fRB->fSetBuilder->sawBOF()) { if (fRB->fSetBuilder->sawBOF()) {
RBBINode *bofTop = new RBBINode(RBBINode::opCat); RBBINode *bofTop = new RBBINode(RBBINode::opCat, *fStatus);
RBBINode *bofLeaf = new RBBINode(RBBINode::leafChar); if (bofTop == nullptr) {
// Delete and exit if memory allocation failed.
if (bofTop == nullptr || bofLeaf == nullptr) {
*fStatus = U_MEMORY_ALLOCATION_ERROR; *fStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(*fStatus)) {
delete bofTop; delete bofTop;
return;
}
RBBINode *bofLeaf = new RBBINode(RBBINode::leafChar, *fStatus);
// Delete and exit if memory allocation failed.
if (bofLeaf == nullptr) {
*fStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(*fStatus)) {
delete bofLeaf; delete bofLeaf;
delete bofTop;
return; return;
} }
bofTop->fLeftChild = bofLeaf; bofTop->fLeftChild = bofLeaf;
@ -120,18 +129,23 @@ void RBBITableBuilder::buildForwardTable() {
// Appears as a cat-node, left child being the original tree, // Appears as a cat-node, left child being the original tree,
// right child being the end marker. // right child being the end marker.
// //
RBBINode *cn = new RBBINode(RBBINode::opCat); RBBINode *cn = new RBBINode(RBBINode::opCat, *fStatus);
// Exit if memory allocation failed. // Exit if memory allocation failed.
if (cn == nullptr) { if (cn == nullptr) {
*fStatus = U_MEMORY_ALLOCATION_ERROR; *fStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(*fStatus)) {
delete cn;
return; return;
} }
cn->fLeftChild = fTree; cn->fLeftChild = fTree;
fTree->fParent = cn; fTree->fParent = cn;
RBBINode *endMarkerNode = cn->fRightChild = new RBBINode(RBBINode::endMark); RBBINode *endMarkerNode = cn->fRightChild = new RBBINode(RBBINode::endMark, *fStatus);
// Delete and exit if memory allocation failed. // Delete and exit if memory allocation failed.
if (cn->fRightChild == nullptr) { if (cn->fRightChild == nullptr) {
*fStatus = U_MEMORY_ALLOCATION_ERROR; *fStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(*fStatus)) {
delete cn; delete cn;
return; return;
} }
@ -142,7 +156,7 @@ void RBBITableBuilder::buildForwardTable() {
// Replace all references to UnicodeSets with the tree for the equivalent // Replace all references to UnicodeSets with the tree for the equivalent
// expression. // expression.
// //
fTree->flattenSets(); fTree->flattenSets(*fStatus, 0);
#ifdef RBBI_DEBUG #ifdef RBBI_DEBUG
if (fRB->fDebugEnv && uprv_strstr(fRB->fDebugEnv, "stree")) { if (fRB->fDebugEnv && uprv_strstr(fRB->fDebugEnv, "stree")) {
RBBIDebugPuts("\nParse tree after flattening Unicode Set references."); RBBIDebugPuts("\nParse tree after flattening Unicode Set references.");

View File

@ -388,7 +388,7 @@ const Locale &ResourceBundle::getLocale() const {
return ncThis->fLocale != nullptr ? *ncThis->fLocale : Locale::getDefault(); return ncThis->fLocale != nullptr ? *ncThis->fLocale : Locale::getDefault();
} }
const Locale ResourceBundle::getLocale(ULocDataLocaleType type, UErrorCode &status) const Locale ResourceBundle::getLocale(ULocDataLocaleType type, UErrorCode &status) const
{ {
return ures_getLocaleByType(fResource, type, &status); return ures_getLocaleByType(fResource, type, &status);
} }

View File

@ -3146,11 +3146,8 @@ ucnv_MBCSGetNextUChar(UConverterToUnicodeArgs *pArgs,
if(c<0) { if(c<0) {
if(U_SUCCESS(*pErrorCode) && source==sourceLimit && lastSource<source) { if(U_SUCCESS(*pErrorCode) && source==sourceLimit && lastSource<source) {
/* incomplete character byte sequence */ /* incomplete character byte sequence */
uint8_t *bytes=cnv->toUBytes;
cnv->toULength = static_cast<int8_t>(source - lastSource); cnv->toULength = static_cast<int8_t>(source - lastSource);
do { uprv_memcpy(cnv->toUBytes, lastSource, cnv->toULength);
*bytes++=*lastSource++;
} while(lastSource<source);
*pErrorCode=U_TRUNCATED_CHAR_FOUND; *pErrorCode=U_TRUNCATED_CHAR_FOUND;
} else if(U_FAILURE(*pErrorCode)) { } else if(U_FAILURE(*pErrorCode)) {
/* callback(illegal) */ /* callback(illegal) */

View File

@ -372,12 +372,8 @@ struct CReg : public icu::UMemory {
CReg(const char16_t* _iso, const char* _id) CReg(const char16_t* _iso, const char* _id)
: next(nullptr) : next(nullptr)
{ {
int32_t len = static_cast<int32_t>(uprv_strlen(_id)); uprv_strncpy(id, _id, sizeof(id)-1);
if (len > static_cast<int32_t>(sizeof(id) - 1)) { id[sizeof(id)-1] = 0;
len = (sizeof(id)-1);
}
uprv_strncpy(id, _id, len);
id[len] = 0;
u_memcpy(iso, _iso, ISO_CURRENCY_CODE_LENGTH); u_memcpy(iso, _iso, ISO_CURRENCY_CODE_LENGTH);
iso[ISO_CURRENCY_CODE_LENGTH] = 0; iso[ISO_CURRENCY_CODE_LENGTH] = 0;
} }
@ -682,6 +678,9 @@ ucurr_getName(const char16_t* currency,
// this function. // this function.
UErrorCode ec2 = U_ZERO_ERROR; UErrorCode ec2 = U_ZERO_ERROR;
if (locale == nullptr) {
locale = uloc_getDefault();
}
CharString loc = ulocimp_getName(locale, ec2); CharString loc = ulocimp_getName(locale, ec2);
if (U_FAILURE(ec2)) { if (U_FAILURE(ec2)) {
*ec = U_ILLEGAL_ARGUMENT_ERROR; *ec = U_ILLEGAL_ARGUMENT_ERROR;
@ -780,6 +779,9 @@ ucurr_getPluralName(const char16_t* currency,
// this function. // this function.
UErrorCode ec2 = U_ZERO_ERROR; UErrorCode ec2 = U_ZERO_ERROR;
if (locale == nullptr) {
locale = uloc_getDefault();
}
CharString loc = ulocimp_getName(locale, ec2); CharString loc = ulocimp_getName(locale, ec2);
if (U_FAILURE(ec2)) { if (U_FAILURE(ec2)) {
*ec = U_ILLEGAL_ARGUMENT_ERROR; *ec = U_ILLEGAL_ARGUMENT_ERROR;
@ -973,6 +975,9 @@ collectCurrencyNames(const char* locale,
// Look up the Currencies resource for the given locale. // Look up the Currencies resource for the given locale.
UErrorCode ec2 = U_ZERO_ERROR; UErrorCode ec2 = U_ZERO_ERROR;
if (locale == nullptr) {
locale = uloc_getDefault();
}
CharString loc = ulocimp_getName(locale, ec2); CharString loc = ulocimp_getName(locale, ec2);
if (U_FAILURE(ec2)) { if (U_FAILURE(ec2)) {
ec = U_ILLEGAL_ARGUMENT_ERROR; ec = U_ILLEGAL_ARGUMENT_ERROR;

View File

@ -482,8 +482,8 @@ constexpr CanonicalizationMap CANONICALIZE_MAP[] = {
/* ### BCP47 Conversion *******************************************/ /* ### BCP47 Conversion *******************************************/
/* Gets the size of the shortest subtag in the given localeID. */ /* Gets the size of the shortest subtag in the given localeID. */
int32_t getShortestSubtagLength(const char *localeID) { int32_t getShortestSubtagLength(std::string_view localeID) {
int32_t localeIDLength = static_cast<int32_t>(uprv_strlen(localeID)); int32_t localeIDLength = static_cast<int32_t>(localeID.length());
int32_t length = localeIDLength; int32_t length = localeIDLength;
int32_t tmpLength = 0; int32_t tmpLength = 0;
int32_t i; int32_t i;
@ -507,8 +507,8 @@ int32_t getShortestSubtagLength(const char *localeID) {
return length; return length;
} }
/* Test if the locale id has BCP47 u extension and does not have '@' */ /* Test if the locale id has BCP47 u extension and does not have '@' */
inline bool _hasBCP47Extension(const char *id) { inline bool _hasBCP47Extension(std::string_view id) {
return id != nullptr && uprv_strstr(id, "@") == nullptr && getShortestSubtagLength(id) == 1; return id.find('@') == std::string_view::npos && getShortestSubtagLength(id) == 1;
} }
/* ### Keywords **************************************************/ /* ### Keywords **************************************************/
@ -523,10 +523,9 @@ inline bool UPRV_OK_VALUE_PUNCTUATION(char c) { return c == '_' || c == '-' || c
#define ULOC_MAX_NO_KEYWORDS 25 #define ULOC_MAX_NO_KEYWORDS 25
U_CAPI const char * U_EXPORT2 U_CAPI const char * U_EXPORT2
locale_getKeywordsStart(const char *localeID) { locale_getKeywordsStart(std::string_view localeID) {
const char *result = nullptr; if (size_t pos = localeID.find('@'); pos != std::string_view::npos) {
if((result = uprv_strchr(localeID, '@')) != nullptr) { return localeID.data() + pos;
return result;
} }
#if (U_CHARSET_FAMILY == U_EBCDIC_FAMILY) #if (U_CHARSET_FAMILY == U_EBCDIC_FAMILY)
else { else {
@ -536,8 +535,8 @@ locale_getKeywordsStart(const char *localeID) {
static const uint8_t ebcdicSigns[] = { 0x7C, 0x44, 0x66, 0x80, 0xAC, 0xAE, 0xAF, 0xB5, 0xEC, 0xEF, 0x00 }; static const uint8_t ebcdicSigns[] = { 0x7C, 0x44, 0x66, 0x80, 0xAC, 0xAE, 0xAF, 0xB5, 0xEC, 0xEF, 0x00 };
const uint8_t *charToFind = ebcdicSigns; const uint8_t *charToFind = ebcdicSigns;
while(*charToFind) { while(*charToFind) {
if((result = uprv_strchr(localeID, *charToFind)) != nullptr) { if (size_t pos = localeID.find(*charToFind); pos != std::string_view::npos) {
return result; return localeID.data() + pos;
} }
charToFind++; charToFind++;
} }
@ -590,7 +589,7 @@ compareKeywordStructs(const void * /*context*/, const void *left, const void *ri
} // namespace } // namespace
U_EXPORT CharString U_EXPORT CharString
ulocimp_getKeywords(const char* localeID, ulocimp_getKeywords(std::string_view localeID,
char prev, char prev,
bool valuesToo, bool valuesToo,
UErrorCode& status) UErrorCode& status)
@ -607,7 +606,7 @@ ulocimp_getKeywords(const char* localeID,
} }
U_EXPORT void U_EXPORT void
ulocimp_getKeywords(const char* localeID, ulocimp_getKeywords(std::string_view localeID,
char prev, char prev,
ByteSink& sink, ByteSink& sink,
bool valuesToo, bool valuesToo,
@ -619,9 +618,8 @@ ulocimp_getKeywords(const char* localeID,
int32_t maxKeywords = ULOC_MAX_NO_KEYWORDS; int32_t maxKeywords = ULOC_MAX_NO_KEYWORDS;
int32_t numKeywords = 0; int32_t numKeywords = 0;
const char* pos = localeID; size_t equalSign = std::string_view::npos;
const char* equalSign = nullptr; size_t semicolon = std::string_view::npos;
const char* semicolon = nullptr;
int32_t i = 0, j, n; int32_t i = 0, j, n;
if(prev == '@') { /* start of keyword definition */ if(prev == '@') { /* start of keyword definition */
@ -629,74 +627,72 @@ ulocimp_getKeywords(const char* localeID,
do { do {
bool duplicate = false; bool duplicate = false;
/* skip leading spaces */ /* skip leading spaces */
while(*pos == ' ') { while (localeID.front() == ' ') {
pos++; localeID.remove_prefix(1);
} }
if (!*pos) { /* handle trailing "; " */ if (localeID.empty()) { /* handle trailing "; " */
break; break;
} }
if(numKeywords == maxKeywords) { if(numKeywords == maxKeywords) {
status = U_INTERNAL_PROGRAM_ERROR; status = U_INTERNAL_PROGRAM_ERROR;
return; return;
} }
equalSign = uprv_strchr(pos, '='); equalSign = localeID.find('=');
semicolon = uprv_strchr(pos, ';'); semicolon = localeID.find(';');
/* lack of '=' [foo@currency] is illegal */ /* lack of '=' [foo@currency] is illegal */
/* ';' before '=' [foo@currency;collation=pinyin] is illegal */ /* ';' before '=' [foo@currency;collation=pinyin] is illegal */
if(!equalSign || (semicolon && semicolon<equalSign)) { if (equalSign == std::string_view::npos ||
(semicolon != std::string_view::npos && semicolon < equalSign)) {
status = U_INVALID_FORMAT_ERROR;
return;
}
/* zero-length keyword is an error. */
if (equalSign == 0) {
status = U_INVALID_FORMAT_ERROR; status = U_INVALID_FORMAT_ERROR;
return; return;
} }
/* need to normalize both keyword and keyword name */ /* need to normalize both keyword and keyword name */
if(equalSign - pos >= ULOC_KEYWORD_BUFFER_LEN) { if (equalSign >= ULOC_KEYWORD_BUFFER_LEN) {
/* keyword name too long for internal buffer */ /* keyword name too long for internal buffer */
status = U_INTERNAL_PROGRAM_ERROR; status = U_INTERNAL_PROGRAM_ERROR;
return; return;
} }
for(i = 0, n = 0; i < equalSign - pos; ++i) { for (i = 0, n = 0; static_cast<size_t>(i) < equalSign; ++i) {
if (pos[i] != ' ') { if (localeID[i] != ' ') {
keywordList[numKeywords].keyword[n++] = uprv_tolower(pos[i]); keywordList[numKeywords].keyword[n++] = uprv_tolower(localeID[i]);
} }
} }
/* zero-length keyword is an error. */
if (n == 0) {
status = U_INVALID_FORMAT_ERROR;
return;
}
keywordList[numKeywords].keyword[n] = 0; keywordList[numKeywords].keyword[n] = 0;
keywordList[numKeywords].keywordLen = n; keywordList[numKeywords].keywordLen = n;
/* now grab the value part. First we skip the '=' */ /* now grab the value part. First we skip the '=' */
equalSign++; equalSign++;
/* then we leading spaces */ /* then we leading spaces */
while(*equalSign == ' ') { while (equalSign < localeID.length() && localeID[equalSign] == ' ') {
equalSign++; equalSign++;
} }
/* Premature end or zero-length value */ /* Premature end or zero-length value */
if (!*equalSign || equalSign == semicolon) { if (equalSign == localeID.length() || equalSign == semicolon) {
status = U_INVALID_FORMAT_ERROR; status = U_INVALID_FORMAT_ERROR;
return; return;
} }
keywordList[numKeywords].valueStart = equalSign; keywordList[numKeywords].valueStart = localeID.data() + equalSign;
pos = semicolon; std::string_view value = localeID;
i = 0; if (semicolon != std::string_view::npos) {
if(pos) { value.remove_suffix(value.length() - semicolon);
while(*(pos - i - 1) == ' ') { localeID.remove_prefix(semicolon + 1);
i++;
}
keywordList[numKeywords].valueLen = static_cast<int32_t>(pos - equalSign - i);
pos++;
} else { } else {
i = static_cast<int32_t>(uprv_strlen(equalSign)); localeID = {};
while(i && equalSign[i-1] == ' ') {
i--;
} }
keywordList[numKeywords].valueLen = i; value.remove_prefix(equalSign);
if (size_t last = value.find_last_not_of(' '); last != std::string_view::npos) {
value.remove_suffix(value.length() - last - 1);
} }
keywordList[numKeywords].valueLen = static_cast<int32_t>(value.length());
/* If this is a duplicate keyword, then ignore it */ /* If this is a duplicate keyword, then ignore it */
for (j=0; j<numKeywords; ++j) { for (j=0; j<numKeywords; ++j) {
if (uprv_strcmp(keywordList[j].keyword, keywordList[numKeywords].keyword) == 0) { if (uprv_strcmp(keywordList[j].keyword, keywordList[numKeywords].keyword) == 0) {
@ -707,7 +703,7 @@ ulocimp_getKeywords(const char* localeID,
if (!duplicate) { if (!duplicate) {
++numKeywords; ++numKeywords;
} }
} while(pos); } while (!localeID.empty());
/* now we have a list of keywords */ /* now we have a list of keywords */
/* we need to sort it */ /* we need to sort it */
@ -784,7 +780,7 @@ ulocimp_getKeywordValue(const char* localeID,
return; return;
} }
if (_hasBCP47Extension(localeID)) { if (localeID != nullptr && _hasBCP47Extension(localeID)) {
tempBuffer = ulocimp_forLanguageTag(localeID, -1, nullptr, status); tempBuffer = ulocimp_forLanguageTag(localeID, -1, nullptr, status);
tmpLocaleID = U_SUCCESS(status) && !tempBuffer.isEmpty() ? tempBuffer.data() : localeID; tmpLocaleID = U_SUCCESS(status) && !tempBuffer.isEmpty() ? tempBuffer.data() : localeID;
} else { } else {
@ -889,7 +885,8 @@ uloc_setKeywordValue(const char* keywordName,
return 0; return 0;
} }
char* keywords = const_cast<char*>(locale_getKeywordsStart(buffer)); char* keywords = const_cast<char*>(
locale_getKeywordsStart({buffer, static_cast<std::string_view::size_type>(bufLen)}));
int32_t baseLen = keywords == nullptr ? bufLen : keywords - buffer; int32_t baseLen = keywords == nullptr ? bufLen : keywords - buffer;
// Remove -1 from the capacity so that this function can guarantee NUL termination. // Remove -1 from the capacity so that this function can guarantee NUL termination.
CheckedArrayByteSink sink(keywords == nullptr ? buffer + bufLen : keywords, CheckedArrayByteSink sink(keywords == nullptr ? buffer + bufLen : keywords,
@ -921,7 +918,7 @@ ulocimp_setKeywordValue(std::string_view keywordName,
{ {
if (U_FAILURE(status)) { return; } if (U_FAILURE(status)) { return; }
std::string_view keywords; std::string_view keywords;
if (const char* start = locale_getKeywordsStart(localeID.data()); start != nullptr) { if (const char* start = locale_getKeywordsStart(localeID.toStringPiece()); start != nullptr) {
// This is safe because CharString::truncate() doesn't actually erase any // This is safe because CharString::truncate() doesn't actually erase any
// data, but simply sets the position for where new data will be written. // data, but simply sets the position for where new data will be written.
int32_t size = start - localeID.data(); int32_t size = start - localeID.data();
@ -1138,15 +1135,18 @@ inline bool _isPrefixLetter(char a) { return a == 'x' || a == 'X' || a == 'i' ||
/*returns true if one of the special prefixes is here (s=string) /*returns true if one of the special prefixes is here (s=string)
'x-' or 'i-' */ 'x-' or 'i-' */
inline bool _isIDPrefix(const char *s) { return _isPrefixLetter(s[0]) && _isIDSeparator(s[1]); } inline bool _isIDPrefix(std::string_view s) {
return s.size() >= 2 && _isPrefixLetter(s[0]) && _isIDSeparator(s[1]);
}
/* Dot terminates it because of POSIX form where dot precedes the codepage /* Dot terminates it because of POSIX form where dot precedes the codepage
* except for variant * except for variant
*/ */
inline bool _isTerminator(char a) { return a == 0 || a == '.' || a == '@'; } inline bool _isTerminator(char a) { return a == '.' || a == '@'; }
inline bool _isBCP47Extension(const char* p) { inline bool _isBCP47Extension(std::string_view p) {
return p[0] == '-' && return p.size() >= 3 &&
p[0] == '-' &&
(p[1] == 't' || p[1] == 'T' || (p[1] == 't' || p[1] == 'T' ||
p[1] == 'u' || p[1] == 'U' || p[1] == 'u' || p[1] == 'U' ||
p[1] == 'x' || p[1] == 'X') && p[1] == 'x' || p[1] == 'X') &&
@ -1202,49 +1202,44 @@ namespace {
* TODO try to use this in Locale * TODO try to use this in Locale
*/ */
void size_t _getLanguage(std::string_view localeID, ByteSink* sink, UErrorCode& status) {
_getLanguage(const char* localeID, size_t skip = 0;
ByteSink* sink, if (localeID.size() == 4 && uprv_strnicmp(localeID.data(), "root", 4) == 0) {
const char** pEnd, skip = 4;
UErrorCode& status) { localeID.remove_prefix(skip);
U_ASSERT(pEnd != nullptr); } else if (localeID.size() >= 3 && uprv_strnicmp(localeID.data(), "und", 3) == 0 &&
*pEnd = localeID; (localeID.size() == 3 ||
if (uprv_stricmp(localeID, "root") == 0) {
localeID += 4;
} else if (uprv_strnicmp(localeID, "und", 3) == 0 &&
(localeID[3] == '\0' ||
localeID[3] == '-' || localeID[3] == '-' ||
localeID[3] == '_' || localeID[3] == '_' ||
localeID[3] == '@')) { localeID[3] == '@')) {
localeID += 3; skip = 3;
localeID.remove_prefix(skip);
} }
constexpr int32_t MAXLEN = ULOC_LANG_CAPACITY - 1; // Minus NUL. constexpr int32_t MAXLEN = ULOC_LANG_CAPACITY - 1; // Minus NUL.
/* if it starts with i- or x- then copy that prefix */ /* if it starts with i- or x- then copy that prefix */
int32_t len = _isIDPrefix(localeID) ? 2 : 0; size_t len = _isIDPrefix(localeID) ? 2 : 0;
while (!_isTerminator(localeID[len]) && !_isIDSeparator(localeID[len])) { while (len < localeID.size() && !_isTerminator(localeID[len]) && !_isIDSeparator(localeID[len])) {
if (len == MAXLEN) { if (len == MAXLEN) {
status = U_ILLEGAL_ARGUMENT_ERROR; status = U_ILLEGAL_ARGUMENT_ERROR;
return; return 0;
} }
len++; len++;
} }
*pEnd = localeID + len; if (sink == nullptr || len == 0) { return skip + len; }
if (sink == nullptr || len == 0) { return; }
int32_t minCapacity = uprv_max(len, 4); // Minimum 3 letters plus NUL. int32_t minCapacity = uprv_max(static_cast<int32_t>(len), 4); // Minimum 3 letters plus NUL.
char scratch[MAXLEN]; char scratch[MAXLEN];
int32_t capacity = 0; int32_t capacity = 0;
char* buffer = sink->GetAppendBuffer( char* buffer = sink->GetAppendBuffer(
minCapacity, minCapacity, scratch, UPRV_LENGTHOF(scratch), &capacity); minCapacity, minCapacity, scratch, UPRV_LENGTHOF(scratch), &capacity);
for (int32_t i = 0; i < len; ++i) { for (size_t i = 0; i < len; ++i) {
buffer[i] = uprv_tolower(localeID[i]); buffer[i] = uprv_tolower(localeID[i]);
} }
if (_isIDSeparator(localeID[1])) { if (localeID.size() >= 2 && _isIDSeparator(localeID[1])) {
buffer[1] = '-'; buffer[1] = '-';
} }
@ -1256,32 +1251,26 @@ _getLanguage(const char* localeID,
if (offset.has_value()) { if (offset.has_value()) {
const char* const alias = LANGUAGES[*offset]; const char* const alias = LANGUAGES[*offset];
sink->Append(alias, static_cast<int32_t>(uprv_strlen(alias))); sink->Append(alias, static_cast<int32_t>(uprv_strlen(alias)));
return; return skip + len;
} }
} }
sink->Append(buffer, len); sink->Append(buffer, static_cast<int32_t>(len));
return skip + len;
} }
void size_t _getScript(std::string_view localeID, ByteSink* sink) {
_getScript(const char* localeID,
ByteSink* sink,
const char** pEnd) {
U_ASSERT(pEnd != nullptr);
*pEnd = localeID;
constexpr int32_t LENGTH = 4; constexpr int32_t LENGTH = 4;
int32_t len = 0; size_t len = 0;
while (!_isTerminator(localeID[len]) && !_isIDSeparator(localeID[len]) && while (len < localeID.size() && !_isTerminator(localeID[len]) && !_isIDSeparator(localeID[len]) &&
uprv_isASCIILetter(localeID[len])) { uprv_isASCIILetter(localeID[len])) {
if (len == LENGTH) { return; } if (len == LENGTH) { return 0; }
len++; len++;
} }
if (len != LENGTH) { return; } if (len != LENGTH) { return 0; }
*pEnd = localeID + LENGTH; if (sink == nullptr) { return len; }
if (sink == nullptr) { return; }
char scratch[LENGTH]; char scratch[LENGTH];
int32_t capacity = 0; int32_t capacity = 0;
@ -1294,27 +1283,21 @@ _getScript(const char* localeID,
} }
sink->Append(buffer, LENGTH); sink->Append(buffer, LENGTH);
return len;
} }
void size_t _getRegion(std::string_view localeID, ByteSink* sink) {
_getRegion(const char* localeID,
ByteSink* sink,
const char** pEnd) {
U_ASSERT(pEnd != nullptr);
*pEnd = localeID;
constexpr int32_t MINLEN = 2; constexpr int32_t MINLEN = 2;
constexpr int32_t MAXLEN = ULOC_COUNTRY_CAPACITY - 1; // Minus NUL. constexpr int32_t MAXLEN = ULOC_COUNTRY_CAPACITY - 1; // Minus NUL.
int32_t len = 0; size_t len = 0;
while (!_isTerminator(localeID[len]) && !_isIDSeparator(localeID[len])) { while (len < localeID.size() && !_isTerminator(localeID[len]) && !_isIDSeparator(localeID[len])) {
if (len == MAXLEN) { return; } if (len == MAXLEN) { return 0; }
len++; len++;
} }
if (len < MINLEN) { return; } if (len < MINLEN) { return 0; }
*pEnd = localeID + len; if (sink == nullptr) { return len; }
if (sink == nullptr) { return; }
char scratch[ULOC_COUNTRY_CAPACITY]; char scratch[ULOC_COUNTRY_CAPACITY];
int32_t capacity = 0; int32_t capacity = 0;
@ -1325,7 +1308,7 @@ _getRegion(const char* localeID,
UPRV_LENGTHOF(scratch), UPRV_LENGTHOF(scratch),
&capacity); &capacity);
for (int32_t i = 0; i < len; ++i) { for (size_t i = 0; i < len; ++i) {
buffer[i] = uprv_toupper(localeID[i]); buffer[i] = uprv_toupper(localeID[i]);
} }
@ -1337,26 +1320,25 @@ _getRegion(const char* localeID,
if (offset.has_value()) { if (offset.has_value()) {
const char* const alias = COUNTRIES[*offset]; const char* const alias = COUNTRIES[*offset];
sink->Append(alias, static_cast<int32_t>(uprv_strlen(alias))); sink->Append(alias, static_cast<int32_t>(uprv_strlen(alias)));
return; return len;
} }
} }
sink->Append(buffer, len); sink->Append(buffer, static_cast<int32_t>(len));
return len;
} }
/** /**
* @param needSeparator if true, then add leading '_' if any variants * @param needSeparator if true, then add leading '_' if any variants
* are added to 'variant' * are added to 'variant'
*/ */
void size_t
_getVariant(const char* localeID, _getVariant(std::string_view localeID,
char prev, char prev,
ByteSink* sink, ByteSink* sink,
const char** pEnd,
bool needSeparator, bool needSeparator,
UErrorCode& status) { UErrorCode& status) {
if (U_FAILURE(status)) return; if (U_FAILURE(status) || localeID.empty()) return 0;
if (pEnd != nullptr) { *pEnd = localeID; }
// Reasonable upper limit for variants // Reasonable upper limit for variants
// There are no strict limitation of the syntax of variant in the legacy // There are no strict limitation of the syntax of variant in the legacy
@ -1369,42 +1351,62 @@ _getVariant(const char* localeID,
constexpr int32_t MAX_VARIANTS_LENGTH = 179; constexpr int32_t MAX_VARIANTS_LENGTH = 179;
/* get one or more variant tags and separate them with '_' */ /* get one or more variant tags and separate them with '_' */
int32_t index = 0; size_t index = 0;
if (_isIDSeparator(prev)) { if (_isIDSeparator(prev)) {
/* get a variant string after a '-' or '_' */ /* get a variant string after a '-' or '_' */
for (index=0; !_isTerminator(localeID[index]); index++) { for (std::string_view sub = localeID;;) {
if (index >= MAX_VARIANTS_LENGTH) { // same as length > MAX_VARIANTS_LENGTH size_t next = sub.find_first_of(".@_-");
// For historical reasons, a trailing separator is included in the variant.
bool finished = next == std::string_view::npos || next + 1 == sub.length();
size_t limit = finished ? sub.length() : next;
index += limit;
if (index > MAX_VARIANTS_LENGTH) {
status = U_ILLEGAL_ARGUMENT_ERROR; status = U_ILLEGAL_ARGUMENT_ERROR;
return; return 0;
}
if (needSeparator) {
if (sink != nullptr) {
sink->Append("_", 1);
}
needSeparator = false;
}
if (sink != nullptr) {
char c = uprv_toupper(localeID[index]);
if (c == '-') c = '_';
sink->Append(&c, 1);
}
}
if (pEnd != nullptr) { *pEnd = localeID+index; }
} }
/* if there is no variant tag after a '-' or '_' then look for '@' */ if (sink != nullptr) {
if (index == 0) { if (needSeparator) {
if (prev=='@') { sink->Append("_", 1);
/* keep localeID */
} else if((localeID=locale_getKeywordsStart(localeID))!=nullptr) {
++localeID; /* point after the '@' */
} else { } else {
return; needSeparator = true;
} }
for(; !_isTerminator(localeID[index]); index++) {
int32_t length = static_cast<int32_t>(limit);
int32_t minCapacity = uprv_min(length, MAX_VARIANTS_LENGTH);
char scratch[MAX_VARIANTS_LENGTH];
int32_t capacity = 0;
char* buffer = sink->GetAppendBuffer(
minCapacity, minCapacity, scratch, UPRV_LENGTHOF(scratch), &capacity);
for (size_t i = 0; i < limit; ++i) {
buffer[i] = uprv_toupper(sub[i]);
}
sink->Append(buffer, length);
}
if (finished) { return index; }
sub.remove_prefix(next);
if (_isTerminator(sub.front()) || _isBCP47Extension(sub)) { return index; }
sub.remove_prefix(1);
index++;
}
}
size_t skip = 0;
/* if there is no variant tag after a '-' or '_' then look for '@' */
if (prev == '@') {
/* keep localeID */
} else if (const char* p = locale_getKeywordsStart(localeID); p != nullptr) {
skip = 1 + p - localeID.data(); /* point after the '@' */
localeID.remove_prefix(skip);
} else {
return 0;
}
for (; index < localeID.size() && !_isTerminator(localeID[index]); index++) {
if (index >= MAX_VARIANTS_LENGTH) { // same as length > MAX_VARIANTS_LENGTH if (index >= MAX_VARIANTS_LENGTH) { // same as length > MAX_VARIANTS_LENGTH
status = U_ILLEGAL_ARGUMENT_ERROR; status = U_ILLEGAL_ARGUMENT_ERROR;
return; return 0;
} }
if (needSeparator) { if (needSeparator) {
if (sink != nullptr) { if (sink != nullptr) {
@ -1418,14 +1420,13 @@ _getVariant(const char* localeID,
sink->Append(&c, 1); sink->Append(&c, 1);
} }
} }
if (pEnd != nullptr) { *pEnd = localeID + index; } return skip + index;
}
} }
} // namespace } // namespace
U_EXPORT CharString U_EXPORT CharString
ulocimp_getLanguage(const char* localeID, UErrorCode& status) { ulocimp_getLanguage(std::string_view localeID, UErrorCode& status) {
return ByteSinkUtil::viaByteSinkToCharString( return ByteSinkUtil::viaByteSinkToCharString(
[&](ByteSink& sink, UErrorCode& status) { [&](ByteSink& sink, UErrorCode& status) {
ulocimp_getSubtags( ulocimp_getSubtags(
@ -1441,7 +1442,7 @@ ulocimp_getLanguage(const char* localeID, UErrorCode& status) {
} }
U_EXPORT CharString U_EXPORT CharString
ulocimp_getScript(const char* localeID, UErrorCode& status) { ulocimp_getScript(std::string_view localeID, UErrorCode& status) {
return ByteSinkUtil::viaByteSinkToCharString( return ByteSinkUtil::viaByteSinkToCharString(
[&](ByteSink& sink, UErrorCode& status) { [&](ByteSink& sink, UErrorCode& status) {
ulocimp_getSubtags( ulocimp_getSubtags(
@ -1457,7 +1458,7 @@ ulocimp_getScript(const char* localeID, UErrorCode& status) {
} }
U_EXPORT CharString U_EXPORT CharString
ulocimp_getRegion(const char* localeID, UErrorCode& status) { ulocimp_getRegion(std::string_view localeID, UErrorCode& status) {
return ByteSinkUtil::viaByteSinkToCharString( return ByteSinkUtil::viaByteSinkToCharString(
[&](ByteSink& sink, UErrorCode& status) { [&](ByteSink& sink, UErrorCode& status) {
ulocimp_getSubtags( ulocimp_getSubtags(
@ -1473,7 +1474,7 @@ ulocimp_getRegion(const char* localeID, UErrorCode& status) {
} }
U_EXPORT CharString U_EXPORT CharString
ulocimp_getVariant(const char* localeID, UErrorCode& status) { ulocimp_getVariant(std::string_view localeID, UErrorCode& status) {
return ByteSinkUtil::viaByteSinkToCharString( return ByteSinkUtil::viaByteSinkToCharString(
[&](ByteSink& sink, UErrorCode& status) { [&](ByteSink& sink, UErrorCode& status) {
ulocimp_getSubtags( ulocimp_getSubtags(
@ -1490,7 +1491,7 @@ ulocimp_getVariant(const char* localeID, UErrorCode& status) {
U_EXPORT void U_EXPORT void
ulocimp_getSubtags( ulocimp_getSubtags(
const char* localeID, std::string_view localeID,
CharString* language, CharString* language,
CharString* script, CharString* script,
CharString* region, CharString* region,
@ -1521,7 +1522,7 @@ ulocimp_getSubtags(
U_EXPORT void U_EXPORT void
ulocimp_getSubtags( ulocimp_getSubtags(
const char* localeID, std::string_view localeID,
ByteSink* language, ByteSink* language,
ByteSink* script, ByteSink* script,
ByteSink* region, ByteSink* region,
@ -1531,7 +1532,7 @@ ulocimp_getSubtags(
if (U_FAILURE(status)) { return; } if (U_FAILURE(status)) { return; }
if (pEnd != nullptr) { if (pEnd != nullptr) {
*pEnd = localeID; *pEnd = localeID.data();
} else if (language == nullptr && } else if (language == nullptr &&
script == nullptr && script == nullptr &&
region == nullptr && region == nullptr &&
@ -1539,62 +1540,94 @@ ulocimp_getSubtags(
return; return;
} }
if (localeID.empty()) { return; }
bool hasRegion = false; bool hasRegion = false;
if (localeID == nullptr) { {
localeID = uloc_getDefault(); size_t len = _getLanguage(localeID, language, status);
if (U_FAILURE(status)) { return; }
if (len > 0) {
localeID.remove_prefix(len);
}
} }
_getLanguage(localeID, language, &localeID, status);
if (U_FAILURE(status)) { return; }
U_ASSERT(localeID != nullptr);
if (pEnd != nullptr) { if (pEnd != nullptr) {
*pEnd = localeID; *pEnd = localeID.data();
} else if (script == nullptr && } else if (script == nullptr &&
region == nullptr && region == nullptr &&
variant == nullptr) { variant == nullptr) {
return; return;
} }
if (_isIDSeparator(*localeID)) { if (localeID.empty()) { return; }
const char* begin = localeID + 1;
const char* end = nullptr; if (_isIDSeparator(localeID.front())) {
_getScript(begin, script, &end); std::string_view sub = localeID;
U_ASSERT(end != nullptr); sub.remove_prefix(1);
if (end != begin) { size_t len = _getScript(sub, script);
localeID = end; if (len > 0) {
if (pEnd != nullptr) { *pEnd = localeID; } localeID.remove_prefix(len + 1);
if (pEnd != nullptr) { *pEnd = localeID.data(); }
} }
} }
if (region == nullptr && variant == nullptr && pEnd == nullptr) { return; } if ((region == nullptr && variant == nullptr && pEnd == nullptr) || localeID.empty()) { return; }
if (_isIDSeparator(*localeID)) { if (_isIDSeparator(localeID.front())) {
const char* begin = localeID + 1; std::string_view sub = localeID;
const char* end = nullptr; sub.remove_prefix(1);
_getRegion(begin, region, &end); size_t len = _getRegion(sub, region);
U_ASSERT(end != nullptr); if (len > 0) {
if (end != begin) {
hasRegion = true; hasRegion = true;
localeID = end; localeID.remove_prefix(len + 1);
if (pEnd != nullptr) { *pEnd = localeID; } if (pEnd != nullptr) { *pEnd = localeID.data(); }
} }
} }
if (variant == nullptr && pEnd == nullptr) { return; } if ((variant == nullptr && pEnd == nullptr) || localeID.empty()) { return; }
if (_isIDSeparator(*localeID) && !_isBCP47Extension(localeID)) { bool hasVariant = false;
if (_isIDSeparator(localeID.front()) && !_isBCP47Extension(localeID)) {
std::string_view sub = localeID;
/* If there was no country ID, skip a possible extra IDSeparator */ /* If there was no country ID, skip a possible extra IDSeparator */
if (!hasRegion && _isIDSeparator(localeID[1])) { size_t skip = !hasRegion && localeID.size() > 1 && _isIDSeparator(localeID[1]) ? 2 : 1;
localeID++; sub.remove_prefix(skip);
} size_t len = _getVariant(sub, localeID[0], variant, false, status);
const char* begin = localeID + 1;
const char* end = nullptr;
_getVariant(begin, *localeID, variant, &end, false, status);
if (U_FAILURE(status)) { return; } if (U_FAILURE(status)) { return; }
U_ASSERT(end != nullptr); if (len > 0) {
if (end != begin && pEnd != nullptr) { *pEnd = end; } hasVariant = true;
localeID.remove_prefix(skip + len);
if (pEnd != nullptr) { *pEnd = localeID.data(); }
}
}
if ((variant == nullptr && pEnd == nullptr) || localeID.empty()) { return; }
if (_isBCP47Extension(localeID)) {
localeID.remove_prefix(2);
constexpr char vaposix[] = "-va-posix";
constexpr size_t length = sizeof vaposix - 1;
for (size_t next;; localeID.remove_prefix(next)) {
next = localeID.find('-', 1);
if (next == std::string_view::npos) { break; }
next = localeID.find('-', next + 1);
bool finished = next == std::string_view::npos;
std::string_view sub = localeID;
if (!finished) { sub.remove_suffix(sub.length() - next); }
if (sub.length() == length && uprv_strnicmp(sub.data(), vaposix, length) == 0) {
if (variant != nullptr) {
if (hasVariant) { variant->Append("_", 1); }
constexpr char posix[] = "POSIX";
variant->Append(posix, sizeof posix - 1);
}
if (pEnd != nullptr) { *pEnd = localeID.data() + length; }
}
if (finished) { break; }
}
} }
} }
@ -1700,7 +1733,7 @@ uloc_openKeywords(const char* localeID,
CharString tempBuffer; CharString tempBuffer;
const char* tmpLocaleID; const char* tmpLocaleID;
if (_hasBCP47Extension(localeID)) { if (localeID != nullptr && _hasBCP47Extension(localeID)) {
tempBuffer = ulocimp_forLanguageTag(localeID, -1, nullptr, *status); tempBuffer = ulocimp_forLanguageTag(localeID, -1, nullptr, *status);
tmpLocaleID = U_SUCCESS(*status) && !tempBuffer.isEmpty() ? tempBuffer.data() : localeID; tmpLocaleID = U_SUCCESS(*status) && !tempBuffer.isEmpty() ? tempBuffer.data() : localeID;
} else { } else {
@ -1753,7 +1786,7 @@ constexpr int32_t I_DEFAULT_LENGTH = UPRV_LENGTHOF(i_default);
* This is the code underlying uloc_getName and uloc_canonicalize. * This is the code underlying uloc_getName and uloc_canonicalize.
*/ */
void void
_canonicalize(const char* localeID, _canonicalize(std::string_view localeID,
ByteSink& sink, ByteSink& sink,
uint32_t options, uint32_t options,
UErrorCode& err) { UErrorCode& err) {
@ -1764,33 +1797,30 @@ _canonicalize(const char* localeID,
int32_t j, fieldCount=0; int32_t j, fieldCount=0;
CharString tempBuffer; // if localeID has a BCP47 extension, tmpLocaleID points to this CharString tempBuffer; // if localeID has a BCP47 extension, tmpLocaleID points to this
CharString localeIDWithHyphens; // if localeID has a BPC47 extension and have _, tmpLocaleID points to this CharString localeIDWithHyphens; // if localeID has a BPC47 extension and have _, tmpLocaleID points to this
const char* origLocaleID; std::string_view origLocaleID;
const char* tmpLocaleID; std::string_view tmpLocaleID;
const char* keywordAssign = nullptr; size_t keywordAssign = std::string_view::npos;
const char* separatorIndicator = nullptr; size_t separatorIndicator = std::string_view::npos;
if (_hasBCP47Extension(localeID)) { if (_hasBCP47Extension(localeID)) {
const char* localeIDPtr = localeID; std::string_view localeIDPtr = localeID;
// convert all underbars to hyphens, unless the "BCP47 extension" comes at the beginning of the string // convert all underbars to hyphens, unless the "BCP47 extension" comes at the beginning of the string
if (uprv_strchr(localeID, '_') != nullptr && localeID[1] != '-' && localeID[1] != '_') { if (localeID.size() >= 2 && localeID.find('_') != std::string_view::npos && localeID[1] != '-' && localeID[1] != '_') {
localeIDWithHyphens.append(localeID, -1, err); localeIDWithHyphens.append(localeID, err);
if (U_SUCCESS(err)) { if (U_SUCCESS(err)) {
for (char* p = localeIDWithHyphens.data(); *p != '\0'; ++p) { for (char* p = localeIDWithHyphens.data(); *p != '\0'; ++p) {
if (*p == '_') { if (*p == '_') {
*p = '-'; *p = '-';
} }
} }
localeIDPtr = localeIDWithHyphens.data(); localeIDPtr = localeIDWithHyphens.toStringPiece();
} }
} }
tempBuffer = ulocimp_forLanguageTag(localeIDPtr, -1, nullptr, err); tempBuffer = ulocimp_forLanguageTag(localeIDPtr.data(), static_cast<int32_t>(localeIDPtr.size()), nullptr, err);
tmpLocaleID = U_SUCCESS(err) && !tempBuffer.isEmpty() ? tempBuffer.data() : localeIDPtr; tmpLocaleID = U_SUCCESS(err) && !tempBuffer.isEmpty() ? static_cast<std::string_view>(tempBuffer.toStringPiece()) : localeIDPtr;
} else { } else {
if (localeID==nullptr) {
localeID=uloc_getDefault();
}
tmpLocaleID=localeID; tmpLocaleID=localeID;
} }
@ -1801,20 +1831,25 @@ _canonicalize(const char* localeID,
CharString script; CharString script;
CharString country; CharString country;
CharString variant; CharString variant;
const char* end = nullptr;
ulocimp_getSubtags( ulocimp_getSubtags(
tmpLocaleID, tmpLocaleID,
&tag, &tag,
&script, &script,
&country, &country,
&variant, &variant,
&tmpLocaleID, &end,
err); err);
if (U_FAILURE(err)) { if (U_FAILURE(err)) {
return; return;
} }
U_ASSERT(end != nullptr);
if (end > tmpLocaleID.data()) {
tmpLocaleID.remove_prefix(end - tmpLocaleID.data());
}
if (tag.length() == I_DEFAULT_LENGTH && if (tag.length() == I_DEFAULT_LENGTH && origLocaleID.length() >= I_DEFAULT_LENGTH &&
uprv_strncmp(origLocaleID, i_default, I_DEFAULT_LENGTH) == 0) { uprv_strncmp(origLocaleID.data(), i_default, I_DEFAULT_LENGTH) == 0) {
tag.clear(); tag.clear();
tag.append(uloc_getDefault(), err); tag.append(uloc_getDefault(), err);
} else { } else {
@ -1839,15 +1874,14 @@ _canonicalize(const char* localeID,
} }
/* Copy POSIX-style charset specifier, if any [mr.utf8] */ /* Copy POSIX-style charset specifier, if any [mr.utf8] */
if (!OPTION_SET(options, _ULOC_CANONICALIZE) && *tmpLocaleID == '.') { if (!OPTION_SET(options, _ULOC_CANONICALIZE) && !tmpLocaleID.empty() && tmpLocaleID.front() == '.') {
tag.append('.', err); tag.append('.', err);
++tmpLocaleID; tmpLocaleID.remove_prefix(1);
const char *atPos = nullptr;
size_t length; size_t length;
if((atPos = uprv_strchr(tmpLocaleID, '@')) != nullptr) { if (size_t atPos = tmpLocaleID.find('@'); atPos != std::string_view::npos) {
length = atPos - tmpLocaleID; length = atPos;
} else { } else {
length = uprv_strlen(tmpLocaleID); length = tmpLocaleID.length();
} }
// The longest charset name we found in IANA charset registry // The longest charset name we found in IANA charset registry
// https://www.iana.org/assignments/character-sets/ is // https://www.iana.org/assignments/character-sets/ is
@ -1859,33 +1893,34 @@ _canonicalize(const char* localeID,
err = U_ILLEGAL_ARGUMENT_ERROR; /* malformed keyword name */ err = U_ILLEGAL_ARGUMENT_ERROR; /* malformed keyword name */
return; return;
} }
tag.append(tmpLocaleID, static_cast<int32_t>(length), err); if (length > 0) {
tmpLocaleID += length; tag.append(tmpLocaleID.data(), static_cast<int32_t>(length), err);
tmpLocaleID.remove_prefix(length);
}
} }
/* Scan ahead to next '@' and determine if it is followed by '=' and/or ';' /* Scan ahead to next '@' and determine if it is followed by '=' and/or ';'
After this, tmpLocaleID either points to '@' or is nullptr */ After this, tmpLocaleID either starts at '@' or is empty. */
if ((tmpLocaleID=locale_getKeywordsStart(tmpLocaleID))!=nullptr) { if (const char* start = locale_getKeywordsStart(tmpLocaleID); start != nullptr) {
keywordAssign = uprv_strchr(tmpLocaleID, '='); if (start > tmpLocaleID.data()) {
separatorIndicator = uprv_strchr(tmpLocaleID, ';'); tmpLocaleID.remove_prefix(start - tmpLocaleID.data());
}
keywordAssign = tmpLocaleID.find('=');
separatorIndicator = tmpLocaleID.find(';');
} else {
tmpLocaleID = {};
} }
/* Copy POSIX-style variant, if any [mr@FOO] */ /* Copy POSIX-style variant, if any [mr@FOO] */
if (!OPTION_SET(options, _ULOC_CANONICALIZE) && if (!OPTION_SET(options, _ULOC_CANONICALIZE) &&
tmpLocaleID != nullptr && keywordAssign == nullptr) { !tmpLocaleID.empty() && keywordAssign == std::string_view::npos) {
for (;;) { tag.append(tmpLocaleID, err);
char c = *tmpLocaleID; tmpLocaleID = {};
if (c == 0) {
break;
}
tag.append(c, err);
++tmpLocaleID;
}
} }
if (OPTION_SET(options, _ULOC_CANONICALIZE)) { if (OPTION_SET(options, _ULOC_CANONICALIZE)) {
/* Handle @FOO variant if @ is present and not followed by = */ /* Handle @FOO variant if @ is present and not followed by = */
if (tmpLocaleID!=nullptr && keywordAssign==nullptr) { if (!tmpLocaleID.empty() && keywordAssign == std::string_view::npos) {
/* Add missing '_' if needed */ /* Add missing '_' if needed */
if (fieldCount < 2 || (fieldCount < 3 && !script.isEmpty())) { if (fieldCount < 2 || (fieldCount < 3 && !script.isEmpty())) {
do { do {
@ -1895,7 +1930,9 @@ _canonicalize(const char* localeID,
} }
CharStringByteSink s(&tag); CharStringByteSink s(&tag);
_getVariant(tmpLocaleID+1, '@', &s, nullptr, !variant.isEmpty(), err); std::string_view sub = tmpLocaleID;
sub.remove_prefix(1);
_getVariant(sub, '@', &s, !variant.isEmpty(), err);
if (U_FAILURE(err)) { return; } if (U_FAILURE(err)) { return; }
} }
@ -1903,7 +1940,7 @@ _canonicalize(const char* localeID,
for (j=0; j<UPRV_LENGTHOF(CANONICALIZE_MAP); j++) { for (j=0; j<UPRV_LENGTHOF(CANONICALIZE_MAP); j++) {
StringPiece id(CANONICALIZE_MAP[j].id); StringPiece id(CANONICALIZE_MAP[j].id);
if (tag == id) { if (tag == id) {
if (id.empty() && tmpLocaleID != nullptr) { if (id.empty() && !tmpLocaleID.empty()) {
break; /* Don't remap "" if keywords present */ break; /* Don't remap "" if keywords present */
} }
tag.clear(); tag.clear();
@ -1916,11 +1953,12 @@ _canonicalize(const char* localeID,
sink.Append(tag.data(), tag.length()); sink.Append(tag.data(), tag.length());
if (!OPTION_SET(options, _ULOC_STRIP_KEYWORDS)) { if (!OPTION_SET(options, _ULOC_STRIP_KEYWORDS)) {
if (tmpLocaleID!=nullptr && keywordAssign!=nullptr && if (!tmpLocaleID.empty() && keywordAssign != std::string_view::npos &&
(!separatorIndicator || separatorIndicator > keywordAssign)) { (separatorIndicator == std::string_view::npos || separatorIndicator > keywordAssign)) {
sink.Append("@", 1); sink.Append("@", 1);
++fieldCount; ++fieldCount;
ulocimp_getKeywords(tmpLocaleID+1, '@', sink, true, err); tmpLocaleID.remove_prefix(1);
ulocimp_getKeywords(tmpLocaleID, '@', sink, true, err);
} }
} }
} }
@ -1989,6 +2027,10 @@ uloc_getLanguage(const char* localeID,
int32_t languageCapacity, int32_t languageCapacity,
UErrorCode* err) UErrorCode* err)
{ {
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
/* uloc_getLanguage will return a 2 character iso-639 code if one exists. *CWB*/ /* uloc_getLanguage will return a 2 character iso-639 code if one exists. *CWB*/
return ByteSinkUtil::viaByteSinkToTerminatedChars( return ByteSinkUtil::viaByteSinkToTerminatedChars(
language, languageCapacity, language, languageCapacity,
@ -2011,6 +2053,10 @@ uloc_getScript(const char* localeID,
int32_t scriptCapacity, int32_t scriptCapacity,
UErrorCode* err) UErrorCode* err)
{ {
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
return ByteSinkUtil::viaByteSinkToTerminatedChars( return ByteSinkUtil::viaByteSinkToTerminatedChars(
script, scriptCapacity, script, scriptCapacity,
[&](ByteSink& sink, UErrorCode& status) { [&](ByteSink& sink, UErrorCode& status) {
@ -2032,6 +2078,10 @@ uloc_getCountry(const char* localeID,
int32_t countryCapacity, int32_t countryCapacity,
UErrorCode* err) UErrorCode* err)
{ {
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
return ByteSinkUtil::viaByteSinkToTerminatedChars( return ByteSinkUtil::viaByteSinkToTerminatedChars(
country, countryCapacity, country, countryCapacity,
[&](ByteSink& sink, UErrorCode& status) { [&](ByteSink& sink, UErrorCode& status) {
@ -2053,6 +2103,10 @@ uloc_getVariant(const char* localeID,
int32_t variantCapacity, int32_t variantCapacity,
UErrorCode* err) UErrorCode* err)
{ {
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
return ByteSinkUtil::viaByteSinkToTerminatedChars( return ByteSinkUtil::viaByteSinkToTerminatedChars(
variant, variantCapacity, variant, variantCapacity,
[&](ByteSink& sink, UErrorCode& status) { [&](ByteSink& sink, UErrorCode& status) {
@ -2074,6 +2128,9 @@ uloc_getName(const char* localeID,
int32_t nameCapacity, int32_t nameCapacity,
UErrorCode* err) UErrorCode* err)
{ {
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
return ByteSinkUtil::viaByteSinkToTerminatedChars( return ByteSinkUtil::viaByteSinkToTerminatedChars(
name, nameCapacity, name, nameCapacity,
[&](ByteSink& sink, UErrorCode& status) { [&](ByteSink& sink, UErrorCode& status) {
@ -2083,7 +2140,7 @@ uloc_getName(const char* localeID,
} }
U_EXPORT CharString U_EXPORT CharString
ulocimp_getName(const char* localeID, ulocimp_getName(std::string_view localeID,
UErrorCode& err) UErrorCode& err)
{ {
return ByteSinkUtil::viaByteSinkToCharString( return ByteSinkUtil::viaByteSinkToCharString(
@ -2094,7 +2151,7 @@ ulocimp_getName(const char* localeID,
} }
U_EXPORT void U_EXPORT void
ulocimp_getName(const char* localeID, ulocimp_getName(std::string_view localeID,
ByteSink& sink, ByteSink& sink,
UErrorCode& err) UErrorCode& err)
{ {
@ -2107,6 +2164,9 @@ uloc_getBaseName(const char* localeID,
int32_t nameCapacity, int32_t nameCapacity,
UErrorCode* err) UErrorCode* err)
{ {
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
return ByteSinkUtil::viaByteSinkToTerminatedChars( return ByteSinkUtil::viaByteSinkToTerminatedChars(
name, nameCapacity, name, nameCapacity,
[&](ByteSink& sink, UErrorCode& status) { [&](ByteSink& sink, UErrorCode& status) {
@ -2116,7 +2176,7 @@ uloc_getBaseName(const char* localeID,
} }
U_EXPORT CharString U_EXPORT CharString
ulocimp_getBaseName(const char* localeID, ulocimp_getBaseName(std::string_view localeID,
UErrorCode& err) UErrorCode& err)
{ {
return ByteSinkUtil::viaByteSinkToCharString( return ByteSinkUtil::viaByteSinkToCharString(
@ -2127,7 +2187,7 @@ ulocimp_getBaseName(const char* localeID,
} }
U_EXPORT void U_EXPORT void
ulocimp_getBaseName(const char* localeID, ulocimp_getBaseName(std::string_view localeID,
ByteSink& sink, ByteSink& sink,
UErrorCode& err) UErrorCode& err)
{ {
@ -2140,6 +2200,9 @@ uloc_canonicalize(const char* localeID,
int32_t nameCapacity, int32_t nameCapacity,
UErrorCode* err) UErrorCode* err)
{ {
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
return ByteSinkUtil::viaByteSinkToTerminatedChars( return ByteSinkUtil::viaByteSinkToTerminatedChars(
name, nameCapacity, name, nameCapacity,
[&](ByteSink& sink, UErrorCode& status) { [&](ByteSink& sink, UErrorCode& status) {
@ -2149,7 +2212,7 @@ uloc_canonicalize(const char* localeID,
} }
U_EXPORT CharString U_EXPORT CharString
ulocimp_canonicalize(const char* localeID, ulocimp_canonicalize(std::string_view localeID,
UErrorCode& err) UErrorCode& err)
{ {
return ByteSinkUtil::viaByteSinkToCharString( return ByteSinkUtil::viaByteSinkToCharString(
@ -2160,7 +2223,7 @@ ulocimp_canonicalize(const char* localeID,
} }
U_EXPORT void U_EXPORT void
ulocimp_canonicalize(const char* localeID, ulocimp_canonicalize(std::string_view localeID,
ByteSink& sink, ByteSink& sink,
UErrorCode& err) UErrorCode& err)
{ {

View File

@ -1043,7 +1043,7 @@ _initializeULanguageTag(ULanguageTag* langtag) {
} }
void void
_appendLanguageToLanguageTag(const char* localeID, icu::ByteSink& sink, bool strict, UErrorCode& status) { _appendLanguageToLanguageTag(std::string_view localeID, icu::ByteSink& sink, bool strict, UErrorCode& status) {
UErrorCode tmpStatus = U_ZERO_ERROR; UErrorCode tmpStatus = U_ZERO_ERROR;
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
@ -1088,7 +1088,7 @@ _appendLanguageToLanguageTag(const char* localeID, icu::ByteSink& sink, bool str
} }
void void
_appendScriptToLanguageTag(const char* localeID, icu::ByteSink& sink, bool strict, UErrorCode& status) { _appendScriptToLanguageTag(std::string_view localeID, icu::ByteSink& sink, bool strict, UErrorCode& status) {
UErrorCode tmpStatus = U_ZERO_ERROR; UErrorCode tmpStatus = U_ZERO_ERROR;
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
@ -1118,7 +1118,7 @@ _appendScriptToLanguageTag(const char* localeID, icu::ByteSink& sink, bool stric
} }
void void
_appendRegionToLanguageTag(const char* localeID, icu::ByteSink& sink, bool strict, UErrorCode& status) { _appendRegionToLanguageTag(std::string_view localeID, icu::ByteSink& sink, bool strict, UErrorCode& status) {
UErrorCode tmpStatus = U_ZERO_ERROR; UErrorCode tmpStatus = U_ZERO_ERROR;
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
@ -1169,7 +1169,7 @@ void _sortVariants(VariantListEntry* first) {
} }
void void
_appendVariantsToLanguageTag(const char* localeID, icu::ByteSink& sink, bool strict, bool& hadPosix, UErrorCode& status) { _appendVariantsToLanguageTag(std::string_view localeID, icu::ByteSink& sink, bool strict, bool& hadPosix, UErrorCode& status) {
if (U_FAILURE(status)) { return; } if (U_FAILURE(status)) { return; }
UErrorCode tmpStatus = U_ZERO_ERROR; UErrorCode tmpStatus = U_ZERO_ERROR;
@ -1872,7 +1872,7 @@ _appendKeywords(ULanguageTag* langtag, icu::ByteSink& sink, UErrorCode& status)
} }
void void
_appendPrivateuseToLanguageTag(const char* localeID, icu::ByteSink& sink, bool strict, bool /*hadPosix*/, UErrorCode& status) { _appendPrivateuseToLanguageTag(std::string_view localeID, icu::ByteSink& sink, bool strict, bool /*hadPosix*/, UErrorCode& status) {
if (U_FAILURE(status)) { return; } if (U_FAILURE(status)) { return; }
UErrorCode tmpStatus = U_ZERO_ERROR; UErrorCode tmpStatus = U_ZERO_ERROR;
@ -2596,6 +2596,9 @@ ulocimp_toLanguageTag(const char* localeID,
bool hadPosix = false; bool hadPosix = false;
const char* pKeywordStart; const char* pKeywordStart;
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
/* Note: uloc_canonicalize returns "en_US_POSIX" for input locale ID "". See #6835 */ /* Note: uloc_canonicalize returns "en_US_POSIX" for input locale ID "". See #6835 */
icu::CharString canonical = ulocimp_canonicalize(localeID, tmpStatus); icu::CharString canonical = ulocimp_canonicalize(localeID, tmpStatus);
if (U_FAILURE(tmpStatus)) { if (U_FAILURE(tmpStatus)) {
@ -2604,7 +2607,7 @@ ulocimp_toLanguageTag(const char* localeID,
} }
/* For handling special case - private use only tag */ /* For handling special case - private use only tag */
pKeywordStart = locale_getKeywordsStart(canonical.data()); pKeywordStart = locale_getKeywordsStart(canonical.toStringPiece());
if (pKeywordStart == canonical.data()) { if (pKeywordStart == canonical.data()) {
int kwdCnt = 0; int kwdCnt = 0;
bool done = false; bool done = false;
@ -2642,12 +2645,12 @@ ulocimp_toLanguageTag(const char* localeID,
} }
} }
_appendLanguageToLanguageTag(canonical.data(), sink, strict, status); _appendLanguageToLanguageTag(canonical.toStringPiece(), sink, strict, status);
_appendScriptToLanguageTag(canonical.data(), sink, strict, status); _appendScriptToLanguageTag(canonical.toStringPiece(), sink, strict, status);
_appendRegionToLanguageTag(canonical.data(), sink, strict, status); _appendRegionToLanguageTag(canonical.toStringPiece(), sink, strict, status);
_appendVariantsToLanguageTag(canonical.data(), sink, strict, hadPosix, status); _appendVariantsToLanguageTag(canonical.toStringPiece(), sink, strict, hadPosix, status);
_appendKeywordsToLanguageTag(canonical.data(), sink, strict, hadPosix, status); _appendKeywordsToLanguageTag(canonical.data(), sink, strict, hadPosix, status);
_appendPrivateuseToLanguageTag(canonical.data(), sink, strict, hadPosix, status); _appendPrivateuseToLanguageTag(canonical.toStringPiece(), sink, strict, hadPosix, status);
} }

View File

@ -10,7 +10,6 @@
#include "unicode/locid.h" #include "unicode/locid.h"
#include "bytesinkutil.h" #include "bytesinkutil.h"
#include "charstr.h"
#include "cmemory.h" #include "cmemory.h"
U_NAMESPACE_USE U_NAMESPACE_USE
@ -24,9 +23,7 @@ ulocale_openForLocaleID(const char* localeID, int32_t length, UErrorCode* err) {
if (length < 0) { if (length < 0) {
return EXTERNAL(icu::Locale::createFromName(localeID).clone()); return EXTERNAL(icu::Locale::createFromName(localeID).clone());
} }
CharString str(localeID, length, *err); // Make a NUL terminated copy. return EXTERNAL(icu::Locale::createFromName(StringPiece{localeID, length}).clone());
if (U_FAILURE(*err)) { return nullptr; }
return EXTERNAL(icu::Locale::createFromName(str.data()).clone());
} }
ULocale* ULocale*

View File

@ -68,42 +68,42 @@ U_EXPORT std::optional<std::string_view>
ulocimp_toLegacyTypeWithFallback(std::string_view keyword, std::string_view value); ulocimp_toLegacyTypeWithFallback(std::string_view keyword, std::string_view value);
U_EXPORT icu::CharString U_EXPORT icu::CharString
ulocimp_getKeywords(const char* localeID, ulocimp_getKeywords(std::string_view localeID,
char prev, char prev,
bool valuesToo, bool valuesToo,
UErrorCode& status); UErrorCode& status);
U_EXPORT void U_EXPORT void
ulocimp_getKeywords(const char* localeID, ulocimp_getKeywords(std::string_view localeID,
char prev, char prev,
icu::ByteSink& sink, icu::ByteSink& sink,
bool valuesToo, bool valuesToo,
UErrorCode& status); UErrorCode& status);
U_EXPORT icu::CharString U_EXPORT icu::CharString
ulocimp_getName(const char* localeID, ulocimp_getName(std::string_view localeID,
UErrorCode& err); UErrorCode& err);
U_EXPORT void U_EXPORT void
ulocimp_getName(const char* localeID, ulocimp_getName(std::string_view localeID,
icu::ByteSink& sink, icu::ByteSink& sink,
UErrorCode& err); UErrorCode& err);
U_EXPORT icu::CharString U_EXPORT icu::CharString
ulocimp_getBaseName(const char* localeID, ulocimp_getBaseName(std::string_view localeID,
UErrorCode& err); UErrorCode& err);
U_EXPORT void U_EXPORT void
ulocimp_getBaseName(const char* localeID, ulocimp_getBaseName(std::string_view localeID,
icu::ByteSink& sink, icu::ByteSink& sink,
UErrorCode& err); UErrorCode& err);
U_EXPORT icu::CharString U_EXPORT icu::CharString
ulocimp_canonicalize(const char* localeID, ulocimp_canonicalize(std::string_view localeID,
UErrorCode& err); UErrorCode& err);
U_EXPORT void U_EXPORT void
ulocimp_canonicalize(const char* localeID, ulocimp_canonicalize(std::string_view localeID,
icu::ByteSink& sink, icu::ByteSink& sink,
UErrorCode& err); UErrorCode& err);
@ -119,16 +119,16 @@ ulocimp_getKeywordValue(const char* localeID,
UErrorCode& status); UErrorCode& status);
U_EXPORT icu::CharString U_EXPORT icu::CharString
ulocimp_getLanguage(const char* localeID, UErrorCode& status); ulocimp_getLanguage(std::string_view localeID, UErrorCode& status);
U_EXPORT icu::CharString U_EXPORT icu::CharString
ulocimp_getScript(const char* localeID, UErrorCode& status); ulocimp_getScript(std::string_view localeID, UErrorCode& status);
U_EXPORT icu::CharString U_EXPORT icu::CharString
ulocimp_getRegion(const char* localeID, UErrorCode& status); ulocimp_getRegion(std::string_view localeID, UErrorCode& status);
U_EXPORT icu::CharString U_EXPORT icu::CharString
ulocimp_getVariant(const char* localeID, UErrorCode& status); ulocimp_getVariant(std::string_view localeID, UErrorCode& status);
U_EXPORT void U_EXPORT void
ulocimp_setKeywordValue(std::string_view keywordName, ulocimp_setKeywordValue(std::string_view keywordName,
@ -145,7 +145,7 @@ ulocimp_setKeywordValue(std::string_view keywords,
U_EXPORT void U_EXPORT void
ulocimp_getSubtags( ulocimp_getSubtags(
const char* localeID, std::string_view localeID,
icu::CharString* language, icu::CharString* language,
icu::CharString* script, icu::CharString* script,
icu::CharString* region, icu::CharString* region,
@ -155,7 +155,7 @@ ulocimp_getSubtags(
U_EXPORT void U_EXPORT void
ulocimp_getSubtags( ulocimp_getSubtags(
const char* localeID, std::string_view localeID,
icu::ByteSink* language, icu::ByteSink* language,
icu::ByteSink* script, icu::ByteSink* script,
icu::ByteSink* region, icu::ByteSink* region,
@ -165,7 +165,7 @@ ulocimp_getSubtags(
inline void inline void
ulocimp_getSubtags( ulocimp_getSubtags(
const char* localeID, std::string_view localeID,
std::nullptr_t, std::nullptr_t,
std::nullptr_t, std::nullptr_t,
std::nullptr_t, std::nullptr_t,
@ -364,7 +364,7 @@ ulocimp_minimizeSubtags(const char* localeID,
UErrorCode& err); UErrorCode& err);
U_CAPI const char * U_EXPORT2 U_CAPI const char * U_EXPORT2
locale_getKeywordsStart(const char *localeID); locale_getKeywordsStart(std::string_view localeID);
bool bool
ultag_isExtensionSubtags(const char* s, int32_t len); ultag_isExtensionSubtags(const char* s, int32_t len);

View File

@ -237,8 +237,13 @@ typedef HANDLE MemoryMap;
pData->map = (char *)data + length; pData->map = (char *)data + length;
pData->pHeader=(const DataHeader *)data; pData->pHeader=(const DataHeader *)data;
pData->mapAddr = data; pData->mapAddr = data;
#if U_PLATFORM == U_PF_IPHONE #if U_PLATFORM == U_PF_IPHONE || U_PLATFORM == U_PF_ANDROID
// Apparently supported from Android 23 and higher:
// https://github.com/ggml-org/llama.cpp/pull/3631
// Checking for the flag itself is safer than checking for __ANDROID_API__.
# ifdef POSIX_MADV_RANDOM
posix_madvise(data, length, POSIX_MADV_RANDOM); posix_madvise(data, length, POSIX_MADV_RANDOM);
# endif
#endif #endif
return true; return true;
} }

View File

@ -58,6 +58,8 @@ U_NAMESPACE_END
U_NAMESPACE_BEGIN U_NAMESPACE_BEGIN
class CharString;
/** /**
* The BreakIterator class implements methods for finding the location * The BreakIterator class implements methods for finding the location
* of boundaries in text. BreakIterator is an abstract base class. * of boundaries in text. BreakIterator is an abstract base class.
@ -646,9 +648,9 @@ protected:
private: private:
/** @internal (private) */ /** @internal (private) */
char actualLocale[ULOC_FULLNAME_CAPACITY]; CharString* actualLocale = nullptr;
char validLocale[ULOC_FULLNAME_CAPACITY]; CharString* validLocale = nullptr;
char requestLocale[ULOC_FULLNAME_CAPACITY]; CharString* requestLocale = nullptr;
}; };
#ifndef U_HIDE_DEPRECATED_API #ifndef U_HIDE_DEPRECATED_API

View File

@ -9,10 +9,13 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if U_SHOW_CPLUSPLUS_API #if U_SHOW_CPLUSPLUS_API || U_SHOW_CPLUSPLUS_HEADER_API
#include <cstddef> #include <cstddef>
#include <string_view> #include <string_view>
#include <type_traits>
#endif
/** /**
* \file * \file
@ -21,8 +24,6 @@
* Also conversion functions from char16_t * to UChar * and OldUChar *. * Also conversion functions from char16_t * to UChar * and OldUChar *.
*/ */
U_NAMESPACE_BEGIN
/** /**
* \def U_ALIASING_BARRIER * \def U_ALIASING_BARRIER
* Barrier for pointer anti-aliasing optimizations even across function boundaries. * Barrier for pointer anti-aliasing optimizations even across function boundaries.
@ -36,6 +37,11 @@ U_NAMESPACE_BEGIN
# define U_ALIASING_BARRIER(ptr) # define U_ALIASING_BARRIER(ptr)
#endif #endif
// ICU DLL-exported
#if U_SHOW_CPLUSPLUS_API
U_NAMESPACE_BEGIN
/** /**
* char16_t * wrapper with implicit conversion from distinct but bit-compatible pointer types. * char16_t * wrapper with implicit conversion from distinct but bit-compatible pointer types.
* @stable ICU 59 * @stable ICU 59
@ -251,6 +257,60 @@ const char16_t *ConstChar16Ptr::get() const { return u_.cp; }
#endif #endif
/// \endcond /// \endcond
U_NAMESPACE_END
#endif // U_SHOW_CPLUSPLUS_API
// Usable in header-only definitions
#if U_SHOW_CPLUSPLUS_API || U_SHOW_CPLUSPLUS_HEADER_API
namespace U_ICU_NAMESPACE_OR_INTERNAL {
#ifndef U_FORCE_HIDE_INTERNAL_API
/** @internal */
template<typename T, typename = std::enable_if_t<std::is_same_v<T, UChar>>>
inline const char16_t *uprv_char16PtrFromUChar(const T *p) {
if constexpr (std::is_same_v<UChar, char16_t>) {
return p;
} else {
#if U_SHOW_CPLUSPLUS_API
return ConstChar16Ptr(p).get();
#else
#ifdef U_ALIASING_BARRIER
U_ALIASING_BARRIER(p);
#endif
return reinterpret_cast<const char16_t *>(p);
#endif
}
}
#if !U_CHAR16_IS_TYPEDEF && (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION < 180000)
/** @internal */
inline const char16_t *uprv_char16PtrFromUint16(const uint16_t *p) {
#if U_SHOW_CPLUSPLUS_API
return ConstChar16Ptr(p).get();
#else
#ifdef U_ALIASING_BARRIER
U_ALIASING_BARRIER(p);
#endif
return reinterpret_cast<const char16_t *>(p);
#endif
}
#endif
#if U_SIZEOF_WCHAR_T==2
/** @internal */
inline const char16_t *uprv_char16PtrFromWchar(const wchar_t *p) {
#if U_SHOW_CPLUSPLUS_API
return ConstChar16Ptr(p).get();
#else
#ifdef U_ALIASING_BARRIER
U_ALIASING_BARRIER(p);
#endif
return reinterpret_cast<const char16_t *>(p);
#endif
}
#endif
#endif
/** /**
* Converts from const char16_t * to const UChar *. * Converts from const char16_t * to const UChar *.
* Includes an aliasing barrier if available. * Includes an aliasing barrier if available.
@ -307,6 +367,15 @@ inline OldUChar *toOldUCharPtr(char16_t *p) {
return reinterpret_cast<OldUChar *>(p); return reinterpret_cast<OldUChar *>(p);
} }
} // U_ICU_NAMESPACE_OR_INTERNAL
#endif // U_SHOW_CPLUSPLUS_API || U_SHOW_CPLUSPLUS_HEADER_API
// ICU DLL-exported
#if U_SHOW_CPLUSPLUS_API
U_NAMESPACE_BEGIN
#ifndef U_FORCE_HIDE_INTERNAL_API #ifndef U_FORCE_HIDE_INTERNAL_API
/** /**
* Is T convertible to a std::u16string_view or some other 16-bit string view? * Is T convertible to a std::u16string_view or some other 16-bit string view?
@ -379,6 +448,6 @@ inline std::u16string_view toU16StringViewNullable(const T& text) {
U_NAMESPACE_END U_NAMESPACE_END
#endif /* U_SHOW_CPLUSPLUS_API */ #endif // U_SHOW_CPLUSPLUS_API
#endif // __CHAR16PTR_H__ #endif // __CHAR16PTR_H__

View File

@ -449,6 +449,11 @@ public:
*/ */
static Locale U_EXPORT2 createFromName(const char *name); static Locale U_EXPORT2 createFromName(const char *name);
#ifndef U_HIDE_INTERNAL_API
/** @internal */
static Locale U_EXPORT2 createFromName(StringPiece name);
#endif /* U_HIDE_INTERNAL_API */
/** /**
* Creates a locale from the given string after canonicalizing * Creates a locale from the given string after canonicalizing
* the string according to CLDR by calling uloc_canonicalize(). * the string according to CLDR by calling uloc_canonicalize().
@ -1133,7 +1138,9 @@ private:
* @param cLocaleID The new locale name. * @param cLocaleID The new locale name.
* @param canonicalize whether to call uloc_canonicalize on cLocaleID * @param canonicalize whether to call uloc_canonicalize on cLocaleID
*/ */
Locale& init(const char* cLocaleID, UBool canonicalize); Locale& init(const char* localeID, UBool canonicalize);
/** @internal */
Locale& init(StringPiece localeID, UBool canonicalize);
/* /*
* Internal constructor to allow construction of a locale object with * Internal constructor to allow construction of a locale object with

View File

@ -450,7 +450,7 @@ public:
* @return a Locale object * @return a Locale object
* @stable ICU 2.8 * @stable ICU 2.8
*/ */
const Locale Locale
getLocale(ULocDataLocaleType type, UErrorCode &status) const; getLocale(ULocDataLocaleType type, UErrorCode &status) const;
#ifndef U_HIDE_INTERNAL_API #ifndef U_HIDE_INTERNAL_API
/** /**

View File

@ -675,14 +675,14 @@ typedef enum UProperty {
* @stable ICU 63 * @stable ICU 63
*/ */
UCHAR_VERTICAL_ORIENTATION=0x1018, UCHAR_VERTICAL_ORIENTATION=0x1018,
#ifndef U_HIDE_DRAFT_API
/** /**
* Enumerated property Identifier_Status. * Enumerated property Identifier_Status.
* Used for UTS #39 General Security Profile for Identifiers * Used for UTS #39 General Security Profile for Identifiers
* (https://www.unicode.org/reports/tr39/#General_Security_Profile). * (https://www.unicode.org/reports/tr39/#General_Security_Profile).
* @draft ICU 75 * @stable ICU 75
*/ */
UCHAR_IDENTIFIER_STATUS=0x1019, UCHAR_IDENTIFIER_STATUS=0x1019,
#ifndef U_HIDE_DRAFT_API
/** /**
* Enumerated property Indic_Conjunct_Break. * Enumerated property Indic_Conjunct_Break.
* Used in the grapheme cluster break algorithm in UAX #29. * Used in the grapheme cluster break algorithm in UAX #29.
@ -796,7 +796,6 @@ typedef enum UProperty {
UCHAR_SCRIPT_EXTENSIONS=0x7000, UCHAR_SCRIPT_EXTENSIONS=0x7000,
/** First constant for Unicode properties with unusual value types. @stable ICU 4.6 */ /** First constant for Unicode properties with unusual value types. @stable ICU 4.6 */
UCHAR_OTHER_PROPERTY_START=UCHAR_SCRIPT_EXTENSIONS, UCHAR_OTHER_PROPERTY_START=UCHAR_SCRIPT_EXTENSIONS,
#ifndef U_HIDE_DRAFT_API
/** /**
* Miscellaneous property Identifier_Type. * Miscellaneous property Identifier_Type.
* Used for UTS #39 General Security Profile for Identifiers * Used for UTS #39 General Security Profile for Identifiers
@ -808,10 +807,9 @@ typedef enum UProperty {
* *
* @see u_hasIDType * @see u_hasIDType
* @see u_getIDTypes * @see u_getIDTypes
* @draft ICU 75 * @stable ICU 75
*/ */
UCHAR_IDENTIFIER_TYPE=0x7001, UCHAR_IDENTIFIER_TYPE=0x7001,
#endif // U_HIDE_DRAFT_API
#ifndef U_HIDE_DEPRECATED_API #ifndef U_HIDE_DEPRECATED_API
/** /**
* One more than the last constant for Unicode properties with unusual value types. * One more than the last constant for Unicode properties with unusual value types.
@ -2791,13 +2789,12 @@ typedef enum UVerticalOrientation {
U_VO_UPRIGHT, U_VO_UPRIGHT,
} UVerticalOrientation; } UVerticalOrientation;
#ifndef U_HIDE_DRAFT_API
/** /**
* Identifier Status constants. * Identifier Status constants.
* See https://www.unicode.org/reports/tr39/#Identifier_Status_and_Type. * See https://www.unicode.org/reports/tr39/#Identifier_Status_and_Type.
* *
* @see UCHAR_IDENTIFIER_STATUS * @see UCHAR_IDENTIFIER_STATUS
* @draft ICU 75 * @stable ICU 75
*/ */
typedef enum UIdentifierStatus { typedef enum UIdentifierStatus {
/* /*
@ -2806,9 +2803,9 @@ typedef enum UIdentifierStatus {
* U_ID_STATUS_<Unicode Identifier_Status value name> * U_ID_STATUS_<Unicode Identifier_Status value name>
*/ */
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_STATUS_RESTRICTED, U_ID_STATUS_RESTRICTED,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_STATUS_ALLOWED, U_ID_STATUS_ALLOWED,
} UIdentifierStatus; } UIdentifierStatus;
@ -2817,7 +2814,7 @@ typedef enum UIdentifierStatus {
* See https://www.unicode.org/reports/tr39/#Identifier_Status_and_Type. * See https://www.unicode.org/reports/tr39/#Identifier_Status_and_Type.
* *
* @see UCHAR_IDENTIFIER_TYPE * @see UCHAR_IDENTIFIER_TYPE
* @draft ICU 75 * @stable ICU 75
*/ */
typedef enum UIdentifierType { typedef enum UIdentifierType {
/* /*
@ -2826,32 +2823,31 @@ typedef enum UIdentifierType {
* U_ID_TYPE_<Unicode Identifier_Type value name> * U_ID_TYPE_<Unicode Identifier_Type value name>
*/ */
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_NOT_CHARACTER, U_ID_TYPE_NOT_CHARACTER,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_DEPRECATED, U_ID_TYPE_DEPRECATED,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_DEFAULT_IGNORABLE, U_ID_TYPE_DEFAULT_IGNORABLE,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_NOT_NFKC, U_ID_TYPE_NOT_NFKC,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_NOT_XID, U_ID_TYPE_NOT_XID,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_EXCLUSION, U_ID_TYPE_EXCLUSION,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_OBSOLETE, U_ID_TYPE_OBSOLETE,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_TECHNICAL, U_ID_TYPE_TECHNICAL,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_UNCOMMON_USE, U_ID_TYPE_UNCOMMON_USE,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_LIMITED_USE, U_ID_TYPE_LIMITED_USE,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_INCLUSION, U_ID_TYPE_INCLUSION,
/** @draft ICU 75 */ /** @stable ICU 75 */
U_ID_TYPE_RECOMMENDED, U_ID_TYPE_RECOMMENDED,
} UIdentifierType; } UIdentifierType;
#endif // U_HIDE_DRAFT_API
/** /**
* Check a binary Unicode property for a code point. * Check a binary Unicode property for a code point.
@ -4057,7 +4053,6 @@ u_isIDStart(UChar32 c);
U_CAPI UBool U_EXPORT2 U_CAPI UBool U_EXPORT2
u_isIDPart(UChar32 c); u_isIDPart(UChar32 c);
#ifndef U_HIDE_DRAFT_API
/** /**
* Does the set of Identifier_Type values code point c contain the given type? * Does the set of Identifier_Type values code point c contain the given type?
* *
@ -4069,7 +4064,7 @@ u_isIDPart(UChar32 c);
* @param c code point * @param c code point
* @param type Identifier_Type to check * @param type Identifier_Type to check
* @return true if type is in Identifier_Type(c) * @return true if type is in Identifier_Type(c)
* @draft ICU 75 * @stable ICU 75
*/ */
U_CAPI bool U_EXPORT2 U_CAPI bool U_EXPORT2
u_hasIDType(UChar32 c, UIdentifierType type); u_hasIDType(UChar32 c, UIdentifierType type);
@ -4104,11 +4099,10 @@ u_hasIDType(UChar32 c, UIdentifierType type);
* function chaining. (See User Guide for details.) * function chaining. (See User Guide for details.)
* @return number of values in c's Identifier_Type, * @return number of values in c's Identifier_Type,
* written to types unless U_BUFFER_OVERFLOW_ERROR indicates insufficient capacity * written to types unless U_BUFFER_OVERFLOW_ERROR indicates insufficient capacity
* @draft ICU 75 * @stable ICU 75
*/ */
U_CAPI int32_t U_EXPORT2 U_CAPI int32_t U_EXPORT2
u_getIDTypes(UChar32 c, UIdentifierType *types, int32_t capacity, UErrorCode *pErrorCode); u_getIDTypes(UChar32 c, UIdentifierType *types, int32_t capacity, UErrorCode *pErrorCode);
#endif // U_HIDE_DRAFT_API
/** /**
* Determines if the specified character should be regarded * Determines if the specified character should be regarded

View File

@ -1173,10 +1173,12 @@ public:
inline U_HEADER_NESTED_NAMESPACE::USetStrings strings() const { inline U_HEADER_NESTED_NAMESPACE::USetStrings strings() const {
return U_HEADER_NESTED_NAMESPACE::USetStrings(toUSet()); return U_HEADER_NESTED_NAMESPACE::USetStrings(toUSet());
} }
#endif // U_HIDE_DRAFT_API
#ifndef U_HIDE_DRAFT_API
/** /**
* Returns a C++ iterator for iterating over all of the elements of this set. * Returns a C++ iterator for iterating over all of the elements of this set.
* Convenient all-in one iteration, but creates a UnicodeString for each * Convenient all-in one iteration, but creates a std::u16string for each
* code point or string. * code point or string.
* (Similar to how Java UnicodeSet *is an* Iterable&lt;String&gt;.) * (Similar to how Java UnicodeSet *is an* Iterable&lt;String&gt;.)
* *
@ -1185,13 +1187,14 @@ public:
* \code * \code
* UnicodeSet set(u"[abcçカ🚴{}{abc}{de}]", errorCode); * UnicodeSet set(u"[abcçカ🚴{}{abc}{de}]", errorCode);
* for (auto el : set) { * for (auto el : set) {
* UnicodeString us(el);
* std::string u8; * std::string u8;
* printf("set.string length %ld \"%s\"\n", (long)el.length(), el.toUTF8String(u8).c_str()); * printf("set.element length %ld \"%s\"\n", (long)us.length(), us.toUTF8String(u8).c_str());
* } * }
* \endcode * \endcode
* *
* @return an all-elements iterator. * @return an all-elements iterator.
* @draft ICU 76 * @draft ICU 77
* @see end * @see end
* @see codePoints * @see codePoints
* @see ranges * @see ranges
@ -1203,7 +1206,7 @@ public:
/** /**
* @return an exclusive-end sentinel for iterating over all of the elements of this set. * @return an exclusive-end sentinel for iterating over all of the elements of this set.
* @draft ICU 76 * @draft ICU 77
* @see begin * @see begin
* @see codePoints * @see codePoints
* @see ranges * @see ranges

View File

@ -32,12 +32,13 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#include "unicode/uchar.h" #include "unicode/uchar.h"
#if U_SHOW_CPLUSPLUS_API #if U_SHOW_CPLUSPLUS_API || U_SHOW_CPLUSPLUS_HEADER_API
#include <string>
#include <string_view> #include <string_view>
#include "unicode/char16ptr.h" #include "unicode/char16ptr.h"
#include "unicode/localpointer.h" #include "unicode/localpointer.h"
#include "unicode/unistr.h" #include "unicode/utf16.h"
#endif // U_SHOW_CPLUSPLUS_API #endif
#ifndef USET_DEFINED #ifndef USET_DEFINED
@ -1392,8 +1393,8 @@ public:
private: private:
friend class USetCodePoints; friend class USetCodePoints;
USetCodePointIterator(const USet *uset, int32_t rangeIndex, int32_t rangeCount) USetCodePointIterator(const USet *pUset, int32_t nRangeIndex, int32_t nRangeCount)
: uset(uset), rangeIndex(rangeIndex), rangeCount(rangeCount), : uset(pUset), rangeIndex(nRangeIndex), rangeCount(nRangeCount),
c(U_SENTINEL), end(U_SENTINEL) { c(U_SENTINEL), end(U_SENTINEL) {
// Fetch the first range. // Fetch the first range.
operator++(); operator++();
@ -1429,7 +1430,7 @@ public:
* Constructs a C++ "range" object over the code points of the USet. * Constructs a C++ "range" object over the code points of the USet.
* @draft ICU 76 * @draft ICU 76
*/ */
USetCodePoints(const USet *uset) : uset(uset), rangeCount(uset_getRangeCount(uset)) {} USetCodePoints(const USet *pUset) : uset(pUset), rangeCount(uset_getRangeCount(pUset)) {}
/** @draft ICU 76 */ /** @draft ICU 76 */
USetCodePoints(const USetCodePoints &other) = default; USetCodePoints(const USetCodePoints &other) = default;
@ -1460,7 +1461,7 @@ struct CodePointRange {
/** @draft ICU 76 */ /** @draft ICU 76 */
struct iterator { struct iterator {
/** @draft ICU 76 */ /** @draft ICU 76 */
iterator(UChar32 c) : c(c) {} iterator(UChar32 aC) : c(aC) {}
/** @draft ICU 76 */ /** @draft ICU 76 */
bool operator==(const iterator &other) const { return c == other.c; } bool operator==(const iterator &other) const { return c == other.c; }
@ -1573,8 +1574,8 @@ public:
private: private:
friend class USetRanges; friend class USetRanges;
USetRangeIterator(const USet *uset, int32_t rangeIndex, int32_t rangeCount) USetRangeIterator(const USet *pUset, int32_t nRangeIndex, int32_t nRangeCount)
: uset(uset), rangeIndex(rangeIndex), rangeCount(rangeCount) {} : uset(pUset), rangeIndex(nRangeIndex), rangeCount(nRangeCount) {}
const USet *uset; const USet *uset;
int32_t rangeIndex; int32_t rangeIndex;
@ -1610,7 +1611,7 @@ public:
* Constructs a C++ "range" object over the code point ranges of the USet. * Constructs a C++ "range" object over the code point ranges of the USet.
* @draft ICU 76 * @draft ICU 76
*/ */
USetRanges(const USet *uset) : uset(uset), rangeCount(uset_getRangeCount(uset)) {} USetRanges(const USet *pUset) : uset(pUset), rangeCount(uset_getRangeCount(pUset)) {}
/** @draft ICU 76 */ /** @draft ICU 76 */
USetRanges(const USetRanges &other) = default; USetRanges(const USetRanges &other) = default;
@ -1657,7 +1658,7 @@ public:
int32_t length; int32_t length;
const UChar *uchars = uset_getString(uset, index, &length); const UChar *uchars = uset_getString(uset, index, &length);
// assert uchars != nullptr; // assert uchars != nullptr;
return {ConstChar16Ptr(uchars), static_cast<uint32_t>(length)}; return {uprv_char16PtrFromUChar(uchars), static_cast<size_t>(length)};
} }
return {}; return {};
} }
@ -1684,8 +1685,8 @@ public:
private: private:
friend class USetStrings; friend class USetStrings;
USetStringIterator(const USet *uset, int32_t index, int32_t count) USetStringIterator(const USet *pUset, int32_t nIndex, int32_t nCount)
: uset(uset), index(index), count(count) {} : uset(pUset), index(nIndex), count(nCount) {}
const USet *uset; const USet *uset;
int32_t index; int32_t index;
@ -1699,9 +1700,11 @@ private:
* using U_HEADER_NESTED_NAMESPACE::USetStrings; * using U_HEADER_NESTED_NAMESPACE::USetStrings;
* LocalUSetPointer uset(uset_openPattern(u"[abcçカ🚴{}{abc}{de}]", -1, &errorCode)); * LocalUSetPointer uset(uset_openPattern(u"[abcçカ🚴{}{abc}{de}]", -1, &errorCode));
* for (auto s : USetStrings(uset.getAlias())) { * for (auto s : USetStrings(uset.getAlias())) {
* UnicodeString us(s); * int32_t len32 = s.length();
* std::string u8; * char utf8[200];
* printf("uset.string length %ld \"%s\"\n", (long)s.length(), us.toUTF8String(u8).c_str()); * u_strToUTF8WithSub(utf8, int32_t{sizeof(utf8) - 1}, nullptr,
* s.data(), len32, 0xFFFD, nullptr, errorCode);
* printf("uset.string length %ld \"%s\"\n", long{len32}, utf8);
* } * }
* \endcode * \endcode
* *
@ -1718,7 +1721,7 @@ public:
* Constructs a C++ "range" object over the strings of the USet. * Constructs a C++ "range" object over the strings of the USet.
* @draft ICU 76 * @draft ICU 76
*/ */
USetStrings(const USet *uset) : uset(uset), count(uset_getStringCount(uset)) {} USetStrings(const USet *pUset) : uset(pUset), count(uset_getStringCount(pUset)) {}
/** @draft ICU 76 */ /** @draft ICU 76 */
USetStrings(const USetStrings &other) = default; USetStrings(const USetStrings &other) = default;
@ -1737,17 +1740,19 @@ private:
const USet *uset; const USet *uset;
int32_t count; int32_t count;
}; };
#endif // U_HIDE_DRAFT_API
#ifndef U_HIDE_DRAFT_API
/** /**
* Iterator returned by USetElements. * Iterator returned by USetElements.
* @draft ICU 76 * @draft ICU 77
*/ */
class USetElementIterator { class USetElementIterator {
public: public:
/** @draft ICU 76 */ /** @draft ICU 77 */
USetElementIterator(const USetElementIterator &other) = default; USetElementIterator(const USetElementIterator &other) = default;
/** @draft ICU 76 */ /** @draft ICU 77 */
bool operator==(const USetElementIterator &other) const { bool operator==(const USetElementIterator &other) const {
// No need to compare rangeCount & end given private constructor // No need to compare rangeCount & end given private constructor
// and assuming we don't compare iterators across the set being modified. // and assuming we don't compare iterators across the set being modified.
@ -1756,26 +1761,28 @@ public:
return uset == other.uset && c == other.c && index == other.index; return uset == other.uset && c == other.c && index == other.index;
} }
/** @draft ICU 76 */ /** @draft ICU 77 */
bool operator!=(const USetElementIterator &other) const { return !operator==(other); } bool operator!=(const USetElementIterator &other) const { return !operator==(other); }
/** @draft ICU 76 */ /** @draft ICU 77 */
UnicodeString operator*() const { std::u16string operator*() const {
if (c >= 0) { if (c >= 0) {
return UnicodeString(c); return c <= 0xffff ?
std::u16string({static_cast<char16_t>(c)}) :
std::u16string({U16_LEAD(c), U16_TRAIL(c)});
} else if (index < totalCount) { } else if (index < totalCount) {
int32_t length; int32_t length;
const UChar *uchars = uset_getString(uset, index - rangeCount, &length); const UChar *uchars = uset_getString(uset, index - rangeCount, &length);
// assert uchars != nullptr; // assert uchars != nullptr;
return UnicodeString(uchars, length); return {uprv_char16PtrFromUChar(uchars), static_cast<size_t>(length)};
} else { } else {
return UnicodeString(); return {};
} }
} }
/** /**
* Pre-increment. * Pre-increment.
* @draft ICU 76 * @draft ICU 77
*/ */
USetElementIterator &operator++() { USetElementIterator &operator++() {
if (c < end) { if (c < end) {
@ -1800,7 +1807,7 @@ public:
/** /**
* Post-increment. * Post-increment.
* @draft ICU 76 * @draft ICU 77
*/ */
USetElementIterator operator++(int) { USetElementIterator operator++(int) {
USetElementIterator result(*this); USetElementIterator result(*this);
@ -1811,8 +1818,8 @@ public:
private: private:
friend class USetElements; friend class USetElements;
USetElementIterator(const USet *uset, int32_t index, int32_t rangeCount, int32_t totalCount) USetElementIterator(const USet *pUset, int32_t nIndex, int32_t nRangeCount, int32_t nTotalCount)
: uset(uset), index(index), rangeCount(rangeCount), totalCount(totalCount), : uset(pUset), index(nIndex), rangeCount(nRangeCount), totalCount(nTotalCount),
c(U_SENTINEL), end(U_SENTINEL) { c(U_SENTINEL), end(U_SENTINEL) {
if (index < rangeCount) { if (index < rangeCount) {
// Fetch the first range. // Fetch the first range.
@ -1840,7 +1847,7 @@ private:
/** /**
* A C++ "range" for iterating over all of the elements of a USet. * A C++ "range" for iterating over all of the elements of a USet.
* Convenient all-in one iteration, but creates a UnicodeString for each * Convenient all-in one iteration, but creates a std::u16string for each
* code point or string. * code point or string.
* *
* Code points are returned first, then empty and multi-character strings. * Code points are returned first, then empty and multi-character strings.
@ -1849,15 +1856,18 @@ private:
* using U_HEADER_NESTED_NAMESPACE::USetElements; * using U_HEADER_NESTED_NAMESPACE::USetElements;
* LocalUSetPointer uset(uset_openPattern(u"[abcçカ🚴{}{abc}{de}]", -1, &errorCode)); * LocalUSetPointer uset(uset_openPattern(u"[abcçカ🚴{}{abc}{de}]", -1, &errorCode));
* for (auto el : USetElements(uset.getAlias())) { * for (auto el : USetElements(uset.getAlias())) {
* std::string u8; * int32_t len32 = el.length();
* printf("uset.string length %ld \"%s\"\n", (long)el.length(), el.toUTF8String(u8).c_str()); * char utf8[200];
* u_strToUTF8WithSub(utf8, int32_t{sizeof(utf8) - 1}, nullptr,
* el.data(), len32, 0xFFFD, nullptr, errorCode);
* printf("uset.element length %ld \"%s\"\n", long{len32}, utf8);
* } * }
* \endcode * \endcode
* *
* C++ UnicodeSet has member functions for iteration, including begin() and end(). * C++ UnicodeSet has member functions for iteration, including begin() and end().
* *
* @return an all-elements iterator. * @return an all-elements iterator.
* @draft ICU 76 * @draft ICU 77
* @see USetCodePoints * @see USetCodePoints
* @see USetRanges * @see USetRanges
* @see USetStrings * @see USetStrings
@ -1866,21 +1876,21 @@ class USetElements {
public: public:
/** /**
* Constructs a C++ "range" object over all of the elements of the USet. * Constructs a C++ "range" object over all of the elements of the USet.
* @draft ICU 76 * @draft ICU 77
*/ */
USetElements(const USet *uset) USetElements(const USet *pUset)
: uset(uset), rangeCount(uset_getRangeCount(uset)), : uset(pUset), rangeCount(uset_getRangeCount(pUset)),
stringCount(uset_getStringCount(uset)) {} stringCount(uset_getStringCount(pUset)) {}
/** @draft ICU 76 */ /** @draft ICU 77 */
USetElements(const USetElements &other) = default; USetElements(const USetElements &other) = default;
/** @draft ICU 76 */ /** @draft ICU 77 */
USetElementIterator begin() const { USetElementIterator begin() const {
return USetElementIterator(uset, 0, rangeCount, rangeCount + stringCount); return USetElementIterator(uset, 0, rangeCount, rangeCount + stringCount);
} }
/** @draft ICU 76 */ /** @draft ICU 77 */
USetElementIterator end() const { USetElementIterator end() const {
return USetElementIterator(uset, rangeCount + stringCount, rangeCount, rangeCount + stringCount); return USetElementIterator(uset, rangeCount + stringCount, rangeCount, rangeCount + stringCount);
} }

View File

@ -124,7 +124,7 @@
* @internal * @internal
*/ */
U_CAPI UChar32 U_EXPORT2 U_CAPI UChar32 U_EXPORT2
utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, UBool strict); utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, int8_t strict);
/** /**
* Function for handling "append code point" with error-checking. * Function for handling "append code point" with error-checking.
@ -148,7 +148,7 @@ utf8_appendCharSafeBody(uint8_t *s, int32_t i, int32_t length, UChar32 c, UBool
* @internal * @internal
*/ */
U_CAPI UChar32 U_EXPORT2 U_CAPI UChar32 U_EXPORT2
utf8_prevCharSafeBody(const uint8_t *s, int32_t start, int32_t *pi, UChar32 c, UBool strict); utf8_prevCharSafeBody(const uint8_t *s, int32_t start, int32_t *pi, UChar32 c, int8_t strict);
/** /**
* Function for handling "skip backward one code point" with error-checking. * Function for handling "skip backward one code point" with error-checking.

View File

@ -598,12 +598,13 @@ typedef enum UErrorCode {
U_MF_DUPLICATE_DECLARATION_ERROR, /**< The same variable is declared in more than one .local or .input declaration. @internal ICU 75 technology preview @deprecated This API is for technology preview only. */ U_MF_DUPLICATE_DECLARATION_ERROR, /**< The same variable is declared in more than one .local or .input declaration. @internal ICU 75 technology preview @deprecated This API is for technology preview only. */
U_MF_OPERAND_MISMATCH_ERROR, /**< An operand provided to a function does not have the required form for that function @internal ICU 75 technology preview @deprecated This API is for technology preview only. */ U_MF_OPERAND_MISMATCH_ERROR, /**< An operand provided to a function does not have the required form for that function @internal ICU 75 technology preview @deprecated This API is for technology preview only. */
U_MF_DUPLICATE_VARIANT_ERROR, /**< A message includes a variant with the same key list as another variant. @internal ICU 76 technology preview @deprecated This API is for technology preview only. */ U_MF_DUPLICATE_VARIANT_ERROR, /**< A message includes a variant with the same key list as another variant. @internal ICU 76 technology preview @deprecated This API is for technology preview only. */
U_MF_BAD_OPTION, /**< An option value provided to a function does not have the required form for that option. @internal ICU 77 technology preview @deprecated This API is for technology preview only. */
#ifndef U_HIDE_DEPRECATED_API #ifndef U_HIDE_DEPRECATED_API
/** /**
* One more than the highest normal formatting API error code. * One more than the highest normal formatting API error code.
* @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420. * @deprecated ICU 58 The numeric value may change over time, see ICU ticket #12420.
*/ */
U_FMT_PARSE_ERROR_LIMIT = 0x10120, U_FMT_PARSE_ERROR_LIMIT = 0x10121,
#endif // U_HIDE_DEPRECATED_API #endif // U_HIDE_DEPRECATED_API
/* /*

View File

@ -53,7 +53,7 @@
* This value will change in the subsequent releases of ICU * This value will change in the subsequent releases of ICU
* @stable ICU 2.4 * @stable ICU 2.4
*/ */
#define U_ICU_VERSION_MAJOR_NUM 76 #define U_ICU_VERSION_MAJOR_NUM 77
/** The current ICU minor version as an integer. /** The current ICU minor version as an integer.
* This value will change in the subsequent releases of ICU * This value will change in the subsequent releases of ICU
@ -79,7 +79,7 @@
* This value will change in the subsequent releases of ICU * This value will change in the subsequent releases of ICU
* @stable ICU 2.6 * @stable ICU 2.6
*/ */
#define U_ICU_VERSION_SUFFIX _76 #define U_ICU_VERSION_SUFFIX _77
/** /**
* \def U_DEF2_ICU_ENTRY_POINT_RENAME * \def U_DEF2_ICU_ENTRY_POINT_RENAME
@ -132,7 +132,7 @@
* This value will change in the subsequent releases of ICU * This value will change in the subsequent releases of ICU
* @stable ICU 2.4 * @stable ICU 2.4
*/ */
#define U_ICU_VERSION "76.1" #define U_ICU_VERSION "77.1"
/** /**
* The current ICU library major version number as a string, for library name suffixes. * The current ICU library major version number as a string, for library name suffixes.
@ -145,13 +145,13 @@
* *
* @stable ICU 2.6 * @stable ICU 2.6
*/ */
#define U_ICU_VERSION_SHORT "76" #define U_ICU_VERSION_SHORT "77"
#ifndef U_HIDE_INTERNAL_API #ifndef U_HIDE_INTERNAL_API
/** Data version in ICU4C. /** Data version in ICU4C.
* @internal ICU 4.4 Internal Use Only * @internal ICU 4.4 Internal Use Only
**/ **/
#define U_ICU_DATA_VERSION "76.1" #define U_ICU_DATA_VERSION "77.1"
#endif /* U_HIDE_INTERNAL_API */ #endif /* U_HIDE_INTERNAL_API */
/*=========================================================================== /*===========================================================================

View File

@ -125,7 +125,7 @@ typedef uint8_t UVersionInfo[U_MAX_VERSION_LENGTH];
U_NAMESPACE_USE U_NAMESPACE_USE
# endif # endif
#ifndef U_HIDE_DRAFT_API #ifndef U_FORCE_HIDE_DRAFT_API
/** /**
* \def U_HEADER_NESTED_NAMESPACE * \def U_HEADER_NESTED_NAMESPACE
* Nested namespace used inside U_ICU_NAMESPACE for header-only APIs. * Nested namespace used inside U_ICU_NAMESPACE for header-only APIs.
@ -150,22 +150,37 @@ typedef uint8_t UVersionInfo[U_MAX_VERSION_LENGTH];
* @draft ICU 76 * @draft ICU 76
*/ */
/**
* \def U_ICU_NAMESPACE_OR_INTERNAL
* Namespace used for header-only APIs that used to be regular C++ APIs.
* Different when used inside ICU to prevent public use of internal instantiations.
* Similar to U_HEADER_ONLY_NAMESPACE, but the public definition is the same as U_ICU_NAMESPACE.
* "U_ICU_NAMESPACE" or "U_ICU_NAMESPACE::internal".
*
* @draft ICU 77
*/
// The first test is the same as for defining U_EXPORT for Windows. // The first test is the same as for defining U_EXPORT for Windows.
#if defined(_MSC_VER) || (UPRV_HAS_DECLSPEC_ATTRIBUTE(__dllexport__) && \ #if defined(_MSC_VER) || (UPRV_HAS_DECLSPEC_ATTRIBUTE(__dllexport__) && \
UPRV_HAS_DECLSPEC_ATTRIBUTE(__dllimport__)) UPRV_HAS_DECLSPEC_ATTRIBUTE(__dllimport__))
# define U_HEADER_NESTED_NAMESPACE header # define U_HEADER_NESTED_NAMESPACE header
# define U_ICU_NAMESPACE_OR_INTERNAL U_ICU_NAMESPACE
#elif defined(U_COMBINED_IMPLEMENTATION) || defined(U_COMMON_IMPLEMENTATION) || \ #elif defined(U_COMBINED_IMPLEMENTATION) || defined(U_COMMON_IMPLEMENTATION) || \
defined(U_I18N_IMPLEMENTATION) || defined(U_IO_IMPLEMENTATION) || \ defined(U_I18N_IMPLEMENTATION) || defined(U_IO_IMPLEMENTATION) || \
defined(U_LAYOUTEX_IMPLEMENTATION) || defined(U_TOOLUTIL_IMPLEMENTATION) defined(U_LAYOUTEX_IMPLEMENTATION) || defined(U_TOOLUTIL_IMPLEMENTATION)
# define U_HEADER_NESTED_NAMESPACE internal # define U_HEADER_NESTED_NAMESPACE internal
# define U_ICU_NAMESPACE_OR_INTERNAL U_ICU_NAMESPACE::internal
namespace U_ICU_NAMESPACE_OR_INTERNAL {}
using namespace U_ICU_NAMESPACE_OR_INTERNAL;
#else #else
# define U_HEADER_NESTED_NAMESPACE header # define U_HEADER_NESTED_NAMESPACE header
# define U_ICU_NAMESPACE_OR_INTERNAL U_ICU_NAMESPACE
#endif #endif
#define U_HEADER_ONLY_NAMESPACE U_ICU_NAMESPACE::U_HEADER_NESTED_NAMESPACE #define U_HEADER_ONLY_NAMESPACE U_ICU_NAMESPACE::U_HEADER_NESTED_NAMESPACE
namespace U_HEADER_ONLY_NAMESPACE {} namespace U_HEADER_ONLY_NAMESPACE {}
#endif // U_HIDE_DRAFT_API #endif // U_FORCE_HIDE_DRAFT_API
#endif /* __cplusplus */ #endif /* __cplusplus */

View File

@ -1945,6 +1945,13 @@ UnicodeString::cloneArrayIfNeeded(int32_t newCapacity,
growCapacity = newCapacity; growCapacity = newCapacity;
} else if(newCapacity <= US_STACKBUF_SIZE && growCapacity > US_STACKBUF_SIZE) { } else if(newCapacity <= US_STACKBUF_SIZE && growCapacity > US_STACKBUF_SIZE) {
growCapacity = US_STACKBUF_SIZE; growCapacity = US_STACKBUF_SIZE;
} else if(newCapacity > growCapacity) {
setToBogus();
return false; // bad inputs
}
if(growCapacity > kMaxCapacity) {
setToBogus();
return false;
} }
// save old values // save old values

View File

@ -2716,6 +2716,9 @@ ures_openWithType(UResourceBundle *r, const char* path, const char* localeID,
UResourceDataEntry *entry; UResourceDataEntry *entry;
if(openType != URES_OPEN_DIRECT) { if(openType != URES_OPEN_DIRECT) {
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
/* first "canonicalize" the locale ID */ /* first "canonicalize" the locale ID */
CharString canonLocaleID = ulocimp_getBaseName(localeID, *status); CharString canonLocaleID = ulocimp_getBaseName(localeID, *status);
if(U_FAILURE(*status)) { if(U_FAILURE(*status)) {
@ -3080,6 +3083,9 @@ ures_getFunctionalEquivalent(char *result, int32_t resultCapacity,
kwVal.clear(); kwVal.clear();
} }
} }
if (locid == nullptr) {
locid = uloc_getDefault();
}
CharString base = ulocimp_getBaseName(locid, subStatus); CharString base = ulocimp_getBaseName(locid, subStatus);
#if defined(URES_TREE_DEBUG) #if defined(URES_TREE_DEBUG)
fprintf(stderr, "getFunctionalEquivalent: \"%s\" [%s=%s] in %s - %s\n", fprintf(stderr, "getFunctionalEquivalent: \"%s\" [%s=%s] in %s - %s\n",
@ -3244,7 +3250,7 @@ ures_getFunctionalEquivalent(char *result, int32_t resultCapacity,
const char *validLoc = ures_getLocaleByType(res, ULOC_VALID_LOCALE, &subStatus); const char *validLoc = ures_getLocaleByType(res, ULOC_VALID_LOCALE, &subStatus);
if (U_SUCCESS(subStatus) && validLoc != nullptr && validLoc[0] != 0 && uprv_strcmp(validLoc, "root") != 0) { if (U_SUCCESS(subStatus) && validLoc != nullptr && validLoc[0] != 0 && uprv_strcmp(validLoc, "root") != 0) {
CharString validLang = ulocimp_getLanguage(validLoc, subStatus); CharString validLang = ulocimp_getLanguage(validLoc, subStatus);
CharString parentLang = ulocimp_getLanguage(parent.data(), subStatus); CharString parentLang = ulocimp_getLanguage(parent.toStringPiece(), subStatus);
if (U_SUCCESS(subStatus) && validLang != parentLang) { if (U_SUCCESS(subStatus) && validLang != parentLang) {
// validLoc is not root and has a different language than parent, use it instead // validLoc is not root and has a different language than parent, use it instead
found.clear().append(validLoc, subStatus); found.clear().append(validLoc, subStatus);

View File

@ -59,6 +59,9 @@ getCodesFromLocale(const char *locale,
if (U_FAILURE(*err)) { return 0; } if (U_FAILURE(*err)) { return 0; }
icu::CharString lang; icu::CharString lang;
icu::CharString script; icu::CharString script;
if (locale == nullptr) {
locale = uloc_getDefault();
}
ulocimp_getSubtags(locale, &lang, &script, nullptr, nullptr, nullptr, *err); ulocimp_getSubtags(locale, &lang, &script, nullptr, nullptr, nullptr, *err);
if (U_FAILURE(*err)) { return 0; } if (U_FAILURE(*err)) { return 0; }
// Multi-script languages, equivalent to the LocaleScript data // Multi-script languages, equivalent to the LocaleScript data

View File

@ -28,6 +28,7 @@
#include "ubidi_props.h" #include "ubidi_props.h"
#include "uassert.h" #include "uassert.h"
#include <limits>
/* /*
* This implementation is designed for 16-bit Unicode strings. * This implementation is designed for 16-bit Unicode strings.
* The main assumption is that the Arabic characters and their * The main assumption is that the Arabic characters and their
@ -747,6 +748,10 @@ handleGeneratedSpaces(char16_t *dest, int32_t sourceLength,
} }
} }
if (static_cast<size_t>(sourceLength) + 1 > std::numeric_limits<size_t>::max() / U_SIZEOF_UCHAR) {
*pErrorCode = U_INDEX_OUTOFBOUNDS_ERROR;
return 0;
}
tempbuffer = static_cast<char16_t*>(uprv_malloc((sourceLength + 1) * U_SIZEOF_UCHAR)); tempbuffer = static_cast<char16_t*>(uprv_malloc((sourceLength + 1) * U_SIZEOF_UCHAR));
/* Test for nullptr */ /* Test for nullptr */
if(tempbuffer == nullptr) { if(tempbuffer == nullptr) {

View File

@ -126,7 +126,7 @@ compareEntries(const UHashTok p1, const UHashTok p2) {
name2.pointer = b2->name; name2.pointer = b2->name;
path1.pointer = b1->path; path1.pointer = b1->path;
path2.pointer = b2->path; path2.pointer = b2->path;
return uhash_compareChars(name1, name2) & uhash_compareChars(path1, path2); return uhash_compareChars(name1, name2) && uhash_compareChars(path1, path2);
} }
static void static void

View File

@ -124,11 +124,9 @@ errorValue(int32_t count, int8_t strict) {
* >0 Obsolete "strict" behavior of UTF8_NEXT_CHAR_SAFE(..., true): * >0 Obsolete "strict" behavior of UTF8_NEXT_CHAR_SAFE(..., true):
* Same as the obsolete "safe" behavior, but non-characters are also treated * Same as the obsolete "safe" behavior, but non-characters are also treated
* like illegal sequences. * like illegal sequences.
*
* Note that a UBool is the same as an int8_t.
*/ */
U_CAPI UChar32 U_EXPORT2 U_CAPI UChar32 U_EXPORT2
utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, UBool strict) { utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, int8_t strict) {
// *pi is one after byte c. // *pi is one after byte c.
int32_t i=*pi; int32_t i=*pi;
// length can be negative for NUL-terminated strings: Read and validate one byte at a time. // length can be negative for NUL-terminated strings: Read and validate one byte at a time.
@ -233,7 +231,7 @@ utf8_appendCharSafeBody(uint8_t *s, int32_t i, int32_t length, UChar32 c, UBool
} }
U_CAPI UChar32 U_EXPORT2 U_CAPI UChar32 U_EXPORT2
utf8_prevCharSafeBody(const uint8_t *s, int32_t start, int32_t *pi, UChar32 c, UBool strict) { utf8_prevCharSafeBody(const uint8_t *s, int32_t start, int32_t *pi, UChar32 c, int8_t strict) {
// *pi is the index of byte c. // *pi is the index of byte c.
int32_t i=*pi; int32_t i=*pi;
if(U8_IS_TRAIL(c) && i>start) { if(U8_IS_TRAIL(c) && i>start) {

View File

@ -140,7 +140,8 @@ _uFmtErrorName[U_FMT_PARSE_ERROR_LIMIT - U_FMT_PARSE_ERROR_START] = {
"U_MF_MISSING_SELECTOR_ANNOTATION_ERROR", "U_MF_MISSING_SELECTOR_ANNOTATION_ERROR",
"U_MF_DUPLICATE_DECLARATION_ERROR", "U_MF_DUPLICATE_DECLARATION_ERROR",
"U_MF_OPERAND_MISMATCH_ERROR", "U_MF_OPERAND_MISMATCH_ERROR",
"U_MF_DUPLICATE_VARIANT_ERROR" "U_MF_DUPLICATE_VARIANT_ERROR",
"U_MF_BAD_OPTION"
}; };
static const char * const static const char * const

View File

@ -160,12 +160,13 @@ BasicTimeZone::getSimpleRulesNear(UDate date, InitialTimeZoneRule*& initial,
|| (tr.getFrom()->getDSTSavings() != 0 && tr.getTo()->getDSTSavings() == 0)) || (tr.getFrom()->getDSTSavings() != 0 && tr.getTo()->getDSTSavings() == 0))
&& (date + MILLIS_PER_YEAR > nextTransitionTime)) { && (date + MILLIS_PER_YEAR > nextTransitionTime)) {
int32_t year, month, dom, dow, doy, mid; int32_t year, mid;
int8_t month, dom, dow;
UDate d; UDate d;
// Get local wall time for the next transition time // Get local wall time for the next transition time
Grego::timeToFields(nextTransitionTime + initialRaw + initialDst, Grego::timeToFields(nextTransitionTime + initialRaw + initialDst,
year, month, dom, dow, doy, mid, status); year, month, dom, dow, mid, status);
if (U_FAILURE(status)) return; if (U_FAILURE(status)) return;
int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom); int32_t weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
// Create DOW rule // Create DOW rule
@ -193,7 +194,7 @@ BasicTimeZone::getSimpleRulesNear(UDate date, InitialTimeZoneRule*& initial,
// Get local wall time for the next transition time // Get local wall time for the next transition time
Grego::timeToFields(tr.getTime() + tr.getFrom()->getRawOffset() + tr.getFrom()->getDSTSavings(), Grego::timeToFields(tr.getTime() + tr.getFrom()->getRawOffset() + tr.getFrom()->getDSTSavings(),
year, month, dom, dow, doy, mid, status); year, month, dom, dow, mid, status);
if (U_FAILURE(status)) return; if (U_FAILURE(status)) return;
weekInMonth = Grego::dayOfWeekInMonth(year, month, dom); weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
// Generate another DOW rule // Generate another DOW rule
@ -225,7 +226,7 @@ BasicTimeZone::getSimpleRulesNear(UDate date, InitialTimeZoneRule*& initial,
// Generate another DOW rule // Generate another DOW rule
Grego::timeToFields(tr.getTime() + tr.getFrom()->getRawOffset() + tr.getFrom()->getDSTSavings(), Grego::timeToFields(tr.getTime() + tr.getFrom()->getRawOffset() + tr.getFrom()->getDSTSavings(),
year, month, dom, dow, doy, mid, status); year, month, dom, dow, mid, status);
if (U_FAILURE(status)) return; if (U_FAILURE(status)) return;
weekInMonth = Grego::dayOfWeekInMonth(year, month, dom); weekInMonth = Grego::dayOfWeekInMonth(year, month, dom);
dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME); dtr = new DateTimeRule(month, weekInMonth, dow, mid, DateTimeRule::WALL_TIME);
@ -486,8 +487,7 @@ BasicTimeZone::getTimeZoneRulesAfter(UDate start, InitialTimeZoneRule*& initial,
} }
} else { } else {
// Calculate the transition year // Calculate the transition year
int32_t year, month, dom, dow, doy, mid; int32_t year = Grego::timeToYear(tzt.getTime(), status);
Grego::timeToFields(tzt.getTime(), year, month, dom, dow, doy, mid, status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return; return;
} }

View File

@ -36,7 +36,6 @@ static const int32_t kGregorianEpoch = 1970; // used as the default value of
BuddhistCalendar::BuddhistCalendar(const Locale& aLocale, UErrorCode& success) BuddhistCalendar::BuddhistCalendar(const Locale& aLocale, UErrorCode& success)
: GregorianCalendar(aLocale, success) : GregorianCalendar(aLocale, success)
{ {
setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly.
} }
BuddhistCalendar::~BuddhistCalendar() BuddhistCalendar::~BuddhistCalendar()
@ -48,12 +47,6 @@ BuddhistCalendar::BuddhistCalendar(const BuddhistCalendar& source)
{ {
} }
BuddhistCalendar& BuddhistCalendar::operator= ( const BuddhistCalendar& right)
{
GregorianCalendar::operator=(right);
return *this;
}
BuddhistCalendar* BuddhistCalendar::clone() const BuddhistCalendar* BuddhistCalendar::clone() const
{ {
return new BuddhistCalendar(*this); return new BuddhistCalendar(*this);

View File

@ -82,13 +82,6 @@ public:
*/ */
BuddhistCalendar(const BuddhistCalendar& source); BuddhistCalendar(const BuddhistCalendar& source);
/**
* Default assignment operator
* @param right the object to be copied.
* @internal
*/
BuddhistCalendar& operator=(const BuddhistCalendar& right);
/** /**
* Create and return a polymorphic copy of this calendar. * Create and return a polymorphic copy of this calendar.
* @return return a polymorphic copy of this calendar. * @return return a polymorphic copy of this calendar.

View File

@ -156,7 +156,7 @@ U_CFUNC void ucal_dump(UCalendar* cal) {
#endif #endif
/* Max value for stamp allowable before recalculation */ /* Max value for stamp allowable before recalculation */
#define STAMP_MAX 10000 #define STAMP_MAX 127
static const char * const gCalTypes[] = { static const char * const gCalTypes[] = {
"gregorian", "gregorian",
@ -700,15 +700,10 @@ fIsTimeSet(false),
fAreFieldsSet(false), fAreFieldsSet(false),
fAreAllFieldsSet(false), fAreAllFieldsSet(false),
fAreFieldsVirtuallySet(false), fAreFieldsVirtuallySet(false),
fNextStamp(static_cast<int32_t>(kMinimumUserStamp)),
fTime(0),
fLenient(true), fLenient(true),
fZone(nullptr),
fRepeatedWallTime(UCAL_WALLTIME_LAST), fRepeatedWallTime(UCAL_WALLTIME_LAST),
fSkippedWallTime(UCAL_WALLTIME_LAST) fSkippedWallTime(UCAL_WALLTIME_LAST)
{ {
validLocale[0] = 0;
actualLocale[0] = 0;
clear(); clear();
if (U_FAILURE(success)) { if (U_FAILURE(success)) {
return; return;
@ -722,26 +717,21 @@ fSkippedWallTime(UCAL_WALLTIME_LAST)
// ------------------------------------- // -------------------------------------
Calendar::Calendar(TimeZone* zone, const Locale& aLocale, UErrorCode& success) Calendar::Calendar(TimeZone* adoptZone, const Locale& aLocale, UErrorCode& success)
: UObject(), : UObject(),
fIsTimeSet(false), fIsTimeSet(false),
fAreFieldsSet(false), fAreFieldsSet(false),
fAreAllFieldsSet(false), fAreAllFieldsSet(false),
fAreFieldsVirtuallySet(false), fAreFieldsVirtuallySet(false),
fNextStamp(static_cast<int32_t>(kMinimumUserStamp)),
fTime(0),
fLenient(true), fLenient(true),
fZone(nullptr),
fRepeatedWallTime(UCAL_WALLTIME_LAST), fRepeatedWallTime(UCAL_WALLTIME_LAST),
fSkippedWallTime(UCAL_WALLTIME_LAST) fSkippedWallTime(UCAL_WALLTIME_LAST)
{ {
validLocale[0] = 0; LocalPointer<TimeZone> zone(adoptZone, success);
actualLocale[0] = 0;
if (U_FAILURE(success)) { if (U_FAILURE(success)) {
delete zone;
return; return;
} }
if (zone == nullptr) { if (zone.isNull()) {
#if defined (U_DEBUG_CAL) #if defined (U_DEBUG_CAL)
fprintf(stderr, "%s:%d: ILLEGAL ARG because timezone cannot be 0\n", fprintf(stderr, "%s:%d: ILLEGAL ARG because timezone cannot be 0\n",
__FILE__, __LINE__); __FILE__, __LINE__);
@ -751,7 +741,7 @@ fSkippedWallTime(UCAL_WALLTIME_LAST)
} }
clear(); clear();
fZone = zone; fZone = zone.orphan();
setWeekData(aLocale, nullptr, success); setWeekData(aLocale, nullptr, success);
} }
@ -763,15 +753,10 @@ fIsTimeSet(false),
fAreFieldsSet(false), fAreFieldsSet(false),
fAreAllFieldsSet(false), fAreAllFieldsSet(false),
fAreFieldsVirtuallySet(false), fAreFieldsVirtuallySet(false),
fNextStamp(static_cast<int32_t>(kMinimumUserStamp)),
fTime(0),
fLenient(true), fLenient(true),
fZone(nullptr),
fRepeatedWallTime(UCAL_WALLTIME_LAST), fRepeatedWallTime(UCAL_WALLTIME_LAST),
fSkippedWallTime(UCAL_WALLTIME_LAST) fSkippedWallTime(UCAL_WALLTIME_LAST)
{ {
validLocale[0] = 0;
actualLocale[0] = 0;
if (U_FAILURE(success)) { if (U_FAILURE(success)) {
return; return;
} }
@ -779,6 +764,7 @@ fSkippedWallTime(UCAL_WALLTIME_LAST)
fZone = zone.clone(); fZone = zone.clone();
if (fZone == nullptr) { if (fZone == nullptr) {
success = U_MEMORY_ALLOCATION_ERROR; success = U_MEMORY_ALLOCATION_ERROR;
return;
} }
setWeekData(aLocale, nullptr, success); setWeekData(aLocale, nullptr, success);
} }
@ -788,6 +774,8 @@ fSkippedWallTime(UCAL_WALLTIME_LAST)
Calendar::~Calendar() Calendar::~Calendar()
{ {
delete fZone; delete fZone;
delete actualLocale;
delete validLocale;
} }
// ------------------------------------- // -------------------------------------
@ -795,7 +783,6 @@ Calendar::~Calendar()
Calendar::Calendar(const Calendar &source) Calendar::Calendar(const Calendar &source)
: UObject(source) : UObject(source)
{ {
fZone = nullptr;
*this = source; *this = source;
} }
@ -806,7 +793,6 @@ Calendar::operator=(const Calendar &right)
{ {
if (this != &right) { if (this != &right) {
uprv_arrayCopy(right.fFields, fFields, UCAL_FIELD_COUNT); uprv_arrayCopy(right.fFields, fFields, UCAL_FIELD_COUNT);
uprv_arrayCopy(right.fIsSet, fIsSet, UCAL_FIELD_COUNT);
uprv_arrayCopy(right.fStamp, fStamp, UCAL_FIELD_COUNT); uprv_arrayCopy(right.fStamp, fStamp, UCAL_FIELD_COUNT);
fTime = right.fTime; fTime = right.fTime;
fIsTimeSet = right.fIsTimeSet; fIsTimeSet = right.fIsTimeSet;
@ -828,10 +814,10 @@ Calendar::operator=(const Calendar &right)
fWeekendCease = right.fWeekendCease; fWeekendCease = right.fWeekendCease;
fWeekendCeaseMillis = right.fWeekendCeaseMillis; fWeekendCeaseMillis = right.fWeekendCeaseMillis;
fNextStamp = right.fNextStamp; fNextStamp = right.fNextStamp;
uprv_strncpy(validLocale, right.validLocale, sizeof(validLocale)); UErrorCode status = U_ZERO_ERROR;
uprv_strncpy(actualLocale, right.actualLocale, sizeof(actualLocale)); U_LOCALE_BASED(locBased, *this);
validLocale[sizeof(validLocale)-1] = 0; locBased.setLocaleIDs(right.validLocale, right.actualLocale, status);
actualLocale[sizeof(validLocale)-1] = 0; U_ASSERT(U_SUCCESS(status));
} }
return *this; return *this;
@ -1167,13 +1153,9 @@ Calendar::setTimeInMillis( double millis, UErrorCode& status ) {
fAreFieldsSet = fAreAllFieldsSet = false; fAreFieldsSet = fAreAllFieldsSet = false;
fIsTimeSet = fAreFieldsVirtuallySet = true; fIsTimeSet = fAreFieldsVirtuallySet = true;
for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) { uprv_memset(fFields, 0, sizeof(fFields));
fFields[i] = 0; uprv_memset(fStamp, kUnset, sizeof(fStamp));
fStamp[i] = kUnset; fNextStamp = kMinimumUserStamp;
fIsSet[i] = false;
}
} }
// ------------------------------------- // -------------------------------------
@ -1208,12 +1190,11 @@ Calendar::set(UCalendarDateFields field, int32_t value)
computeFields(ec); computeFields(ec);
} }
fFields[field] = value; fFields[field] = value;
/* Ensure that the fNextStamp value doesn't go pass max value for int32_t */ /* Ensure that the fNextStamp value doesn't go pass max value for int8_t */
if (fNextStamp == STAMP_MAX) { if (fNextStamp == STAMP_MAX) {
recalculateStamp(); recalculateStamp();
} }
fStamp[field] = fNextStamp++; fStamp[field] = fNextStamp++;
fIsSet[field] = true; // Remove later
fIsTimeSet = fAreFieldsSet = fAreFieldsVirtuallySet = false; fIsTimeSet = fAreFieldsSet = fAreFieldsVirtuallySet = false;
} }
@ -1270,11 +1251,9 @@ void Calendar::setRelatedYear(int32_t year)
void void
Calendar::clear() Calendar::clear()
{ {
for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) { uprv_memset(fFields, 0, sizeof(fFields));
fFields[i] = 0; // Must do this; other code depends on it uprv_memset(fStamp, kUnset, sizeof(fStamp));
fStamp[i] = kUnset; fNextStamp = kMinimumUserStamp;
fIsSet[i] = false; // Remove later
}
fIsTimeSet = fAreFieldsSet = fAreAllFieldsSet = fAreFieldsVirtuallySet = false; fIsTimeSet = fAreFieldsSet = fAreAllFieldsSet = fAreFieldsVirtuallySet = false;
// fTime is not 'cleared' - may be used if no fields are set. // fTime is not 'cleared' - may be used if no fields are set.
} }
@ -1296,12 +1275,10 @@ Calendar::clear(UCalendarDateFields field)
if (field == UCAL_MONTH) { if (field == UCAL_MONTH) {
fFields[UCAL_ORDINAL_MONTH] = 0; fFields[UCAL_ORDINAL_MONTH] = 0;
fStamp[UCAL_ORDINAL_MONTH] = kUnset; fStamp[UCAL_ORDINAL_MONTH] = kUnset;
fIsSet[UCAL_ORDINAL_MONTH] = false; // Remove later
} }
if (field == UCAL_ORDINAL_MONTH) { if (field == UCAL_ORDINAL_MONTH) {
fFields[UCAL_MONTH] = 0; fFields[UCAL_MONTH] = 0;
fStamp[UCAL_MONTH] = kUnset; fStamp[UCAL_MONTH] = kUnset;
fIsSet[UCAL_MONTH] = false; // Remove later
} }
fIsTimeSet = fAreFieldsSet = fAreAllFieldsSet = fAreFieldsVirtuallySet = false; fIsTimeSet = fAreFieldsSet = fAreAllFieldsSet = fAreFieldsVirtuallySet = false;
} }
@ -1434,10 +1411,8 @@ void Calendar::computeFields(UErrorCode &ec)
for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) { for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) {
if ((mask & 1) == 0) { if ((mask & 1) == 0) {
fStamp[i] = kInternallySet; fStamp[i] = kInternallySet;
fIsSet[i] = true; // Remove later
} else { } else {
fStamp[i] = kUnset; fStamp[i] = kUnset;
fIsSet[i] = false; // Remove later
} }
mask >>= 1; mask >>= 1;
} }
@ -1467,7 +1442,7 @@ void Calendar::computeFields(UErrorCode &ec)
//__FILE__, __LINE__, fFields[UCAL_JULIAN_DAY], localMillis); //__FILE__, __LINE__, fFields[UCAL_JULIAN_DAY], localMillis);
#endif #endif
computeGregorianAndDOWFields(fFields[UCAL_JULIAN_DAY], ec); computeGregorianFields(fFields[UCAL_JULIAN_DAY], ec);
// Call framework method to have subclass compute its fields. // Call framework method to have subclass compute its fields.
// These must include, at a minimum, MONTH, DAY_OF_MONTH, // These must include, at a minimum, MONTH, DAY_OF_MONTH,
@ -1538,32 +1513,6 @@ uint8_t Calendar::julianDayToDayOfWeek(int32_t julian)
return result; return result;
} }
/**
* Compute the Gregorian calendar year, month, and day of month from
* the given Julian day. These values are not stored in fields, but in
* member variables gregorianXxx. Also compute the DAY_OF_WEEK and
* DOW_LOCAL fields.
*/
void Calendar::computeGregorianAndDOWFields(int32_t julianDay, UErrorCode &ec)
{
computeGregorianFields(julianDay, ec);
if (U_FAILURE(ec)) {
return;
}
// Compute day of week: JD 0 = Monday
int32_t dow = julianDayToDayOfWeek(julianDay);
internalSet(UCAL_DAY_OF_WEEK,dow);
// Calculate 1-based localized day of week
int32_t dowLocal = dow - getFirstDayOfWeek() + 1;
if (dowLocal < 1) {
dowLocal += 7;
}
internalSet(UCAL_DOW_LOCAL,dowLocal);
fFields[UCAL_DOW_LOCAL] = dowLocal;
}
/** /**
* Compute the Gregorian calendar year, month, and day of month from the * Compute the Gregorian calendar year, month, and day of month from the
* Julian day. These values are not stored in fields, but in member * Julian day. These values are not stored in fields, but in member
@ -1575,14 +1524,13 @@ void Calendar::computeGregorianFields(int32_t julianDay, UErrorCode& ec) {
if (U_FAILURE(ec)) { if (U_FAILURE(ec)) {
return; return;
} }
int32_t gregorianDayOfWeekUnused;
if (uprv_add32_overflow( if (uprv_add32_overflow(
julianDay, -kEpochStartAsJulianDay, &julianDay)) { julianDay, -kEpochStartAsJulianDay, &julianDay)) {
ec = U_ILLEGAL_ARGUMENT_ERROR; ec = U_ILLEGAL_ARGUMENT_ERROR;
return; return;
} }
Grego::dayToFields(julianDay, fGregorianYear, fGregorianMonth, Grego::dayToFields(julianDay, fGregorianYear, fGregorianMonth,
fGregorianDayOfMonth, gregorianDayOfWeekUnused, fGregorianDayOfMonth,
fGregorianDayOfYear, ec); fGregorianDayOfYear, ec);
} }
@ -1610,8 +1558,19 @@ void Calendar::computeWeekFields(UErrorCode &ec) {
if(U_FAILURE(ec)) { if(U_FAILURE(ec)) {
return; return;
} }
// Compute day of week: JD 0 = Monday
int32_t dayOfWeek = julianDayToDayOfWeek(fFields[UCAL_JULIAN_DAY]);
internalSet(UCAL_DAY_OF_WEEK, dayOfWeek);
int32_t firstDayOfWeek = getFirstDayOfWeek();
// Calculate 1-based localized day of week
int32_t dowLocal = dayOfWeek - firstDayOfWeek + 1;
if (dowLocal < 1) {
dowLocal += 7;
}
internalSet(UCAL_DOW_LOCAL,dowLocal);
int32_t eyear = fFields[UCAL_EXTENDED_YEAR]; int32_t eyear = fFields[UCAL_EXTENDED_YEAR];
int32_t dayOfWeek = fFields[UCAL_DAY_OF_WEEK];
int32_t dayOfYear = fFields[UCAL_DAY_OF_YEAR]; int32_t dayOfYear = fFields[UCAL_DAY_OF_YEAR];
// WEEK_OF_YEAR start // WEEK_OF_YEAR start
@ -1624,10 +1583,11 @@ void Calendar::computeWeekFields(UErrorCode &ec) {
// first week of the next year. ASSUME that the year length is less than // first week of the next year. ASSUME that the year length is less than
// 7000 days. // 7000 days.
int32_t yearOfWeekOfYear = eyear; int32_t yearOfWeekOfYear = eyear;
int32_t relDow = (dayOfWeek + 7 - getFirstDayOfWeek()) % 7; // 0..6 int32_t relDow = (dayOfWeek + 7 - firstDayOfWeek) % 7; // 0..6
int32_t relDowJan1 = (dayOfWeek - dayOfYear + 7001 - getFirstDayOfWeek()) % 7; // 0..6 int32_t relDowJan1 = (dayOfWeek - dayOfYear + 7001 - firstDayOfWeek) % 7; // 0..6
int32_t woy = (dayOfYear - 1 + relDowJan1) / 7; // 0..53 int32_t woy = (dayOfYear - 1 + relDowJan1) / 7; // 0..53
if ((7 - relDowJan1) >= getMinimalDaysInFirstWeek()) { int32_t minimalDaysInFirstWeek = getMinimalDaysInFirstWeek();
if ((7 - relDowJan1) >= minimalDaysInFirstWeek) {
++woy; ++woy;
} }
@ -1639,11 +1599,13 @@ void Calendar::computeWeekFields(UErrorCode &ec) {
// to handle the case in which we are the first week of the // to handle the case in which we are the first week of the
// next year. // next year.
int32_t prevDoy = dayOfYear + handleGetYearLength(eyear - 1); int32_t prevDoy = dayOfYear + handleGetYearLength(eyear - 1, ec);
if(U_FAILURE(ec)) return;
woy = weekNumber(prevDoy, dayOfWeek); woy = weekNumber(prevDoy, dayOfWeek);
yearOfWeekOfYear--; yearOfWeekOfYear--;
} else { } else {
int32_t lastDoy = handleGetYearLength(eyear); int32_t lastDoy = handleGetYearLength(eyear, ec);
if(U_FAILURE(ec)) return;
// Fast check: For it to be week 1 of the next year, the DOY // Fast check: For it to be week 1 of the next year, the DOY
// must be on or after L-5, where L is yearLength(), then it // must be on or after L-5, where L is yearLength(), then it
// cannot possibly be week 1 of the next year: // cannot possibly be week 1 of the next year:
@ -1655,7 +1617,7 @@ void Calendar::computeWeekFields(UErrorCode &ec) {
if (lastRelDow < 0) { if (lastRelDow < 0) {
lastRelDow += 7; lastRelDow += 7;
} }
if (((6 - lastRelDow) >= getMinimalDaysInFirstWeek()) && if (((6 - lastRelDow) >= minimalDaysInFirstWeek) &&
((dayOfYear + 7 - relDow) > lastDoy)) { ((dayOfYear + 7 - relDow) > lastDoy)) {
woy = 1; woy = 1;
yearOfWeekOfYear++; yearOfWeekOfYear++;
@ -2946,7 +2908,7 @@ void Calendar::validateField(UCalendarDateFields field, UErrorCode &status) {
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return; return;
} }
validateField(field, 1, handleGetYearLength(y), status); validateField(field, 1, handleGetYearLength(y, status), status);
break; break;
case UCAL_DAY_OF_WEEK_IN_MONTH: case UCAL_DAY_OF_WEEK_IN_MONTH:
if (internalGet(field) == 0) { if (internalGet(field) == 0) {
@ -3607,9 +3569,19 @@ int32_t Calendar::handleComputeJulianDay(UCalendarDateFields bestField, UErrorCo
fprintf(stderr, "%s:%d - y=%d, y-1=%d doy%d, njd%d (C.F. %d)\n", fprintf(stderr, "%s:%d - y=%d, y-1=%d doy%d, njd%d (C.F. %d)\n",
__FILE__, __LINE__, year, year-1, testDate, julianDay+testDate, nextJulianDay); __FILE__, __LINE__, year, year-1, testDate, julianDay+testDate, nextJulianDay);
#endif #endif
if(julianDay+testDate > nextJulianDay) { // is it past Dec 31? (nextJulianDay is day BEFORE year+1's Jan 1) if (uprv_add32_overflow(julianDay, testDate, &testDate)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
if(testDate > nextJulianDay) { // is it past Dec 31? (nextJulianDay is day BEFORE year+1's Jan 1)
// Fire up the calculating engines.. retry YWOY = (year-1) // Fire up the calculating engines.. retry YWOY = (year-1)
julianDay = handleComputeMonthStart(year-1, 0, false, status); // jd before Jan 1 of previous year int32_t prevYear;
if (uprv_add32_overflow(year, -1, &prevYear)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
julianDay = handleComputeMonthStart(prevYear, 0, false, status); // jd before Jan 1 of previous year
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return 0; return 0;
} }
@ -3834,16 +3806,20 @@ int32_t Calendar::handleGetExtendedYearFromWeekFields(int32_t yearWoy, int32_t w
int32_t Calendar::handleGetMonthLength(int32_t extendedYear, int32_t month, UErrorCode& status) const int32_t Calendar::handleGetMonthLength(int32_t extendedYear, int32_t month, UErrorCode& status) const
{ {
return handleComputeMonthStart(extendedYear, month+1, true, status) - int32_t nextMonth;
if (uprv_add32_overflow(month, 1, &nextMonth)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
return handleComputeMonthStart(extendedYear, nextMonth, true, status) -
handleComputeMonthStart(extendedYear, month, true, status); handleComputeMonthStart(extendedYear, month, true, status);
} }
int32_t Calendar::handleGetYearLength(int32_t eyear) const int32_t Calendar::handleGetYearLength(int32_t eyear, UErrorCode& status) const
{ {
UErrorCode status = U_ZERO_ERROR;
int32_t result = handleComputeMonthStart(eyear+1, 0, false, status) - int32_t result = handleComputeMonthStart(eyear+1, 0, false, status) -
handleComputeMonthStart(eyear, 0, false, status); handleComputeMonthStart(eyear, 0, false, status);
U_ASSERT(U_SUCCESS(status)); if (U_FAILURE(status)) return 0;
return result; return result;
} }
@ -3882,7 +3858,7 @@ Calendar::getActualMaximum(UCalendarDateFields field, UErrorCode& status) const
} }
cal->setLenient(true); cal->setLenient(true);
cal->prepareGetActual(field,false,status); cal->prepareGetActual(field,false,status);
result = handleGetYearLength(cal->get(UCAL_EXTENDED_YEAR, status)); result = handleGetYearLength(cal->get(UCAL_EXTENDED_YEAR, status), status);
delete cal; delete cal;
} }
break; break;
@ -4141,7 +4117,7 @@ Calendar::setWeekData(const Locale& desiredLocale, const char *type, UErrorCode&
if (U_SUCCESS(status)) { if (U_SUCCESS(status)) {
U_LOCALE_BASED(locBased,*this); U_LOCALE_BASED(locBased,*this);
locBased.setLocaleIDs(ures_getLocaleByType(monthNames.getAlias(), ULOC_VALID_LOCALE, &status), locBased.setLocaleIDs(ures_getLocaleByType(monthNames.getAlias(), ULOC_VALID_LOCALE, &status),
ures_getLocaleByType(monthNames.getAlias(), ULOC_ACTUAL_LOCALE, &status)); ures_getLocaleByType(monthNames.getAlias(), ULOC_ACTUAL_LOCALE, &status), status);
} else { } else {
status = U_USING_FALLBACK_WARNING; status = U_USING_FALLBACK_WARNING;
return; return;
@ -4229,14 +4205,12 @@ Calendar::updateTime(UErrorCode& status)
Locale Locale
Calendar::getLocale(ULocDataLocaleType type, UErrorCode& status) const { Calendar::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this); return LocaleBased::getLocale(validLocale, actualLocale, type, status);
return locBased.getLocale(type, status);
} }
const char * const char *
Calendar::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const { Calendar::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this); return LocaleBased::getLocaleID(validLocale, actualLocale, type, status);
return locBased.getLocaleID(type, status);
} }
void void
@ -4245,7 +4219,7 @@ Calendar::recalculateStamp() {
int32_t currentValue; int32_t currentValue;
int32_t j, i; int32_t j, i;
fNextStamp = 1; fNextStamp = kInternallySet;
for (j = 0; j < UCAL_FIELD_COUNT; j++) { for (j = 0; j < UCAL_FIELD_COUNT; j++) {
currentValue = STAMP_MAX; currentValue = STAMP_MAX;

View File

@ -53,7 +53,6 @@ static const int32_t LIMITS[UCAL_FIELD_COUNT][4] = {
CECalendar::CECalendar(const Locale& aLocale, UErrorCode& success) CECalendar::CECalendar(const Locale& aLocale, UErrorCode& success)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success) : Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success)
{ {
setTimeInMillis(getNow(), success);
} }
CECalendar::CECalendar (const CECalendar& other) CECalendar::CECalendar (const CECalendar& other)
@ -65,13 +64,6 @@ CECalendar::~CECalendar()
{ {
} }
CECalendar&
CECalendar::operator=(const CECalendar& right)
{
Calendar::operator=(right);
return *this;
}
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Calendar framework // Calendar framework
//------------------------------------------------------------------------- //-------------------------------------------------------------------------

View File

@ -82,13 +82,6 @@ protected:
*/ */
virtual ~CECalendar(); virtual ~CECalendar();
/**
* Default assignment operator
* @param right Calendar object to be copied
* @internal
*/
CECalendar& operator=(const CECalendar& right);
protected: protected:
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Calendar framework // Calendar framework

View File

@ -130,7 +130,6 @@ ChineseCalendar::ChineseCalendar(const Locale& aLocale, UErrorCode& success)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success), : Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success),
hasLeapMonthBetweenWinterSolstices(false) hasLeapMonthBetweenWinterSolstices(false)
{ {
setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly.
} }
ChineseCalendar::ChineseCalendar(const ChineseCalendar& other) : Calendar(other) { ChineseCalendar::ChineseCalendar(const ChineseCalendar& other) : Calendar(other) {
@ -219,7 +218,9 @@ int32_t ChineseCalendar::handleGetExtendedYear(UErrorCode& status) {
} }
int32_t year; int32_t year;
if (newestStamp(UCAL_ERA, UCAL_YEAR, kUnset) <= fStamp[UCAL_EXTENDED_YEAR]) { // if UCAL_EXTENDED_YEAR is not older than UCAL_ERA nor UCAL_YEAR
if (newerField(UCAL_EXTENDED_YEAR, newerField(UCAL_ERA, UCAL_YEAR)) ==
UCAL_EXTENDED_YEAR) {
year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1 year = internalGet(UCAL_EXTENDED_YEAR, 1); // Default to year 1
} else { } else {
// adjust to the instance specific epoch // adjust to the instance specific epoch
@ -252,11 +253,16 @@ int32_t ChineseCalendar::handleGetExtendedYear(UErrorCode& status) {
* @stable ICU 2.8 * @stable ICU 2.8
*/ */
int32_t ChineseCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month, UErrorCode& status) const { int32_t ChineseCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month, UErrorCode& status) const {
bool isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) == 1;
return handleGetMonthLengthWithLeap(extendedYear, month, isLeapMonth, status);
}
int32_t ChineseCalendar::handleGetMonthLengthWithLeap(int32_t extendedYear, int32_t month, bool leap, UErrorCode& status) const {
const Setting setting = getSetting(status); const Setting setting = getSetting(status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return 0; return 0;
} }
int32_t thisStart = handleComputeMonthStart(extendedYear, month, true, status); int32_t thisStart = handleComputeMonthStartWithLeap(extendedYear, month, leap, status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return 0; return 0;
} }
@ -332,18 +338,24 @@ struct MonthInfo computeMonthInfo(
* @stable ICU 2.8 * @stable ICU 2.8
*/ */
int64_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool useMonth, UErrorCode& status) const { int64_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, UBool useMonth, UErrorCode& status) const {
bool isLeapMonth = false;
if (useMonth) {
isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) != 0;
}
return handleComputeMonthStartWithLeap(eyear, month, isLeapMonth, status);
}
int64_t ChineseCalendar::handleComputeMonthStartWithLeap(int32_t eyear, int32_t month, bool isLeapMonth, UErrorCode& status) const {
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return 0; return 0;
} }
// If the month is out of range, adjust it into range, and // If the month is out of range, adjust it into range, and
// modify the extended year value accordingly. // modify the extended year value accordingly.
if (month < 0 || month > 11) { if (month < 0 || month > 11) {
double m = month; if (uprv_add32_overflow(eyear, ClockMath::floorDivide(month, 12, &month), &eyear)) {
if (uprv_add32_overflow(eyear, ClockMath::floorDivide(m, 12.0, &m), &eyear)) {
status = U_ILLEGAL_ARGUMENT_ERROR; status = U_ILLEGAL_ARGUMENT_ERROR;
return 0; return 0;
} }
month = static_cast<int32_t>(m);
} }
const Setting setting = getSetting(status); const Setting setting = getSetting(status);
@ -362,19 +374,9 @@ int64_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U
return 0; return 0;
} }
// Ignore IS_LEAP_MONTH field if useMonth is false int32_t newMonthYear = Grego::dayToYear(newMoon, status);
bool isLeapMonth = false;
if (useMonth) {
isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) != 0;
}
int32_t unusedMonth; struct MonthInfo monthInfo = computeMonthInfo(setting, newMonthYear, newMoon, status);
int32_t unusedDayOfWeek;
int32_t unusedDayOfMonth;
int32_t unusedDayOfYear;
Grego::dayToFields(newMoon, gyear, unusedMonth, unusedDayOfWeek, unusedDayOfMonth, unusedDayOfYear, status);
struct MonthInfo monthInfo = computeMonthInfo(setting, gyear, newMoon, status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return 0; return 0;
} }
@ -794,6 +796,9 @@ struct MonthInfo computeMonthInfo(
solsticeBefore = solsticeAfter; solsticeBefore = solsticeAfter;
solsticeAfter = winterSolstice(setting, gyear + 1, status); solsticeAfter = winterSolstice(setting, gyear + 1, status);
} }
if (!(solsticeBefore <= days && days < solsticeAfter)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
}
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return output; return output;
} }
@ -1043,7 +1048,12 @@ void ChineseCalendar::offsetMonth(int32_t newMoon, int32_t dayOfMonth, int32_t d
} }
// Find the target dayOfMonth // Find the target dayOfMonth
int32_t jd = newMoon + kEpochStartAsJulianDay - 1 + dayOfMonth; int32_t jd;
if (uprv_add32_overflow(newMoon, kEpochStartAsJulianDay - 1, &jd) ||
uprv_add32_overflow(jd, dayOfMonth, &jd)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
// Pin the dayOfMonth. In this calendar all months are 29 or 30 days // Pin the dayOfMonth. In this calendar all months are 29 or 30 days
// so pinning just means handling dayOfMonth 30. // so pinning just means handling dayOfMonth 30.
@ -1182,6 +1192,27 @@ ChineseCalendar::Setting ChineseCalendar::getSetting(UErrorCode&) const {
}; };
} }
int32_t
ChineseCalendar::getActualMaximum(UCalendarDateFields field, UErrorCode& status) const
{
if (U_FAILURE(status)) {
return 0;
}
if (field == UCAL_DATE) {
LocalPointer<ChineseCalendar> cal(clone(), status);
if(U_FAILURE(status)) {
return 0;
}
cal->setLenient(true);
cal->prepareGetActual(field,false,status);
int32_t year = cal->get(UCAL_EXTENDED_YEAR, status);
int32_t month = cal->get(UCAL_MONTH, status);
bool leap = cal->get(UCAL_IS_LEAP_MONTH, status) != 0;
return handleGetMonthLengthWithLeap(year, month, leap, status);
}
return Calendar::getActualMaximum(field, status);
}
U_NAMESPACE_END U_NAMESPACE_END
#endif #endif

View File

@ -194,6 +194,10 @@ class U_I18N_API ChineseCalendar : public Calendar {
virtual void handleComputeFields(int32_t julianDay, UErrorCode &status) override; virtual void handleComputeFields(int32_t julianDay, UErrorCode &status) override;
virtual const UFieldResolutionTable* getFieldResolutionTable() const override; virtual const UFieldResolutionTable* getFieldResolutionTable() const override;
private:
int32_t handleGetMonthLengthWithLeap(int32_t extendedYear, int32_t month, bool isLeap, UErrorCode& status) const;
int64_t handleComputeMonthStartWithLeap(int32_t eyear, int32_t month, bool isLeap, UErrorCode& status) const;
public: public:
virtual void add(UCalendarDateFields field, int32_t amount, UErrorCode &status) override; virtual void add(UCalendarDateFields field, int32_t amount, UErrorCode &status) override;
virtual void add(EDateFields field, int32_t amount, UErrorCode &status) override; virtual void add(EDateFields field, int32_t amount, UErrorCode &status) override;
@ -254,6 +258,8 @@ class U_I18N_API ChineseCalendar : public Calendar {
*/ */
virtual const char * getType() const override; virtual const char * getType() const override;
virtual int32_t getActualMaximum(UCalendarDateFields field, UErrorCode& status) const override;
struct Setting { struct Setting {
int32_t epochYear; int32_t epochYear;
const TimeZone* zoneAstroCalc; const TimeZone* zoneAstroCalc;

View File

@ -613,18 +613,24 @@ CollationRuleParser::parseSetting(UErrorCode &errorCode) {
return; return;
} }
// localeID minus all keywords // localeID minus all keywords
char baseID[ULOC_FULLNAME_CAPACITY]; CharString baseID = ulocimp_getBaseName(localeID.toStringPiece(), errorCode);
int32_t length = uloc_getBaseName(localeID.data(), baseID, ULOC_FULLNAME_CAPACITY, &errorCode); if (U_FAILURE(errorCode)) {
if(U_FAILURE(errorCode) || length >= ULOC_KEYWORDS_CAPACITY) {
errorCode = U_ZERO_ERROR; errorCode = U_ZERO_ERROR;
setParseError("expected language tag in [import langTag]", errorCode); setParseError("expected language tag in [import langTag]", errorCode);
return; return;
} }
if(length == 0) { if (baseID.isEmpty()) {
uprv_strcpy(baseID, "root"); baseID.copyFrom("root", errorCode);
} else if(*baseID == '_') { } else if (baseID[0] == '_') {
uprv_memmove(baseID + 3, baseID, length + 1); // CharString doesn't have any insert() method, only append().
uprv_memcpy(baseID, "und", 3); constexpr char und[] = "und";
constexpr int32_t length = sizeof und - 1;
int32_t dummy;
char* tail = baseID.getAppendBuffer(length, length, dummy, errorCode);
char* head = baseID.data();
uprv_memmove(head + length, head, baseID.length());
uprv_memcpy(head, und, length);
baseID.append(tail, length, errorCode);
} }
// @collation=type, or length=0 if not specified // @collation=type, or length=0 if not specified
CharString collationType = ulocimp_getKeywordValue(localeID.data(), "collation", errorCode); CharString collationType = ulocimp_getKeywordValue(localeID.data(), "collation", errorCode);
@ -637,7 +643,7 @@ CollationRuleParser::parseSetting(UErrorCode &errorCode) {
setParseError("[import langTag] is not supported", errorCode); setParseError("[import langTag] is not supported", errorCode);
} else { } else {
UnicodeString importedRules; UnicodeString importedRules;
importer->getRules(baseID, importer->getRules(baseID.data(),
!collationType.isEmpty() ? collationType.data() : "standard", !collationType.isEmpty() ? collationType.data() : "standard",
importedRules, errorReason, errorCode); importedRules, errorReason, errorCode);
if(U_FAILURE(errorCode)) { if(U_FAILURE(errorCode)) {

View File

@ -40,6 +40,7 @@
#if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL) #if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL)
#include <stdio.h> #include <stdio.h>
#endif #endif
#include <typeinfo>
// ***************************************************************************** // *****************************************************************************
// class DateFormat // class DateFormat
@ -279,9 +280,8 @@ UnicodeString&
DateFormat::format(UDate date, UnicodeString& appendTo, FieldPosition& fieldPosition) const { DateFormat::format(UDate date, UnicodeString& appendTo, FieldPosition& fieldPosition) const {
if (fCalendar != nullptr) { if (fCalendar != nullptr) {
UErrorCode ec = U_ZERO_ERROR; UErrorCode ec = U_ZERO_ERROR;
const auto* calType = fCalendar->getType();
// Avoid a heap allocation and corresponding free for the common case // Avoid a heap allocation and corresponding free for the common case
if (uprv_strcmp(calType, "gregorian") == 0) { if (typeid(*fCalendar) == typeid(GregorianCalendar)) {
GregorianCalendar cal(*static_cast<GregorianCalendar*>(fCalendar)); GregorianCalendar cal(*static_cast<GregorianCalendar*>(fCalendar));
cal.setTime(date, ec); cal.setTime(date, ec);
if (U_SUCCESS(ec)) { if (U_SUCCESS(ec)) {
@ -309,9 +309,8 @@ DateFormat::format(UDate date, UnicodeString& appendTo, FieldPositionIterator* p
UErrorCode& status) const { UErrorCode& status) const {
if (fCalendar != nullptr) { if (fCalendar != nullptr) {
UErrorCode ec = U_ZERO_ERROR; UErrorCode ec = U_ZERO_ERROR;
const auto* calType = fCalendar->getType();
// Avoid a heap allocation and corresponding free for the common case // Avoid a heap allocation and corresponding free for the common case
if (uprv_strcmp(calType, "gregorian") == 0) { if (typeid(*fCalendar) == typeid(GregorianCalendar)) {
GregorianCalendar cal(*static_cast<GregorianCalendar*>(fCalendar)); GregorianCalendar cal(*static_cast<GregorianCalendar*>(fCalendar));
cal.setTime(date, ec); cal.setTime(date, ec);
if (U_SUCCESS(ec)) { if (U_SUCCESS(ec)) {

View File

@ -118,7 +118,6 @@ DecimalFormatSymbols::DecimalFormatSymbols(const Locale& loc, const NumberingSys
DecimalFormatSymbols::DecimalFormatSymbols() DecimalFormatSymbols::DecimalFormatSymbols()
: UObject(), locale(Locale::getRoot()) { : UObject(), locale(Locale::getRoot()) {
*validLocale = *actualLocale = 0;
initialize(); initialize();
} }
@ -136,6 +135,8 @@ DecimalFormatSymbols::createWithLastResortData(UErrorCode& status) {
DecimalFormatSymbols::~DecimalFormatSymbols() DecimalFormatSymbols::~DecimalFormatSymbols()
{ {
delete actualLocale;
delete validLocale;
} }
// ------------------------------------- // -------------------------------------
@ -163,8 +164,12 @@ DecimalFormatSymbols::operator=(const DecimalFormatSymbols& rhs)
currencySpcAfterSym[i].fastCopyFrom(rhs.currencySpcAfterSym[i]); currencySpcAfterSym[i].fastCopyFrom(rhs.currencySpcAfterSym[i]);
} }
locale = rhs.locale; locale = rhs.locale;
uprv_strcpy(validLocale, rhs.validLocale);
uprv_strcpy(actualLocale, rhs.actualLocale); UErrorCode status = U_ZERO_ERROR;
U_LOCALE_BASED(locBased, *this);
locBased.setLocaleIDs(rhs.validLocale, rhs.actualLocale, status);
U_ASSERT(U_SUCCESS(status));
fIsCustomCurrencySymbol = rhs.fIsCustomCurrencySymbol; fIsCustomCurrencySymbol = rhs.fIsCustomCurrencySymbol;
fIsCustomIntlCurrencySymbol = rhs.fIsCustomIntlCurrencySymbol; fIsCustomIntlCurrencySymbol = rhs.fIsCustomIntlCurrencySymbol;
fCodePointZero = rhs.fCodePointZero; fCodePointZero = rhs.fCodePointZero;
@ -203,8 +208,8 @@ DecimalFormatSymbols::operator==(const DecimalFormatSymbols& that) const
} }
// No need to check fCodePointZero since it is based on fSymbols // No need to check fCodePointZero since it is based on fSymbols
return locale == that.locale && return locale == that.locale &&
uprv_strcmp(validLocale, that.validLocale) == 0 && LocaleBased::equalIDs(actualLocale, that.actualLocale) &&
uprv_strcmp(actualLocale, that.actualLocale) == 0; LocaleBased::equalIDs(validLocale, that.validLocale);
} }
// ------------------------------------- // -------------------------------------
@ -353,7 +358,6 @@ DecimalFormatSymbols::initialize(const Locale& loc, UErrorCode& status,
UBool useLastResortData, const NumberingSystem* ns) UBool useLastResortData, const NumberingSystem* ns)
{ {
if (U_FAILURE(status)) { return; } if (U_FAILURE(status)) { return; }
*validLocale = *actualLocale = 0;
// First initialize all the symbols to the fallbacks for anything we can't find // First initialize all the symbols to the fallbacks for anything we can't find
initialize(); initialize();
@ -409,7 +413,8 @@ DecimalFormatSymbols::initialize(const Locale& loc, UErrorCode& status,
ULOC_VALID_LOCALE, &status), ULOC_VALID_LOCALE, &status),
ures_getLocaleByType( ures_getLocaleByType(
numberElementsRes.getAlias(), numberElementsRes.getAlias(),
ULOC_ACTUAL_LOCALE, &status)); ULOC_ACTUAL_LOCALE, &status),
status);
// Now load the rest of the data from the data sink. // Now load the rest of the data from the data sink.
// Start with loading this nsName if it is not Latin. // Start with loading this nsName if it is not Latin.
@ -568,8 +573,7 @@ void DecimalFormatSymbols::setCurrency(const char16_t* currency, UErrorCode& sta
Locale Locale
DecimalFormatSymbols::getLocale(ULocDataLocaleType type, UErrorCode& status) const { DecimalFormatSymbols::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this); return LocaleBased::getLocale(validLocale, actualLocale, type, status);
return locBased.getLocale(type, status);
} }
const UnicodeString& const UnicodeString&

View File

@ -402,9 +402,8 @@ void
DateFormatSymbols::copyData(const DateFormatSymbols& other) { DateFormatSymbols::copyData(const DateFormatSymbols& other) {
UErrorCode status = U_ZERO_ERROR; UErrorCode status = U_ZERO_ERROR;
U_LOCALE_BASED(locBased, *this); U_LOCALE_BASED(locBased, *this);
locBased.setLocaleIDs( locBased.setLocaleIDs(other.validLocale, other.actualLocale, status);
other.getLocale(ULOC_VALID_LOCALE, status), U_ASSERT(U_SUCCESS(status));
other.getLocale(ULOC_ACTUAL_LOCALE, status));
assignArray(fEras, fErasCount, other.fEras, other.fErasCount); assignArray(fEras, fErasCount, other.fEras, other.fErasCount);
assignArray(fEraNames, fEraNamesCount, other.fEraNames, other.fEraNamesCount); assignArray(fEraNames, fEraNamesCount, other.fEraNames, other.fEraNamesCount);
assignArray(fNarrowEras, fNarrowErasCount, other.fNarrowEras, other.fNarrowErasCount); assignArray(fNarrowEras, fNarrowErasCount, other.fNarrowEras, other.fNarrowErasCount);
@ -497,6 +496,8 @@ DateFormatSymbols& DateFormatSymbols::operator=(const DateFormatSymbols& other)
DateFormatSymbols::~DateFormatSymbols() DateFormatSymbols::~DateFormatSymbols()
{ {
dispose(); dispose();
delete actualLocale;
delete validLocale;
} }
void DateFormatSymbols::dispose() void DateFormatSymbols::dispose()
@ -536,6 +537,10 @@ void DateFormatSymbols::dispose()
delete[] fStandaloneWideDayPeriods; delete[] fStandaloneWideDayPeriods;
delete[] fStandaloneNarrowDayPeriods; delete[] fStandaloneNarrowDayPeriods;
delete actualLocale;
actualLocale = nullptr;
delete validLocale;
validLocale = nullptr;
disposeZoneStrings(); disposeZoneStrings();
} }
@ -2302,7 +2307,7 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError
// of it that we need except for the time-zone and localized-pattern data, which // of it that we need except for the time-zone and localized-pattern data, which
// are stored in a separate file // are stored in a separate file
locBased.setLocaleIDs(ures_getLocaleByType(cb.getAlias(), ULOC_VALID_LOCALE, &status), locBased.setLocaleIDs(ures_getLocaleByType(cb.getAlias(), ULOC_VALID_LOCALE, &status),
ures_getLocaleByType(cb.getAlias(), ULOC_ACTUAL_LOCALE, &status)); ures_getLocaleByType(cb.getAlias(), ULOC_ACTUAL_LOCALE, &status), status);
// Load eras // Load eras
initField(&fEras, fErasCount, calendarSink, buildResourcePath(path, gErasTag, gNamesAbbrTag, status), status); initField(&fEras, fErasCount, calendarSink, buildResourcePath(path, gErasTag, gNamesAbbrTag, status), status);
@ -2528,8 +2533,7 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError
Locale Locale
DateFormatSymbols::getLocale(ULocDataLocaleType type, UErrorCode& status) const { DateFormatSymbols::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this); return LocaleBased::getLocale(validLocale, actualLocale, type, status);
return locBased.getLocale(type, status);
} }
U_NAMESPACE_END U_NAMESPACE_END

View File

@ -305,8 +305,9 @@ void EraRules::initCurrentEra() {
localMillis += (rawOffset + dstOffset); localMillis += (rawOffset + dstOffset);
} }
int year, month0, dom, dow, doy, mid; int32_t year, mid;
Grego::timeToFields(localMillis, year, month0, dom, dow, doy, mid, ec); int8_t month0, dom;
Grego::timeToFields(localMillis, year, month0, dom, mid, ec);
if (U_FAILURE(ec)) return; if (U_FAILURE(ec)) return;
int currentEncodedDate = encodeDate(year, month0 + 1 /* changes to 1-base */, dom); int currentEncodedDate = encodeDate(year, month0 + 1 /* changes to 1-base */, dom);
int eraIdx = numEras - 1; int eraIdx = numEras - 1;

View File

@ -24,6 +24,7 @@
#include "utypeinfo.h" // for 'typeid' to work #include "utypeinfo.h" // for 'typeid' to work
#include "unicode/utypes.h" #include "unicode/utypes.h"
#include "charstr.h"
#ifndef U_I18N_IMPLEMENTATION #ifndef U_I18N_IMPLEMENTATION
#error U_I18N_IMPLEMENTATION not set - must be set for all ICU source files in i18n/ - see https://unicode-org.github.io/icu/userguide/howtouseicu #error U_I18N_IMPLEMENTATION not set - must be set for all ICU source files in i18n/ - see https://unicode-org.github.io/icu/userguide/howtouseicu
@ -72,13 +73,14 @@ FieldPosition::clone() const {
Format::Format() Format::Format()
: UObject() : UObject()
{ {
*validLocale = *actualLocale = 0;
} }
// ------------------------------------- // -------------------------------------
Format::~Format() Format::~Format()
{ {
delete actualLocale;
delete validLocale;
} }
// ------------------------------------- // -------------------------------------
@ -97,8 +99,10 @@ Format&
Format::operator=(const Format& that) Format::operator=(const Format& that)
{ {
if (this != &that) { if (this != &that) {
uprv_strcpy(validLocale, that.validLocale); UErrorCode status = U_ZERO_ERROR;
uprv_strcpy(actualLocale, that.actualLocale); U_LOCALE_BASED(locBased, *this);
locBased.setLocaleIDs(that.validLocale, that.actualLocale, status);
U_ASSERT(U_SUCCESS(status));
} }
return *this; return *this;
} }
@ -196,20 +200,20 @@ void Format::syntaxError(const UnicodeString& pattern,
Locale Locale
Format::getLocale(ULocDataLocaleType type, UErrorCode& status) const { Format::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this); return LocaleBased::getLocale(validLocale, actualLocale, type, status);
return locBased.getLocale(type, status);
} }
const char * const char *
Format::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const { Format::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this); return LocaleBased::getLocaleID(validLocale,actualLocale, type, status);
return locBased.getLocaleID(type, status);
} }
void void
Format::setLocaleIDs(const char* valid, const char* actual) { Format::setLocaleIDs(const char* valid, const char* actual) {
U_LOCALE_BASED(locBased, *this); U_LOCALE_BASED(locBased, *this);
locBased.setLocaleIDs(valid, actual); UErrorCode status = U_ZERO_ERROR;
locBased.setLocaleIDs(valid, actual, status);
U_ASSERT(U_SUCCESS(status));
} }
U_NAMESPACE_END U_NAMESPACE_END

View File

@ -193,6 +193,11 @@ ucfpos_close(UConstrainedFieldPosition* ptr) {
} }
// -Wreturn-local-addr first found in https://gcc.gnu.org/onlinedocs/gcc-4.8.5/gcc/Warning-Options.html#Warning-Options
#if U_GCC_MAJOR_MINOR >= 409
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-local-addr"
#endif
U_CAPI const char16_t* U_EXPORT2 U_CAPI const char16_t* U_EXPORT2
ufmtval_getString( ufmtval_getString(
const UFormattedValue* ufmtval, const UFormattedValue* ufmtval,
@ -213,6 +218,9 @@ ufmtval_getString(
// defined to return memory owned by the ufmtval argument. // defined to return memory owned by the ufmtval argument.
return readOnlyAlias.getBuffer(); return readOnlyAlias.getBuffer();
} }
#if U_GCC_MAJOR_MINOR >= 409
#pragma GCC diagnostic pop
#endif
U_CAPI UBool U_EXPORT2 U_CAPI UBool U_EXPORT2

View File

@ -147,6 +147,7 @@ UOBJECT_DEFINE_RTTI_IMPLEMENTATION(GregorianCalendar)
// in Java, -12219292800000L // in Java, -12219292800000L
//const UDate GregorianCalendar::kPapalCutover = -12219292800000L; //const UDate GregorianCalendar::kPapalCutover = -12219292800000L;
static const uint32_t kCutoverJulianDay = 2299161; static const uint32_t kCutoverJulianDay = 2299161;
static const int32_t kDefaultCutoverYear = 1582;
static const UDate kPapalCutover = (2299161.0 - kEpochStartAsJulianDay) * U_MILLIS_PER_DAY; static const UDate kPapalCutover = (2299161.0 - kEpochStartAsJulianDay) * U_MILLIS_PER_DAY;
//static const UDate kPapalCutoverJulian = (2299161.0 - kEpochStartAsJulianDay); //static const UDate kPapalCutoverJulian = (2299161.0 - kEpochStartAsJulianDay);
@ -155,7 +156,7 @@ static const UDate kPapalCutover = (2299161.0 - kEpochStartAsJulianDay) * U_MILL
GregorianCalendar::GregorianCalendar(UErrorCode& status) GregorianCalendar::GregorianCalendar(UErrorCode& status)
: Calendar(status), : Calendar(status),
fGregorianCutover(kPapalCutover), fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582), fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(kDefaultCutoverYear),
fIsGregorian(true), fInvertGregorian(false) fIsGregorian(true), fInvertGregorian(false)
{ {
setTimeInMillis(getNow(), status); setTimeInMillis(getNow(), status);
@ -164,34 +165,22 @@ fIsGregorian(true), fInvertGregorian(false)
// ------------------------------------- // -------------------------------------
GregorianCalendar::GregorianCalendar(TimeZone* zone, UErrorCode& status) GregorianCalendar::GregorianCalendar(TimeZone* zone, UErrorCode& status)
: Calendar(zone, Locale::getDefault(), status), : GregorianCalendar(zone, Locale::getDefault(), status)
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fIsGregorian(true), fInvertGregorian(false)
{ {
setTimeInMillis(getNow(), status);
} }
// ------------------------------------- // -------------------------------------
GregorianCalendar::GregorianCalendar(const TimeZone& zone, UErrorCode& status) GregorianCalendar::GregorianCalendar(const TimeZone& zone, UErrorCode& status)
: Calendar(zone, Locale::getDefault(), status), : GregorianCalendar(zone, Locale::getDefault(), status)
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fIsGregorian(true), fInvertGregorian(false)
{ {
setTimeInMillis(getNow(), status);
} }
// ------------------------------------- // -------------------------------------
GregorianCalendar::GregorianCalendar(const Locale& aLocale, UErrorCode& status) GregorianCalendar::GregorianCalendar(const Locale& aLocale, UErrorCode& status)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, status), : GregorianCalendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, status)
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fIsGregorian(true), fInvertGregorian(false)
{ {
setTimeInMillis(getNow(), status);
} }
// ------------------------------------- // -------------------------------------
@ -200,7 +189,7 @@ GregorianCalendar::GregorianCalendar(TimeZone* zone, const Locale& aLocale,
UErrorCode& status) UErrorCode& status)
: Calendar(zone, aLocale, status), : Calendar(zone, aLocale, status),
fGregorianCutover(kPapalCutover), fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582), fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(kDefaultCutoverYear),
fIsGregorian(true), fInvertGregorian(false) fIsGregorian(true), fInvertGregorian(false)
{ {
setTimeInMillis(getNow(), status); setTimeInMillis(getNow(), status);
@ -212,7 +201,7 @@ GregorianCalendar::GregorianCalendar(const TimeZone& zone, const Locale& aLocale
UErrorCode& status) UErrorCode& status)
: Calendar(zone, aLocale, status), : Calendar(zone, aLocale, status),
fGregorianCutover(kPapalCutover), fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582), fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(kDefaultCutoverYear),
fIsGregorian(true), fInvertGregorian(false) fIsGregorian(true), fInvertGregorian(false)
{ {
setTimeInMillis(getNow(), status); setTimeInMillis(getNow(), status);
@ -224,7 +213,7 @@ GregorianCalendar::GregorianCalendar(int32_t year, int32_t month, int32_t date,
UErrorCode& status) UErrorCode& status)
: Calendar(TimeZone::createDefault(), Locale::getDefault(), status), : Calendar(TimeZone::createDefault(), Locale::getDefault(), status),
fGregorianCutover(kPapalCutover), fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582), fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(kDefaultCutoverYear),
fIsGregorian(true), fInvertGregorian(false) fIsGregorian(true), fInvertGregorian(false)
{ {
set(UCAL_ERA, AD); set(UCAL_ERA, AD);
@ -237,15 +226,8 @@ GregorianCalendar::GregorianCalendar(int32_t year, int32_t month, int32_t date,
GregorianCalendar::GregorianCalendar(int32_t year, int32_t month, int32_t date, GregorianCalendar::GregorianCalendar(int32_t year, int32_t month, int32_t date,
int32_t hour, int32_t minute, UErrorCode& status) int32_t hour, int32_t minute, UErrorCode& status)
: Calendar(TimeZone::createDefault(), Locale::getDefault(), status), : GregorianCalendar(year, month, date, status)
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fIsGregorian(true), fInvertGregorian(false)
{ {
set(UCAL_ERA, AD);
set(UCAL_YEAR, year);
set(UCAL_MONTH, month);
set(UCAL_DATE, date);
set(UCAL_HOUR_OF_DAY, hour); set(UCAL_HOUR_OF_DAY, hour);
set(UCAL_MINUTE, minute); set(UCAL_MINUTE, minute);
} }
@ -255,17 +237,8 @@ GregorianCalendar::GregorianCalendar(int32_t year, int32_t month, int32_t date,
GregorianCalendar::GregorianCalendar(int32_t year, int32_t month, int32_t date, GregorianCalendar::GregorianCalendar(int32_t year, int32_t month, int32_t date,
int32_t hour, int32_t minute, int32_t second, int32_t hour, int32_t minute, int32_t second,
UErrorCode& status) UErrorCode& status)
: Calendar(TimeZone::createDefault(), Locale::getDefault(), status), : GregorianCalendar(year, month, date, hour, minute, status)
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fIsGregorian(true), fInvertGregorian(false)
{ {
set(UCAL_ERA, AD);
set(UCAL_YEAR, year);
set(UCAL_MONTH, month);
set(UCAL_DATE, date);
set(UCAL_HOUR_OF_DAY, hour);
set(UCAL_MINUTE, minute);
set(UCAL_SECOND, second); set(UCAL_SECOND, second);
} }
@ -601,7 +574,8 @@ int32_t GregorianCalendar::handleGetMonthLength(int32_t extendedYear, int32_t mo
return isLeapYear(extendedYear) ? kLeapMonthLength[month] : kMonthLength[month]; return isLeapYear(extendedYear) ? kLeapMonthLength[month] : kMonthLength[month];
} }
int32_t GregorianCalendar::handleGetYearLength(int32_t eyear) const { int32_t GregorianCalendar::handleGetYearLength(int32_t eyear, UErrorCode& status) const {
if (U_FAILURE(status)) return 0;
return isLeapYear(eyear) ? 366 : 365; return isLeapYear(eyear) ? 366 : 365;
} }
@ -871,13 +845,14 @@ GregorianCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& s
} }
if (month == UCAL_JANUARY) { if (month == UCAL_JANUARY) {
if (woy >= 52) { if (woy >= 52) {
isoDoy += handleGetYearLength(isoYear); isoDoy += handleGetYearLength(isoYear, status);
} }
} else { } else {
if (woy == 1) { if (woy == 1) {
isoDoy -= handleGetYearLength(isoYear - 1); isoDoy -= handleGetYearLength(isoYear - 1, status);
} }
} }
if (U_FAILURE(status)) return;
if (uprv_add32_overflow(woy, amount, &woy)) { if (uprv_add32_overflow(woy, amount, &woy)) {
status = U_ILLEGAL_ARGUMENT_ERROR; status = U_ILLEGAL_ARGUMENT_ERROR;
return; return;
@ -890,7 +865,8 @@ GregorianCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& s
// days at the end of the year are going to fall into // days at the end of the year are going to fall into
// week 1 of the next year, we drop the last week by // week 1 of the next year, we drop the last week by
// subtracting 7 from the last day of the year. // subtracting 7 from the last day of the year.
int32_t lastDoy = handleGetYearLength(isoYear); int32_t lastDoy = handleGetYearLength(isoYear, status);
if (U_FAILURE(status)) return;
int32_t lastRelDow = (lastDoy - isoDoy + internalGet(UCAL_DAY_OF_WEEK) - int32_t lastRelDow = (lastDoy - isoDoy + internalGet(UCAL_DAY_OF_WEEK) -
getFirstDayOfWeek()) % 7; getFirstDayOfWeek()) % 7;
if (lastRelDow < 0) lastRelDow += 7; if (lastRelDow < 0) lastRelDow += 7;
@ -1186,14 +1162,10 @@ int32_t GregorianCalendar::handleGetExtendedYear(UErrorCode& status) {
int32_t year = kEpochYear; int32_t year = kEpochYear;
// year field to use // year field to use
int32_t yearField = UCAL_EXTENDED_YEAR;
// There are three separate fields which could be used to // There are three separate fields which could be used to
// derive the proper year. Use the one most recently set. // derive the proper year. Use the one most recently set.
if (fStamp[yearField] < fStamp[UCAL_YEAR]) UCalendarDateFields yearField = newerField(
yearField = UCAL_YEAR; newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR), UCAL_YEAR_WOY);
if (fStamp[yearField] < fStamp[UCAL_YEAR_WOY])
yearField = UCAL_YEAR_WOY;
// based on the "best" year field, get the year // based on the "best" year field, get the year
switch(yearField) { switch(yearField) {

View File

@ -117,57 +117,110 @@ int64_t Grego::fieldsToDay(int32_t year, int32_t month, int32_t dom) {
return julian - JULIAN_1970_CE; // JD => epoch day return julian - JULIAN_1970_CE; // JD => epoch day
} }
void Grego::dayToFields(int32_t day, int32_t& year, int32_t& month, void Grego::dayToFields(int32_t day, int32_t& year, int8_t& month,
int32_t& dom, int32_t& dow, int32_t& doy, UErrorCode& status) { int8_t& dom, int8_t& dow, int16_t& doy, UErrorCode& status) {
year = dayToYear(day, doy, status); // one-based doy
if (U_FAILURE(status)) return; if (U_FAILURE(status)) return;
// Convert from 1970 CE epoch to 1 CE epoch (Gregorian calendar) // Convert from 1970 CE epoch to 1 CE epoch (Gregorian calendar)
if (uprv_add32_overflow(day, JULIAN_1970_CE - JULIAN_1_CE, &day)) { if (uprv_add32_overflow(day, JULIAN_1970_CE - JULIAN_1_CE, &day)) {
status = U_ILLEGAL_ARGUMENT_ERROR; status = U_ILLEGAL_ARGUMENT_ERROR;
return; return;
} }
// Convert from the day number to the multiple radix
// representation. We use 400-year, 100-year, and 4-year cycles.
// For example, the 4-year cycle has 4 years + 1 leap day; giving
// 1461 == 365*4 + 1 days.
int32_t n400 = ClockMath::floorDivide(day, 146097, &doy); // 400-year cycle length
int32_t n100 = ClockMath::floorDivide(doy, 36524, &doy); // 100-year cycle length
int32_t n4 = ClockMath::floorDivide(doy, 1461, &doy); // 4-year cycle length
int32_t n1 = ClockMath::floorDivide(doy, 365, &doy);
year = 400*n400 + 100*n100 + 4*n4 + n1;
if (n100 == 4 || n1 == 4) {
doy = 365; // Dec 31 at end of 4- or 400-year cycle
} else {
++year;
}
UBool isLeap = isLeapYear(year);
// Gregorian day zero is a Monday. // Gregorian day zero is a Monday.
dow = (day + 1) % 7; dow = (day + 1) % 7;
dow += (dow < 0) ? (UCAL_SUNDAY + 7) : UCAL_SUNDAY; dow += (dow < 0) ? (UCAL_SUNDAY + 7) : UCAL_SUNDAY;
// Common Julian/Gregorian calculation // Common Julian/Gregorian calculation
int32_t correction = 0; int32_t correction = 0;
bool isLeap = isLeapYear(year);
int32_t march1 = isLeap ? 60 : 59; // zero-based DOY for March 1 int32_t march1 = isLeap ? 60 : 59; // zero-based DOY for March 1
if (doy >= march1) { if (doy > march1) {
correction = isLeap ? 1 : 2; correction = isLeap ? 1 : 2;
} }
month = (12 * (doy + correction) + 6) / 367; // zero-based month month = (12 * (doy - 1 + correction) + 6) / 367; // zero-based month
dom = doy - DAYS_BEFORE[month + (isLeap ? 12 : 0)] + 1; // one-based DOM dom = doy - DAYS_BEFORE[month + (isLeap ? 12 : 0)]; // one-based DOM
doy++; // one-based doy
} }
void Grego::timeToFields(UDate time, int32_t& year, int32_t& month, int32_t Grego::dayToYear(int32_t day, UErrorCode& status) {
int32_t& dom, int32_t& dow, int32_t& doy, int32_t& mid, UErrorCode& status) { int16_t unusedDOY;
return dayToYear(day, unusedDOY, status);
}
int32_t Grego::dayToYear(int32_t day, int16_t& doy, UErrorCode& status) {
if (U_FAILURE(status)) return 0;
// Convert from 1970 CE epoch to 1 CE epoch (Gregorian calendar)
if (uprv_add32_overflow(day, JULIAN_1970_CE - JULIAN_1_CE, &day)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
// Convert from the day number to the multiple radix
// representation. We use 400-year, 100-year, and 4-year cycles.
// For example, the 4-year cycle has 4 years + 1 leap day; giving
// 1461 == 365*4 + 1 days.
int32_t doy32;
int32_t n400 = ClockMath::floorDivide(day, 146097, &doy32); // 400-year cycle length
int32_t n100 = ClockMath::floorDivide(doy32, 36524, &doy32); // 100-year cycle length
int32_t n4 = ClockMath::floorDivide(doy32, 1461, &doy32); // 4-year cycle length
int32_t n1 = ClockMath::floorDivide(doy32, 365, &doy32);
int32_t year = 400*n400 + 100*n100 + 4*n4 + n1;
if (n100 == 4 || n1 == 4) {
doy = 365; // Dec 31 at end of 4- or 400-year cycle
} else {
doy = doy32;
++year;
}
doy++; // one-based doy
return year;
}
void Grego::dayToFields(int32_t day, int32_t& year, int8_t& month,
int8_t& dom, int8_t& dow, UErrorCode& status) {
int16_t unusedDOY;
dayToFields(day, year, month, dom, dow, unusedDOY, status);
}
void Grego::dayToFields(int32_t day, int32_t& year, int8_t& month,
int8_t& dom, int16_t& doy, UErrorCode& status) {
int8_t unusedDOW;
dayToFields(day, year, month, dom, unusedDOW, doy, status);
}
void Grego::timeToFields(UDate time, int32_t& year, int8_t& month,
int8_t& dom, int32_t& mid, UErrorCode& status) {
int8_t unusedDOW;
timeToFields(time, year, month, dom, unusedDOW, mid, status);
}
void Grego::timeToFields(UDate time, int32_t& year, int8_t& month,
int8_t& dom, int8_t& dow, int32_t& mid, UErrorCode& status) {
int16_t unusedDOY;
timeToFields(time, year, month, dom, dow, unusedDOY, mid, status);
}
void Grego::timeToFields(UDate time, int32_t& year, int8_t& month,
int8_t& dom, int8_t& dow, int16_t& doy, int32_t& mid, UErrorCode& status) {
if (U_FAILURE(status)) return; if (U_FAILURE(status)) return;
double millisInDay; double day = ClockMath::floorDivide(time, U_MILLIS_PER_DAY, &mid);
double day = ClockMath::floorDivide(static_cast<double>(time), static_cast<double>(U_MILLIS_PER_DAY), &millisInDay); if (day > INT32_MAX || day < INT32_MIN) {
mid = static_cast<int32_t>(millisInDay); status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
dayToFields(day, year, month, dom, dow, doy, status); dayToFields(day, year, month, dom, dow, doy, status);
} }
int32_t Grego::timeToYear(UDate time, UErrorCode& status) {
if (U_FAILURE(status)) return 0;
double day = ClockMath::floorDivide(time, double(U_MILLIS_PER_DAY));
if (day > INT32_MAX || day < INT32_MIN) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
return Grego::dayToYear(day, status);
}
int32_t Grego::dayOfWeek(int32_t day) { int32_t Grego::dayOfWeek(int32_t day) {
int32_t dow; int32_t dow;
ClockMath::floorDivide(day + int{UCAL_THURSDAY}, 7, &dow); ClockMath::floorDivide(day + int{UCAL_THURSDAY}, 7, &dow);

View File

@ -210,8 +210,21 @@ class Grego {
* @param doy output parameter to receive day-of-year (1-based) * @param doy output parameter to receive day-of-year (1-based)
* @param status error code. * @param status error code.
*/ */
static void dayToFields(int32_t day, int32_t& year, int32_t& month, static void dayToFields(int32_t day, int32_t& year, int8_t& month,
int32_t& dom, int32_t& dow, int32_t& doy, UErrorCode& status); int8_t& dom, int8_t& dow, int16_t& doy, UErrorCode& status);
/**
* Convert a 1970-epoch day number to proleptic Gregorian year,
* month, day-of-month, and day-of-week.
* @param day 1970-epoch day
* @param year output parameter to receive year
* @param month output parameter to receive month (0-based, 0==Jan)
* @param dom output parameter to receive day-of-month (1-based)
* @param doy output parameter to receive day-of-year (1-based)
* @param status error code.
*/
static void dayToFields(int32_t day, int32_t& year, int8_t& month,
int8_t& dom, int16_t& doy, UErrorCode& status);
/** /**
* Convert a 1970-epoch day number to proleptic Gregorian year, * Convert a 1970-epoch day number to proleptic Gregorian year,
@ -223,8 +236,24 @@ class Grego {
* @param dow output parameter to receive day-of-week (1-based, 1==Sun) * @param dow output parameter to receive day-of-week (1-based, 1==Sun)
* @param status error code. * @param status error code.
*/ */
static inline void dayToFields(int32_t day, int32_t& year, int32_t& month, static void dayToFields(int32_t day, int32_t& year, int8_t& month,
int32_t& dom, int32_t& dow, UErrorCode& status); int8_t& dom, int8_t& dow, UErrorCode& status);
/**
* Convert a 1970-epoch day number to proleptic Gregorian year.
* @param day 1970-epoch day
* @param status error code.
* @return year.
*/
static int32_t dayToYear(int32_t day, UErrorCode& status);
/**
* Convert a 1970-epoch day number to proleptic Gregorian year.
* @param day 1970-epoch day
* @param doy output parameter to receive day-of-year (1-based)
* @param status error code.
* @return year.
*/
static int32_t dayToYear(int32_t day, int16_t& doy, UErrorCode& status);
/** /**
* Convert a 1970-epoch milliseconds to proleptic Gregorian year, * Convert a 1970-epoch milliseconds to proleptic Gregorian year,
@ -238,8 +267,43 @@ class Grego {
* @param mid output parameter to receive millis-in-day * @param mid output parameter to receive millis-in-day
* @param status error code. * @param status error code.
*/ */
static void timeToFields(UDate time, int32_t& year, int32_t& month, static void timeToFields(UDate time, int32_t& year, int8_t& month,
int32_t& dom, int32_t& dow, int32_t& doy, int32_t& mid, UErrorCode& status); int8_t& dom, int8_t& dow, int16_t& doy, int32_t& mid, UErrorCode& status);
/**
* Convert a 1970-epoch milliseconds to proleptic Gregorian year,
* month, day-of-month, and day-of-week, day of year and millis-in-day.
* @param time 1970-epoch milliseconds
* @param year output parameter to receive year
* @param month output parameter to receive month (0-based, 0==Jan)
* @param dom output parameter to receive day-of-month (1-based)
* @param dow output parameter to receive day-of-week (1-based, 1==Sun)
* @param mid output parameter to receive millis-in-day
* @param status error code.
*/
static void timeToFields(UDate time, int32_t& year, int8_t& month,
int8_t& dom, int8_t& dow, int32_t& mid, UErrorCode& status);
/**
* Convert a 1970-epoch milliseconds to proleptic Gregorian year,
* month, day-of-month, and day-of-week, day of year and millis-in-day.
* @param time 1970-epoch milliseconds
* @param year output parameter to receive year
* @param month output parameter to receive month (0-based, 0==Jan)
* @param dom output parameter to receive day-of-month (1-based)
* @param mid output parameter to receive millis-in-day
* @param status error code.
*/
static void timeToFields(UDate time, int32_t& year, int8_t& month,
int8_t& dom, int32_t& mid, UErrorCode& status);
/**
* Convert a 1970-epoch milliseconds to proleptic Gregorian year.
* @param time 1970-epoch milliseconds
* @param status error code.
* @return year.
*/
static int32_t timeToYear(UDate time, UErrorCode& status);
/** /**
* Return the day of week on the 1970-epoch day * Return the day of week on the 1970-epoch day
@ -305,12 +369,6 @@ Grego::previousMonthLength(int y, int m) {
return (m > 0) ? monthLength(y, m-1) : 31; return (m > 0) ? monthLength(y, m-1) : 31;
} }
inline void Grego::dayToFields(int32_t day, int32_t& year, int32_t& month,
int32_t& dom, int32_t& dow, UErrorCode& status) {
int32_t doy_unused;
dayToFields(day,year,month,dom,dow,doy_unused, status);
}
inline double Grego::julianDayToMillis(int32_t julian) inline double Grego::julianDayToMillis(int32_t julian)
{ {
return (static_cast<double>(julian) - kEpochStartAsJulianDay) * kOneDay; return (static_cast<double>(julian) - kEpochStartAsJulianDay) * kOneDay;

View File

@ -164,7 +164,6 @@ HebrewCalendar::HebrewCalendar(const Locale& aLocale, UErrorCode& success)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success) : Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success)
{ {
setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly.
} }
@ -591,13 +590,8 @@ int32_t HebrewCalendar::handleGetMonthLength(int32_t extendedYear, int32_t month
* Returns the number of days in the given Hebrew year * Returns the number of days in the given Hebrew year
* @internal * @internal
*/ */
int32_t HebrewCalendar::handleGetYearLength(int32_t eyear) const { int32_t HebrewCalendar::handleGetYearLength(int32_t eyear, UErrorCode& status) const {
UErrorCode status = U_ZERO_ERROR; return daysInYear(eyear, status);
int32_t len = daysInYear(eyear, status);
if (U_FAILURE(status)) {
return 12;
}
return len;
} }
void HebrewCalendar::validateField(UCalendarDateFields field, UErrorCode &status) { void HebrewCalendar::validateField(UCalendarDateFields field, UErrorCode &status) {

View File

@ -326,9 +326,9 @@ public:
* calendar system. Subclasses should override this method if they can * calendar system. Subclasses should override this method if they can
* provide a more correct or more efficient implementation than the * provide a more correct or more efficient implementation than the
* default implementation in Calendar. * default implementation in Calendar.
* @stable ICU 2.0 * @internal
*/ */
virtual int32_t handleGetYearLength(int32_t eyear) const override; virtual int32_t handleGetYearLength(int32_t eyear, UErrorCode& status) const override;
/** /**
* Subclasses may override this method to compute several fields * Subclasses may override this method to compute several fields

View File

@ -41,7 +41,6 @@ IndianCalendar* IndianCalendar::clone() const {
IndianCalendar::IndianCalendar(const Locale& aLocale, UErrorCode& success) IndianCalendar::IndianCalendar(const Locale& aLocale, UErrorCode& success)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success) : Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success)
{ {
setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly.
} }
IndianCalendar::IndianCalendar(const IndianCalendar& other) : Calendar(other) { IndianCalendar::IndianCalendar(const IndianCalendar& other) : Calendar(other) {
@ -129,7 +128,8 @@ int32_t IndianCalendar::handleGetMonthLength(int32_t eyear, int32_t month, UErro
* *
* @param eyear The year in Saka Era. * @param eyear The year in Saka Era.
*/ */
int32_t IndianCalendar::handleGetYearLength(int32_t eyear) const { int32_t IndianCalendar::handleGetYearLength(int32_t eyear, UErrorCode& status) const {
if (U_FAILURE(status)) return 0;
return isGregorianLeap(eyear + INDIAN_ERA_START) ? 366 : 365; return isGregorianLeap(eyear + INDIAN_ERA_START) ? 366 : 365;
} }
/* /*
@ -143,18 +143,6 @@ static double gregorianToJD(int32_t year, int32_t month, int32_t date) {
return Grego::fieldsToDay(year, month, date) + kEpochStartAsJulianDay - 0.5; return Grego::fieldsToDay(year, month, date) + kEpochStartAsJulianDay - 0.5;
} }
/*
* Returns the Gregorian Date corresponding to a given Julian Day
* Month is 0 based.
* @param jd The Julian Day
*/
static int32_t* jdToGregorian(double jd, int32_t gregorianDate[3], UErrorCode& status) {
int32_t gdow;
Grego::dayToFields(jd - kEpochStartAsJulianDay,
gregorianDate[0], gregorianDate[1], gregorianDate[2], gdow, status);
return gregorianDate;
}
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Functions for converting from field values to milliseconds.... // Functions for converting from field values to milliseconds....
@ -266,10 +254,9 @@ int32_t IndianCalendar::handleGetExtendedYear(UErrorCode& status) {
void IndianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) { void IndianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) {
double jdAtStartOfGregYear; double jdAtStartOfGregYear;
int32_t leapMonth, IndianYear, yday, IndianMonth, IndianDayOfMonth, mday; int32_t leapMonth, IndianYear, yday, IndianMonth, IndianDayOfMonth, mday;
int32_t gregorianYear; // Stores gregorian date corresponding to Julian day; // Stores gregorian date corresponding to Julian day;
int32_t gd[3]; int32_t gregorianYear = Grego::dayToYear(julianDay - kEpochStartAsJulianDay, status);
gregorianYear = jdToGregorian(julianDay, gd, status)[0]; // Gregorian date for Julian day
if (U_FAILURE(status)) return; if (U_FAILURE(status)) return;
IndianYear = gregorianYear - INDIAN_ERA_START; // Year in Saka era IndianYear = gregorianYear - INDIAN_ERA_START; // Year in Saka era
jdAtStartOfGregYear = gregorianToJD(gregorianYear, 0, 1); // JD at start of Gregorian year jdAtStartOfGregYear = gregorianToJD(gregorianYear, 0, 1); // JD at start of Gregorian year

View File

@ -215,7 +215,7 @@ public:
* Return the number of days in the given Indian year * Return the number of days in the given Indian year
* @internal * @internal
*/ */
virtual int32_t handleGetYearLength(int32_t extendedYear) const override; virtual int32_t handleGetYearLength(int32_t extendedYear, UErrorCode& status) const override;
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Functions for converting from field values to milliseconds.... // Functions for converting from field values to milliseconds....

View File

@ -202,7 +202,6 @@ IslamicCalendar* IslamicCalendar::clone() const {
IslamicCalendar::IslamicCalendar(const Locale& aLocale, UErrorCode& success) IslamicCalendar::IslamicCalendar(const Locale& aLocale, UErrorCode& success)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success) : Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success)
{ {
setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly.
} }
IslamicCalendar::~IslamicCalendar() IslamicCalendar::~IslamicCalendar()
@ -444,15 +443,8 @@ int32_t yearLength(int32_t extendedYear, UErrorCode& status) {
* Return the number of days in the given Islamic year * Return the number of days in the given Islamic year
* @draft ICU 2.4 * @draft ICU 2.4
*/ */
int32_t IslamicCalendar::handleGetYearLength(int32_t extendedYear) const { int32_t IslamicCalendar::handleGetYearLength(int32_t extendedYear, UErrorCode& status) const {
UErrorCode status = U_ZERO_ERROR; return yearLength(extendedYear, status);
int32_t length = yearLength(extendedYear, status);
if (U_FAILURE(status)) {
// fallback to normal Islamic calendar length 355 day a year if we
// encounter error and cannot propagate.
return 355;
}
return length;
} }
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
@ -706,7 +698,8 @@ int32_t IslamicCivilCalendar::handleGetMonthLength(int32_t extendedYear, int32_t
* Return the number of days in the given Islamic year * Return the number of days in the given Islamic year
* @draft ICU 2.4 * @draft ICU 2.4
*/ */
int32_t IslamicCivilCalendar::handleGetYearLength(int32_t extendedYear) const { int32_t IslamicCivilCalendar::handleGetYearLength(int32_t extendedYear, UErrorCode& status) const {
if (U_FAILURE(status)) return 0;
return 354 + (civilLeapYear(extendedYear) ? 1 : 0); return 354 + (civilLeapYear(extendedYear) ? 1 : 0);
} }
@ -872,7 +865,7 @@ int32_t IslamicUmalquraCalendar::handleGetMonthLength(int32_t extendedYear, int3
int32_t IslamicUmalquraCalendar::yearLength(int32_t extendedYear, UErrorCode& status) const { int32_t IslamicUmalquraCalendar::yearLength(int32_t extendedYear, UErrorCode& status) const {
if (extendedYear<UMALQURA_YEAR_START || extendedYear>UMALQURA_YEAR_END) { if (extendedYear<UMALQURA_YEAR_START || extendedYear>UMALQURA_YEAR_END) {
return IslamicCivilCalendar::handleGetYearLength(extendedYear); return IslamicCivilCalendar::handleGetYearLength(extendedYear, status);
} }
int length = 0; int length = 0;
for(int i=0; i<12; i++) { for(int i=0; i<12; i++) {
@ -888,15 +881,8 @@ int32_t IslamicUmalquraCalendar::yearLength(int32_t extendedYear, UErrorCode& st
* Return the number of days in the given Islamic year * Return the number of days in the given Islamic year
* @draft ICU 2.4 * @draft ICU 2.4
*/ */
int32_t IslamicUmalquraCalendar::handleGetYearLength(int32_t extendedYear) const { int32_t IslamicUmalquraCalendar::handleGetYearLength(int32_t extendedYear, UErrorCode& status) const {
UErrorCode status = U_ZERO_ERROR; return yearLength(extendedYear, status);
int32_t length = yearLength(extendedYear, status);
if (U_FAILURE(status)) {
// fallback to normal Islamic calendar length 355 day a year if we
// encounter error and cannot propagate.
return 355;
}
return length;
} }
/** /**

View File

@ -235,7 +235,7 @@ class U_I18N_API IslamicCalendar : public Calendar {
* Return the number of days in the given Islamic year * Return the number of days in the given Islamic year
* @internal * @internal
*/ */
virtual int32_t handleGetYearLength(int32_t extendedYear) const override; virtual int32_t handleGetYearLength(int32_t extendedYear, UErrorCode& status) const override;
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
// Functions for converting from field values to milliseconds.... // Functions for converting from field values to milliseconds....
@ -438,7 +438,7 @@ class U_I18N_API IslamicCivilCalendar : public IslamicCalendar {
* Return the number of days in the given Islamic year * Return the number of days in the given Islamic year
* @internal * @internal
*/ */
virtual int32_t handleGetYearLength(int32_t extendedYear) const override; virtual int32_t handleGetYearLength(int32_t extendedYear, UErrorCode& status) const override;
/** /**
* Override Calendar to compute several fields specific to the Islamic * Override Calendar to compute several fields specific to the Islamic
@ -621,7 +621,7 @@ class U_I18N_API IslamicUmalquraCalendar : public IslamicCivilCalendar {
* Return the number of days in the given Islamic year * Return the number of days in the given Islamic year
* @internal * @internal
*/ */
virtual int32_t handleGetYearLength(int32_t extendedYear) const override; virtual int32_t handleGetYearLength(int32_t extendedYear, UErrorCode& status) const override;
/** /**
* Override Calendar to compute several fields specific to the Islamic * Override Calendar to compute several fields specific to the Islamic

View File

@ -115,7 +115,6 @@ JapaneseCalendar::JapaneseCalendar(const Locale& aLocale, UErrorCode& success)
: GregorianCalendar(aLocale, success) : GregorianCalendar(aLocale, success)
{ {
init(success); init(success);
setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly.
} }
JapaneseCalendar::~JapaneseCalendar() JapaneseCalendar::~JapaneseCalendar()
@ -130,12 +129,6 @@ JapaneseCalendar::JapaneseCalendar(const JapaneseCalendar& source)
U_ASSERT(U_SUCCESS(status)); U_ASSERT(U_SUCCESS(status));
} }
JapaneseCalendar& JapaneseCalendar::operator= ( const JapaneseCalendar& right)
{
GregorianCalendar::operator=(right);
return *this;
}
JapaneseCalendar* JapaneseCalendar::clone() const JapaneseCalendar* JapaneseCalendar::clone() const
{ {
return new JapaneseCalendar(*this); return new JapaneseCalendar(*this);

View File

@ -104,13 +104,6 @@ public:
*/ */
JapaneseCalendar(const JapaneseCalendar& source); JapaneseCalendar(const JapaneseCalendar& source);
/**
* Default assignment operator
* @param right the object to be copied.
* @internal
*/
JapaneseCalendar& operator=(const JapaneseCalendar& right);
/** /**
* Create and return a polymorphic copy of this calendar. * Create and return a polymorphic copy of this calendar.
* @return return a polymorphic copy of this calendar. * @return return a polymorphic copy of this calendar.

View File

@ -41,26 +41,26 @@ static const int32_t gOffsets[] = {
2, 2,
7, 7,
17, 17,
27, 28,
31, 32,
333, 334,
344, 345,
362, 363,
366, 367,
375, 376,
378, 379,
382, 383,
390, 391,
412, 413,
416, 417,
431,
432, 432,
438, 433,
449, 439,
455, 450,
459, 456,
461, 460,
495 462,
496
}; };
static const int32_t kCurrencyOffset = 5; static const int32_t kCurrencyOffset = 5;
@ -121,6 +121,7 @@ static const char * const gSubTypes[] = {
"permille", "permille",
"permillion", "permillion",
"permyriad", "permyriad",
"portion-per-1e9",
"liter-per-100-kilometer", "liter-per-100-kilometer",
"liter-per-kilometer", "liter-per-kilometer",
"mile-per-gallon", "mile-per-gallon",
@ -811,6 +812,14 @@ MeasureUnit MeasureUnit::getPermyriad() {
return MeasureUnit(3, 9); return MeasureUnit(3, 9);
} }
MeasureUnit *MeasureUnit::createPortionPer1E9(UErrorCode &status) {
return MeasureUnit::create(3, 10, status);
}
MeasureUnit MeasureUnit::getPortionPer1E9() {
return MeasureUnit(3, 10);
}
MeasureUnit *MeasureUnit::createLiterPer100Kilometers(UErrorCode &status) { MeasureUnit *MeasureUnit::createLiterPer100Kilometers(UErrorCode &status) {
return MeasureUnit::create(4, 0, status); return MeasureUnit::create(4, 0, status);
} }
@ -2400,6 +2409,7 @@ MeasureUnitImpl MeasureUnitImpl::copy(UErrorCode &status) const {
MeasureUnitImpl result; MeasureUnitImpl result;
result.complexity = complexity; result.complexity = complexity;
result.identifier.append(identifier, status); result.identifier.append(identifier, status);
result.constantDenominator = constantDenominator;
for (int32_t i = 0; i < singleUnits.length(); i++) { for (int32_t i = 0; i < singleUnits.length(); i++) {
SingleUnitImpl *item = result.singleUnits.emplaceBack(*singleUnits[i]); SingleUnitImpl *item = result.singleUnits.emplaceBack(*singleUnits[i]);
if (!item) { if (!item) {

View File

@ -15,6 +15,7 @@
#include "charstr.h" #include "charstr.h"
#include "cmemory.h" #include "cmemory.h"
#include "cstring.h" #include "cstring.h"
#include "double-conversion-string-to-double.h"
#include "measunit_impl.h" #include "measunit_impl.h"
#include "resource.h" #include "resource.h"
#include "uarrsort.h" #include "uarrsort.h"
@ -30,13 +31,15 @@
#include "unicode/ustringtrie.h" #include "unicode/ustringtrie.h"
#include "uresimp.h" #include "uresimp.h"
#include "util.h" #include "util.h"
#include <limits.h>
#include <cstdlib> #include <cstdlib>
U_NAMESPACE_BEGIN U_NAMESPACE_BEGIN
namespace { namespace {
using icu::double_conversion::StringToDoubleConverter;
// TODO: Propose a new error code for this? // TODO: Propose a new error code for this?
constexpr UErrorCode kUnitIdentifierSyntaxError = U_ILLEGAL_ARGUMENT_ERROR; constexpr UErrorCode kUnitIdentifierSyntaxError = U_ILLEGAL_ARGUMENT_ERROR;
@ -467,7 +470,30 @@ void U_CALLCONV initUnitExtras(UErrorCode& status) {
class Token { class Token {
public: public:
Token(int32_t match) : fMatch(match) {} Token(int64_t match) : fMatch(match) {
if (fMatch < kCompoundPartOffset) {
this->fType = TYPE_PREFIX;
} else if (fMatch < kInitialCompoundPartOffset) {
this->fType = TYPE_COMPOUND_PART;
} else if (fMatch < kPowerPartOffset) {
this->fType = TYPE_INITIAL_COMPOUND_PART;
} else if (fMatch < kSimpleUnitOffset) {
this->fType = TYPE_POWER_PART;
} else {
this->fType = TYPE_SIMPLE_UNIT;
}
}
static Token constantToken(StringPiece str, UErrorCode &status) {
Token result;
auto value = Token::parseStringToLong(str, status);
if (U_FAILURE(status)) {
return result;
}
result.fMatch = value;
result.fType = TYPE_CONSTANT_DENOMINATOR;
return result;
}
enum Type { enum Type {
TYPE_UNDEFINED, TYPE_UNDEFINED,
@ -478,25 +504,20 @@ public:
TYPE_INITIAL_COMPOUND_PART, TYPE_INITIAL_COMPOUND_PART,
TYPE_POWER_PART, TYPE_POWER_PART,
TYPE_SIMPLE_UNIT, TYPE_SIMPLE_UNIT,
TYPE_CONSTANT_DENOMINATOR,
}; };
// Calling getType() is invalid, resulting in an assertion failure, if Token // Calling getType() is invalid, resulting in an assertion failure, if Token
// value isn't positive. // value isn't positive.
Type getType() const { Type getType() const {
U_ASSERT(fMatch > 0); U_ASSERT(fMatch >= 0);
if (fMatch < kCompoundPartOffset) { return this->fType;
return TYPE_PREFIX;
} }
if (fMatch < kInitialCompoundPartOffset) {
return TYPE_COMPOUND_PART; // Retrieve the value of the constant denominator if the token is of type TYPE_CONSTANT_DENOMINATOR.
} uint64_t getConstantDenominator() const {
if (fMatch < kPowerPartOffset) { U_ASSERT(getType() == TYPE_CONSTANT_DENOMINATOR);
return TYPE_INITIAL_COMPOUND_PART; return static_cast<uint64_t>(fMatch);
}
if (fMatch < kSimpleUnitOffset) {
return TYPE_POWER_PART;
}
return TYPE_SIMPLE_UNIT;
} }
UMeasurePrefix getUnitPrefix() const { UMeasurePrefix getUnitPrefix() const {
@ -530,8 +551,41 @@ public:
return fMatch - kSimpleUnitOffset; return fMatch - kSimpleUnitOffset;
} }
// TODO: Consider moving this to a separate utility class.
// Utility function to parse a string into an unsigned long value.
// The value must be a positive integer within the range [1, INT64_MAX].
// The input can be in integer or scientific notation.
static uint64_t parseStringToLong(const StringPiece strNum, UErrorCode &status) {
// We are processing well-formed input, so we don't need any special options to
// StringToDoubleConverter.
StringToDoubleConverter converter(0, 0, 0, "", "");
int32_t count;
double double_result = converter.StringToDouble(strNum.data(), strNum.length(), &count);
if (count != strNum.length()) {
status = kUnitIdentifierSyntaxError;
return 0;
}
if (U_FAILURE(status) || double_result < 1.0 || double_result > static_cast<double>(INT64_MAX)) {
status = kUnitIdentifierSyntaxError;
return 0;
}
// Check if the value is integer.
uint64_t int_result = static_cast<uint64_t>(double_result);
const double kTolerance = 1e-9;
if (abs(double_result - int_result) > kTolerance) {
status = kUnitIdentifierSyntaxError;
return 0;
}
return int_result;
}
private: private:
int32_t fMatch; Token() = default;
int64_t fMatch;
Type fType = TYPE_UNDEFINED;
}; };
class Parser { class Parser {
@ -555,6 +609,50 @@ public:
return {source}; return {source};
} }
/**
* A single unit or a constant denominator.
*/
struct SingleUnitOrConstant {
enum ValueType {
kSingleUnit,
kConstantDenominator,
};
ValueType type = kSingleUnit;
SingleUnitImpl singleUnit;
uint64_t constantDenominator;
static SingleUnitOrConstant singleUnitValue(SingleUnitImpl singleUnit) {
SingleUnitOrConstant result;
result.type = kSingleUnit;
result.singleUnit = singleUnit;
result.constantDenominator = 0;
return result;
}
static SingleUnitOrConstant constantDenominatorValue(uint64_t constant) {
SingleUnitOrConstant result;
result.type = kConstantDenominator;
result.singleUnit = {};
result.constantDenominator = constant;
return result;
}
uint64_t getConstantDenominator() const {
U_ASSERT(type == kConstantDenominator);
return constantDenominator;
}
SingleUnitImpl getSingleUnit() const {
U_ASSERT(type == kSingleUnit);
return singleUnit;
}
bool isSingleUnit() const { return type == kSingleUnit; }
bool isConstantDenominator() const { return type == kConstantDenominator; }
};
MeasureUnitImpl parse(UErrorCode& status) { MeasureUnitImpl parse(UErrorCode& status) {
MeasureUnitImpl result; MeasureUnitImpl result;
@ -569,12 +667,19 @@ public:
while (hasNext()) { while (hasNext()) {
bool sawAnd = false; bool sawAnd = false;
SingleUnitImpl singleUnit = nextSingleUnit(sawAnd, status); auto singleUnitOrConstant = nextSingleUnitOrConstant(sawAnd, status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return result; return result;
} }
bool added = result.appendSingleUnit(singleUnit, status); if (singleUnitOrConstant.isConstantDenominator()) {
result.constantDenominator = singleUnitOrConstant.getConstantDenominator();
result.complexity = UMEASURE_UNIT_COMPOUND;
continue;
}
U_ASSERT(singleUnitOrConstant.isSingleUnit());
bool added = result.appendSingleUnit(singleUnitOrConstant.getSingleUnit(), status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return result; return result;
} }
@ -604,6 +709,12 @@ public:
} }
} }
if (result.singleUnits.length() == 0) {
// The identifier was empty or only had a constant denominator.
status = kUnitIdentifierSyntaxError;
return result; // add it for code consistency.
}
return result; return result;
} }
@ -622,6 +733,10 @@ private:
// identifier is invalid pending TODO(CLDR-13701). // identifier is invalid pending TODO(CLDR-13701).
bool fAfterPer = false; bool fAfterPer = false;
// Set to true when we've just seen a "per-". This is used to determine if
// the next token can be a constant denominator token.
bool fJustSawPer = false;
Parser() : fSource(""), fTrie(u"") {} Parser() : fSource(""), fTrie(u"") {}
Parser(StringPiece source) Parser(StringPiece source)
@ -640,6 +755,10 @@ private:
// Saves the position in the fSource string for the end of the most // Saves the position in the fSource string for the end of the most
// recent matching token. // recent matching token.
int32_t previ = -1; int32_t previ = -1;
// Saves the position in the fSource string for later use in case of unit constant found.
int32_t currentFIndex = fIndex;
// Find the longest token that matches a value in the trie: // Find the longest token that matches a value in the trie:
while (fIndex < fSource.length()) { while (fIndex < fSource.length()) {
auto result = fTrie.next(fSource.data()[fIndex++]); auto result = fTrie.next(fSource.data()[fIndex++]);
@ -658,14 +777,27 @@ private:
// continue; // continue;
} }
if (match < 0) { if (match >= 0) {
status = kUnitIdentifierSyntaxError;
} else {
fIndex = previ; fIndex = previ;
}
return {match}; return {match};
} }
// If no match was found, we check if the token is a constant denominator.
// 1. We find the index of the start of the next token or the end of the string.
int32_t endOfConstantIndex = fSource.find("-", currentFIndex);
endOfConstantIndex = (endOfConstantIndex == -1) ? fSource.length() : endOfConstantIndex;
if (endOfConstantIndex <= currentFIndex) {
status = kUnitIdentifierSyntaxError;
return {match};
}
// 2. We extract the substring from the start of the constant to the end of the constant.
StringPiece constantDenominatorStr =
fSource.substr(currentFIndex, endOfConstantIndex - currentFIndex);
fIndex = endOfConstantIndex;
return Token::constantToken(constantDenominatorStr, status);
}
/** /**
* Returns the next "single unit" via result. * Returns the next "single unit" via result.
* *
@ -680,10 +812,10 @@ private:
* unit", sawAnd is set to true. If not, it is left as is. * unit", sawAnd is set to true. If not, it is left as is.
* @param status ICU error code. * @param status ICU error code.
*/ */
SingleUnitImpl nextSingleUnit(bool &sawAnd, UErrorCode &status) { SingleUnitOrConstant nextSingleUnitOrConstant(bool &sawAnd, UErrorCode &status) {
SingleUnitImpl result; SingleUnitImpl singleUnitResult;
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return result; return {};
} }
// state: // state:
@ -695,19 +827,22 @@ private:
bool atStart = fIndex == 0; bool atStart = fIndex == 0;
Token token = nextToken(status); Token token = nextToken(status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return result; return {};
} }
fJustSawPer = false;
if (atStart) { if (atStart) {
// Identifiers optionally start with "per-". // Identifiers optionally start with "per-".
if (token.getType() == Token::TYPE_INITIAL_COMPOUND_PART) { if (token.getType() == Token::TYPE_INITIAL_COMPOUND_PART) {
U_ASSERT(token.getInitialCompoundPart() == INITIAL_COMPOUND_PART_PER); U_ASSERT(token.getInitialCompoundPart() == INITIAL_COMPOUND_PART_PER);
fAfterPer = true; fAfterPer = true;
result.dimensionality = -1; fJustSawPer = true;
singleUnitResult.dimensionality = -1;
token = nextToken(status); token = nextToken(status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return result; return {};
} }
} }
} else { } else {
@ -715,7 +850,7 @@ private:
// via a compound part: // via a compound part:
if (token.getType() != Token::TYPE_COMPOUND_PART) { if (token.getType() != Token::TYPE_COMPOUND_PART) {
status = kUnitIdentifierSyntaxError; status = kUnitIdentifierSyntaxError;
return result; return {};
} }
switch (token.getMatch()) { switch (token.getMatch()) {
@ -724,15 +859,16 @@ private:
// Mixed compound units not yet supported, // Mixed compound units not yet supported,
// TODO(CLDR-13701). // TODO(CLDR-13701).
status = kUnitIdentifierSyntaxError; status = kUnitIdentifierSyntaxError;
return result; return {};
} }
fAfterPer = true; fAfterPer = true;
result.dimensionality = -1; fJustSawPer = true;
singleUnitResult.dimensionality = -1;
break; break;
case COMPOUND_PART_TIMES: case COMPOUND_PART_TIMES:
if (fAfterPer) { if (fAfterPer) {
result.dimensionality = -1; singleUnitResult.dimensionality = -1;
} }
break; break;
@ -741,7 +877,7 @@ private:
// Can't start with "-and-", and mixed compound units // Can't start with "-and-", and mixed compound units
// not yet supported, TODO(CLDR-13701). // not yet supported, TODO(CLDR-13701).
status = kUnitIdentifierSyntaxError; status = kUnitIdentifierSyntaxError;
return result; return {};
} }
sawAnd = true; sawAnd = true;
break; break;
@ -749,52 +885,65 @@ private:
token = nextToken(status); token = nextToken(status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return result; return {};
} }
} }
if (token.getType() == Token::TYPE_CONSTANT_DENOMINATOR) {
if (!fJustSawPer) {
status = kUnitIdentifierSyntaxError;
return {};
}
return SingleUnitOrConstant::constantDenominatorValue(token.getConstantDenominator());
}
// Read tokens until we have a complete SingleUnit or we reach the end. // Read tokens until we have a complete SingleUnit or we reach the end.
while (true) { while (true) {
switch (token.getType()) { switch (token.getType()) {
case Token::TYPE_POWER_PART: case Token::TYPE_POWER_PART:
if (state > 0) { if (state > 0) {
status = kUnitIdentifierSyntaxError; status = kUnitIdentifierSyntaxError;
return result; return {};
} }
result.dimensionality *= token.getPower(); singleUnitResult.dimensionality *= token.getPower();
state = 1; state = 1;
break; break;
case Token::TYPE_PREFIX: case Token::TYPE_PREFIX:
if (state > 1) { if (state > 1) {
status = kUnitIdentifierSyntaxError; status = kUnitIdentifierSyntaxError;
return result; return {};
} }
result.unitPrefix = token.getUnitPrefix(); singleUnitResult.unitPrefix = token.getUnitPrefix();
state = 2; state = 2;
break; break;
case Token::TYPE_SIMPLE_UNIT: case Token::TYPE_SIMPLE_UNIT:
result.index = token.getSimpleUnitIndex(); singleUnitResult.index = token.getSimpleUnitIndex();
return result; break;
default: default:
status = kUnitIdentifierSyntaxError; status = kUnitIdentifierSyntaxError;
return result; return {};
}
if (token.getType() == Token::TYPE_SIMPLE_UNIT) {
break;
} }
if (!hasNext()) { if (!hasNext()) {
// We ran out of tokens before finding a complete single unit. // We ran out of tokens before finding a complete single unit.
status = kUnitIdentifierSyntaxError; status = kUnitIdentifierSyntaxError;
return result; return {};
} }
token = nextToken(status); token = nextToken(status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return result; return {};
} }
} }
return result; return SingleUnitOrConstant::singleUnitValue(singleUnitResult);
} }
}; };
@ -1120,6 +1269,51 @@ MeasureUnitImpl::extractIndividualUnitsWithIndices(UErrorCode &status) const {
return result; return result;
} }
int32_t countCharacter(const CharString &str, char c) {
int32_t count = 0;
for (int32_t i = 0, n = str.length(); i < n; i++) {
if (str[i] == c) {
count++;
}
}
return count;
}
/**
* Internal function that returns a string of the constants in the correct
* format.
*
* Example:
* 1000 --> "-per-1000"
* 1000000 --> "-per-1e6"
*
* NOTE: this function is only used when the constant denominator is greater
* than 0.
*/
CharString getConstantsString(uint64_t constantDenominator, UErrorCode &status) {
U_ASSERT(constantDenominator > 0 && constantDenominator <= LLONG_MAX);
CharString result;
result.appendNumber(constantDenominator, status);
if (U_FAILURE(status)) {
return result;
}
if (constantDenominator <= 1000) {
return result;
}
// Check if the constant is a power of 10.
int32_t zeros = countCharacter(result, '0');
if (zeros == result.length() - 1 && result[0] == '1') {
result.clear();
result.append(StringPiece("1e"), status);
result.appendNumber(zeros, status);
}
return result;
}
/** /**
* Normalize a MeasureUnitImpl and generate the identifier string in place. * Normalize a MeasureUnitImpl and generate the identifier string in place.
*/ */
@ -1128,7 +1322,7 @@ void MeasureUnitImpl::serialize(UErrorCode &status) {
return; return;
} }
if (this->singleUnits.length() == 0) { if (this->singleUnits.length() == 0 && this->constantDenominator == 0) {
// Dimensionless, constructed by the default constructor. // Dimensionless, constructed by the default constructor.
return; return;
} }
@ -1145,6 +1339,7 @@ void MeasureUnitImpl::serialize(UErrorCode &status) {
CharString result; CharString result;
bool beforePer = true; bool beforePer = true;
bool firstTimeNegativeDimension = false; bool firstTimeNegativeDimension = false;
bool constantDenominatorAppended = false;
for (int32_t i = 0; i < this->singleUnits.length(); i++) { for (int32_t i = 0; i < this->singleUnits.length(); i++) {
if (beforePer && (*this->singleUnits[i]).dimensionality < 0) { if (beforePer && (*this->singleUnits[i]).dimensionality < 0) {
beforePer = false; beforePer = false;
@ -1168,43 +1363,103 @@ void MeasureUnitImpl::serialize(UErrorCode &status) {
} else { } else {
result.append(StringPiece("-per-"), status); result.append(StringPiece("-per-"), status);
} }
} else {
if (result.length() != 0) { if (this->constantDenominator > 0) {
result.append(getConstantsString(this->constantDenominator, status), status);
result.append(StringPiece("-"), status); result.append(StringPiece("-"), status);
constantDenominatorAppended = true;
} }
} else if (result.length() != 0) {
result.append(StringPiece("-"), status);
} }
} }
this->singleUnits[i]->appendNeutralIdentifier(result, status); this->singleUnits[i]->appendNeutralIdentifier(result, status);
} }
if (!constantDenominatorAppended && this->constantDenominator > 0) {
result.append(StringPiece("-per-"), status);
result.append(getConstantsString(this->constantDenominator, status), status);
}
if (U_FAILURE(status)) {
return;
}
this->identifier = CharString(result, status); this->identifier = CharString(result, status);
} }
MeasureUnit MeasureUnitImpl::build(UErrorCode& status) && { MeasureUnit MeasureUnitImpl::build(UErrorCode &status) && {
this->serialize(status); this->serialize(status);
return MeasureUnit(std::move(*this)); return MeasureUnit(std::move(*this));
} }
MeasureUnit MeasureUnit::forIdentifier(StringPiece identifier, UErrorCode& status) { MeasureUnit MeasureUnit::forIdentifier(StringPiece identifier, UErrorCode &status) {
return Parser::from(identifier, status).parse(status).build(status); return Parser::from(identifier, status).parse(status).build(status);
} }
UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode& status) const { UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode &status) const {
MeasureUnitImpl temp; MeasureUnitImpl temp;
return MeasureUnitImpl::forMeasureUnit(*this, temp, status).complexity; return MeasureUnitImpl::forMeasureUnit(*this, temp, status).complexity;
} }
UMeasurePrefix MeasureUnit::getPrefix(UErrorCode& status) const { UMeasurePrefix MeasureUnit::getPrefix(UErrorCode &status) const {
return SingleUnitImpl::forMeasureUnit(*this, status).unitPrefix; return SingleUnitImpl::forMeasureUnit(*this, status).unitPrefix;
} }
MeasureUnit MeasureUnit::withPrefix(UMeasurePrefix prefix, UErrorCode& status) const UPRV_NO_SANITIZE_UNDEFINED { MeasureUnit MeasureUnit::withPrefix(UMeasurePrefix prefix,
UErrorCode &status) const UPRV_NO_SANITIZE_UNDEFINED {
SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status); SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status);
singleUnit.unitPrefix = prefix; singleUnit.unitPrefix = prefix;
return singleUnit.build(status); return singleUnit.build(status);
} }
uint64_t MeasureUnit::getConstantDenominator(UErrorCode &status) const {
auto complexity = this->getComplexity(status);
if (U_FAILURE(status)) {
return 0;
}
if (complexity != UMEASURE_UNIT_SINGLE && complexity != UMEASURE_UNIT_COMPOUND) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
if (this->fImpl == nullptr) {
return 0;
}
return this->fImpl->constantDenominator;
}
MeasureUnit MeasureUnit::withConstantDenominator(uint64_t denominator, UErrorCode &status) const {
// To match the behavior of the Java API, we do not allow a constant denominator
// bigger than LONG_MAX.
if (denominator > LONG_MAX) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return {};
}
auto complexity = this->getComplexity(status);
if (U_FAILURE(status)) {
return {};
}
if (complexity != UMEASURE_UNIT_SINGLE && complexity != UMEASURE_UNIT_COMPOUND) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return {};
}
MeasureUnitImpl impl = MeasureUnitImpl::forMeasureUnitMaybeCopy(*this, status);
if (U_FAILURE(status)) {
return {};
}
impl.constantDenominator = denominator;
impl.complexity = (impl.singleUnits.length() < 2 && denominator == 0) ? UMEASURE_UNIT_SINGLE
: UMEASURE_UNIT_COMPOUND;
return std::move(impl).build(status);
}
int32_t MeasureUnit::getDimensionality(UErrorCode& status) const { int32_t MeasureUnit::getDimensionality(UErrorCode& status) const {
SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status); SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status);
if (U_FAILURE(status)) { return 0; } if (U_FAILURE(status)) { return 0; }
@ -1222,6 +1477,11 @@ MeasureUnit MeasureUnit::withDimensionality(int32_t dimensionality, UErrorCode&
MeasureUnit MeasureUnit::reciprocal(UErrorCode& status) const { MeasureUnit MeasureUnit::reciprocal(UErrorCode& status) const {
MeasureUnitImpl impl = MeasureUnitImpl::forMeasureUnitMaybeCopy(*this, status); MeasureUnitImpl impl = MeasureUnitImpl::forMeasureUnitMaybeCopy(*this, status);
// The reciprocal of a unit that has a constant denominator is not allowed.
if (impl.constantDenominator != 0) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return {};
}
impl.takeReciprocal(status); impl.takeReciprocal(status);
return std::move(impl).build(status); return std::move(impl).build(status);
} }
@ -1237,9 +1497,25 @@ MeasureUnit MeasureUnit::product(const MeasureUnit& other, UErrorCode& status) c
for (int32_t i = 0; i < otherImpl.singleUnits.length(); i++) { for (int32_t i = 0; i < otherImpl.singleUnits.length(); i++) {
impl.appendSingleUnit(*otherImpl.singleUnits[i], status); impl.appendSingleUnit(*otherImpl.singleUnits[i], status);
} }
if (impl.singleUnits.length() > 1) {
uint64_t currentConstatDenominator = this->getConstantDenominator(status);
uint64_t otherConstantDenominator = other.getConstantDenominator(status);
// TODO: we can also multiply the constant denominators instead of returning an error.
if (currentConstatDenominator != 0 && otherConstantDenominator != 0) {
// There is only `one` constant denominator in a compound unit.
// Therefore, we Cannot multiply units that both of them have a constant denominator
status = U_ILLEGAL_ARGUMENT_ERROR;
return {};
}
// Because either one of the constant denominators is zero, we can use the maximum of them.
impl.constantDenominator = uprv_max(currentConstatDenominator, otherConstantDenominator);
if (impl.singleUnits.length() > 1 || impl.constantDenominator > 0) {
impl.complexity = UMEASURE_UNIT_COMPOUND; impl.complexity = UMEASURE_UNIT_COMPOUND;
} }
return std::move(impl).build(status); return std::move(impl).build(status);
} }

View File

@ -328,6 +328,14 @@ class U_I18N_API MeasureUnitImpl : public UMemory {
*/ */
CharString identifier; CharString identifier;
/**
* Represents the unit constant denominator.
*
* NOTE:
* if set to 0, it means that the constant is not set.
*/
uint64_t constantDenominator = 0;
// For calling serialize // For calling serialize
// TODO(icu-units#147): revisit serialization // TODO(icu-units#147): revisit serialization
friend class number::impl::LongNameHandler; friend class number::impl::LongNameHandler;

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -11,8 +13,10 @@
#include "unicode/messageformat2_data_model.h" #include "unicode/messageformat2_data_model.h"
#include "unicode/messageformat2_formattable.h" #include "unicode/messageformat2_formattable.h"
#include "unicode/messageformat2.h" #include "unicode/messageformat2.h"
#include "unicode/normalizer2.h"
#include "unicode/unistr.h" #include "unicode/unistr.h"
#include "messageformat2_allocation.h" #include "messageformat2_allocation.h"
#include "messageformat2_checker.h"
#include "messageformat2_evaluation.h" #include "messageformat2_evaluation.h"
#include "messageformat2_macros.h" #include "messageformat2_macros.h"
@ -37,7 +41,7 @@ static Formattable evalLiteral(const Literal& lit) {
// The fallback for a variable name is itself. // The fallback for a variable name is itself.
UnicodeString str(DOLLAR); UnicodeString str(DOLLAR);
str += var; str += var;
const Formattable* val = context.getGlobal(var, errorCode); const Formattable* val = context.getGlobal(*this, var, errorCode);
if (U_SUCCESS(errorCode)) { if (U_SUCCESS(errorCode)) {
return (FormattedPlaceholder(*val, str)); return (FormattedPlaceholder(*val, str));
} }
@ -51,7 +55,7 @@ static Formattable evalLiteral(const Literal& lit) {
return FormattedPlaceholder(evalLiteral(lit), lit.quoted()); return FormattedPlaceholder(evalLiteral(lit), lit.quoted());
} }
[[nodiscard]] FormattedPlaceholder MessageFormatter::formatOperand(const Environment& env, [[nodiscard]] InternalValue* MessageFormatter::formatOperand(const Environment& env,
const Operand& rand, const Operand& rand,
MessageContext& context, MessageContext& context,
UErrorCode &status) const { UErrorCode &status) const {
@ -60,7 +64,7 @@ static Formattable evalLiteral(const Literal& lit) {
} }
if (rand.isNull()) { if (rand.isNull()) {
return FormattedPlaceholder(); return create<InternalValue>(InternalValue(FormattedPlaceholder()), status);
} }
if (rand.isVariable()) { if (rand.isVariable()) {
// Check if it's local or global // Check if it's local or global
@ -71,15 +75,19 @@ static Formattable evalLiteral(const Literal& lit) {
// Eager vs. lazy evaluation is an open issue: // Eager vs. lazy evaluation is an open issue:
// see https://github.com/unicode-org/message-format-wg/issues/299 // see https://github.com/unicode-org/message-format-wg/issues/299
// NFC-normalize the variable name. See
// https://github.com/unicode-org/message-format-wg/blob/main/spec/syntax.md#names-and-identifiers
const VariableName normalized = normalizeNFC(var);
// Look up the variable in the environment // Look up the variable in the environment
if (env.has(var)) { if (env.has(normalized)) {
// `var` is a local -- look it up // `var` is a local -- look it up
const Closure& rhs = env.lookup(var); const Closure& rhs = env.lookup(normalized);
// Format the expression using the environment from the closure // Format the expression using the environment from the closure
return formatExpression(rhs.getEnv(), rhs.getExpr(), context, status); return formatExpression(rhs.getEnv(), rhs.getExpr(), context, status);
} }
// Variable wasn't found in locals -- check if it's global // Variable wasn't found in locals -- check if it's global
FormattedPlaceholder result = evalArgument(var, context, status); FormattedPlaceholder result = evalArgument(normalized, context, status);
if (status == U_ILLEGAL_ARGUMENT_ERROR) { if (status == U_ILLEGAL_ARGUMENT_ERROR) {
status = U_ZERO_ERROR; status = U_ZERO_ERROR;
// Unbound variable -- set a resolution error // Unbound variable -- set a resolution error
@ -88,12 +96,12 @@ static Formattable evalLiteral(const Literal& lit) {
// https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#fallback-resolution // https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#fallback-resolution
UnicodeString str(DOLLAR); UnicodeString str(DOLLAR);
str += var; str += var;
return FormattedPlaceholder(str); return create<InternalValue>(InternalValue(FormattedPlaceholder(str)), status);
} }
return result; return create<InternalValue>(InternalValue(std::move(result)), status);
} else { } else {
U_ASSERT(rand.isLiteral()); U_ASSERT(rand.isLiteral());
return formatLiteral(rand.asLiteral()); return create<InternalValue>(InternalValue(formatLiteral(rand.asLiteral())), status);
} }
} }
@ -114,28 +122,32 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O
// Options are fully evaluated before calling the function // Options are fully evaluated before calling the function
// Format the operand // Format the operand
FormattedPlaceholder rhsVal = formatOperand(env, v, context, status); LocalPointer<InternalValue> rhsVal(formatOperand(env, v, context, status));
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return {}; return {};
} }
if (!rhsVal.isFallback()) { // Note: this means option values are "eagerly" evaluated.
resolvedOpt.adoptInstead(create<ResolvedFunctionOption>(ResolvedFunctionOption(k, rhsVal.asFormattable()), status)); // Currently, options don't have options. This will be addressed by the
// full FormattedPlaceholder redesign.
FormattedPlaceholder optValue = rhsVal->forceFormatting(context.getErrors(), status);
resolvedOpt.adoptInstead(create<ResolvedFunctionOption>
(ResolvedFunctionOption(k,
optValue.asFormattable()),
status));
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return {}; return {};
} }
optionsVector->adoptElement(resolvedOpt.orphan(), status); optionsVector->adoptElement(resolvedOpt.orphan(), status);
} }
}
return FunctionOptions(std::move(*optionsVector), status); return FunctionOptions(std::move(*optionsVector), status);
} }
// Overload that dispatches on argument type. Syntax doesn't provide for options in this case. // Overload that dispatches on argument type. Syntax doesn't provide for options in this case.
[[nodiscard]] FormattedPlaceholder MessageFormatter::evalFormatterCall(FormattedPlaceholder&& argument, [[nodiscard]] InternalValue* MessageFormatter::evalFunctionCall(FormattedPlaceholder&& argument,
MessageContext& context, MessageContext& context,
UErrorCode& status) const { UErrorCode& status) const {
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
return {}; return nullptr;
} }
// These cases should have been checked for already // These cases should have been checked for already
@ -153,8 +165,8 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O
// No formatter for this type -- follow default behavior // No formatter for this type -- follow default behavior
break; break;
} }
return evalFormatterCall(functionName, return evalFunctionCall(functionName,
std::move(argument), create<InternalValue>(std::move(argument), status),
FunctionOptions(), FunctionOptions(),
context, context,
status); status);
@ -167,12 +179,13 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O
} }
// No formatter for this type, or it's a primitive type (which will be formatted later) // No formatter for this type, or it's a primitive type (which will be formatted later)
// -- just return the argument itself // -- just return the argument itself
return std::move(argument); return create<InternalValue>(std::move(argument), status);
} }
// Overload that dispatches on function name // Overload that dispatches on function name
[[nodiscard]] FormattedPlaceholder MessageFormatter::evalFormatterCall(const FunctionName& functionName, // Adopts `arg`
FormattedPlaceholder&& argument, [[nodiscard]] InternalValue* MessageFormatter::evalFunctionCall(const FunctionName& functionName,
InternalValue* arg_,
FunctionOptions&& options, FunctionOptions&& options,
MessageContext& context, MessageContext& context,
UErrorCode& status) const { UErrorCode& status) const {
@ -180,69 +193,41 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O
return {}; return {};
} }
DynamicErrors& errs = context.getErrors(); LocalPointer<InternalValue> arg(arg_);
// Look up the formatter or selector
LocalPointer<Formatter> formatterImpl(nullptr);
LocalPointer<Selector> selectorImpl(nullptr);
if (isFormatter(functionName)) {
formatterImpl.adoptInstead(getFormatter(functionName, status));
U_ASSERT(U_SUCCESS(status));
}
if (isSelector(functionName)) {
selectorImpl.adoptInstead(getSelector(context, functionName, status));
U_ASSERT(U_SUCCESS(status));
}
if (formatterImpl == nullptr && selectorImpl == nullptr) {
// Unknown function error
context.getErrors().setUnknownFunction(functionName, status);
if (arg->hasNullOperand()) {
// Non-selector used as selector; an error would have been recorded earlier
UnicodeString fallback(COLON); UnicodeString fallback(COLON);
fallback += functionName; fallback += functionName;
if (!argument.isNullOperand()) { return new InternalValue(FormattedPlaceholder(fallback));
fallback = argument.fallback;
}
if (isFormatter(functionName)) {
LocalPointer<Formatter> formatterImpl(getFormatter(functionName, status));
if (U_FAILURE(status)) {
if (status == U_MF_FORMATTING_ERROR) {
errs.setFormattingError(functionName, status);
status = U_ZERO_ERROR;
return {};
}
if (status == U_MF_UNKNOWN_FUNCTION_ERROR) {
errs.setUnknownFunction(functionName, status);
status = U_ZERO_ERROR;
return {};
}
// Other errors are non-recoverable
return {};
}
U_ASSERT(formatterImpl != nullptr);
UErrorCode savedStatus = status;
FormattedPlaceholder result = formatterImpl->format(std::move(argument), std::move(options), status);
// Update errors
if (savedStatus != status) {
if (U_FAILURE(status)) {
if (status == U_MF_OPERAND_MISMATCH_ERROR) {
status = U_ZERO_ERROR;
errs.setOperandMismatchError(functionName, status);
} else { } else {
status = U_ZERO_ERROR; return new InternalValue(FormattedPlaceholder(arg->getFallback()));
// Convey any error generated by the formatter
// as a formatting error, except for operand mismatch errors
errs.setFormattingError(functionName, status);
}
return FormattedPlaceholder(fallback);
} else {
// Ignore warnings
status = savedStatus;
} }
} }
// Ignore the output if any errors occurred return new InternalValue(arg.orphan(),
if (errs.hasFormattingError()) { std::move(options),
return FormattedPlaceholder(fallback); functionName,
} formatterImpl.isValid() ? formatterImpl.orphan() : nullptr,
return result; selectorImpl.isValid() ? selectorImpl.orphan() : nullptr);
}
// No formatter with this name -- set error
if (isSelector(functionName)) {
errs.setFormattingError(functionName, status);
} else {
errs.setUnknownFunction(functionName, status);
}
return FormattedPlaceholder(fallback);
} }
// Formats an expression using `globalEnv` for the values of variables // Formats an expression using `globalEnv` for the values of variables
[[nodiscard]] FormattedPlaceholder MessageFormatter::formatExpression(const Environment& globalEnv, [[nodiscard]] InternalValue* MessageFormatter::formatExpression(const Environment& globalEnv,
const Expression& expr, const Expression& expr,
MessageContext& context, MessageContext& context,
UErrorCode &status) const { UErrorCode &status) const {
@ -252,19 +237,18 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O
const Operand& rand = expr.getOperand(); const Operand& rand = expr.getOperand();
// Format the operand (formatOperand handles the case of a null operand) // Format the operand (formatOperand handles the case of a null operand)
FormattedPlaceholder randVal = formatOperand(globalEnv, rand, context, status); LocalPointer<InternalValue> randVal(formatOperand(globalEnv, rand, context, status));
// Don't call the function on error values FormattedPlaceholder maybeRand = randVal->takeArgument(status);
if (randVal.isFallback()) {
return randVal;
}
if (!expr.isFunctionCall()) { if (!expr.isFunctionCall() && U_SUCCESS(status)) {
// Dispatch based on type of `randVal` // Dispatch based on type of `randVal`
return evalFormatterCall(std::move(randVal), if (maybeRand.isFallback()) {
context, return randVal.orphan();
status); }
} else { return evalFunctionCall(std::move(maybeRand), context, status);
} else if (expr.isFunctionCall()) {
status = U_ZERO_ERROR;
const Operator* rator = expr.getOperator(status); const Operator* rator = expr.getOperator(status);
U_ASSERT(U_SUCCESS(status)); U_ASSERT(U_SUCCESS(status));
const FunctionName& functionName = rator->getFunctionName(); const FunctionName& functionName = rator->getFunctionName();
@ -273,19 +257,14 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O
FunctionOptions resolvedOptions = resolveOptions(globalEnv, options, context, status); FunctionOptions resolvedOptions = resolveOptions(globalEnv, options, context, status);
// Call the formatter function // Call the formatter function
// The fallback for a nullary function call is the function name return evalFunctionCall(functionName,
UnicodeString fallback; randVal.orphan(),
if (rand.isNull()) {
fallback = UnicodeString(COLON);
fallback += functionName;
} else {
fallback = randVal.fallback;
}
return evalFormatterCall(functionName,
std::move(randVal),
std::move(resolvedOptions), std::move(resolvedOptions),
context, context,
status); status);
} else {
status = U_ZERO_ERROR;
return randVal.orphan();
} }
} }
@ -301,11 +280,13 @@ void MessageFormatter::formatPattern(MessageContext& context, const Environment&
// Markup is ignored // Markup is ignored
} else { } else {
// Format the expression // Format the expression
FormattedPlaceholder partVal = formatExpression(globalEnv, part.contents(), context, status); LocalPointer<InternalValue> partVal(
formatExpression(globalEnv, part.contents(), context, status));
FormattedPlaceholder partResult = partVal->forceFormatting(context.getErrors(),
status);
// Force full evaluation, e.g. applying default formatters to // Force full evaluation, e.g. applying default formatters to
// unformatted input (or formatting numbers as strings) // unformatted input (or formatting numbers as strings)
UnicodeString partResult = partVal.formatToString(locale, status); result += partResult.formatToString(locale, status);
result += partResult;
// Handle formatting errors. `formatToString()` can't take a context and thus can't // Handle formatting errors. `formatToString()` can't take a context and thus can't
// register an error directly // register an error directly
if (status == U_MF_FORMATTING_ERROR) { if (status == U_MF_FORMATTING_ERROR) {
@ -328,14 +309,14 @@ void MessageFormatter::resolveSelectors(MessageContext& context, const Environme
CHECK_ERROR(status); CHECK_ERROR(status);
U_ASSERT(!dataModel.hasPattern()); U_ASSERT(!dataModel.hasPattern());
const Expression* selectors = dataModel.getSelectorsInternal(); const VariableName* selectors = dataModel.getSelectorsInternal();
// 1. Let res be a new empty list of resolved values that support selection. // 1. Let res be a new empty list of resolved values that support selection.
// (Implicit, since `res` is an out-parameter) // (Implicit, since `res` is an out-parameter)
// 2. For each expression exp of the message's selectors // 2. For each expression exp of the message's selectors
for (int32_t i = 0; i < dataModel.numSelectors(); i++) { for (int32_t i = 0; i < dataModel.numSelectors(); i++) {
// 2i. Let rv be the resolved value of exp. // 2i. Let rv be the resolved value of exp.
ResolvedSelector rv = formatSelectorExpression(env, selectors[i], context, status); LocalPointer<InternalValue> rv(formatOperand(env, Operand(selectors[i]), context, status));
if (rv.hasSelector()) { if (rv->canSelect()) {
// 2ii. If selection is supported for rv: // 2ii. If selection is supported for rv:
// (True if this code has been reached) // (True if this code has been reached)
} else { } else {
@ -344,17 +325,17 @@ void MessageFormatter::resolveSelectors(MessageContext& context, const Environme
// Append nomatch as the last element of the list res. // Append nomatch as the last element of the list res.
// Emit a Selection Error. // Emit a Selection Error.
// (Note: in this case, rv, being a fallback, serves as `nomatch`) // (Note: in this case, rv, being a fallback, serves as `nomatch`)
#if U_DEBUG DynamicErrors& err = context.getErrors();
const DynamicErrors& err = context.getErrors(); err.setSelectorError(rv->getFunctionName(), status);
U_ASSERT(err.hasError()); rv.adoptInstead(new InternalValue(FormattedPlaceholder(rv->getFallback())));
U_ASSERT(rv.argument().isFallback()); if (!rv.isValid()) {
#endif status = U_MEMORY_ALLOCATION_ERROR;
return;
}
} }
// 2ii(a). Append rv as the last element of the list res. // 2ii(a). Append rv as the last element of the list res.
// (Also fulfills 2iii) // (Also fulfills 2iii)
LocalPointer<ResolvedSelector> v(create<ResolvedSelector>(std::move(rv), status)); res.adoptElement(rv.orphan(), status);
CHECK_ERROR(status);
res.adoptElement(v.orphan(), status);
} }
} }
@ -362,18 +343,17 @@ void MessageFormatter::resolveSelectors(MessageContext& context, const Environme
// `keys` and `matches` are vectors of strings // `keys` and `matches` are vectors of strings
void MessageFormatter::matchSelectorKeys(const UVector& keys, void MessageFormatter::matchSelectorKeys(const UVector& keys,
MessageContext& context, MessageContext& context,
ResolvedSelector&& rv, InternalValue* rv, // Does not adopt `rv`
UVector& keysOut, UVector& keysOut,
UErrorCode& status) const { UErrorCode& status) const {
CHECK_ERROR(status); CHECK_ERROR(status);
if (!rv.hasSelector()) { if (U_FAILURE(status)) {
// Return an empty list of matches // Return an empty list of matches
status = U_ZERO_ERROR;
return; return;
} }
auto selectorImpl = rv.getSelector();
U_ASSERT(selectorImpl != nullptr);
UErrorCode savedStatus = status; UErrorCode savedStatus = status;
// Convert `keys` to an array // Convert `keys` to an array
@ -400,15 +380,17 @@ void MessageFormatter::matchSelectorKeys(const UVector& keys,
int32_t prefsLen = 0; int32_t prefsLen = 0;
// Call the selector // Call the selector
selectorImpl->selectKey(rv.takeArgument(), rv.takeOptions(), FunctionName name = rv->getFunctionName();
adoptedKeys.getAlias(), keysLen, adoptedPrefs.getAlias(), prefsLen, rv->forceSelection(context.getErrors(),
adoptedKeys.getAlias(), keysLen,
adoptedPrefs.getAlias(), prefsLen,
status); status);
// Update errors // Update errors
if (savedStatus != status) { if (savedStatus != status) {
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
status = U_ZERO_ERROR; status = U_ZERO_ERROR;
context.getErrors().setSelectorError(rv.getSelectorName(), status); context.getErrors().setSelectorError(name, status);
} else { } else {
// Ignore warnings // Ignore warnings
status = savedStatus; status = savedStatus;
@ -461,8 +443,8 @@ void MessageFormatter::resolvePreferences(MessageContext& context, UVector& res,
if (!key.isWildcard()) { if (!key.isWildcard()) {
// 2ii(b)(a) Assert that key is a literal. // 2ii(b)(a) Assert that key is a literal.
// (Not needed) // (Not needed)
// 2ii(b)(b) Let `ks` be the resolved value of `key`. // 2ii(b)(b) Let `ks` be the resolved value of `key` in Unicode Normalization Form C.
ks = key.asLiteral().unquoted(); ks = normalizeNFC(key.asLiteral().unquoted());
// 2ii(b)(c) Append `ks` as the last element of the list `keys`. // 2ii(b)(c) Append `ks` as the last element of the list `keys`.
ksP.adoptInstead(create<UnicodeString>(std::move(ks), status)); ksP.adoptInstead(create<UnicodeString>(std::move(ks), status));
CHECK_ERROR(status); CHECK_ERROR(status);
@ -471,7 +453,7 @@ void MessageFormatter::resolvePreferences(MessageContext& context, UVector& res,
} }
// 2iii. Let `rv` be the resolved value at index `i` of `res`. // 2iii. Let `rv` be the resolved value at index `i` of `res`.
U_ASSERT(i < res.size()); U_ASSERT(i < res.size());
ResolvedSelector rv = std::move(*(static_cast<ResolvedSelector*>(res[i]))); InternalValue* rv = static_cast<InternalValue*>(res[i]);
// 2iv. Let matches be the result of calling the method MatchSelectorKeys(rv, keys) // 2iv. Let matches be the result of calling the method MatchSelectorKeys(rv, keys)
LocalPointer<UVector> matches(createUVector(status)); LocalPointer<UVector> matches(createUVector(status));
matchSelectorKeys(*keys, context, std::move(rv), *matches, status); matchSelectorKeys(*keys, context, std::move(rv), *matches, status);
@ -523,7 +505,7 @@ void MessageFormatter::filterVariants(const UVector& pref, UVector& vars, UError
// 2i(c). Assert that `key` is a literal. // 2i(c). Assert that `key` is a literal.
// (Not needed) // (Not needed)
// 2i(d). Let `ks` be the resolved value of `key`. // 2i(d). Let `ks` be the resolved value of `key`.
UnicodeString ks = key.asLiteral().unquoted(); UnicodeString ks = normalizeNFC(key.asLiteral().unquoted());
// 2i(e). Let `matches` be the list of strings at index `i` of `pref`. // 2i(e). Let `matches` be the list of strings at index `i` of `pref`.
const UVector& matches = *(static_cast<UVector*>(pref[i])); // `matches` is a vector of strings const UVector& matches = *(static_cast<UVector*>(pref[i])); // `matches` is a vector of strings
// 2i(f). If `matches` includes `ks` // 2i(f). If `matches` includes `ks`
@ -585,7 +567,7 @@ void MessageFormatter::sortVariants(const UVector& pref, UVector& vars, UErrorCo
// 5iii(c)(a). Assert that `key` is a literal. // 5iii(c)(a). Assert that `key` is a literal.
// (Not needed) // (Not needed)
// 5iii(c)(b). Let `ks` be the resolved value of `key`. // 5iii(c)(b). Let `ks` be the resolved value of `key`.
UnicodeString ks = key.asLiteral().unquoted(); UnicodeString ks = normalizeNFC(key.asLiteral().unquoted());
// 5iii(c)(c) Let matchpref be the integer position of ks in `matches`. // 5iii(c)(c) Let matchpref be the integer position of ks in `matches`.
matchpref = vectorFind(matches, ks); matchpref = vectorFind(matches, ks);
U_ASSERT(matchpref >= 0); U_ASSERT(matchpref >= 0);
@ -604,123 +586,13 @@ void MessageFormatter::sortVariants(const UVector& pref, UVector& vars, UErrorCo
// 7. Select the pattern of `var` // 7. Select the pattern of `var`
} }
// Evaluate the operand
ResolvedSelector MessageFormatter::resolveVariables(const Environment& env, const Operand& rand, MessageContext& context, UErrorCode &status) const {
if (U_FAILURE(status)) {
return {};
}
if (rand.isNull()) {
return ResolvedSelector(FormattedPlaceholder());
}
if (rand.isLiteral()) {
return ResolvedSelector(formatLiteral(rand.asLiteral()));
}
// Must be variable
const VariableName& var = rand.asVariable();
// Resolve the variable
if (env.has(var)) {
const Closure& referent = env.lookup(var);
// Resolve the referent
return resolveVariables(referent.getEnv(), referent.getExpr(), context, status);
}
// Either this is a global var or an unbound var --
// either way, it can't be bound to a function call.
// Check globals
FormattedPlaceholder val = evalArgument(var, context, status);
if (status == U_ILLEGAL_ARGUMENT_ERROR) {
status = U_ZERO_ERROR;
// Unresolved variable -- could be a previous warning. Nothing to resolve
U_ASSERT(context.getErrors().hasUnresolvedVariableError());
return ResolvedSelector(FormattedPlaceholder(var));
}
// Pass through other errors
return ResolvedSelector(std::move(val));
}
// Evaluate the expression except for not performing the top-level function call
// (which is expected to be a selector, but may not be, in error cases)
ResolvedSelector MessageFormatter::resolveVariables(const Environment& env,
const Expression& expr,
MessageContext& context,
UErrorCode &status) const {
if (U_FAILURE(status)) {
return {};
}
// Function call -- resolve the operand and options
if (expr.isFunctionCall()) {
const Operator* rator = expr.getOperator(status);
U_ASSERT(U_SUCCESS(status));
// Already checked that rator is non-reserved
const FunctionName& selectorName = rator->getFunctionName();
if (isSelector(selectorName)) {
auto selector = getSelector(context, selectorName, status);
if (U_SUCCESS(status)) {
FunctionOptions resolvedOptions = resolveOptions(env, rator->getOptionsInternal(), context, status);
// Operand may be the null argument, but resolveVariables() handles that
FormattedPlaceholder argument = formatOperand(env, expr.getOperand(), context, status);
return ResolvedSelector(selectorName, selector, std::move(resolvedOptions), std::move(argument));
}
} else if (isFormatter(selectorName)) {
context.getErrors().setSelectorError(selectorName, status);
} else {
context.getErrors().setUnknownFunction(selectorName, status);
}
// Non-selector used as selector; an error would have been recorded earlier
UnicodeString fallback(COLON);
fallback += selectorName;
if (!expr.getOperand().isNull()) {
fallback = formatOperand(env, expr.getOperand(), context, status).fallback;
}
return ResolvedSelector(FormattedPlaceholder(fallback));
} else {
// Might be a variable reference, so expand one more level of variable
return resolveVariables(env, expr.getOperand(), context, status);
}
}
ResolvedSelector MessageFormatter::formatSelectorExpression(const Environment& globalEnv, const Expression& expr, MessageContext& context, UErrorCode &status) const {
if (U_FAILURE(status)) {
return {};
}
// Resolve expression to determine if it's a function call
ResolvedSelector exprResult = resolveVariables(globalEnv, expr, context, status);
DynamicErrors& err = context.getErrors();
// If there is a selector, then `resolveVariables()` recorded it in the context
if (exprResult.hasSelector()) {
// Check if there was an error
if (exprResult.argument().isFallback()) {
// Use a null expression if it's a syntax or data model warning;
// create a valid (non-fallback) formatted placeholder from the
// fallback string otherwise
if (err.hasSyntaxError() || err.hasDataModelError()) {
return ResolvedSelector(FormattedPlaceholder()); // Null operand
} else {
return ResolvedSelector(exprResult.takeArgument());
}
}
return exprResult;
}
// No selector was found; error should already have been set
U_ASSERT(err.hasMissingSelectorAnnotationError() || err.hasUnknownFunctionError() || err.hasSelectorError());
return ResolvedSelector(FormattedPlaceholder(exprResult.argument().fallback));
}
void MessageFormatter::formatSelectors(MessageContext& context, const Environment& env, UErrorCode &status, UnicodeString& result) const { void MessageFormatter::formatSelectors(MessageContext& context, const Environment& env, UErrorCode &status, UnicodeString& result) const {
CHECK_ERROR(status); CHECK_ERROR(status);
// See https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#pattern-selection // See https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#pattern-selection
// Resolve Selectors // Resolve Selectors
// res is a vector of FormattedPlaceholders // res is a vector of InternalValues
LocalPointer<UVector> res(createUVector(status)); LocalPointer<UVector> res(createUVector(status));
CHECK_ERROR(status); CHECK_ERROR(status);
resolveSelectors(context, env, status, *res); resolveSelectors(context, env, status, *res);
@ -761,16 +633,21 @@ void MessageFormatter::formatSelectors(MessageContext& context, const Environmen
UnicodeString MessageFormatter::formatToString(const MessageArguments& arguments, UErrorCode &status) { UnicodeString MessageFormatter::formatToString(const MessageArguments& arguments, UErrorCode &status) {
EMPTY_ON_ERROR(status); EMPTY_ON_ERROR(status);
// Create a new environment that will store closures for all local variables
Environment* env = Environment::create(status);
// Create a new context with the given arguments and the `errors` structure // Create a new context with the given arguments and the `errors` structure
MessageContext context(arguments, *errors, status); MessageContext context(arguments, *errors, status);
UnicodeString result;
if (!(errors->hasSyntaxError() || errors->hasDataModelError())) {
// Create a new environment that will store closures for all local variables
// Check for unresolved variable errors // Check for unresolved variable errors
// checkDeclarations needs a reference to the pointer to the environment
// since it uses its `env` argument as an out-parameter. So it needs to be
// temporarily not a LocalPointer...
Environment* env(Environment::create(status));
checkDeclarations(context, env, status); checkDeclarations(context, env, status);
// ...and then it's adopted to avoid leaks
LocalPointer<Environment> globalEnv(env); LocalPointer<Environment> globalEnv(env);
UnicodeString result;
if (dataModel.hasPattern()) { if (dataModel.hasPattern()) {
formatPattern(context, *globalEnv, dataModel.getPattern(), status, result); formatPattern(context, *globalEnv, dataModel.getPattern(), status, result);
} else { } else {
@ -783,6 +660,8 @@ UnicodeString MessageFormatter::formatToString(const MessageArguments& arguments
formatSelectors(context, *globalEnv, status, result); formatSelectors(context, *globalEnv, status, result);
} }
} }
}
// Update status according to all errors seen while formatting // Update status according to all errors seen while formatting
if (signalErrors) { if (signalErrors) {
context.checkErrors(status); context.checkErrors(status);
@ -813,12 +692,14 @@ void MessageFormatter::check(MessageContext& context, const Environment& localEn
// Check that variable is in scope // Check that variable is in scope
const VariableName& var = rand.asVariable(); const VariableName& var = rand.asVariable();
UnicodeString normalized = normalizeNFC(var);
// Check local scope // Check local scope
if (localEnv.has(var)) { if (localEnv.has(normalized)) {
return; return;
} }
// Check global scope // Check global scope
context.getGlobal(var, status); context.getGlobal(*this, normalized, status);
if (status == U_ILLEGAL_ARGUMENT_ERROR) { if (status == U_ILLEGAL_ARGUMENT_ERROR) {
status = U_ZERO_ERROR; status = U_ZERO_ERROR;
context.getErrors().setUnresolvedVariable(var, status); context.getErrors().setUnresolvedVariable(var, status);
@ -855,7 +736,10 @@ void MessageFormatter::checkDeclarations(MessageContext& context, Environment*&
// memoizing the value of localEnv up to this point // memoizing the value of localEnv up to this point
// Add the LHS to the environment for checking the next declaration // Add the LHS to the environment for checking the next declaration
env = Environment::create(decl.getVariable(), Closure(rhs, *env), env, status); env = Environment::create(normalizeNFC(decl.getVariable()),
Closure(rhs, *env),
env,
status);
CHECK_ERROR(status); CHECK_ERROR(status);
} }
} }
@ -866,3 +750,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */ #endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -10,6 +10,8 @@
#if U_SHOW_CPLUSPLUS_API #if U_SHOW_CPLUSPLUS_API
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -139,6 +141,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */ #endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT2_UTILS_H #endif // MESSAGEFORMAT2_UTILS_H

View File

@ -3,12 +3,16 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
#include "unicode/messageformat2.h"
#include "unicode/messageformat2_arguments.h" #include "unicode/messageformat2_arguments.h"
#include "unicode/messageformat2_data_model_names.h" #include "unicode/messageformat2_data_model_names.h"
#include "messageformat2_evaluation.h"
#include "uvector.h" // U_ASSERT #include "uvector.h" // U_ASSERT
U_NAMESPACE_BEGIN U_NAMESPACE_BEGIN
@ -22,11 +26,15 @@ namespace message2 {
using Arguments = MessageArguments; using Arguments = MessageArguments;
const Formattable* Arguments::getArgument(const VariableName& arg, UErrorCode& errorCode) const { const Formattable* Arguments::getArgument(const MessageFormatter& context,
const VariableName& arg,
UErrorCode& errorCode) const {
if (U_SUCCESS(errorCode)) { if (U_SUCCESS(errorCode)) {
U_ASSERT(argsLen == 0 || arguments.isValid()); U_ASSERT(argsLen == 0 || arguments.isValid());
for (int32_t i = 0; i < argsLen; i++) { for (int32_t i = 0; i < argsLen; i++) {
if (argumentNames[i] == arg) { UnicodeString normalized = context.normalizeNFC(argumentNames[i]);
// arg already assumed to be normalized
if (normalized == arg) {
return &arguments[i]; return &arguments[i];
} }
} }
@ -57,3 +65,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */ #endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -3,12 +3,16 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
#include "unicode/messageformat2.h"
#include "messageformat2_allocation.h" #include "messageformat2_allocation.h"
#include "messageformat2_checker.h" #include "messageformat2_checker.h"
#include "messageformat2_evaluation.h"
#include "messageformat2_macros.h" #include "messageformat2_macros.h"
#include "uvector.h" // U_ASSERT #include "uvector.h" // U_ASSERT
@ -104,6 +108,14 @@ TypeEnvironment::~TypeEnvironment() {}
// --------------------- // ---------------------
Key Checker::normalizeNFC(const Key& k) const {
if (k.isWildcard()) {
return k;
}
return Key(Literal(k.asLiteral().isQuoted(),
context.normalizeNFC(k.asLiteral().unquoted())));
}
static bool areDefaultKeys(const Key* keys, int32_t len) { static bool areDefaultKeys(const Key* keys, int32_t len) {
U_ASSERT(len > 0); U_ASSERT(len > 0);
for (int32_t i = 0; i < len; i++) { for (int32_t i = 0; i < len; i++) {
@ -185,7 +197,7 @@ void Checker::checkVariants(UErrorCode& status) {
// This variant was already checked, // This variant was already checked,
// so we know keys1.len == len // so we know keys1.len == len
for (int32_t kk = 0; kk < len; kk++) { for (int32_t kk = 0; kk < len; kk++) {
if (!(keys[kk] == keys1[kk])) { if (!(normalizeNFC(keys[kk]) == normalizeNFC(keys1[kk]))) {
allEqual = false; allEqual = false;
break; break;
} }
@ -205,18 +217,14 @@ void Checker::checkVariants(UErrorCode& status) {
} }
} }
void Checker::requireAnnotated(const TypeEnvironment& t, const Expression& selectorExpr, UErrorCode& status) { void Checker::requireAnnotated(const TypeEnvironment& t,
const VariableName& selectorVar,
UErrorCode& status) {
CHECK_ERROR(status); CHECK_ERROR(status);
if (selectorExpr.isFunctionCall()) { if (t.get(selectorVar) == TypeEnvironment::Type::Annotated) {
return; // No error return; // No error
} }
const Operand& rand = selectorExpr.getOperand();
if (rand.isVariable()) {
if (t.get(rand.asVariable()) == TypeEnvironment::Type::Annotated) {
return; // No error
}
}
// If this code is reached, an error was detected // If this code is reached, an error was detected
errors.addError(StaticErrorType::MissingSelectorAnnotation, status); errors.addError(StaticErrorType::MissingSelectorAnnotation, status);
} }
@ -226,7 +234,7 @@ void Checker::checkSelectors(const TypeEnvironment& t, UErrorCode& status) {
// Check each selector; if it's not annotated, emit a // Check each selector; if it's not annotated, emit a
// "missing selector annotation" error // "missing selector annotation" error
const Expression* selectors = dataModel.getSelectorsInternal(); const VariableName* selectors = dataModel.getSelectorsInternal();
for (int32_t i = 0; i < dataModel.numSelectors(); i++) { for (int32_t i = 0; i < dataModel.numSelectors(); i++) {
requireAnnotated(t, selectors[i], status); requireAnnotated(t, selectors[i], status);
} }
@ -312,3 +320,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */ #endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -10,6 +10,8 @@
#if U_SHOW_CPLUSPLUS_API #if U_SHOW_CPLUSPLUS_API
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -56,15 +58,20 @@ namespace message2 {
// an explicit declaration // an explicit declaration
}; // class TypeEnvironment }; // class TypeEnvironment
class MessageFormatter;
// Checks a data model for semantic errors // Checks a data model for semantic errors
// (Errors are defined in https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md ) // (Errors are defined in https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md )
class Checker { class Checker {
public: public:
void check(UErrorCode&); void check(UErrorCode&);
Checker(const MFDataModel& m, StaticErrors& e) : dataModel(m), errors(e) {} Checker(const MFDataModel& d, StaticErrors& e, const MessageFormatter& mf)
: dataModel(d), errors(e), context(mf) {}
private: private:
void requireAnnotated(const TypeEnvironment&, const Expression&, UErrorCode&); Key normalizeNFC(const Key&) const;
void requireAnnotated(const TypeEnvironment&, const VariableName&, UErrorCode&);
void addFreeVars(TypeEnvironment& t, const Operand&, UErrorCode&); void addFreeVars(TypeEnvironment& t, const Operand&, UErrorCode&);
void addFreeVars(TypeEnvironment& t, const Operator&, UErrorCode&); void addFreeVars(TypeEnvironment& t, const Operator&, UErrorCode&);
void addFreeVars(TypeEnvironment& t, const OptionMap&, UErrorCode&); void addFreeVars(TypeEnvironment& t, const OptionMap&, UErrorCode&);
@ -78,6 +85,9 @@ namespace message2 {
void check(const Pattern&); void check(const Pattern&);
const MFDataModel& dataModel; const MFDataModel& dataModel;
StaticErrors& errors; StaticErrors& errors;
// Used for NFC normalization
const MessageFormatter& context;
}; // class Checker }; // class Checker
} // namespace message2 } // namespace message2
@ -88,6 +98,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */ #endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT_CHECKER_H #endif // MESSAGEFORMAT_CHECKER_H

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -691,7 +693,7 @@ Matcher::Matcher(const Matcher& other) {
numSelectors = other.numSelectors; numSelectors = other.numSelectors;
numVariants = other.numVariants; numVariants = other.numVariants;
UErrorCode localErrorCode = U_ZERO_ERROR; UErrorCode localErrorCode = U_ZERO_ERROR;
selectors.adoptInstead(copyArray<Expression>(other.selectors.getAlias(), selectors.adoptInstead(copyArray<VariableName>(other.selectors.getAlias(),
numSelectors, numSelectors,
localErrorCode)); localErrorCode));
variants.adoptInstead(copyArray<Variant>(other.variants.getAlias(), variants.adoptInstead(copyArray<Variant>(other.variants.getAlias(),
@ -702,7 +704,7 @@ Matcher::Matcher(const Matcher& other) {
} }
} }
Matcher::Matcher(Expression* ss, int32_t ns, Variant* vs, int32_t nv) Matcher::Matcher(VariableName* ss, int32_t ns, Variant* vs, int32_t nv)
: selectors(ss), numSelectors(ns), variants(vs), numVariants(nv) {} : selectors(ss), numSelectors(ns), variants(vs), numVariants(nv) {}
Matcher::~Matcher() {} Matcher::~Matcher() {}
@ -724,7 +726,7 @@ const Binding* MFDataModel::getLocalVariablesInternal() const {
return bindings.getAlias(); return bindings.getAlias();
} }
const Expression* MFDataModel::getSelectorsInternal() const { const VariableName* MFDataModel::getSelectorsInternal() const {
U_ASSERT(!bogus); U_ASSERT(!bogus);
U_ASSERT(!hasPattern()); U_ASSERT(!hasPattern());
return std::get_if<Matcher>(&body)->selectors.getAlias(); return std::get_if<Matcher>(&body)->selectors.getAlias();
@ -786,15 +788,13 @@ MFDataModel::Builder& MFDataModel::Builder::addBinding(Binding&& b, UErrorCode&
return *this; return *this;
} }
/* MFDataModel::Builder& MFDataModel::Builder::addSelector(VariableName&& selector,
selector must be non-null UErrorCode& status) {
*/
MFDataModel::Builder& MFDataModel::Builder::addSelector(Expression&& selector, UErrorCode& status) noexcept {
THIS_ON_ERROR(status); THIS_ON_ERROR(status);
buildSelectorsMessage(status); buildSelectorsMessage(status);
U_ASSERT(selectors != nullptr); U_ASSERT(selectors != nullptr);
selectors->adoptElement(create<Expression>(std::move(selector), status), status); selectors->adoptElement(create<VariableName>(std::move(selector), status), status);
return *this; return *this;
} }
@ -830,11 +830,11 @@ MFDataModel::MFDataModel(const MFDataModel& other) : body(Pattern()) {
if (other.hasPattern()) { if (other.hasPattern()) {
body = *std::get_if<Pattern>(&other.body); body = *std::get_if<Pattern>(&other.body);
} else { } else {
const Expression* otherSelectors = other.getSelectorsInternal(); const VariableName* otherSelectors = other.getSelectorsInternal();
const Variant* otherVariants = other.getVariantsInternal(); const Variant* otherVariants = other.getVariantsInternal();
int32_t numSelectors = other.numSelectors(); int32_t numSelectors = other.numSelectors();
int32_t numVariants = other.numVariants(); int32_t numVariants = other.numVariants();
Expression* copiedSelectors = copyArray(otherSelectors, numSelectors, localErrorCode); VariableName* copiedSelectors = copyArray(otherSelectors, numSelectors, localErrorCode);
Variant* copiedVariants = copyArray(otherVariants, numVariants, localErrorCode); Variant* copiedVariants = copyArray(otherVariants, numVariants, localErrorCode);
if (U_FAILURE(localErrorCode)) { if (U_FAILURE(localErrorCode)) {
bogus = true; bogus = true;
@ -863,7 +863,9 @@ MFDataModel::MFDataModel(const MFDataModel::Builder& builder, UErrorCode& errorC
int32_t numVariants = builder.variants->size(); int32_t numVariants = builder.variants->size();
int32_t numSelectors = builder.selectors->size(); int32_t numSelectors = builder.selectors->size();
LocalArray<Variant> variants(copyVectorToArray<Variant>(*builder.variants, errorCode), errorCode); LocalArray<Variant> variants(copyVectorToArray<Variant>(*builder.variants, errorCode), errorCode);
LocalArray<Expression> selectors(copyVectorToArray<Expression>(*builder.selectors, errorCode), errorCode); LocalArray<VariableName> selectors(copyVectorToArray<VariableName>(*builder.selectors,
errorCode),
errorCode);
if (U_FAILURE(errorCode)) { if (U_FAILURE(errorCode)) {
bogus = true; bogus = true;
return; return;
@ -918,3 +920,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */ #endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -290,3 +292,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */ #endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -15,6 +15,8 @@
* \brief C++ API: Formats messages using the draft MessageFormat 2.0. * \brief C++ API: Formats messages using the draft MessageFormat 2.0.
*/ */
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -151,6 +153,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */ #endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT2_ERRORS_H #endif // MESSAGEFORMAT2_ERRORS_H

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -89,35 +91,54 @@ FunctionOptions::FunctionOptions(FunctionOptions&& other) {
FunctionOptions::~FunctionOptions() { FunctionOptions::~FunctionOptions() {
if (options != nullptr) { if (options != nullptr) {
delete[] options; delete[] options;
options = nullptr;
} }
} }
// ResolvedSelector
// ----------------
ResolvedSelector::ResolvedSelector(const FunctionName& fn, static bool containsOption(const UVector& opts, const ResolvedFunctionOption& opt) {
Selector* sel, for (int32_t i = 0; i < opts.size(); i++) {
FunctionOptions&& opts, if (static_cast<ResolvedFunctionOption*>(opts[i])->getName()
FormattedPlaceholder&& val) == opt.getName()) {
: selectorName(fn), selector(sel), options(std::move(opts)), value(std::move(val)) { return true;
U_ASSERT(sel != nullptr); }
}
return false;
} }
ResolvedSelector::ResolvedSelector(FormattedPlaceholder&& val) : value(std::move(val)) {} // Options in `this` take precedence
// `this` can't be used after mergeOptions is called
FunctionOptions FunctionOptions::mergeOptions(FunctionOptions&& other,
UErrorCode& status) {
UVector mergedOptions(status);
mergedOptions.setDeleter(uprv_deleteUObject);
ResolvedSelector& ResolvedSelector::operator=(ResolvedSelector&& other) noexcept { if (U_FAILURE(status)) {
selectorName = std::move(other.selectorName); return {};
selector.adoptInstead(other.selector.orphan()); }
options = std::move(other.options);
value = std::move(other.value); // Create a new vector consisting of the options from this `FunctionOptions`
return *this; for (int32_t i = 0; i < functionOptionsLen; i++) {
mergedOptions.adoptElement(create<ResolvedFunctionOption>(std::move(options[i]), status),
status);
}
// Add each option from `other` that doesn't appear in this `FunctionOptions`
for (int i = 0; i < other.functionOptionsLen; i++) {
// Note: this is quadratic in the length of `options`
if (!containsOption(mergedOptions, other.options[i])) {
mergedOptions.adoptElement(create<ResolvedFunctionOption>(std::move(other.options[i]),
status),
status);
}
}
delete[] options;
options = nullptr;
functionOptionsLen = 0;
return FunctionOptions(std::move(mergedOptions), status);
} }
ResolvedSelector::ResolvedSelector(ResolvedSelector&& other) {
*this = std::move(other);
}
ResolvedSelector::~ResolvedSelector() {}
// PrioritizedVariant // PrioritizedVariant
// ------------------ // ------------------
@ -190,18 +211,210 @@ PrioritizedVariant::~PrioritizedVariant() {}
errors.checkErrors(status); errors.checkErrors(status);
} }
const Formattable* MessageContext::getGlobal(const VariableName& v, UErrorCode& errorCode) const { const Formattable* MessageContext::getGlobal(const MessageFormatter& context,
return arguments.getArgument(v, errorCode); const VariableName& v,
UErrorCode& errorCode) const {
return arguments.getArgument(context, v, errorCode);
} }
MessageContext::MessageContext(const MessageArguments& args, MessageContext::MessageContext(const MessageArguments& args,
const StaticErrors& e, const StaticErrors& e,
UErrorCode& status) : arguments(args), errors(e, status) {} UErrorCode& status) : arguments(args), errors(e, status) {}
MessageContext::~MessageContext() {} MessageContext::~MessageContext() {}
// InternalValue
// -------------
bool InternalValue::isFallback() const {
return std::holds_alternative<FormattedPlaceholder>(argument)
&& std::get_if<FormattedPlaceholder>(&argument)->isFallback();
}
bool InternalValue::hasNullOperand() const {
return std::holds_alternative<FormattedPlaceholder>(argument)
&& std::get_if<FormattedPlaceholder>(&argument)->isNullOperand();
}
FormattedPlaceholder InternalValue::takeArgument(UErrorCode& errorCode) {
if (U_FAILURE(errorCode)) {
return {};
}
if (std::holds_alternative<FormattedPlaceholder>(argument)) {
return std::move(*std::get_if<FormattedPlaceholder>(&argument));
}
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
return {};
}
const UnicodeString& InternalValue::getFallback() const {
if (std::holds_alternative<FormattedPlaceholder>(argument)) {
return std::get_if<FormattedPlaceholder>(&argument)->getFallback();
}
return (*std::get_if<InternalValue*>(&argument))->getFallback();
}
const Selector* InternalValue::getSelector(UErrorCode& errorCode) const {
if (U_FAILURE(errorCode)) {
return nullptr;
}
if (selector == nullptr) {
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
}
return selector;
}
InternalValue::InternalValue(FormattedPlaceholder&& arg) {
argument = std::move(arg);
selector = nullptr;
formatter = nullptr;
}
InternalValue::InternalValue(InternalValue* operand,
FunctionOptions&& opts,
const FunctionName& functionName,
const Formatter* f,
const Selector* s) {
argument = operand;
options = std::move(opts);
name = functionName;
selector = s;
formatter = f;
U_ASSERT(selector != nullptr || formatter != nullptr);
}
// `this` cannot be used after calling this method
void InternalValue::forceSelection(DynamicErrors& errs,
const UnicodeString* keys,
int32_t keysLen,
UnicodeString* prefs,
int32_t& prefsLen,
UErrorCode& errorCode) {
if (U_FAILURE(errorCode)) {
return;
}
if (!canSelect()) {
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
// Find the argument and complete set of options by traversing `argument`
FunctionOptions opts;
InternalValue* p = this;
FunctionName selectorName = name;
while (std::holds_alternative<InternalValue*>(p->argument)) {
if (p->name != selectorName) {
// Can only compose calls to the same selector
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
// First argument to mergeOptions takes precedence
opts = opts.mergeOptions(std::move(p->options), errorCode);
if (U_FAILURE(errorCode)) {
return;
}
InternalValue* next = *std::get_if<InternalValue*>(&p->argument);
p = next;
}
FormattedPlaceholder arg = std::move(*std::get_if<FormattedPlaceholder>(&p->argument));
selector->selectKey(std::move(arg), std::move(opts),
keys, keysLen,
prefs, prefsLen, errorCode);
if (U_FAILURE(errorCode)) {
errorCode = U_ZERO_ERROR;
errs.setSelectorError(selectorName, errorCode);
}
}
FormattedPlaceholder InternalValue::forceFormatting(DynamicErrors& errs, UErrorCode& errorCode) {
if (U_FAILURE(errorCode)) {
return {};
}
if (formatter == nullptr && selector == nullptr) {
U_ASSERT(std::holds_alternative<FormattedPlaceholder>(argument));
return std::move(*std::get_if<FormattedPlaceholder>(&argument));
}
if (formatter == nullptr) {
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
return {};
}
FormattedPlaceholder arg;
if (std::holds_alternative<FormattedPlaceholder>(argument)) {
arg = std::move(*std::get_if<FormattedPlaceholder>(&argument));
} else {
arg = (*std::get_if<InternalValue*>(&argument))->forceFormatting(errs,
errorCode);
}
if (U_FAILURE(errorCode)) {
return {};
}
// The fallback for a nullary function call is the function name
UnicodeString fallback;
if (arg.isNullOperand()) {
fallback = u":";
fallback += name;
} else {
fallback = arg.getFallback();
}
// Call the function with the argument
FormattedPlaceholder result = formatter->format(std::move(arg), std::move(options), errorCode);
if (U_FAILURE(errorCode)) {
if (errorCode == U_MF_OPERAND_MISMATCH_ERROR) {
errorCode = U_ZERO_ERROR;
errs.setOperandMismatchError(name, errorCode);
} else {
errorCode = U_ZERO_ERROR;
// Convey any error generated by the formatter
// as a formatting error, except for operand mismatch errors
errs.setFormattingError(name, errorCode);
}
}
// Ignore the output if any error occurred
if (errs.hasFormattingError()) {
return FormattedPlaceholder(fallback);
}
return result;
}
InternalValue& InternalValue::operator=(InternalValue&& other) noexcept {
argument = std::move(other.argument);
other.argument = nullptr;
options = std::move(other.options);
name = other.name;
selector = other.selector;
formatter = other.formatter;
other.selector = nullptr;
other.formatter = nullptr;
return *this;
}
InternalValue::~InternalValue() {
delete selector;
selector = nullptr;
delete formatter;
formatter = nullptr;
if (std::holds_alternative<InternalValue*>(argument)) {
delete *std::get_if<InternalValue*>(&argument);
argument = nullptr;
}
}
} // namespace message2 } // namespace message2
U_NAMESPACE_END U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */ #endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -14,6 +14,7 @@
* \file * \file
* \brief C++ API: Formats messages using the draft MessageFormat 2.0. * \brief C++ API: Formats messages using the draft MessageFormat 2.0.
*/ */
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
@ -63,38 +64,6 @@ namespace message2 {
return 1; return 1;
} }
// Encapsulates a value to be scrutinized by a `match` with its resolved
// options and the name of the selector
class ResolvedSelector : public UObject {
public:
ResolvedSelector() {}
ResolvedSelector(const FunctionName& fn,
Selector* selector,
FunctionOptions&& options,
FormattedPlaceholder&& value);
// Used either for errors, or when selector isn't yet known
explicit ResolvedSelector(FormattedPlaceholder&& value);
bool hasSelector() const { return selector.isValid(); }
const FormattedPlaceholder& argument() const { return value; }
FormattedPlaceholder&& takeArgument() { return std::move(value); }
const Selector* getSelector() {
U_ASSERT(selector.isValid());
return selector.getAlias();
}
FunctionOptions&& takeOptions() {
return std::move(options);
}
const FunctionName& getSelectorName() const { return selectorName; }
virtual ~ResolvedSelector();
ResolvedSelector& operator=(ResolvedSelector&&) noexcept;
ResolvedSelector(ResolvedSelector&&);
private:
FunctionName selectorName; // For error reporting
LocalPointer<Selector> selector;
FunctionOptions options;
FormattedPlaceholder value;
}; // class ResolvedSelector
// Closures and environments // Closures and environments
// ------------------------- // -------------------------
@ -174,11 +143,15 @@ namespace message2 {
// The context contains all the information needed to process // The context contains all the information needed to process
// an entire message: arguments, formatter cache, and error list // an entire message: arguments, formatter cache, and error list
class MessageFormatter;
class MessageContext : public UMemory { class MessageContext : public UMemory {
public: public:
MessageContext(const MessageArguments&, const StaticErrors&, UErrorCode&); MessageContext(const MessageArguments&, const StaticErrors&, UErrorCode&);
const Formattable* getGlobal(const VariableName&, UErrorCode&) const; const Formattable* getGlobal(const MessageFormatter&,
const VariableName&,
UErrorCode&) const;
// If any errors were set, update `status` accordingly // If any errors were set, update `status` accordingly
void checkErrors(UErrorCode& status) const; void checkErrors(UErrorCode& status) const;
@ -191,8 +164,47 @@ namespace message2 {
const MessageArguments& arguments; // External message arguments const MessageArguments& arguments; // External message arguments
// Errors accumulated during parsing/formatting // Errors accumulated during parsing/formatting
DynamicErrors errors; DynamicErrors errors;
}; // class MessageContext }; // class MessageContext
// InternalValue
// ----------------
class InternalValue : public UObject {
public:
const FunctionName& getFunctionName() const { return name; }
bool canSelect() const { return selector != nullptr; }
const Selector* getSelector(UErrorCode&) const;
FormattedPlaceholder forceFormatting(DynamicErrors& errs,
UErrorCode& errorCode);
void forceSelection(DynamicErrors& errs,
const UnicodeString* keys,
int32_t keysLen,
UnicodeString* prefs,
int32_t& prefsLen,
UErrorCode& errorCode);
// Needs to be deep-copyable and movable
virtual ~InternalValue();
InternalValue(FormattedPlaceholder&&);
// Formatter and selector may be null
InternalValue(InternalValue*, FunctionOptions&&, const FunctionName&, const Formatter*,
const Selector*);
const UnicodeString& getFallback() const;
bool isFallback() const;
bool hasNullOperand() const;
// Can't be used anymore after calling this
FormattedPlaceholder takeArgument(UErrorCode& errorCode);
InternalValue(InternalValue&& other) { *this = std::move(other); }
InternalValue& operator=(InternalValue&& other) noexcept;
private:
// InternalValue is owned (if present)
std::variant<InternalValue*, FormattedPlaceholder> argument;
FunctionOptions options;
FunctionName name;
const Selector* selector; // May be null
const Formatter* formatter; // May be null, but one or the other should be non-null unless argument is a FormattedPlaceholder
}; // class InternalValue
} // namespace message2 } // namespace message2
U_NAMESPACE_END U_NAMESPACE_END
@ -201,6 +213,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */ #endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT2_EVALUATION_H #endif // MESSAGEFORMAT2_EVALUATION_H

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -336,3 +338,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */ #endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -43,7 +45,8 @@ namespace message2 {
// Parse the pattern // Parse the pattern
MFDataModel::Builder tree(errorCode); MFDataModel::Builder tree(errorCode);
Parser(pat, tree, *errors, normalizedInput).parse(parseError, errorCode); Parser(pat, tree, *errors, normalizedInput, errorCode)
.parse(parseError, errorCode);
// Fail on syntax errors // Fail on syntax errors
if (errors->hasSyntaxError()) { if (errors->hasSyntaxError()) {
@ -116,6 +119,24 @@ namespace message2 {
// MessageFormatter // MessageFormatter
// Returns the NFC-normalized version of s, returning s itself
// if it's already normalized.
UnicodeString MessageFormatter::normalizeNFC(const UnicodeString& s) const {
UErrorCode status = U_ZERO_ERROR;
// Check if string is already normalized
UNormalizationCheckResult result = nfcNormalizer->quickCheck(s, status);
// If so, return it
if (U_SUCCESS(status) && result == UNORM_YES) {
return s;
}
// Otherwise, normalize it
UnicodeString normalized = nfcNormalizer->normalize(s, status);
if (U_FAILURE(status)) {
return {};
}
return normalized;
}
MessageFormatter::MessageFormatter(const MessageFormatter::Builder& builder, UErrorCode &success) : locale(builder.locale), customMFFunctionRegistry(builder.customMFFunctionRegistry) { MessageFormatter::MessageFormatter(const MessageFormatter::Builder& builder, UErrorCode &success) : locale(builder.locale), customMFFunctionRegistry(builder.customMFFunctionRegistry) {
CHECK_ERROR(success); CHECK_ERROR(success);
@ -132,9 +153,13 @@ namespace message2 {
.adoptFormatter(FunctionName(UnicodeString("time")), time, success) .adoptFormatter(FunctionName(UnicodeString("time")), time, success)
.adoptFormatter(FunctionName(UnicodeString("number")), number, success) .adoptFormatter(FunctionName(UnicodeString("number")), number, success)
.adoptFormatter(FunctionName(UnicodeString("integer")), integer, success) .adoptFormatter(FunctionName(UnicodeString("integer")), integer, success)
.adoptFormatter(FunctionName(UnicodeString("test:function")), new StandardFunctions::TestFormatFactory(), success)
.adoptFormatter(FunctionName(UnicodeString("test:format")), new StandardFunctions::TestFormatFactory(), success)
.adoptSelector(FunctionName(UnicodeString("number")), new StandardFunctions::PluralFactory(UPLURAL_TYPE_CARDINAL), success) .adoptSelector(FunctionName(UnicodeString("number")), new StandardFunctions::PluralFactory(UPLURAL_TYPE_CARDINAL), success)
.adoptSelector(FunctionName(UnicodeString("integer")), new StandardFunctions::PluralFactory(StandardFunctions::PluralFactory::integer()), success) .adoptSelector(FunctionName(UnicodeString("integer")), new StandardFunctions::PluralFactory(StandardFunctions::PluralFactory::integer()), success)
.adoptSelector(FunctionName(UnicodeString("string")), new StandardFunctions::TextFactory(), success); .adoptSelector(FunctionName(UnicodeString("string")), new StandardFunctions::TextFactory(), success)
.adoptSelector(FunctionName(UnicodeString("test:function")), new StandardFunctions::TestSelectFactory(), success)
.adoptSelector(FunctionName(UnicodeString("test:select")), new StandardFunctions::TestSelectFactory(), success);
CHECK_ERROR(success); CHECK_ERROR(success);
standardMFFunctionRegistry = standardFunctionsBuilder.build(); standardMFFunctionRegistry = standardFunctionsBuilder.build();
CHECK_ERROR(success); CHECK_ERROR(success);
@ -163,6 +188,8 @@ namespace message2 {
errors = errorsNew.orphan(); errors = errorsNew.orphan();
} }
nfcNormalizer = Normalizer2::getNFCInstance(success);
// Note: we currently evaluate variables lazily, // Note: we currently evaluate variables lazily,
// without memoization. This call is still necessary // without memoization. This call is still necessary
// to check out-of-scope uses of local variables in // to check out-of-scope uses of local variables in
@ -170,7 +197,7 @@ namespace message2 {
// only be checked when arguments are known) // only be checked when arguments are known)
// Check for resolution errors // Check for resolution errors
Checker(dataModel, *errors).check(success); Checker(dataModel, *errors, *this).check(success);
} }
void MessageFormatter::cleanup() noexcept { void MessageFormatter::cleanup() noexcept {
@ -191,6 +218,7 @@ namespace message2 {
signalErrors = other.signalErrors; signalErrors = other.signalErrors;
errors = other.errors; errors = other.errors;
other.errors = nullptr; other.errors = nullptr;
nfcNormalizer = other.nfcNormalizer;
return *this; return *this;
} }
@ -256,8 +284,11 @@ namespace message2 {
return formatter; return formatter;
} }
bool MessageFormatter::getDefaultFormatterNameByType(const UnicodeString& type, FunctionName& name) const { bool MessageFormatter::getDefaultFormatterNameByType(const UnicodeString& type,
U_ASSERT(hasCustomMFFunctionRegistry()); FunctionName& name) const {
if (!hasCustomMFFunctionRegistry()) {
return false;
}
const MFFunctionRegistry& reg = getCustomMFFunctionRegistry(); const MFFunctionRegistry& reg = getCustomMFFunctionRegistry();
return reg.getDefaultFormatterNameByType(type, name); return reg.getDefaultFormatterNameByType(type, name);
} }
@ -352,3 +383,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */ #endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -85,10 +87,11 @@ MFFunctionRegistry::Builder::Builder(UErrorCode& errorCode) {
formattersByType = new Hashtable(); formattersByType = new Hashtable();
if (!(formatters != nullptr && selectors != nullptr && formattersByType != nullptr)) { if (!(formatters != nullptr && selectors != nullptr && formattersByType != nullptr)) {
errorCode = U_MEMORY_ALLOCATION_ERROR; errorCode = U_MEMORY_ALLOCATION_ERROR;
} } else {
formatters->setValueDeleter(uprv_deleteUObject); formatters->setValueDeleter(uprv_deleteUObject);
selectors->setValueDeleter(uprv_deleteUObject); selectors->setValueDeleter(uprv_deleteUObject);
formattersByType->setValueDeleter(uprv_deleteUObject); formattersByType->setValueDeleter(uprv_deleteUObject);
}
} }
MFFunctionRegistry::Builder::~Builder() { MFFunctionRegistry::Builder::~Builder() {
@ -158,9 +161,13 @@ void MFFunctionRegistry::checkStandard() const {
checkFormatter("time"); checkFormatter("time");
checkFormatter("number"); checkFormatter("number");
checkFormatter("integer"); checkFormatter("integer");
checkFormatter("test:function");
checkFormatter("test:format");
checkSelector("number"); checkSelector("number");
checkSelector("integer"); checkSelector("integer");
checkSelector("string"); checkSelector("string");
checkSelector("test:function");
checkSelector("test:select");
} }
// Formatter/selector helpers // Formatter/selector helpers
@ -424,14 +431,14 @@ static FormattedPlaceholder notANumber(const FormattedPlaceholder& input) {
return FormattedPlaceholder(input, FormattedValue(UnicodeString("NaN"))); return FormattedPlaceholder(input, FormattedValue(UnicodeString("NaN")));
} }
static double parseNumberLiteral(const FormattedPlaceholder& input, UErrorCode& errorCode) { static double parseNumberLiteral(const Formattable& input, UErrorCode& errorCode) {
if (U_FAILURE(errorCode)) { if (U_FAILURE(errorCode)) {
return {}; return {};
} }
// Copying string to avoid GCC dangling-reference warning // Copying string to avoid GCC dangling-reference warning
// (although the reference is safe) // (although the reference is safe)
UnicodeString inputStr = input.asFormattable().getString(errorCode); UnicodeString inputStr = input.getString(errorCode);
// Precondition: `input`'s source Formattable has type string // Precondition: `input`'s source Formattable has type string
if (U_FAILURE(errorCode)) { if (U_FAILURE(errorCode)) {
return {}; return {};
@ -463,8 +470,42 @@ static double parseNumberLiteral(const FormattedPlaceholder& input, UErrorCode&
return result; return result;
} }
static UChar32 digitToChar(int32_t val, UErrorCode errorCode) {
if (U_FAILURE(errorCode)) {
return '0';
}
if (val < 0 || val > 9) {
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
}
switch(val) {
case 0:
return '0';
case 1:
return '1';
case 2:
return '2';
case 3:
return '3';
case 4:
return '4';
case 5:
return '5';
case 6:
return '6';
case 7:
return '7';
case 8:
return '8';
case 9:
return '9';
default:
errorCode = U_ILLEGAL_ARGUMENT_ERROR;
return '0';
}
}
static FormattedPlaceholder tryParsingNumberLiteral(const number::LocalizedNumberFormatter& nf, const FormattedPlaceholder& input, UErrorCode& errorCode) { static FormattedPlaceholder tryParsingNumberLiteral(const number::LocalizedNumberFormatter& nf, const FormattedPlaceholder& input, UErrorCode& errorCode) {
double numberValue = parseNumberLiteral(input, errorCode); double numberValue = parseNumberLiteral(input.asFormattable(), errorCode);
if (U_FAILURE(errorCode)) { if (U_FAILURE(errorCode)) {
return notANumber(input); return notANumber(input);
} }
@ -1235,6 +1276,273 @@ void StandardFunctions::TextSelector::selectKey(FormattedPlaceholder&& toFormat,
StandardFunctions::TextFactory::~TextFactory() {} StandardFunctions::TextFactory::~TextFactory() {}
StandardFunctions::TextSelector::~TextSelector() {} StandardFunctions::TextSelector::~TextSelector() {}
// ------------ TestFormatFactory
Formatter* StandardFunctions::TestFormatFactory::createFormatter(const Locale& locale, UErrorCode& errorCode) {
NULL_ON_ERROR(errorCode);
// Results are not locale-dependent
(void) locale;
Formatter* result = new TestFormat();
if (result == nullptr) {
errorCode = U_MEMORY_ALLOCATION_ERROR;
}
return result;
}
StandardFunctions::TestFormatFactory::~TestFormatFactory() {}
StandardFunctions::TestFormat::~TestFormat() {}
// Extract numeric value from a Formattable or, if it's a string,
// parse it as a number according to the MF2 `number-literal` grammar production
double formattableToNumber(const Formattable& arg, UErrorCode& status) {
if (U_FAILURE(status)) {
return 0;
}
double result = 0;
switch (arg.getType()) {
case UFMT_DOUBLE: {
result = arg.getDouble(status);
U_ASSERT(U_SUCCESS(status));
break;
}
case UFMT_LONG: {
result = (double) arg.getLong(status);
U_ASSERT(U_SUCCESS(status));
break;
}
case UFMT_INT64: {
result = (double) arg.getInt64(status);
U_ASSERT(U_SUCCESS(status));
break;
}
case UFMT_STRING: {
// Try to parse the string as a number
result = parseNumberLiteral(arg, status);
if (U_FAILURE(status)) {
status = U_MF_OPERAND_MISMATCH_ERROR;
}
break;
}
default: {
// Other types can't be parsed as a number
status = U_MF_OPERAND_MISMATCH_ERROR;
break;
}
}
return result;
}
/* static */ void StandardFunctions::TestFormat::testFunctionParameters(const FormattedPlaceholder& arg,
const FunctionOptions& options,
int32_t& decimalPlaces,
bool& failsFormat,
bool& failsSelect,
double& input,
UErrorCode& status) {
CHECK_ERROR(status);
// 1. Let DecimalPlaces be 0.
decimalPlaces = 0;
// 2. Let FailsFormat be false.
failsFormat = false;
// 3. Let FailsSelect be false.
failsSelect = false;
// 4. Let arg be the resolved value of the expression operand.
// (already true)
// Step 5 omitted because composition isn't fully implemented yet
// 6. Else if arg is a numerical value or a string matching the number-literal production, then
input = formattableToNumber(arg.asFormattable(), status);
if (U_FAILURE(status)) {
// 7. Else,
// 7i. Emit "bad-input" Resolution Error.
status = U_MF_OPERAND_MISMATCH_ERROR;
// 7ii. Use a fallback value as the resolved value of the expression.
// Further steps of this algorithm are not followed.
}
// 8. If the decimalPlaces option is set, then
Formattable opt;
if (options.getFunctionOption(UnicodeString("decimalPlaces"), opt)) {
// 8i. If its value resolves to a numerical integer value 0 or 1
// or their corresponding string representations '0' or '1', then
double decimalPlacesInput = formattableToNumber(opt, status);
if (U_SUCCESS(status)) {
if (decimalPlacesInput == 0 || decimalPlacesInput == 1) {
// 8ia. Set DecimalPlaces to be the numerical value of the option.
decimalPlaces = decimalPlacesInput;
}
}
// 8ii. Else if its value is not an unresolved value set by option resolution,
else {
// 8iia. Emit "bad-option" Resolution Error.
status = U_MF_BAD_OPTION;
// 8iib. Use a fallback value as the resolved value of the expression.
}
}
// 9. If the fails option is set, then
Formattable failsOpt;
if (options.getFunctionOption(UnicodeString("fails"), failsOpt)) {
UnicodeString failsString = failsOpt.getString(status);
if (U_SUCCESS(status)) {
// 9i. If its value resolves to the string 'always', then
if (failsString == u"always") {
// 9ia. Set FailsFormat to be true
failsFormat = true;
// 9ib. Set FailsSelect to be true.
failsSelect = true;
}
// 9ii. Else if its value resolves to the string "format", then
else if (failsString == u"format") {
// 9ia. Set FailsFormat to be true
failsFormat = true;
}
// 9iii. Else if its value resolves to the string "select", then
else if (failsString == u"select") {
// 9iiia. Set FailsSelect to be true.
failsSelect = true;
}
// 9iv. Else if its value does not resolve to the string "never", then
else if (failsString != u"never") {
// 9iv(a). Emit "bad-option" Resolution Error.
status = U_MF_BAD_OPTION;
}
} else {
// 9iv. again
status = U_MF_BAD_OPTION;
}
}
}
FormattedPlaceholder StandardFunctions::TestFormat::format(FormattedPlaceholder&& arg,
FunctionOptions&& options,
UErrorCode& status) const{
int32_t decimalPlaces;
bool failsFormat;
bool failsSelect;
double input;
testFunctionParameters(arg, options, decimalPlaces,
failsFormat, failsSelect, input, status);
if (U_FAILURE(status)) {
return FormattedPlaceholder(arg.getFallback());
}
// If FailsFormat is true, attempting to format the placeholder to any
// formatting target will fail.
if (failsFormat) {
status = U_MF_FORMATTING_ERROR;
return FormattedPlaceholder(arg.getFallback());
}
UnicodeString result;
// When :test:function is used as a formatter, a placeholder resolving to a value
// with a :test:function expression is formatted as a concatenation of the following parts:
// 1. If Input is less than 0, the character - U+002D Hyphen-Minus.
if (input < 0) {
result += HYPHEN;
}
// 2. The truncated absolute integer value of Input, i.e. floor(abs(Input)), formatted as a
// sequence of decimal digit characters (U+0030...U+0039).
char buffer[256];
bool ignore;
int ignoreLen;
int ignorePoint;
double_conversion::DoubleToStringConverter::DoubleToAscii(floor(abs(input)),
double_conversion::DoubleToStringConverter::DtoaMode::SHORTEST,
0,
buffer,
256,
&ignore,
&ignoreLen,
&ignorePoint);
result += UnicodeString(buffer);
// 3. If DecimalPlaces is 1, then
if (decimalPlaces == 1) {
// 3i. The character . U+002E Full Stop.
result += u".";
// 3ii. The single decimal digit character representing the value
// floor((abs(Input) - floor(abs(Input))) * 10)
int32_t val = floor((abs(input) - floor(abs(input)) * 10));
result += digitToChar(val, status);
U_ASSERT(U_SUCCESS(status));
}
return FormattedPlaceholder(result);
}
// ------------ TestSelectFactory
StandardFunctions::TestSelectFactory::~TestSelectFactory() {}
StandardFunctions::TestSelect::~TestSelect() {}
Selector* StandardFunctions::TestSelectFactory::createSelector(const Locale& locale,
UErrorCode& errorCode) const {
NULL_ON_ERROR(errorCode);
// Results are not locale-dependent
(void) locale;
Selector* result = new TestSelect();
if (result == nullptr) {
errorCode = U_MEMORY_ALLOCATION_ERROR;
}
return result;
}
void StandardFunctions::TestSelect::selectKey(FormattedPlaceholder&& val,
FunctionOptions&& options,
const UnicodeString* keys,
int32_t keysLen,
UnicodeString* prefs,
int32_t& prefsLen,
UErrorCode& status) const {
int32_t decimalPlaces;
bool failsFormat;
bool failsSelect;
double input;
TestFormat::testFunctionParameters(val, options, decimalPlaces,
failsFormat, failsSelect, input, status);
if (U_FAILURE(status)) {
return;
}
if (failsSelect) {
status = U_MF_SELECTOR_ERROR;
return;
}
// If the Input is 1 and DecimalPlaces is 1, the method will return some slice
// of the list « '1.0', '1' », depending on whether those values are included in keys.
bool include1point0 = false;
bool include1 = false;
if (input == 1 && decimalPlaces == 1) {
include1point0 = true;
include1 = true;
} else if (input == 1 && decimalPlaces == 0) {
include1 = true;
}
// If the Input is 1 and DecimalPlaces is 0, the method will return the list « '1' » if
// keys includes '1', or an empty list otherwise.
// If the Input is any other value, the method will return an empty list.
for (int32_t i = 0; i < keysLen; i++) {
if ((keys[i] == u"1" && include1)
|| (keys[i] == u"1.0" && include1point0)) {
prefs[prefsLen] = keys[i];
prefsLen++;
}
}
}
} // namespace message2 } // namespace message2
U_NAMESPACE_END U_NAMESPACE_END
@ -1242,3 +1550,4 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -10,6 +10,8 @@
#if U_SHOW_CPLUSPLUS_API #if U_SHOW_CPLUSPLUS_API
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -209,6 +211,60 @@ namespace message2 {
TextSelector(const Locale& l) : locale(l) {} TextSelector(const Locale& l) : locale(l) {}
}; };
// See https://github.com/unicode-org/message-format-wg/blob/main/test/README.md
class TestFormatFactory : public FormatterFactory {
public:
Formatter* createFormatter(const Locale& locale, UErrorCode& status) override;
TestFormatFactory() {}
virtual ~TestFormatFactory();
};
class TestSelect;
class TestFormat : public Formatter {
public:
FormattedPlaceholder format(FormattedPlaceholder&& toFormat, FunctionOptions&& options, UErrorCode& status) const override;
virtual ~TestFormat();
private:
friend class TestFormatFactory;
friend class TestSelect;
TestFormat() {}
static void testFunctionParameters(const FormattedPlaceholder& arg,
const FunctionOptions& options,
int32_t& decimalPlaces,
bool& failsFormat,
bool& failsSelect,
double& input,
UErrorCode& status);
};
// See https://github.com/unicode-org/message-format-wg/blob/main/test/README.md
class TestSelectFactory : public SelectorFactory {
public:
Selector* createSelector(const Locale& locale, UErrorCode& status) const override;
TestSelectFactory() {}
virtual ~TestSelectFactory();
};
class TestSelect : public Selector {
public:
void selectKey(FormattedPlaceholder&& val,
FunctionOptions&& options,
const UnicodeString* keys,
int32_t keysLen,
UnicodeString* prefs,
int32_t& prefsLen,
UErrorCode& status) const override;
virtual ~TestSelect();
private:
friend class TestSelectFactory;
TestSelect() {}
};
}; };
extern void formatDateWithDefaults(const Locale& locale, UDate date, UnicodeString&, UErrorCode& errorCode); extern void formatDateWithDefaults(const Locale& locale, UDate date, UnicodeString&, UErrorCode& errorCode);
@ -226,6 +282,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */ #endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT2_FUNCTION_REGISTRY_INTERNAL_H #endif // MESSAGEFORMAT2_FUNCTION_REGISTRY_INTERNAL_H

View File

@ -10,6 +10,8 @@
#if U_SHOW_CPLUSPLUS_API #if U_SHOW_CPLUSPLUS_API
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -97,6 +99,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */ #endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT2_MACROS_H #endif // MESSAGEFORMAT2_MACROS_H

View File

@ -3,13 +3,18 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
#include "unicode/uniset.h"
#include "messageformat2_errors.h" #include "messageformat2_errors.h"
#include "messageformat2_macros.h" #include "messageformat2_macros.h"
#include "messageformat2_parser.h" #include "messageformat2_parser.h"
#include "ucln_in.h"
#include "umutex.h"
#include "uvector.h" // U_ASSERT #include "uvector.h" // U_ASSERT
U_NAMESPACE_BEGIN U_NAMESPACE_BEGIN
@ -91,14 +96,282 @@ static void copyContext(const UChar in[U_PARSE_CONTEXT_LEN], UChar out[U_PARSE_C
} }
// ------------------------------------- // -------------------------------------
// Predicates // Initialization of UnicodeSets
// Returns true if `c` is in the interval [`first`, `last`] namespace unisets {
static bool inRange(UChar32 c, UChar32 first, UChar32 last) {
U_ASSERT(first < last); UnicodeSet* gUnicodeSets[unisets::UNISETS_KEY_COUNT] = {};
return c >= first && c <= last;
inline UnicodeSet* getImpl(Key key) {
return gUnicodeSets[key];
} }
icu::UInitOnce gMF2ParseUniSetsInitOnce {};
}
UnicodeSet* initContentChars(UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* result = new UnicodeSet(0x0001, 0x0008); // Omit NULL, HTAB and LF
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
result->add(0x000B, 0x000C); // Omit CR
result->add(0x000E, 0x001F); // Omit SP
result->add(0x0021, 0x002D); // Omit '.'
result->add(0x002F, 0x003F); // Omit '@'
result->add(0x0041, 0x005B); // Omit '\'
result->add(0x005D, 0x007A); // Omit { | }
result->add(0x007E, 0x2FFF); // Omit IDEOGRAPHIC_SPACE
result->add(0x3001, 0x10FFFF); // Allowing surrogates is intentional
result->freeze();
return result;
}
UnicodeSet* initWhitespace(UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* result = new UnicodeSet();
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
result->add(SPACE);
result->add(HTAB);
result->add(CR);
result->add(LF);
result->add(IDEOGRAPHIC_SPACE);
result->freeze();
return result;
}
UnicodeSet* initBidiControls(UErrorCode& status) {
UnicodeSet* result = new UnicodeSet(UnicodeString("[\\u061C]"), status);
if (U_FAILURE(status)) {
return nullptr;
}
result->add(0x200E, 0x200F);
result->add(0x2066, 0x2069);
result->freeze();
return result;
}
UnicodeSet* initAlpha(UErrorCode& status) {
UnicodeSet* result = new UnicodeSet(UnicodeString("[:letter:]"), status);
if (U_FAILURE(status)) {
return nullptr;
}
result->freeze();
return result;
}
UnicodeSet* initDigits(UErrorCode& status) {
UnicodeSet* result = new UnicodeSet(UnicodeString("[:number:]"), status);
if (U_FAILURE(status)) {
return nullptr;
}
result->freeze();
return result;
}
UnicodeSet* initNameStartChars(UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* isAlpha = unisets::gUnicodeSets[unisets::ALPHA] = initAlpha(status);
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* result = new UnicodeSet(*isAlpha);
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
};
result->add(UNDERSCORE);
result->add(0x00C0, 0x00D6);
result->add(0x00D8, 0x00F6);
result->add(0x00F8, 0x02FF);
result->add(0x0370, 0x037D);
result->add(0x037F, 0x061B);
result->add(0x061D, 0x1FFF);
result->add(0x200C, 0x200D);
result->add(0x2070, 0x218F);
result->add(0x2C00, 0x2FEF);
result->add(0x3001, 0xD7FF);
result->add(0xF900, 0xFDCF);
result->add(0xFDF0, 0xFFFD);
result->add(0x100000, 0xEFFFF);
result->freeze();
return result;
}
UnicodeSet* initNameChars(UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* nameStart = unisets::gUnicodeSets[unisets::NAME_START] = initNameStartChars(status);
UnicodeSet* digit = unisets::gUnicodeSets[unisets::DIGIT] = initDigits(status);
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* result = new UnicodeSet();
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
};
result->addAll(*nameStart);
result->addAll(*digit);
result->add(HYPHEN);
result->add(PERIOD);
result->add(0x00B7);
result->add(0x0300, 0x036F);
result->add(0x203F, 0x2040);
result->freeze();
return result;
}
UnicodeSet* initTextChars(UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* content = unisets::gUnicodeSets[unisets::CONTENT] = initContentChars(status);
UnicodeSet* whitespace = unisets::gUnicodeSets[unisets::WHITESPACE] = initWhitespace(status);
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* result = new UnicodeSet();
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
};
result->addAll(*content);
result->addAll(*whitespace);
result->add(PERIOD);
result->add(AT);
result->add(PIPE);
result->freeze();
return result;
}
UnicodeSet* initQuotedChars(UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
unisets::gUnicodeSets[unisets::TEXT] = initTextChars(status);
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* result = new UnicodeSet();
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
};
// content and whitespace were initialized by `initTextChars()`
UnicodeSet* content = unisets::getImpl(unisets::CONTENT);
if (content == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
result->addAll(*content);
UnicodeSet* whitespace = unisets::getImpl(unisets::WHITESPACE);
if (whitespace == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
result->addAll(*whitespace);
result->add(PERIOD);
result->add(AT);
result->add(LEFT_CURLY_BRACE);
result->add(RIGHT_CURLY_BRACE);
result->freeze();
return result;
}
UnicodeSet* initEscapableChars(UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* result = new UnicodeSet();
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
result->add(PIPE);
result->add(BACKSLASH);
result->add(LEFT_CURLY_BRACE);
result->add(RIGHT_CURLY_BRACE);
result->freeze();
return result;
}
namespace unisets {
UBool U_CALLCONV cleanupMF2ParseUniSets() {
for (int32_t i = 0; i < UNISETS_KEY_COUNT; i++) {
delete gUnicodeSets[i];
gUnicodeSets[i] = nullptr;
}
gMF2ParseUniSetsInitOnce.reset();
return true;
}
void U_CALLCONV initMF2ParseUniSets(UErrorCode& status) {
ucln_i18n_registerCleanup(UCLN_I18N_MF2_UNISETS, cleanupMF2ParseUniSets);
/*
Each of the init functions initializes the UnicodeSets
that it depends on.
initBidiControls (no dependencies)
initEscapableChars (no dependencies)
initNameChars depends on
initDigits
initNameStartChars depends on
initAlpha
initQuotedChars depends on
initTextChars depends on
initContentChars
initWhitespace
*/
gUnicodeSets[unisets::BIDI] = initBidiControls(status);
gUnicodeSets[unisets::NAME_CHAR] = initNameChars(status);
gUnicodeSets[unisets::QUOTED] = initQuotedChars(status);
gUnicodeSets[unisets::ESCAPABLE] = initEscapableChars(status);
if (U_FAILURE(status)) {
cleanupMF2ParseUniSets();
}
}
const UnicodeSet* get(Key key, UErrorCode& status) {
umtx_initOnce(gMF2ParseUniSetsInitOnce, &initMF2ParseUniSets, status);
if (U_FAILURE(status)) {
return nullptr;
}
UnicodeSet* result = getImpl(key);
if (result == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
}
return result;
}
}
// -------------------------------------
// Predicates
/* /*
The following helper predicates should exactly match nonterminals in the MessageFormat 2 grammar: The following helper predicates should exactly match nonterminals in the MessageFormat 2 grammar:
@ -113,76 +386,50 @@ static bool inRange(UChar32 c, UChar32 first, UChar32 last) {
`isWhitespace()` : `s` `isWhitespace()` : `s`
*/ */
static bool isContentChar(UChar32 c) { bool Parser::isContentChar(UChar32 c) const {
return inRange(c, 0x0001, 0x0008) // Omit NULL, HTAB and LF return contentChars->contains(c);
|| inRange(c, 0x000B, 0x000C) // Omit CR
|| inRange(c, 0x000E, 0x001F) // Omit SP
|| inRange(c, 0x0021, 0x002D) // Omit '.'
|| inRange(c, 0x002F, 0x003F) // Omit '@'
|| inRange(c, 0x0041, 0x005B) // Omit '\'
|| inRange(c, 0x005D, 0x007A) // Omit { | }
|| inRange(c, 0x007E, 0xD7FF) // Omit surrogates
|| inRange(c, 0xE000, 0x10FFFF);
} }
// See `s` in the MessageFormat 2 grammar // See `bidi` in the MF2 grammar
inline bool isWhitespace(UChar32 c) { bool Parser::isBidiControl(UChar32 c) const {
switch (c) { return bidiControlChars->contains(c);
case SPACE:
case HTAB:
case CR:
case LF:
case IDEOGRAPHIC_SPACE:
return true;
default:
return false;
}
} }
static bool isTextChar(UChar32 c) { // See `ws` in the MessageFormat 2 grammar
return isContentChar(c) bool Parser::isWhitespace(UChar32 c) const {
|| isWhitespace(c) return whitespaceChars->contains(c);
|| c == PERIOD
|| c == AT
|| c == PIPE;
} }
static bool isAlpha(UChar32 c) { return inRange(c, 0x0041, 0x005A) || inRange(c, 0x0061, 0x007A); } bool Parser::isTextChar(UChar32 c) const {
return textChars->contains(c);
static bool isDigit(UChar32 c) { return inRange(c, 0x0030, 0x0039); }
static bool isNameStart(UChar32 c) {
return isAlpha(c) || c == UNDERSCORE || inRange(c, 0x00C0, 0x00D6) || inRange(c, 0x00D8, 0x00F6) ||
inRange(c, 0x00F8, 0x02FF) || inRange(c, 0x0370, 0x037D) || inRange(c, 0x037F, 0x1FFF) ||
inRange(c, 0x200C, 0x200D) || inRange(c, 0x2070, 0x218F) || inRange(c, 0x2C00, 0x2FEF) ||
inRange(c, 0x3001, 0xD7FF) || inRange(c, 0xF900, 0xFDCF) || inRange(c, 0xFDF0, 0xFFFD) ||
inRange(c, 0x10000, 0xEFFFF);
} }
static bool isNameChar(UChar32 c) { bool Parser::isAlpha(UChar32 c) const {
return isNameStart(c) || isDigit(c) || c == HYPHEN || c == PERIOD || c == 0x00B7 || return alphaChars->contains(c);
inRange(c, 0x0300, 0x036F) || inRange(c, 0x203F, 0x2040);
} }
static bool isUnquotedStart(UChar32 c) { bool Parser::isDigit(UChar32 c) const {
return isNameStart(c) || isDigit(c) || c == HYPHEN || c == PERIOD || c == 0x00B7 || return digitChars->contains(c);
inRange(c, 0x0300, 0x036F) || inRange(c, 0x203F, 0x2040);
} }
static bool isQuotedChar(UChar32 c) { bool Parser::isNameStart(UChar32 c) const {
return isContentChar(c) return nameStartChars->contains(c);
|| isWhitespace(c)
|| c == PERIOD
|| c == AT
|| c == LEFT_CURLY_BRACE
|| c == RIGHT_CURLY_BRACE;
} }
static bool isEscapableChar(UChar32 c) { bool Parser::isNameChar(UChar32 c) const {
return c == PIPE return nameChars->contains(c);
|| c == BACKSLASH }
|| c == LEFT_CURLY_BRACE
|| c == RIGHT_CURLY_BRACE; bool Parser::isUnquotedStart(UChar32 c) const {
return isNameChar(c);
}
bool Parser::isQuotedChar(UChar32 c) const {
return quotedChars->contains(c);
}
bool Parser::isEscapableChar(UChar32 c) const {
return escapableChars->contains(c);
} }
// Returns true iff `c` can begin a `function` nonterminal // Returns true iff `c` can begin a `function` nonterminal
@ -203,12 +450,12 @@ static bool isAnnotationStart(UChar32 c) {
} }
// Returns true iff `c` can begin a `literal` nonterminal // Returns true iff `c` can begin a `literal` nonterminal
static bool isLiteralStart(UChar32 c) { bool Parser::isLiteralStart(UChar32 c) const {
return (c == PIPE || isNameStart(c) || c == HYPHEN || isDigit(c)); return (c == PIPE || isNameStart(c) || c == HYPHEN || isDigit(c));
} }
// Returns true iff `c` can begin a `key` nonterminal // Returns true iff `c` can begin a `key` nonterminal
static bool isKeyStart(UChar32 c) { bool Parser::isKeyStart(UChar32 c) const {
return (c == ASTERISK || isLiteralStart(c)); return (c == ASTERISK || isLiteralStart(c));
} }
@ -347,7 +594,7 @@ option, or the optional space before an attribute.
No pre, no post. No pre, no post.
A message may end with whitespace, so `index` may equal `len()` on exit. A message may end with whitespace, so `index` may equal `len()` on exit.
*/ */
void Parser::parseWhitespaceMaybeRequired(bool required, UErrorCode& errorCode) { void Parser::parseRequiredWS(UErrorCode& errorCode) {
bool sawWhitespace = false; bool sawWhitespace = false;
// The loop exits either when we consume all the input, // The loop exits either when we consume all the input,
@ -358,7 +605,7 @@ void Parser::parseWhitespaceMaybeRequired(bool required, UErrorCode& errorCode)
// If whitespace isn't required -- or if we saw it already -- // If whitespace isn't required -- or if we saw it already --
// then the caller is responsible for checking this case and // then the caller is responsible for checking this case and
// setting an error if necessary. // setting an error if necessary.
if (!required || sawWhitespace) { if (sawWhitespace) {
// Not an error. // Not an error.
return; return;
} }
@ -380,24 +627,51 @@ void Parser::parseWhitespaceMaybeRequired(bool required, UErrorCode& errorCode)
} }
} }
if (!sawWhitespace && required) { if (!sawWhitespace) {
ERROR(errorCode); ERROR(errorCode);
} }
} }
void Parser::parseOptionalBidi() {
while (true) {
if (!inBounds()) {
return;
}
if (isBidiControl(peek())) {
next();
} else {
break;
}
}
}
/* /*
No pre, no post, for the same reason as `parseWhitespaceMaybeRequired()`. No pre, no post, because a message may end with whitespace
Matches `s` in the MF2 grammar
*/ */
void Parser::parseRequiredWhitespace(UErrorCode& errorCode) { void Parser::parseRequiredWhitespace(UErrorCode& errorCode) {
parseWhitespaceMaybeRequired(true, errorCode); parseOptionalBidi();
parseRequiredWS(errorCode);
parseOptionalWhitespace();
normalizedInput += SPACE; normalizedInput += SPACE;
} }
/* /*
No pre, no post, for the same reason as `parseWhitespaceMaybeRequired()`. No pre, no post, for the same reason as `parseWhitespaceMaybeRequired()`.
*/ */
void Parser::parseOptionalWhitespace(UErrorCode& errorCode) { void Parser::parseOptionalWhitespace() {
parseWhitespaceMaybeRequired(false, errorCode); while (true) {
if (!inBounds()) {
return;
}
auto cp = peek();
if (isWhitespace(cp) || isBidiControl(cp)) {
maybeAdvanceLine();
next();
} else {
break;
}
}
} }
// Consumes a single character, signaling an error if `peek()` != `c` // Consumes a single character, signaling an error if `peek()` != `c`
@ -442,11 +716,11 @@ void Parser::parseToken(const std::u16string_view& token, UErrorCode& errorCode)
*/ */
void Parser::parseTokenWithWhitespace(const std::u16string_view& token, UErrorCode& errorCode) { void Parser::parseTokenWithWhitespace(const std::u16string_view& token, UErrorCode& errorCode) {
// No need for error check or bounds check before parseOptionalWhitespace // No need for error check or bounds check before parseOptionalWhitespace
parseOptionalWhitespace(errorCode); parseOptionalWhitespace();
// Establish precondition // Establish precondition
CHECK_BOUNDS(errorCode); CHECK_BOUNDS(errorCode);
parseToken(token, errorCode); parseToken(token, errorCode);
parseOptionalWhitespace(errorCode); parseOptionalWhitespace();
// Guarantee postcondition // Guarantee postcondition
CHECK_BOUNDS(errorCode); CHECK_BOUNDS(errorCode);
} }
@ -458,12 +732,12 @@ void Parser::parseTokenWithWhitespace(const std::u16string_view& token, UErrorCo
then consumes optional whitespace again then consumes optional whitespace again
*/ */
void Parser::parseTokenWithWhitespace(UChar32 c, UErrorCode& errorCode) { void Parser::parseTokenWithWhitespace(UChar32 c, UErrorCode& errorCode) {
// No need for error check or bounds check before parseOptionalWhitespace(errorCode) // No need for error check or bounds check before parseOptionalWhitespace()
parseOptionalWhitespace(errorCode); parseOptionalWhitespace();
// Establish precondition // Establish precondition
CHECK_BOUNDS(errorCode); CHECK_BOUNDS(errorCode);
parseToken(c, errorCode); parseToken(c, errorCode);
parseOptionalWhitespace(errorCode); parseOptionalWhitespace();
// Guarantee postcondition // Guarantee postcondition
CHECK_BOUNDS(errorCode); CHECK_BOUNDS(errorCode);
} }
@ -482,11 +756,17 @@ UnicodeString Parser::parseName(UErrorCode& errorCode) {
U_ASSERT(inBounds()); U_ASSERT(inBounds());
if (!isNameStart(peek())) { if (!(isNameStart(peek()) || isBidiControl(peek()))) {
ERROR(errorCode); ERROR(errorCode);
return name; return name;
} }
// name = [bidi] name-start *name-char [bidi]
// [bidi]
parseOptionalBidi();
// name-start *name-char
while (isNameChar(peek())) { while (isNameChar(peek())) {
UChar32 c = peek(); UChar32 c = peek();
name += c; name += c;
@ -497,6 +777,10 @@ UnicodeString Parser::parseName(UErrorCode& errorCode) {
break; break;
} }
} }
// [bidi]
parseOptionalBidi();
return name; return name;
} }
@ -510,21 +794,13 @@ VariableName Parser::parseVariableName(UErrorCode& errorCode) {
VariableName result; VariableName result;
U_ASSERT(inBounds()); U_ASSERT(inBounds());
// If the '$' is missing, we don't want a binding
// for this variable to be created.
bool valid = peek() == DOLLAR;
parseToken(DOLLAR, errorCode); parseToken(DOLLAR, errorCode);
if (!inBounds()) { if (!inBounds()) {
ERROR(errorCode); ERROR(errorCode);
return result; return result;
} }
UnicodeString varName = parseName(errorCode); return VariableName(parseName(errorCode));
// Set the name to "" if the variable wasn't
// declared correctly
if (!valid) {
varName.remove();
}
return VariableName(varName);
} }
/* /*
@ -853,7 +1129,7 @@ void Parser::parseAttribute(AttributeAdder<T>& attrAdder, UErrorCode& errorCode)
// about whether whitespace precedes another // about whether whitespace precedes another
// attribute, or the '=' sign // attribute, or the '=' sign
int32_t savedIndex = index; int32_t savedIndex = index;
parseOptionalWhitespace(errorCode); parseOptionalWhitespace();
Operand rand; Operand rand;
if (peek() == EQUALS) { if (peek() == EQUALS) {
@ -861,19 +1137,9 @@ void Parser::parseAttribute(AttributeAdder<T>& attrAdder, UErrorCode& errorCode)
parseTokenWithWhitespace(EQUALS, errorCode); parseTokenWithWhitespace(EQUALS, errorCode);
UnicodeString rhsStr; UnicodeString rhsStr;
// Parse RHS, which is either a literal or variable // Parse RHS, which must be a literal
switch (peek()) { // attribute = "@" identifier [o "=" o literal]
case DOLLAR: {
rand = Operand(parseVariableName(errorCode));
break;
}
default: {
// Must be a literal
rand = Operand(parseLiteral(errorCode)); rand = Operand(parseLiteral(errorCode));
break;
}
}
U_ASSERT(!rand.isNull());
} else { } else {
// attribute -> "@" identifier [[s] "=" [s]] // attribute -> "@" identifier [[s] "=" [s]]
// Use null operand, which `rand` is already set to // Use null operand, which `rand` is already set to
@ -881,7 +1147,7 @@ void Parser::parseAttribute(AttributeAdder<T>& attrAdder, UErrorCode& errorCode)
index = savedIndex; index = savedIndex;
} }
attrAdder.addAttribute(lhs, std::move(rand), errorCode); attrAdder.addAttribute(lhs, std::move(Operand(rand)), errorCode);
} }
/* /*
@ -1149,7 +1415,7 @@ the comment in `parseOptions()` for details.
// (the character is either the required space before an annotation, or optional // (the character is either the required space before an annotation, or optional
// trailing space after the literal or variable). It's still ambiguous which // trailing space after the literal or variable). It's still ambiguous which
// one does apply. // one does apply.
parseOptionalWhitespace(status); parseOptionalWhitespace();
// Restore precondition // Restore precondition
CHECK_BOUNDS(status); CHECK_BOUNDS(status);
@ -1220,7 +1486,7 @@ Expression Parser::parseExpression(UErrorCode& status) {
// Parse opening brace // Parse opening brace
parseToken(LEFT_CURLY_BRACE, status); parseToken(LEFT_CURLY_BRACE, status);
// Optional whitespace after opening brace // Optional whitespace after opening brace
parseOptionalWhitespace(status); parseOptionalWhitespace();
Expression::Builder exprBuilder(status); Expression::Builder exprBuilder(status);
// Restore precondition // Restore precondition
@ -1263,7 +1529,7 @@ Expression Parser::parseExpression(UErrorCode& status) {
// Parse optional space // Parse optional space
// (the last [s] in e.g. "{" [s] literal [s annotation] *(s attribute) [s] "}") // (the last [s] in e.g. "{" [s] literal [s annotation] *(s attribute) [s] "}")
parseOptionalWhitespace(status); parseOptionalWhitespace();
// Either an operand or operator (or both) must have been set already, // Either an operand or operator (or both) must have been set already,
// so there can't be an error // so there can't be an error
@ -1339,7 +1605,7 @@ void Parser::parseInputDeclaration(UErrorCode& status) {
CHECK_BOUNDS(status); CHECK_BOUNDS(status);
parseToken(ID_INPUT, status); parseToken(ID_INPUT, status);
parseOptionalWhitespace(status); parseOptionalWhitespace();
// Restore precondition before calling parseExpression() // Restore precondition before calling parseExpression()
CHECK_BOUNDS(status); CHECK_BOUNDS(status);
@ -1400,7 +1666,7 @@ void Parser::parseDeclarations(UErrorCode& status) {
// Avoid looping infinitely // Avoid looping infinitely
CHECK_ERROR(status); CHECK_ERROR(status);
parseOptionalWhitespace(status); parseOptionalWhitespace();
// Restore precondition // Restore precondition
CHECK_BOUNDS(status); CHECK_BOUNDS(status);
} }
@ -1510,8 +1776,8 @@ This is addressed using "backtracking" (similarly to `parseOptions()`).
// We've seen at least one whitespace-key pair, so now we can parse // We've seen at least one whitespace-key pair, so now we can parse
// *(s key) [s] // *(s key) [s]
while (peek() != LEFT_CURLY_BRACE || isWhitespace(peek())) { // Try to recover from errors while (peek() != LEFT_CURLY_BRACE || isWhitespace(peek()) || isBidiControl(peek())) {
bool wasWhitespace = isWhitespace(peek()); bool wasWhitespace = isWhitespace(peek()) || isBidiControl(peek());
parseRequiredWhitespace(status); parseRequiredWhitespace(status);
if (!wasWhitespace) { if (!wasWhitespace) {
// Avoid infinite loop when parsing something like: // Avoid infinite loop when parsing something like:
@ -1569,7 +1835,7 @@ Markup Parser::parseMarkup(UErrorCode& status) {
// Consume the '{' // Consume the '{'
next(); next();
normalizedInput += LEFT_CURLY_BRACE; normalizedInput += LEFT_CURLY_BRACE;
parseOptionalWhitespace(status); parseOptionalWhitespace();
bool closing = false; bool closing = false;
switch (peek()) { switch (peek()) {
case NUMBER_SIGN: { case NUMBER_SIGN: {
@ -1596,19 +1862,19 @@ Markup Parser::parseMarkup(UErrorCode& status) {
// Parse the options, which must begin with a ' ' // Parse the options, which must begin with a ' '
// if present // if present
if (inBounds() && isWhitespace(peek())) { if (inBounds() && (isWhitespace(peek()) || isBidiControl(peek()))) {
OptionAdder<Markup::Builder> optionAdder(builder); OptionAdder<Markup::Builder> optionAdder(builder);
parseOptions(optionAdder, status); parseOptions(optionAdder, status);
} }
// Parse the attributes, which also must begin // Parse the attributes, which also must begin
// with a ' ' // with a ' '
if (inBounds() && isWhitespace(peek())) { if (inBounds() && (isWhitespace(peek()) || isBidiControl(peek()))) {
AttributeAdder<Markup::Builder> attrAdder(builder); AttributeAdder<Markup::Builder> attrAdder(builder);
parseAttributes(attrAdder, status); parseAttributes(attrAdder, status);
} }
parseOptionalWhitespace(status); parseOptionalWhitespace();
bool standalone = false; bool standalone = false;
// Check if this is a standalone or not // Check if this is a standalone or not
@ -1656,7 +1922,7 @@ std::variant<Expression, Markup> Parser::parsePlaceholder(UErrorCode& status) {
isMarkup = true; isMarkup = true;
break; break;
} }
if (!isWhitespace(c)){ if (!(isWhitespace(c) || isBidiControl(c))) {
break; break;
} }
tempIndex++; tempIndex++;
@ -1712,7 +1978,7 @@ Pattern Parser::parseSimpleMessage(UErrorCode& status) {
break; break;
} }
// Don't loop infinitely // Don't loop infinitely
if (errors.hasSyntaxError()) { if (errors.hasSyntaxError() || U_FAILURE(status)) {
break; break;
} }
} }
@ -1720,6 +1986,22 @@ Pattern Parser::parseSimpleMessage(UErrorCode& status) {
return result.build(status); return result.build(status);
} }
void Parser::parseVariant(UErrorCode& status) {
CHECK_ERROR(status);
// At least one key is required
SelectorKeys keyList(parseNonEmptyKeys(status));
// parseNonEmptyKeys() consumes any trailing whitespace,
// so the pattern can be consumed next.
// Restore precondition before calling parsePattern()
// (which must return a non-null value)
CHECK_BOUNDS(status);
Pattern rhs = parseQuotedPattern(status);
dataModel.addVariant(std::move(keyList), std::move(rhs), status);
}
/* /*
Consume a `selectors` (matching the nonterminal in the grammar), Consume a `selectors` (matching the nonterminal in the grammar),
@ -1739,22 +2021,25 @@ void Parser::parseSelectors(UErrorCode& status) {
// Parse selectors // Parse selectors
// "Backtracking" is required here. It's not clear if whitespace is // "Backtracking" is required here. It's not clear if whitespace is
// (`[s]` selector) or (`[s]` variant) // (`[s]` selector) or (`[s]` variant)
while (isWhitespace(peek()) || peek() == LEFT_CURLY_BRACE) { while (isWhitespace(peek()) || peek() == DOLLAR) {
parseOptionalWhitespace(status); int32_t whitespaceStart = index;
parseRequiredWhitespace(status);
// Restore precondition // Restore precondition
CHECK_BOUNDS(status); CHECK_BOUNDS(status);
if (peek() != LEFT_CURLY_BRACE) { if (peek() != DOLLAR) {
// This is not necessarily an error, but rather, // This is not necessarily an error, but rather,
// means the whitespace we parsed was the optional // means the whitespace we parsed was the optional
// whitespace preceding the first variant, not the // whitespace preceding the first variant, not the
// optional whitespace preceding a subsequent expression. // required whitespace preceding a subsequent variable.
// In that case, "push back" the whitespace.
normalizedInput.truncate(normalizedInput.length() - 1);
index = whitespaceStart;
break; break;
} }
Expression expression; VariableName var = parseVariableName(status);
expression = parseExpression(status);
empty = false; empty = false;
dataModel.addSelector(std::move(expression), status); dataModel.addSelector(std::move(var), status);
CHECK_ERROR(status); CHECK_ERROR(status);
} }
@ -1770,27 +2055,29 @@ void Parser::parseSelectors(UErrorCode& status) {
} \ } \
// Parse variants // Parse variants
while (isWhitespace(peek()) || isKeyStart(peek())) { // matcher = match-statement s variant *(o variant)
// Trailing whitespace is allowed
parseOptionalWhitespace(status); // Parse first variant
parseRequiredWhitespace(status);
if (!inBounds()) {
ERROR(status);
return;
}
parseVariant(status);
if (!inBounds()) {
// Not an error; there might be only one variant
return;
}
while (isWhitespace(peek()) || isBidiControl(peek()) || isKeyStart(peek())) {
parseOptionalWhitespace();
// Restore the precondition.
// Trailing whitespace is allowed.
if (!inBounds()) { if (!inBounds()) {
return; return;
} }
// At least one key is required parseVariant(status);
SelectorKeys keyList(parseNonEmptyKeys(status));
CHECK_ERROR(status);
// parseNonEmptyKeys() consumes any trailing whitespace,
// so the pattern can be consumed next.
// Restore precondition before calling parsePattern()
// (which must return a non-null value)
CHECK_BOUNDS(status);
Pattern rhs = parseQuotedPattern(status);
dataModel.addVariant(std::move(keyList), std::move(rhs), status);
// Restore the precondition, *without* erroring out if we've // Restore the precondition, *without* erroring out if we've
// reached the end of input. That's because it's valid for the // reached the end of input. That's because it's valid for the
@ -1799,6 +2086,10 @@ void Parser::parseSelectors(UErrorCode& status) {
// Because if we don't check it here, the `isWhitespace()` call in // Because if we don't check it here, the `isWhitespace()` call in
// the loop head will read off the end of the input string. // the loop head will read off the end of the input string.
CHECK_END_OF_INPUT CHECK_END_OF_INPUT
if (errors.hasSyntaxError() || U_FAILURE(status)) {
break;
}
} }
} }
@ -1871,7 +2162,7 @@ void Parser::parse(UParseError &parseErrorResult, UErrorCode& status) {
bool complex = false; bool complex = false;
// First, "look ahead" to determine if this is a simple or complex // First, "look ahead" to determine if this is a simple or complex
// message. To do that, check the first non-whitespace character. // message. To do that, check the first non-whitespace character.
while (inBounds(index) && isWhitespace(peek())) { while (inBounds(index) && (isWhitespace(peek()) || isBidiControl(peek()))) {
next(); next();
} }
@ -1891,10 +2182,10 @@ void Parser::parse(UParseError &parseErrorResult, UErrorCode& status) {
// Message can be empty, so we need to only look ahead // Message can be empty, so we need to only look ahead
// if we know it's non-empty // if we know it's non-empty
if (complex) { if (complex) {
parseOptionalWhitespace(status); parseOptionalWhitespace();
parseDeclarations(status); parseDeclarations(status);
parseBody(status); parseBody(status);
parseOptionalWhitespace(status); parseOptionalWhitespace();
} else { } else {
// Simple message // Simple message
// For normalization, quote the pattern // For normalization, quote the pattern
@ -1926,3 +2217,4 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -10,12 +10,15 @@
#include "unicode/messageformat2_data_model.h" #include "unicode/messageformat2_data_model.h"
#include "unicode/parseerr.h" #include "unicode/parseerr.h"
#include "unicode/uniset.h"
#include "messageformat2_allocation.h" #include "messageformat2_allocation.h"
#include "messageformat2_errors.h" #include "messageformat2_errors.h"
#if U_SHOW_CPLUSPLUS_API #if U_SHOW_CPLUSPLUS_API
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -54,6 +57,26 @@ namespace message2 {
} }
}; };
// Initialization of UnicodeSets
namespace unisets {
enum Key {
CONTENT,
WHITESPACE,
BIDI,
ALPHA,
DIGIT,
NAME_START,
NAME_CHAR,
TEXT,
QUOTED,
ESCAPABLE,
UNISETS_KEY_COUNT
};
U_I18N_API const UnicodeSet* get(Key key, UErrorCode& status);
}
// Parser class (private) // Parser class (private)
class Parser : public UMemory { class Parser : public UMemory {
public: public:
@ -82,8 +105,23 @@ namespace message2 {
UChar postContext[U_PARSE_CONTEXT_LEN]; UChar postContext[U_PARSE_CONTEXT_LEN];
} MessageParseError; } MessageParseError;
Parser(const UnicodeString &input, MFDataModel::Builder& dataModelBuilder, StaticErrors& e, UnicodeString& normalizedInputRef) Parser(const UnicodeString &input,
: source(input), index(0), errors(e), normalizedInput(normalizedInputRef), dataModel(dataModelBuilder) { MFDataModel::Builder& dataModelBuilder,
StaticErrors& e,
UnicodeString& normalizedInputRef,
UErrorCode& status)
: contentChars(unisets::get(unisets::CONTENT, status)),
whitespaceChars(unisets::get(unisets::WHITESPACE, status)),
bidiControlChars(unisets::get(unisets::BIDI, status)),
alphaChars(unisets::get(unisets::ALPHA, status)),
digitChars(unisets::get(unisets::DIGIT, status)),
nameStartChars(unisets::get(unisets::NAME_START, status)),
nameChars(unisets::get(unisets::NAME_CHAR, status)),
textChars(unisets::get(unisets::TEXT, status)),
quotedChars(unisets::get(unisets::QUOTED, status)),
escapableChars(unisets::get(unisets::ESCAPABLE, status)),
source(input), index(0), errors(e), normalizedInput(normalizedInputRef), dataModel(dataModelBuilder) {
(void) status;
parseError.line = 0; parseError.line = 0;
parseError.offset = 0; parseError.offset = 0;
parseError.lengthBeforeCurrentLine = 0; parseError.lengthBeforeCurrentLine = 0;
@ -91,6 +129,20 @@ namespace message2 {
parseError.postContext[0] = '\0'; parseError.postContext[0] = '\0';
} }
bool isContentChar(UChar32) const;
bool isBidiControl(UChar32) const;
bool isWhitespace(UChar32) const;
bool isTextChar(UChar32) const;
bool isQuotedChar(UChar32) const;
bool isEscapableChar(UChar32) const;
bool isAlpha(UChar32) const;
bool isDigit(UChar32) const;
bool isNameStart(UChar32) const;
bool isNameChar(UChar32) const;
bool isUnquotedStart(UChar32) const;
bool isLiteralStart(UChar32) const;
bool isKeyStart(UChar32) const;
static void translateParseError(const MessageParseError&, UParseError&); static void translateParseError(const MessageParseError&, UParseError&);
static void setParseError(MessageParseError&, uint32_t); static void setParseError(MessageParseError&, uint32_t);
void maybeAdvanceLine(); void maybeAdvanceLine();
@ -101,10 +153,12 @@ namespace message2 {
void parseLocalDeclaration(UErrorCode&); void parseLocalDeclaration(UErrorCode&);
void parseInputDeclaration(UErrorCode&); void parseInputDeclaration(UErrorCode&);
void parseSelectors(UErrorCode&); void parseSelectors(UErrorCode&);
void parseVariant(UErrorCode&);
void parseWhitespaceMaybeRequired(bool, UErrorCode&); void parseRequiredWS(UErrorCode&);
void parseRequiredWhitespace(UErrorCode&); void parseRequiredWhitespace(UErrorCode&);
void parseOptionalWhitespace(UErrorCode&); void parseOptionalBidi();
void parseOptionalWhitespace();
void parseToken(UChar32, UErrorCode&); void parseToken(UChar32, UErrorCode&);
void parseTokenWithWhitespace(UChar32, UErrorCode&); void parseTokenWithWhitespace(UChar32, UErrorCode&);
void parseToken(const std::u16string_view&, UErrorCode&); void parseToken(const std::u16string_view&, UErrorCode&);
@ -149,6 +203,18 @@ namespace message2 {
bool inBounds(uint32_t i) const { return source.moveIndex32(index, i) < source.length(); } bool inBounds(uint32_t i) const { return source.moveIndex32(index, i) < source.length(); }
bool allConsumed() const { return (int32_t) index == source.length(); } bool allConsumed() const { return (int32_t) index == source.length(); }
// UnicodeSets for checking character ranges
const UnicodeSet* contentChars;
const UnicodeSet* whitespaceChars;
const UnicodeSet* bidiControlChars;
const UnicodeSet* alphaChars;
const UnicodeSet* digitChars;
const UnicodeSet* nameStartChars;
const UnicodeSet* nameChars;
const UnicodeSet* textChars;
const UnicodeSet* quotedChars;
const UnicodeSet* escapableChars;
// The input string // The input string
const UnicodeString &source; const UnicodeString &source;
// The current position within the input string -- counting in UChar32 // The current position within the input string -- counting in UChar32
@ -165,8 +231,8 @@ namespace message2 {
// The parent builder // The parent builder
MFDataModel::Builder &dataModel; MFDataModel::Builder &dataModel;
}; // class Parser
}; // class Parser
} // namespace message2 } // namespace message2
U_NAMESPACE_END U_NAMESPACE_END
@ -175,6 +241,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */ #endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT_PARSER_H #endif // MESSAGEFORMAT_PARSER_H

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h" #include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -244,11 +246,12 @@ void Serializer::serializeDeclarations() {
void Serializer::serializeSelectors() { void Serializer::serializeSelectors() {
U_ASSERT(!dataModel.hasPattern()); U_ASSERT(!dataModel.hasPattern());
const Expression* selectors = dataModel.getSelectorsInternal(); const VariableName* selectors = dataModel.getSelectorsInternal();
emit(ID_MATCH); emit(ID_MATCH);
for (int32_t i = 0; i < dataModel.numSelectors(); i++) { for (int32_t i = 0; i < dataModel.numSelectors(); i++) {
// No whitespace needed here -- see `selectors` in the grammar whitespace();
emit(DOLLAR);
emit(selectors[i]); emit(selectors[i]);
} }
} }
@ -256,6 +259,7 @@ void Serializer::serializeSelectors() {
void Serializer::serializeVariants() { void Serializer::serializeVariants() {
U_ASSERT(!dataModel.hasPattern()); U_ASSERT(!dataModel.hasPattern());
const Variant* variants = dataModel.getVariantsInternal(); const Variant* variants = dataModel.getVariantsInternal();
whitespace();
for (int32_t i = 0; i < dataModel.numVariants(); i++) { for (int32_t i = 0; i < dataModel.numVariants(); i++) {
const Variant& v = variants[i]; const Variant& v = variants[i];
emit(v.getKeys()); emit(v.getKeys());
@ -285,3 +289,4 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -10,6 +10,8 @@
#if U_SHOW_CPLUSPLUS_API #if U_SHOW_CPLUSPLUS_API
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING #if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2 #if !UCONFIG_NO_MF2
@ -63,6 +65,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */ #endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */ #endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT_SERIALIZER_H #endif // MESSAGEFORMAT_SERIALIZER_H

View File

@ -152,7 +152,7 @@ NFRuleSet::NFRuleSet(RuleBasedNumberFormat *_owner, UnicodeString* descriptions,
UnicodeString& description = descriptions[index]; // !!! make sure index is valid UnicodeString& description = descriptions[index]; // !!! make sure index is valid
if (description.length() == 0) { if (description.isEmpty()) {
// throw new IllegalArgumentException("Empty rule set description"); // throw new IllegalArgumentException("Empty rule set description");
status = U_PARSE_ERROR; status = U_PARSE_ERROR;
return; return;
@ -177,16 +177,16 @@ NFRuleSet::NFRuleSet(RuleBasedNumberFormat *_owner, UnicodeString* descriptions,
name.setTo(UNICODE_STRING_SIMPLE("%default")); name.setTo(UNICODE_STRING_SIMPLE("%default"));
} }
if (description.length() == 0) { if (description.isEmpty()) {
// throw new IllegalArgumentException("Empty rule set description"); // throw new IllegalArgumentException("Empty rule set description");
status = U_PARSE_ERROR; status = U_PARSE_ERROR;
} }
fIsPublic = name.indexOf(gPercentPercent, 2, 0) != 0; fIsPublic = name.indexOf(gPercentPercent, 2, 0) != 0;
if ( name.endsWith(gNoparse,8) ) { if (name.endsWith(gNoparse, 8)) {
fIsParseable = false; fIsParseable = false;
name.truncate(name.length()-8); // remove the @noparse from the name name.truncate(name.length() - 8); // remove the @noparse from the name
} }
// all of the other members of NFRuleSet are initialized // all of the other members of NFRuleSet are initialized

View File

@ -19,7 +19,6 @@
#if U_HAVE_RBNF #if U_HAVE_RBNF
#include <limits>
#include "unicode/localpointer.h" #include "unicode/localpointer.h"
#include "unicode/rbnf.h" #include "unicode/rbnf.h"
#include "unicode/tblcoll.h" #include "unicode/tblcoll.h"
@ -65,6 +64,7 @@ NFRule::~NFRule()
static const char16_t gLeftBracket = 0x005b; static const char16_t gLeftBracket = 0x005b;
static const char16_t gRightBracket = 0x005d; static const char16_t gRightBracket = 0x005d;
static const char16_t gVerticalLine = 0x007C;
static const char16_t gColon = 0x003a; static const char16_t gColon = 0x003a;
static const char16_t gZero = 0x0030; static const char16_t gZero = 0x0030;
static const char16_t gNine = 0x0039; static const char16_t gNine = 0x0039;
@ -147,6 +147,7 @@ NFRule::makeRules(UnicodeString& description,
// then it's really shorthand for two rules (with one exception) // then it's really shorthand for two rules (with one exception)
LocalPointer<NFRule> rule2; LocalPointer<NFRule> rule2;
UnicodeString sbuf; UnicodeString sbuf;
int32_t orElseOp = description.indexOf(gVerticalLine);
// we'll actually only split the rule into two rules if its // we'll actually only split the rule into two rules if its
// base value is an even multiple of its divisor (or it's one // base value is an even multiple of its divisor (or it's one
@ -194,9 +195,13 @@ NFRule::makeRules(UnicodeString& description,
rule2->radix = rule1->radix; rule2->radix = rule1->radix;
rule2->exponent = rule1->exponent; rule2->exponent = rule1->exponent;
// rule2's rule text omits the stuff in brackets: initialize // By default, rule2's rule text omits the stuff in brackets,
// its rule text and substitutions accordingly // unless it contains a | between the brackets.
// Initialize its rule text and substitutions accordingly.
sbuf.append(description, 0, brack1); sbuf.append(description, 0, brack1);
if (orElseOp >= 0) {
sbuf.append(description, orElseOp + 1, brack2 - orElseOp - 1);
}
if (brack2 + 1 < description.length()) { if (brack2 + 1 < description.length()) {
sbuf.append(description, brack2 + 1, description.length() - brack2 - 1); sbuf.append(description, brack2 + 1, description.length() - brack2 - 1);
} }
@ -207,7 +212,12 @@ NFRule::makeRules(UnicodeString& description,
// the brackets themselves: initialize _its_ rule text and // the brackets themselves: initialize _its_ rule text and
// substitutions accordingly // substitutions accordingly
sbuf.setTo(description, 0, brack1); sbuf.setTo(description, 0, brack1);
if (orElseOp >= 0) {
sbuf.append(description, brack1 + 1, orElseOp - brack1 - 1);
}
else {
sbuf.append(description, brack1 + 1, brack2 - brack1 - 1); sbuf.append(description, brack1 + 1, brack2 - brack1 - 1);
}
if (brack2 + 1 < description.length()) { if (brack2 + 1 < description.length()) {
sbuf.append(description, brack2 + 1, description.length() - brack2 - 1); sbuf.append(description, brack2 + 1, description.length() - brack2 - 1);
} }
@ -286,18 +296,17 @@ NFRule::parseRuleDescriptor(UnicodeString& description, UErrorCode& status)
// into "tempValue", skip periods, commas, and spaces, // into "tempValue", skip periods, commas, and spaces,
// stop on a slash or > sign (or at the end of the string), // stop on a slash or > sign (or at the end of the string),
// and throw an exception on any other character // and throw an exception on any other character
int64_t ll_10 = 10;
while (p < descriptorLength) { while (p < descriptorLength) {
c = descriptor.charAt(p); c = descriptor.charAt(p);
if (c >= gZero && c <= gNine) { if (c >= gZero && c <= gNine) {
int32_t single_digit = static_cast<int32_t>(c - gZero); int64_t digit = static_cast<int64_t>(c - gZero);
if ((val > 0 && val > (std::numeric_limits<int64_t>::max() - single_digit) / 10) || if ((val > 0 && val > (INT64_MAX - digit) / 10) ||
(val < 0 && val < (std::numeric_limits<int64_t>::min() - single_digit) / 10)) { (val < 0 && val < (INT64_MIN - digit) / 10)) {
// out of int64_t range // out of int64_t range
status = U_PARSE_ERROR; status = U_PARSE_ERROR;
return; return;
} }
val = val * ll_10 + single_digit; val = val * 10 + digit;
} }
else if (c == gSlash || c == gGreaterThan) { else if (c == gSlash || c == gGreaterThan) {
break; break;
@ -322,11 +331,17 @@ NFRule::parseRuleDescriptor(UnicodeString& description, UErrorCode& status)
if (c == gSlash) { if (c == gSlash) {
val = 0; val = 0;
++p; ++p;
ll_10 = 10;
while (p < descriptorLength) { while (p < descriptorLength) {
c = descriptor.charAt(p); c = descriptor.charAt(p);
if (c >= gZero && c <= gNine) { if (c >= gZero && c <= gNine) {
val = val * ll_10 + static_cast<int32_t>(c - gZero); int64_t digit = static_cast<int64_t>(c - gZero);
if ((val > 0 && val > (INT64_MAX - digit) / 10) ||
(val < 0 && val < (INT64_MIN - digit) / 10)) {
// out of int64_t range
status = U_PARSE_ERROR;
return;
}
val = val * 10 + digit;
} }
else if (c == gGreaterThan) { else if (c == gGreaterThan) {
break; break;
@ -400,7 +415,7 @@ NFRule::parseRuleDescriptor(UnicodeString& description, UErrorCode& status)
// finally, if the rule body begins with an apostrophe, strip it off // finally, if the rule body begins with an apostrophe, strip it off
// (this is generally used to put whitespace at the beginning of // (this is generally used to put whitespace at the beginning of
// a rule's rule text) // a rule's rule text)
if (description.length() > 0 && description.charAt(0) == gTick) { if (!description.isEmpty() && description.charAt(0) == gTick) {
description.removeBetween(0, 1); description.removeBetween(0, 1);
} }

View File

@ -1133,7 +1133,7 @@ void DecimalQuantity::setDigitPos(int32_t position, int8_t value) {
} }
void DecimalQuantity::shiftLeft(int32_t numDigits) { void DecimalQuantity::shiftLeft(int32_t numDigits) {
if (!usingBytes && precision + numDigits > 16) { if (!usingBytes && precision + numDigits >= 16) {
switchStorage(); switchStorage();
} }
if (usingBytes) { if (usingBytes) {

View File

@ -48,8 +48,12 @@ constexpr int32_t PER_INDEX = StandardPlural::Form::COUNT + 1;
* Gender of the word, in languages with grammatical gender. * Gender of the word, in languages with grammatical gender.
*/ */
constexpr int32_t GENDER_INDEX = StandardPlural::Form::COUNT + 2; constexpr int32_t GENDER_INDEX = StandardPlural::Form::COUNT + 2;
/**
* Denominator constant of the unit.
*/
constexpr int32_t CONSTANT_DENOMINATOR_INDEX = StandardPlural::Form::COUNT + 3;
// Number of keys in the array populated by PluralTableSink. // Number of keys in the array populated by PluralTableSink.
constexpr int32_t ARRAY_LENGTH = StandardPlural::Form::COUNT + 3; constexpr int32_t ARRAY_LENGTH = StandardPlural::Form::COUNT + 4;
// TODO(icu-units#28): load this list from resources, after creating a "&set" // TODO(icu-units#28): load this list from resources, after creating a "&set"
// function for use in ldml2icu rules. // function for use in ldml2icu rules.
@ -1010,6 +1014,11 @@ void LongNameHandler::forArbitraryUnit(const Locale &loc,
// denominator (the part after the "-per-). If both are empty, fail // denominator (the part after the "-per-). If both are empty, fail
MeasureUnitImpl unit; MeasureUnitImpl unit;
MeasureUnitImpl perUnit; MeasureUnitImpl perUnit;
if (unitRef.getConstantDenominator(status) != 0) {
perUnit.constantDenominator = unitRef.getConstantDenominator(status);
}
{ {
MeasureUnitImpl fullUnit = MeasureUnitImpl::forMeasureUnitMaybeCopy(unitRef, status); MeasureUnitImpl fullUnit = MeasureUnitImpl::forMeasureUnitMaybeCopy(unitRef, status);
if (U_FAILURE(status)) { if (U_FAILURE(status)) {
@ -1196,6 +1205,12 @@ void LongNameHandler::processPatternTimes(MeasureUnitImpl &&productUnit,
DerivedComponents derivedTimesCases(loc, "case", "times"); DerivedComponents derivedTimesCases(loc, "case", "times");
DerivedComponents derivedPowerCases(loc, "case", "power"); DerivedComponents derivedPowerCases(loc, "case", "power");
if (productUnit.constantDenominator != 0) {
CharString constantString;
constantString.appendNumber(productUnit.constantDenominator, status);
outArray[CONSTANT_DENOMINATOR_INDEX] = UnicodeString::fromUTF8(constantString.toStringPiece());
}
// 4. For each single_unit in product_unit // 4. For each single_unit in product_unit
for (int32_t singleUnitIndex = 0; singleUnitIndex < productUnit.singleUnits.length(); for (int32_t singleUnitIndex = 0; singleUnitIndex < productUnit.singleUnits.length();
singleUnitIndex++) { singleUnitIndex++) {
@ -1454,6 +1469,39 @@ void LongNameHandler::processPatternTimes(MeasureUnitImpl &&productUnit,
} }
} }
} }
// 5. Handling constant denominator if it exists.
if (productUnit.constantDenominator != 0) {
int32_t pluralIndex = -1;
for (int32_t index = 0; index < StandardPlural::Form::COUNT; index++) {
if (!outArray[index].isBogus()) {
pluralIndex = index;
break;
}
}
U_ASSERT(pluralIndex >= 0); // "No plural form found for constant denominator"
// TODO(ICU-23039):
// Improve the handling of constant_denominator representation.
// For instance, a constant_denominator of 1000000 should be adaptable to
// formats like
// 1,000,000, 1e6, or 1 million.
// Furthermore, ensure consistent pluralization rules for units. For example,
// "meter per 100 seconds" should be evaluated for correct singular/plural
// usage: "second" or "seconds"?
// Similarly, "kilogram per 1000 meters" should be checked for "meter" or
// "meters"?
if (outArray[pluralIndex].length() == 0) {
outArray[pluralIndex] = outArray[CONSTANT_DENOMINATOR_INDEX];
} else {
UnicodeString tmp;
timesPatternFormatter.format(outArray[CONSTANT_DENOMINATOR_INDEX], outArray[pluralIndex],
tmp, status);
outArray[pluralIndex] = tmp;
}
}
for (int32_t pluralIndex = 0; pluralIndex < StandardPlural::Form::COUNT; pluralIndex++) { for (int32_t pluralIndex = 0; pluralIndex < StandardPlural::Form::COUNT; pluralIndex++) {
if (globalPlaceholder[pluralIndex] == PH_BEGINNING) { if (globalPlaceholder[pluralIndex] == PH_BEGINNING) {
UnicodeString tmp; UnicodeString tmp;

View File

@ -74,9 +74,11 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
!properties.currencyPluralInfo.fPtr.isNull() || !properties.currencyPluralInfo.fPtr.isNull() ||
!properties.currencyUsage.isNull() || !properties.currencyUsage.isNull() ||
warehouse.affixProvider.get().hasCurrencySign()); warehouse.affixProvider.get().hasCurrencySign());
CurrencyUnit currency = resolveCurrency(properties, locale, status); CurrencyUnit currency;
UCurrencyUsage currencyUsage = properties.currencyUsage.getOrDefault(UCURR_USAGE_STANDARD); UCurrencyUsage currencyUsage;
if (useCurrency) { if (useCurrency) {
currency = resolveCurrency(properties, locale, status);
currencyUsage = properties.currencyUsage.getOrDefault(UCURR_USAGE_STANDARD);
// NOTE: Slicing is OK. // NOTE: Slicing is OK.
macros.unit = currency; // NOLINT macros.unit = currency; // NOLINT
} }
@ -129,6 +131,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
} }
Precision precision; Precision precision;
if (!properties.currencyUsage.isNull()) { if (!properties.currencyUsage.isNull()) {
U_ASSERT(useCurrency);
precision = Precision::constructCurrency(currencyUsage).withCurrency(currency); precision = Precision::constructCurrency(currencyUsage).withCurrency(currency);
} else if (roundingIncrement != 0.0) { } else if (roundingIncrement != 0.0) {
if (PatternStringUtils::ignoreRoundingIncrement(roundingIncrement, maxFrac)) { if (PatternStringUtils::ignoreRoundingIncrement(roundingIncrement, maxFrac)) {
@ -276,7 +279,7 @@ MacroProps NumberPropertyMapper::oldToNew(const DecimalFormatProperties& propert
exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt; exportedProperties->maximumIntegerDigits = maxInt == -1 ? INT32_MAX : maxInt;
Precision rounding_; Precision rounding_;
if (precision.fType == Precision::PrecisionType::RND_CURRENCY) { if (useCurrency && precision.fType == Precision::PrecisionType::RND_CURRENCY) {
rounding_ = precision.withCurrency(currency, status); rounding_ = precision.withCurrency(currency, status);
} else { } else {
rounding_ = precision; rounding_ = precision;

Some files were not shown because too many files have changed in this diff Show More