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 © 2016-2024 Unicode, Inc.
Copyright © 2016-2025 Unicode, Inc.
NOTICE TO USER: Carefully read the following legal agreement. BY
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
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):
* deps/icu-small/source/data/in/icudt76l.dat.bz2 : compressed data file
It is a strict subset of ICU 77 source files with the following exception(s):
* deps/icu-small/source/data/in/icudt77l.dat.bz2 : compressed data file
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 ext[4]={'\0'};
CharString actualLocale;
CharString actual;
int32_t size;
const char16_t* brkfname = nullptr;
UResourceBundle brkRulesStack;
@ -94,7 +94,7 @@ BreakIterator::buildInstance(const Locale& loc, const char *type, UErrorCode &st
// Use the string if we found it
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);
int len = 0;
@ -123,10 +123,9 @@ BreakIterator::buildInstance(const Locale& loc, const char *type, UErrorCode &st
if (U_SUCCESS(status) && result != nullptr) {
U_LOCALE_BASED(locBased, *(BreakIterator*)result);
locBased.setLocaleIDs(ures_getLocaleByType(b, ULOC_VALID_LOCALE, &status),
actualLocale.data());
uprv_strncpy(result->requestLocale, loc.getName(), ULOC_FULLNAME_CAPACITY);
result->requestLocale[ULOC_FULLNAME_CAPACITY-1] = 0; // always terminate
locBased.setLocaleIDs(ures_getLocaleByType(b, ULOC_VALID_LOCALE, &status),
actual.data(), status);
LocaleBased::setLocaleID(loc.getName(), result->requestLocale, status);
}
ures_close(b);
@ -206,26 +205,32 @@ BreakIterator::getAvailableLocales(int32_t& count)
BreakIterator::BreakIterator()
{
*validLocale = *actualLocale = *requestLocale = 0;
}
BreakIterator::BreakIterator(const BreakIterator &other) : UObject(other) {
uprv_strncpy(actualLocale, other.actualLocale, sizeof(actualLocale));
uprv_strncpy(validLocale, other.validLocale, sizeof(validLocale));
uprv_strncpy(requestLocale, other.requestLocale, sizeof(requestLocale));
UErrorCode status = U_ZERO_ERROR;
U_LOCALE_BASED(locBased, *this);
locBased.setLocaleIDs(other.validLocale, other.actualLocale, status);
LocaleBased::setLocaleID(other.requestLocale, requestLocale, status);
U_ASSERT(U_SUCCESS(status));
}
BreakIterator &BreakIterator::operator =(const BreakIterator &other) {
if (this != &other) {
uprv_strncpy(actualLocale, other.actualLocale, sizeof(actualLocale));
uprv_strncpy(validLocale, other.validLocale, sizeof(validLocale));
uprv_strncpy(requestLocale, other.requestLocale, sizeof(requestLocale));
UErrorCode status = U_ZERO_ERROR;
U_LOCALE_BASED(locBased, *this);
locBased.setLocaleIDs(other.validLocale, other.actualLocale, status);
LocaleBased::setLocaleID(other.requestLocale, requestLocale, status);
U_ASSERT(U_SUCCESS(status));
}
return *this;
}
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.
if (U_SUCCESS(status) && (result != nullptr) && *actualLoc.getName() != 0) {
U_LOCALE_BASED(locBased, *result);
locBased.setLocaleIDs(actualLoc.getName(), actualLoc.getName());
locBased.setLocaleIDs(actualLoc.getName(), actualLoc.getName(), status);
}
return result;
}
@ -488,6 +493,7 @@ BreakIterator::makeInstance(const Locale& loc, int32_t kind, UErrorCode& status)
}
if (U_FAILURE(status)) {
delete result;
return nullptr;
}
@ -496,20 +502,25 @@ BreakIterator::makeInstance(const Locale& loc, int32_t kind, UErrorCode& status)
Locale
BreakIterator::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
if (type == ULOC_REQUESTED_LOCALE) {
return {requestLocale};
if (U_FAILURE(status)) {
return Locale::getRoot();
}
U_LOCALE_BASED(locBased, *this);
return locBased.getLocale(type, status);
if (type == ULOC_REQUESTED_LOCALE) {
return requestLocale == nullptr ?
Locale::getRoot() : Locale(requestLocale->data());
}
return LocaleBased::getLocale(validLocale, actualLocale, type, status);
}
const char *
BreakIterator::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const {
if (type == ULOC_REQUESTED_LOCALE) {
return requestLocale;
if (U_FAILURE(status)) {
return nullptr;
}
U_LOCALE_BASED(locBased, *this);
return locBased.getLocaleID(type, status);
if (type == ULOC_REQUESTED_LOCALE) {
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) {
UErrorCode status = U_ZERO_ERROR;
U_LOCALE_BASED(locBased, (*this));
locBased.setLocaleIDs(valid, actual);
locBased.setLocaleIDs(valid.getName(), actual.getName(), status);
U_ASSERT(U_SUCCESS(status));
}
U_NAMESPACE_END

View File

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

View File

@ -74,6 +74,7 @@ public:
* use a UErrorCode where memory allocations might be needed.
*/
CharString &copyFrom(const CharString &other, UErrorCode &errorCode);
CharString &copyFrom(StringPiece s, UErrorCode &errorCode);
UBool isEmpty() const { return len==0; }
int32_t length() const { return len; }
@ -135,7 +136,7 @@ public:
}
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

File diff suppressed because it is too large Load Diff

View File

@ -12,44 +12,84 @@
*/
#include "locbased.h"
#include "cstring.h"
#include "charstr.h"
U_NAMESPACE_BEGIN
Locale LocaleBased::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
const char* id = getLocaleID(type, status);
Locale LocaleBased::getLocale(const CharString* valid, const CharString* actual,
ULocDataLocaleType type, UErrorCode& status) {
const char* id = getLocaleID(valid, actual, type, status);
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)) {
return nullptr;
}
switch(type) {
case ULOC_VALID_LOCALE:
return valid;
return valid == nullptr ? "" : valid->data();
case ULOC_ACTUAL_LOCALE:
return actual;
return actual == nullptr ? "" : actual->data();
default:
status = U_ILLEGAL_ARGUMENT_ERROR;
return nullptr;
}
}
void LocaleBased::setLocaleIDs(const char* validID, const char* actualID) {
if (validID != nullptr) {
uprv_strncpy(valid, validID, ULOC_FULLNAME_CAPACITY);
valid[ULOC_FULLNAME_CAPACITY-1] = 0; // always terminate
}
if (actualID != nullptr) {
uprv_strncpy(actual, actualID, ULOC_FULLNAME_CAPACITY);
actual[ULOC_FULLNAME_CAPACITY-1] = 0; // always terminate
void LocaleBased::setLocaleIDs(const CharString* validID, const CharString* actualID, UErrorCode& status) {
setValidLocaleID(validID, status);
setActualLocaleID(actualID,status);
}
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);
}
}
}
void LocaleBased::setLocaleIDs(const Locale& validID, const Locale& actualID) {
uprv_strcpy(valid, validID.getName());
uprv_strcpy(actual, actualID.getName());
void LocaleBased::setLocaleID(const CharString* id, CharString*& dest, UErrorCode& status) {
if (U_FAILURE(status)) { return; }
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

View File

@ -19,13 +19,14 @@
/**
* Macro to declare a locale LocaleBased wrapper object for the given
* 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) \
LocaleBased varname((objname).validLocale, (objname).actualLocale)
U_NAMESPACE_BEGIN
class CharString;
/**
* A utility class that unifies the implementation of getLocale() by
* 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
* will be aliased for the lifetime of this object.
*/
inline LocaleBased(char* validAlias, char* 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);
inline LocaleBased(CharString*& validAlias, CharString*& actualAlias);
/**
* Return locale meta-data for the service object wrapped by this
* object. Either the valid or the actual locale may be
* retrieved.
* @param valid The valid locale.
* @param actual The actual locale.
* @param type either ULOC_VALID_LOCALE or ULOC_ACTUAL_LOCALE
* @param status input-output error code
* @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
* object. Either the valid or the actual locale may be
* retrieved.
* @param valid The valid locale.
* @param actual The actual locale.
* @param type either ULOC_VALID_LOCALE or ULOC_ACTUAL_LOCALE
* @param status input-output error code
* @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
@ -75,31 +78,40 @@ class U_COMMON_API LocaleBased : public UMemory {
* @param valid the ID of the valid 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);
/**
* Set the locale meta-data for the service object wrapped by this
* object.
* @param valid the ID of the valid locale
* @param actual the ID of the actual locale
*/
void setLocaleIDs(const Locale& valid, const Locale& actual);
static void setLocaleID(const char* id, CharString*& dest, UErrorCode& status);
static void setLocaleID(const CharString* id, CharString*& dest, UErrorCode& status);
static bool equalIDs(const CharString* left, const CharString* right);
private:
char* valid;
char* actual;
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);
CharString*& valid;
CharString*& actual;
};
inline LocaleBased::LocaleBased(char* validAlias, char* actualAlias) :
inline LocaleBased::LocaleBased(CharString*& validAlias, CharString*& actualAlias) :
valid(validAlias), actual(actualAlias) {
}
inline LocaleBased::LocaleBased(const char* validAlias,
const char* actualAlias) :
// ugh: cast away const
valid(const_cast<char*>(validAlias)), actual(const_cast<char*>(actualAlias)) {
inline void LocaleBased::setValidLocaleID(const CharString* id, UErrorCode& status) {
setLocaleID(id, valid, status);
}
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

View File

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

View File

@ -1828,8 +1828,13 @@ ulocimp_isCanonicalizedLocaleForTest(const char* localeName)
U_NAMESPACE_BEGIN
/*This function initializes a Locale from a C locale ID*/
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;
/* Free our current storage */
@ -1854,19 +1859,28 @@ Locale& Locale::init(const char* localeID, UBool canonicalize)
int32_t length;
UErrorCode err;
if(localeID == nullptr) {
// not an error, just set the default locale
return *this = getDefault();
}
/* preset all fields to empty */
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
err = U_ZERO_ERROR;
length = canonicalize ?
uloc_canonicalize(localeID, fullName, sizeof(fullNameBuffer), &err) :
uloc_getName(localeID, fullName, sizeof(fullNameBuffer), &err);
length = parse(localeID, fullName, sizeof fullNameBuffer, err);
if (err == U_BUFFER_OVERFLOW_ERROR || length >= static_cast<int32_t>(sizeof(fullNameBuffer))) {
U_ASSERT(baseName == nullptr);
@ -1877,9 +1891,7 @@ Locale& Locale::init(const char* localeID, UBool canonicalize)
}
fullName = newFullName;
err = U_ZERO_ERROR;
length = canonicalize ?
uloc_canonicalize(localeID, fullName, length+1, &err) :
uloc_getName(localeID, fullName, length+1, &err);
length = parse(localeID, fullName, length + 1, err);
}
if(U_FAILURE(err) || err == U_STRING_NOT_TERMINATED_WARNING) {
/* 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::createCanonical(const char* name) {
Locale loc("");

View File

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

View File

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

View File

@ -193,7 +193,7 @@ u_strToPunycode(const char16_t *src, int32_t srcLength,
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;
return 0;
}

View File

@ -76,7 +76,7 @@
#include <float.h>
#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

View File

@ -47,7 +47,10 @@ static int gLastSerial = 0;
// 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
fSerialNum = ++gLastSerial;
#endif
@ -65,10 +68,13 @@ RBBINode::RBBINode(NodeType t) : UMemory() {
fVal = 0;
fPrecedence = precZero;
UErrorCode status = U_ZERO_ERROR;
fFirstPosSet = new UVector(status); // TODO - get a real status from somewhere
fFirstPosSet = new UVector(status);
fLastPosSet = 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;}
else if (t==opOr) {fPrecedence = precOpOr;}
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
fSerialNum = ++gLastSerial;
#endif
@ -94,10 +103,13 @@ RBBINode::RBBINode(const RBBINode &other) : UMemory(other) {
fVal = other.fVal;
fRuleRoot = false;
fChainIn = other.fChainIn;
UErrorCode status = U_ZERO_ERROR;
fFirstPosSet = new UVector(status); // TODO - get a real status from somewhere
fLastPosSet = 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.
//
//-------------------------------------------------------------------------
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;
if (fType == RBBINode::varRef) {
// If the current node is a variable reference, skip over it
// 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) {
n = this;
} else {
n = new RBBINode(*this);
n = new RBBINode(*this, status);
if (U_FAILURE(status)) {
delete n;
return nullptr;
}
// Check for null pointer.
if (n != nullptr) {
if (fLeftChild != nullptr) {
n->fLeftChild = fLeftChild->cloneTree();
n->fLeftChild->fParent = n;
if (n == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
return nullptr;
}
if (fLeftChild != nullptr) {
n->fLeftChild = fLeftChild->cloneTree(status, depth+1);
if (U_FAILURE(status)) {
delete n;
return nullptr;
}
if (fRightChild != nullptr) {
n->fRightChild = fRightChild->cloneTree();
n->fRightChild->fParent = n;
n->fLeftChild->fParent = n;
}
if (fRightChild != nullptr) {
n->fRightChild = fRightChild->cloneTree(status, depth+1);
if (U_FAILURE(status)) {
delete n;
return nullptr;
}
n->fRightChild->fParent = n;
}
}
return n;
@ -239,7 +278,6 @@ RBBINode *RBBINode::cloneTree() {
// nested references are handled by cloneTree(), not here.
//
//-------------------------------------------------------------------------
constexpr int kRecursiveDepthLimit = 3500;
RBBINode *RBBINode::flattenVariables(UErrorCode& status, int depth) {
if (U_FAILURE(status)) {
return this;
@ -251,21 +289,34 @@ RBBINode *RBBINode::flattenVariables(UErrorCode& status, int depth) {
return this;
}
if (fType == varRef) {
RBBINode *retNode = fLeftChild->cloneTree();
if (retNode != nullptr) {
retNode->fRuleRoot = this->fRuleRoot;
retNode->fChainIn = this->fChainIn;
RBBINode *retNode = fLeftChild->cloneTree(status, depth+1);
if (U_FAILURE(status)) {
return this;
}
retNode->fRuleRoot = this->fRuleRoot;
retNode->fChainIn = this->fChainIn;
delete this; // TODO: undefined behavior. Fix.
return retNode;
}
if (fLeftChild != nullptr) {
fLeftChild = fLeftChild->flattenVariables(status, depth+1);
if (fLeftChild == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(status)) {
return this;
}
fLeftChild->fParent = this;
}
if (fRightChild != nullptr) {
fRightChild = fRightChild->flattenVariables(status, depth+1);
if (fRightChild == nullptr) {
status = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(status)) {
return this;
}
fRightChild->fParent = this;
}
return this;
@ -280,7 +331,16 @@ RBBINode *RBBINode::flattenVariables(UErrorCode& status, int depth) {
// 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);
if (fLeftChild != nullptr) {
@ -288,11 +348,15 @@ void RBBINode::flattenSets() {
RBBINode *setRefNode = fLeftChild;
RBBINode *usetNode = setRefNode->fLeftChild;
RBBINode *replTree = usetNode->fLeftChild;
fLeftChild = replTree->cloneTree();
fLeftChild = replTree->cloneTree(status, depth+1);
if (U_FAILURE(status)) {
delete setRefNode;
return;
}
fLeftChild->fParent = this;
delete setRefNode;
} else {
fLeftChild->flattenSets();
fLeftChild->flattenSets(status, depth+1);
}
}
@ -301,11 +365,15 @@ void RBBINode::flattenSets() {
RBBINode *setRefNode = fRightChild;
RBBINode *usetNode = setRefNode->fLeftChild;
RBBINode *replTree = usetNode->fLeftChild;
fRightChild = replTree->cloneTree();
fRightChild = replTree->cloneTree(status, depth+1);
if (U_FAILURE(status)) {
delete setRefNode;
return;
}
fRightChild->fParent = this;
delete setRefNode;
} else {
fRightChild->flattenSets();
fRightChild->flattenSets(status, depth+1);
}
}
}

View File

@ -91,14 +91,14 @@ class RBBINode : public UMemory {
UVector *fFollowPos;
RBBINode(NodeType t);
RBBINode(const RBBINode &other);
RBBINode(NodeType t, UErrorCode& status);
RBBINode(const RBBINode &other, UErrorCode& status);
~RBBINode();
static void NRDeleteNode(RBBINode *node);
RBBINode *cloneTree();
RBBINode *cloneTree(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);
#ifdef RBBI_DEBUG

View File

@ -767,15 +767,24 @@ void RBBIRuleScanner::findSetFor(const UnicodeString &s, RBBINode *node, Unicode
c = s.char32At(0);
setToAdopt = new UnicodeSet(c, c);
}
if (setToAdopt == nullptr) {
error(U_MEMORY_ALLOCATION_ERROR);
return;
}
}
//
// Make a new uset node to refer to this UnicodeSet
// 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) {
error(U_MEMORY_ALLOCATION_ERROR);
localStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(localStatus)) {
delete usetNode;
error(localStatus);
delete setToAdopt;
return;
}
@ -1191,7 +1200,7 @@ RBBINode *RBBIRuleScanner::pushNewNode(RBBINode::NodeType t) {
return nullptr;
}
fNodeStackPtr++;
fNodeStack[fNodeStackPtr] = new RBBINode(t);
fNodeStack[fNodeStackPtr] = new RBBINode(t, *fRB->fStatus);
if (fNodeStack[fNodeStackPtr] == nullptr) {
*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) {
RBBINode *leafNode = new RBBINode(RBBINode::leafChar);
RBBINode *leafNode = new RBBINode(RBBINode::leafChar, *fStatus);
if (U_FAILURE(*fStatus)) {
delete leafNode;
return;
}
if (leafNode == nullptr) {
*fStatus = U_MEMORY_ALLOCATION_ERROR;
return;
@ -388,9 +392,13 @@ void RBBISetBuilder::addValToSet(RBBINode *usetNode, uint32_t val) {
// There are already input symbols present for this set.
// Set up an OR node, with the previous stuff as the left 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) {
*fStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(*fStatus)) {
delete orNode;
delete leafNode;
return;
}
orNode->fLeftChild = usetNode->fLeftChild;

View File

@ -99,13 +99,22 @@ void RBBITableBuilder::buildForwardTable() {
// {bof} fake character.
//
if (fRB->fSetBuilder->sawBOF()) {
RBBINode *bofTop = new RBBINode(RBBINode::opCat);
RBBINode *bofLeaf = new RBBINode(RBBINode::leafChar);
// Delete and exit if memory allocation failed.
if (bofTop == nullptr || bofLeaf == nullptr) {
RBBINode *bofTop = new RBBINode(RBBINode::opCat, *fStatus);
if (bofTop == nullptr) {
*fStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(*fStatus)) {
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 bofTop;
return;
}
bofTop->fLeftChild = bofLeaf;
@ -120,18 +129,23 @@ void RBBITableBuilder::buildForwardTable() {
// Appears as a cat-node, left child being the original tree,
// right child being the end marker.
//
RBBINode *cn = new RBBINode(RBBINode::opCat);
RBBINode *cn = new RBBINode(RBBINode::opCat, *fStatus);
// Exit if memory allocation failed.
if (cn == nullptr) {
*fStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(*fStatus)) {
delete cn;
return;
}
cn->fLeftChild = fTree;
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.
if (cn->fRightChild == nullptr) {
*fStatus = U_MEMORY_ALLOCATION_ERROR;
}
if (U_FAILURE(*fStatus)) {
delete cn;
return;
}
@ -142,7 +156,7 @@ void RBBITableBuilder::buildForwardTable() {
// Replace all references to UnicodeSets with the tree for the equivalent
// expression.
//
fTree->flattenSets();
fTree->flattenSets(*fStatus, 0);
#ifdef RBBI_DEBUG
if (fRB->fDebugEnv && uprv_strstr(fRB->fDebugEnv, "stree")) {
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();
}
const Locale ResourceBundle::getLocale(ULocDataLocaleType type, UErrorCode &status) const
Locale ResourceBundle::getLocale(ULocDataLocaleType type, UErrorCode &status) const
{
return ures_getLocaleByType(fResource, type, &status);
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1043,7 +1043,7 @@ _initializeULanguageTag(ULanguageTag* langtag) {
}
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;
if (U_FAILURE(status)) {
@ -1088,7 +1088,7 @@ _appendLanguageToLanguageTag(const char* localeID, icu::ByteSink& sink, bool str
}
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;
if (U_FAILURE(status)) {
@ -1118,7 +1118,7 @@ _appendScriptToLanguageTag(const char* localeID, icu::ByteSink& sink, bool stric
}
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;
if (U_FAILURE(status)) {
@ -1169,7 +1169,7 @@ void _sortVariants(VariantListEntry* first) {
}
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; }
UErrorCode tmpStatus = U_ZERO_ERROR;
@ -1872,7 +1872,7 @@ _appendKeywords(ULanguageTag* langtag, icu::ByteSink& sink, UErrorCode& status)
}
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; }
UErrorCode tmpStatus = U_ZERO_ERROR;
@ -2596,6 +2596,9 @@ ulocimp_toLanguageTag(const char* localeID,
bool hadPosix = false;
const char* pKeywordStart;
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
/* Note: uloc_canonicalize returns "en_US_POSIX" for input locale ID "". See #6835 */
icu::CharString canonical = ulocimp_canonicalize(localeID, tmpStatus);
if (U_FAILURE(tmpStatus)) {
@ -2604,7 +2607,7 @@ ulocimp_toLanguageTag(const char* localeID,
}
/* For handling special case - private use only tag */
pKeywordStart = locale_getKeywordsStart(canonical.data());
pKeywordStart = locale_getKeywordsStart(canonical.toStringPiece());
if (pKeywordStart == canonical.data()) {
int kwdCnt = 0;
bool done = false;
@ -2642,12 +2645,12 @@ ulocimp_toLanguageTag(const char* localeID,
}
}
_appendLanguageToLanguageTag(canonical.data(), sink, strict, status);
_appendScriptToLanguageTag(canonical.data(), sink, strict, status);
_appendRegionToLanguageTag(canonical.data(), sink, strict, status);
_appendVariantsToLanguageTag(canonical.data(), sink, strict, hadPosix, status);
_appendLanguageToLanguageTag(canonical.toStringPiece(), sink, strict, status);
_appendScriptToLanguageTag(canonical.toStringPiece(), sink, strict, status);
_appendRegionToLanguageTag(canonical.toStringPiece(), sink, strict, status);
_appendVariantsToLanguageTag(canonical.toStringPiece(), 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 "bytesinkutil.h"
#include "charstr.h"
#include "cmemory.h"
U_NAMESPACE_USE
@ -24,9 +23,7 @@ ulocale_openForLocaleID(const char* localeID, int32_t length, UErrorCode* err) {
if (length < 0) {
return EXTERNAL(icu::Locale::createFromName(localeID).clone());
}
CharString str(localeID, length, *err); // Make a NUL terminated copy.
if (U_FAILURE(*err)) { return nullptr; }
return EXTERNAL(icu::Locale::createFromName(str.data()).clone());
return EXTERNAL(icu::Locale::createFromName(StringPiece{localeID, length}).clone());
}
ULocale*

View File

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

View File

@ -237,8 +237,13 @@ typedef HANDLE MemoryMap;
pData->map = (char *)data + length;
pData->pHeader=(const DataHeader *)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);
# endif
#endif
return true;
}

View File

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

View File

@ -9,10 +9,13 @@
#include "unicode/utypes.h"
#if U_SHOW_CPLUSPLUS_API
#if U_SHOW_CPLUSPLUS_API || U_SHOW_CPLUSPLUS_HEADER_API
#include <cstddef>
#include <string_view>
#include <type_traits>
#endif
/**
* \file
@ -21,8 +24,6 @@
* Also conversion functions from char16_t * to UChar * and OldUChar *.
*/
U_NAMESPACE_BEGIN
/**
* \def U_ALIASING_BARRIER
* Barrier for pointer anti-aliasing optimizations even across function boundaries.
@ -36,6 +37,11 @@ U_NAMESPACE_BEGIN
# define U_ALIASING_BARRIER(ptr)
#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.
* @stable ICU 59
@ -251,6 +257,60 @@ const char16_t *ConstChar16Ptr::get() const { return u_.cp; }
#endif
/// \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 *.
* Includes an aliasing barrier if available.
@ -307,6 +367,15 @@ inline OldUChar *toOldUCharPtr(char16_t *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
/**
* 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
#endif /* U_SHOW_CPLUSPLUS_API */
#endif // U_SHOW_CPLUSPLUS_API
#endif // __CHAR16PTR_H__

View File

@ -449,6 +449,11 @@ public:
*/
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
* the string according to CLDR by calling uloc_canonicalize().
@ -1133,7 +1138,9 @@ private:
* @param cLocaleID The new locale name.
* @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

View File

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

View File

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

View File

@ -1173,10 +1173,12 @@ public:
inline U_HEADER_NESTED_NAMESPACE::USetStrings strings() const {
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.
* 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.
* (Similar to how Java UnicodeSet *is an* Iterable&lt;String&gt;.)
*
@ -1185,13 +1187,14 @@ public:
* \code
* UnicodeSet set(u"[abcçカ🚴{}{abc}{de}]", errorCode);
* for (auto el : set) {
* UnicodeString us(el);
* 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
*
* @return an all-elements iterator.
* @draft ICU 76
* @draft ICU 77
* @see end
* @see codePoints
* @see ranges
@ -1203,7 +1206,7 @@ public:
/**
* @return an exclusive-end sentinel for iterating over all of the elements of this set.
* @draft ICU 76
* @draft ICU 77
* @see begin
* @see codePoints
* @see ranges

View File

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

View File

@ -124,7 +124,7 @@
* @internal
*/
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.
@ -148,7 +148,7 @@ utf8_appendCharSafeBody(uint8_t *s, int32_t i, int32_t length, UChar32 c, UBool
* @internal
*/
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.

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_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_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
/**
* One more than the highest normal formatting API error code.
* @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
/*

View File

@ -53,7 +53,7 @@
* This value will change in the subsequent releases of ICU
* @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.
* This value will change in the subsequent releases of ICU
@ -79,7 +79,7 @@
* This value will change in the subsequent releases of ICU
* @stable ICU 2.6
*/
#define U_ICU_VERSION_SUFFIX _76
#define U_ICU_VERSION_SUFFIX _77
/**
* \def U_DEF2_ICU_ENTRY_POINT_RENAME
@ -132,7 +132,7 @@
* This value will change in the subsequent releases of ICU
* @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.
@ -145,13 +145,13 @@
*
* @stable ICU 2.6
*/
#define U_ICU_VERSION_SHORT "76"
#define U_ICU_VERSION_SHORT "77"
#ifndef U_HIDE_INTERNAL_API
/** Data version in ICU4C.
* @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 */
/*===========================================================================

View File

@ -125,7 +125,7 @@ typedef uint8_t UVersionInfo[U_MAX_VERSION_LENGTH];
U_NAMESPACE_USE
# endif
#ifndef U_HIDE_DRAFT_API
#ifndef U_FORCE_HIDE_DRAFT_API
/**
* \def U_HEADER_NESTED_NAMESPACE
* 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
*/
/**
* \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.
#if defined(_MSC_VER) || (UPRV_HAS_DECLSPEC_ATTRIBUTE(__dllexport__) && \
UPRV_HAS_DECLSPEC_ATTRIBUTE(__dllimport__))
# define U_HEADER_NESTED_NAMESPACE header
# define U_ICU_NAMESPACE_OR_INTERNAL U_ICU_NAMESPACE
#elif defined(U_COMBINED_IMPLEMENTATION) || defined(U_COMMON_IMPLEMENTATION) || \
defined(U_I18N_IMPLEMENTATION) || defined(U_IO_IMPLEMENTATION) || \
defined(U_LAYOUTEX_IMPLEMENTATION) || defined(U_TOOLUTIL_IMPLEMENTATION)
# 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
# define U_HEADER_NESTED_NAMESPACE header
# define U_ICU_NAMESPACE_OR_INTERNAL U_ICU_NAMESPACE
#endif
#define U_HEADER_ONLY_NAMESPACE U_ICU_NAMESPACE::U_HEADER_NESTED_NAMESPACE
namespace U_HEADER_ONLY_NAMESPACE {}
#endif // U_HIDE_DRAFT_API
#endif // U_FORCE_HIDE_DRAFT_API
#endif /* __cplusplus */

View File

@ -1945,6 +1945,13 @@ UnicodeString::cloneArrayIfNeeded(int32_t newCapacity,
growCapacity = newCapacity;
} else if(newCapacity <= 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

View File

@ -2716,6 +2716,9 @@ ures_openWithType(UResourceBundle *r, const char* path, const char* localeID,
UResourceDataEntry *entry;
if(openType != URES_OPEN_DIRECT) {
if (localeID == nullptr) {
localeID = uloc_getDefault();
}
/* first "canonicalize" the locale ID */
CharString canonLocaleID = ulocimp_getBaseName(localeID, *status);
if(U_FAILURE(*status)) {
@ -3080,6 +3083,9 @@ ures_getFunctionalEquivalent(char *result, int32_t resultCapacity,
kwVal.clear();
}
}
if (locid == nullptr) {
locid = uloc_getDefault();
}
CharString base = ulocimp_getBaseName(locid, subStatus);
#if defined(URES_TREE_DEBUG)
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);
if (U_SUCCESS(subStatus) && validLoc != nullptr && validLoc[0] != 0 && uprv_strcmp(validLoc, "root") != 0) {
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) {
// validLoc is not root and has a different language than parent, use it instead
found.clear().append(validLoc, subStatus);

View File

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

View File

@ -28,6 +28,7 @@
#include "ubidi_props.h"
#include "uassert.h"
#include <limits>
/*
* This implementation is designed for 16-bit Unicode strings.
* 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));
/* Test for nullptr */
if(tempbuffer == nullptr) {

View File

@ -126,7 +126,7 @@ compareEntries(const UHashTok p1, const UHashTok p2) {
name2.pointer = b2->name;
path1.pointer = b1->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

View File

@ -124,11 +124,9 @@ errorValue(int32_t count, int8_t strict) {
* >0 Obsolete "strict" behavior of UTF8_NEXT_CHAR_SAFE(..., true):
* Same as the obsolete "safe" behavior, but non-characters are also treated
* like illegal sequences.
*
* Note that a UBool is the same as an int8_t.
*/
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.
int32_t i=*pi;
// 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
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.
int32_t i=*pi;
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_DUPLICATE_DECLARATION_ERROR",
"U_MF_OPERAND_MISMATCH_ERROR",
"U_MF_DUPLICATE_VARIANT_ERROR"
"U_MF_DUPLICATE_VARIANT_ERROR",
"U_MF_BAD_OPTION"
};
static const char * const

View File

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

View File

@ -82,13 +82,6 @@ public:
*/
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.
* @return return a polymorphic copy of this calendar.

View File

@ -156,7 +156,7 @@ U_CFUNC void ucal_dump(UCalendar* cal) {
#endif
/* Max value for stamp allowable before recalculation */
#define STAMP_MAX 10000
#define STAMP_MAX 127
static const char * const gCalTypes[] = {
"gregorian",
@ -700,15 +700,10 @@ fIsTimeSet(false),
fAreFieldsSet(false),
fAreAllFieldsSet(false),
fAreFieldsVirtuallySet(false),
fNextStamp(static_cast<int32_t>(kMinimumUserStamp)),
fTime(0),
fLenient(true),
fZone(nullptr),
fRepeatedWallTime(UCAL_WALLTIME_LAST),
fSkippedWallTime(UCAL_WALLTIME_LAST)
{
validLocale[0] = 0;
actualLocale[0] = 0;
clear();
if (U_FAILURE(success)) {
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(),
fIsTimeSet(false),
fAreFieldsSet(false),
fAreAllFieldsSet(false),
fAreFieldsVirtuallySet(false),
fNextStamp(static_cast<int32_t>(kMinimumUserStamp)),
fTime(0),
fLenient(true),
fZone(nullptr),
fRepeatedWallTime(UCAL_WALLTIME_LAST),
fSkippedWallTime(UCAL_WALLTIME_LAST)
{
validLocale[0] = 0;
actualLocale[0] = 0;
LocalPointer<TimeZone> zone(adoptZone, success);
if (U_FAILURE(success)) {
delete zone;
return;
}
if (zone == nullptr) {
if (zone.isNull()) {
#if defined (U_DEBUG_CAL)
fprintf(stderr, "%s:%d: ILLEGAL ARG because timezone cannot be 0\n",
__FILE__, __LINE__);
@ -751,7 +741,7 @@ fSkippedWallTime(UCAL_WALLTIME_LAST)
}
clear();
fZone = zone;
fZone = zone.orphan();
setWeekData(aLocale, nullptr, success);
}
@ -763,15 +753,10 @@ fIsTimeSet(false),
fAreFieldsSet(false),
fAreAllFieldsSet(false),
fAreFieldsVirtuallySet(false),
fNextStamp(static_cast<int32_t>(kMinimumUserStamp)),
fTime(0),
fLenient(true),
fZone(nullptr),
fRepeatedWallTime(UCAL_WALLTIME_LAST),
fSkippedWallTime(UCAL_WALLTIME_LAST)
{
validLocale[0] = 0;
actualLocale[0] = 0;
if (U_FAILURE(success)) {
return;
}
@ -779,6 +764,7 @@ fSkippedWallTime(UCAL_WALLTIME_LAST)
fZone = zone.clone();
if (fZone == nullptr) {
success = U_MEMORY_ALLOCATION_ERROR;
return;
}
setWeekData(aLocale, nullptr, success);
}
@ -788,6 +774,8 @@ fSkippedWallTime(UCAL_WALLTIME_LAST)
Calendar::~Calendar()
{
delete fZone;
delete actualLocale;
delete validLocale;
}
// -------------------------------------
@ -795,7 +783,6 @@ Calendar::~Calendar()
Calendar::Calendar(const Calendar &source)
: UObject(source)
{
fZone = nullptr;
*this = source;
}
@ -806,7 +793,6 @@ Calendar::operator=(const Calendar &right)
{
if (this != &right) {
uprv_arrayCopy(right.fFields, fFields, UCAL_FIELD_COUNT);
uprv_arrayCopy(right.fIsSet, fIsSet, UCAL_FIELD_COUNT);
uprv_arrayCopy(right.fStamp, fStamp, UCAL_FIELD_COUNT);
fTime = right.fTime;
fIsTimeSet = right.fIsTimeSet;
@ -828,10 +814,10 @@ Calendar::operator=(const Calendar &right)
fWeekendCease = right.fWeekendCease;
fWeekendCeaseMillis = right.fWeekendCeaseMillis;
fNextStamp = right.fNextStamp;
uprv_strncpy(validLocale, right.validLocale, sizeof(validLocale));
uprv_strncpy(actualLocale, right.actualLocale, sizeof(actualLocale));
validLocale[sizeof(validLocale)-1] = 0;
actualLocale[sizeof(validLocale)-1] = 0;
UErrorCode status = U_ZERO_ERROR;
U_LOCALE_BASED(locBased, *this);
locBased.setLocaleIDs(right.validLocale, right.actualLocale, status);
U_ASSERT(U_SUCCESS(status));
}
return *this;
@ -1167,13 +1153,9 @@ Calendar::setTimeInMillis( double millis, UErrorCode& status ) {
fAreFieldsSet = fAreAllFieldsSet = false;
fIsTimeSet = fAreFieldsVirtuallySet = true;
for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) {
fFields[i] = 0;
fStamp[i] = kUnset;
fIsSet[i] = false;
}
uprv_memset(fFields, 0, sizeof(fFields));
uprv_memset(fStamp, kUnset, sizeof(fStamp));
fNextStamp = kMinimumUserStamp;
}
// -------------------------------------
@ -1208,12 +1190,11 @@ Calendar::set(UCalendarDateFields field, int32_t value)
computeFields(ec);
}
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) {
recalculateStamp();
}
fStamp[field] = fNextStamp++;
fIsSet[field] = true; // Remove later
fIsTimeSet = fAreFieldsSet = fAreFieldsVirtuallySet = false;
}
@ -1270,11 +1251,9 @@ void Calendar::setRelatedYear(int32_t year)
void
Calendar::clear()
{
for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) {
fFields[i] = 0; // Must do this; other code depends on it
fStamp[i] = kUnset;
fIsSet[i] = false; // Remove later
}
uprv_memset(fFields, 0, sizeof(fFields));
uprv_memset(fStamp, kUnset, sizeof(fStamp));
fNextStamp = kMinimumUserStamp;
fIsTimeSet = fAreFieldsSet = fAreAllFieldsSet = fAreFieldsVirtuallySet = false;
// fTime is not 'cleared' - may be used if no fields are set.
}
@ -1296,12 +1275,10 @@ Calendar::clear(UCalendarDateFields field)
if (field == UCAL_MONTH) {
fFields[UCAL_ORDINAL_MONTH] = 0;
fStamp[UCAL_ORDINAL_MONTH] = kUnset;
fIsSet[UCAL_ORDINAL_MONTH] = false; // Remove later
}
if (field == UCAL_ORDINAL_MONTH) {
fFields[UCAL_MONTH] = 0;
fStamp[UCAL_MONTH] = kUnset;
fIsSet[UCAL_MONTH] = false; // Remove later
}
fIsTimeSet = fAreFieldsSet = fAreAllFieldsSet = fAreFieldsVirtuallySet = false;
}
@ -1434,10 +1411,8 @@ void Calendar::computeFields(UErrorCode &ec)
for (int32_t i=0; i<UCAL_FIELD_COUNT; ++i) {
if ((mask & 1) == 0) {
fStamp[i] = kInternallySet;
fIsSet[i] = true; // Remove later
} else {
fStamp[i] = kUnset;
fIsSet[i] = false; // Remove later
}
mask >>= 1;
}
@ -1467,7 +1442,7 @@ void Calendar::computeFields(UErrorCode &ec)
//__FILE__, __LINE__, fFields[UCAL_JULIAN_DAY], localMillis);
#endif
computeGregorianAndDOWFields(fFields[UCAL_JULIAN_DAY], ec);
computeGregorianFields(fFields[UCAL_JULIAN_DAY], ec);
// Call framework method to have subclass compute its fields.
// These must include, at a minimum, MONTH, DAY_OF_MONTH,
@ -1538,32 +1513,6 @@ uint8_t Calendar::julianDayToDayOfWeek(int32_t julian)
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
* 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)) {
return;
}
int32_t gregorianDayOfWeekUnused;
if (uprv_add32_overflow(
julianDay, -kEpochStartAsJulianDay, &julianDay)) {
ec = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
Grego::dayToFields(julianDay, fGregorianYear, fGregorianMonth,
fGregorianDayOfMonth, gregorianDayOfWeekUnused,
fGregorianDayOfMonth,
fGregorianDayOfYear, ec);
}
@ -1610,8 +1558,19 @@ void Calendar::computeWeekFields(UErrorCode &ec) {
if(U_FAILURE(ec)) {
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 dayOfWeek = fFields[UCAL_DAY_OF_WEEK];
int32_t dayOfYear = fFields[UCAL_DAY_OF_YEAR];
// 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
// 7000 days.
int32_t yearOfWeekOfYear = eyear;
int32_t relDow = (dayOfWeek + 7 - getFirstDayOfWeek()) % 7; // 0..6
int32_t relDowJan1 = (dayOfWeek - dayOfYear + 7001 - getFirstDayOfWeek()) % 7; // 0..6
int32_t relDow = (dayOfWeek + 7 - firstDayOfWeek) % 7; // 0..6
int32_t relDowJan1 = (dayOfWeek - dayOfYear + 7001 - firstDayOfWeek) % 7; // 0..6
int32_t woy = (dayOfYear - 1 + relDowJan1) / 7; // 0..53
if ((7 - relDowJan1) >= getMinimalDaysInFirstWeek()) {
int32_t minimalDaysInFirstWeek = getMinimalDaysInFirstWeek();
if ((7 - relDowJan1) >= minimalDaysInFirstWeek) {
++woy;
}
@ -1639,11 +1599,13 @@ void Calendar::computeWeekFields(UErrorCode &ec) {
// to handle the case in which we are the first week of the
// 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);
yearOfWeekOfYear--;
} 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
// must be on or after L-5, where L is yearLength(), then it
// cannot possibly be week 1 of the next year:
@ -1655,7 +1617,7 @@ void Calendar::computeWeekFields(UErrorCode &ec) {
if (lastRelDow < 0) {
lastRelDow += 7;
}
if (((6 - lastRelDow) >= getMinimalDaysInFirstWeek()) &&
if (((6 - lastRelDow) >= minimalDaysInFirstWeek) &&
((dayOfYear + 7 - relDow) > lastDoy)) {
woy = 1;
yearOfWeekOfYear++;
@ -2946,7 +2908,7 @@ void Calendar::validateField(UCalendarDateFields field, UErrorCode &status) {
if (U_FAILURE(status)) {
return;
}
validateField(field, 1, handleGetYearLength(y), status);
validateField(field, 1, handleGetYearLength(y, status), status);
break;
case UCAL_DAY_OF_WEEK_IN_MONTH:
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",
__FILE__, __LINE__, year, year-1, testDate, julianDay+testDate, nextJulianDay);
#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)
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)) {
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
{
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);
}
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) -
handleComputeMonthStart(eyear, 0, false, status);
U_ASSERT(U_SUCCESS(status));
if (U_FAILURE(status)) return 0;
return result;
}
@ -3882,7 +3858,7 @@ Calendar::getActualMaximum(UCalendarDateFields field, UErrorCode& status) const
}
cal->setLenient(true);
cal->prepareGetActual(field,false,status);
result = handleGetYearLength(cal->get(UCAL_EXTENDED_YEAR, status));
result = handleGetYearLength(cal->get(UCAL_EXTENDED_YEAR, status), status);
delete cal;
}
break;
@ -4141,7 +4117,7 @@ Calendar::setWeekData(const Locale& desiredLocale, const char *type, UErrorCode&
if (U_SUCCESS(status)) {
U_LOCALE_BASED(locBased,*this);
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 {
status = U_USING_FALLBACK_WARNING;
return;
@ -4229,14 +4205,12 @@ Calendar::updateTime(UErrorCode& status)
Locale
Calendar::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this);
return locBased.getLocale(type, status);
return LocaleBased::getLocale(validLocale, actualLocale, type, status);
}
const char *
Calendar::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this);
return locBased.getLocaleID(type, status);
return LocaleBased::getLocaleID(validLocale, actualLocale, type, status);
}
void
@ -4245,7 +4219,7 @@ Calendar::recalculateStamp() {
int32_t currentValue;
int32_t j, i;
fNextStamp = 1;
fNextStamp = kInternallySet;
for (j = 0; j < UCAL_FIELD_COUNT; j++) {
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)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success)
{
setTimeInMillis(getNow(), success);
}
CECalendar::CECalendar (const CECalendar& other)
@ -65,13 +64,6 @@ CECalendar::~CECalendar()
{
}
CECalendar&
CECalendar::operator=(const CECalendar& right)
{
Calendar::operator=(right);
return *this;
}
//-------------------------------------------------------------------------
// Calendar framework
//-------------------------------------------------------------------------

View File

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

View File

@ -130,7 +130,6 @@ ChineseCalendar::ChineseCalendar(const Locale& aLocale, UErrorCode& success)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success),
hasLeapMonthBetweenWinterSolstices(false)
{
setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly.
}
ChineseCalendar::ChineseCalendar(const ChineseCalendar& other) : Calendar(other) {
@ -219,7 +218,9 @@ int32_t ChineseCalendar::handleGetExtendedYear(UErrorCode& status) {
}
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
} else {
// adjust to the instance specific epoch
@ -252,11 +253,16 @@ int32_t ChineseCalendar::handleGetExtendedYear(UErrorCode& status) {
* @stable ICU 2.8
*/
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);
if (U_FAILURE(status)) {
return 0;
}
int32_t thisStart = handleComputeMonthStart(extendedYear, month, true, status);
int32_t thisStart = handleComputeMonthStartWithLeap(extendedYear, month, leap, status);
if (U_FAILURE(status)) {
return 0;
}
@ -332,18 +338,24 @@ struct MonthInfo computeMonthInfo(
* @stable ICU 2.8
*/
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)) {
return 0;
}
// If the month is out of range, adjust it into range, and
// modify the extended year value accordingly.
if (month < 0 || month > 11) {
double m = month;
if (uprv_add32_overflow(eyear, ClockMath::floorDivide(m, 12.0, &m), &eyear)) {
if (uprv_add32_overflow(eyear, ClockMath::floorDivide(month, 12, &month), &eyear)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return 0;
}
month = static_cast<int32_t>(m);
}
const Setting setting = getSetting(status);
@ -362,19 +374,9 @@ int64_t ChineseCalendar::handleComputeMonthStart(int32_t eyear, int32_t month, U
return 0;
}
// Ignore IS_LEAP_MONTH field if useMonth is false
bool isLeapMonth = false;
if (useMonth) {
isLeapMonth = internalGet(UCAL_IS_LEAP_MONTH) != 0;
}
int32_t newMonthYear = Grego::dayToYear(newMoon, status);
int32_t unusedMonth;
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);
struct MonthInfo monthInfo = computeMonthInfo(setting, newMonthYear, newMoon, status);
if (U_FAILURE(status)) {
return 0;
}
@ -794,6 +796,9 @@ struct MonthInfo computeMonthInfo(
solsticeBefore = solsticeAfter;
solsticeAfter = winterSolstice(setting, gyear + 1, status);
}
if (!(solsticeBefore <= days && days < solsticeAfter)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
}
if (U_FAILURE(status)) {
return output;
}
@ -1043,7 +1048,12 @@ void ChineseCalendar::offsetMonth(int32_t newMoon, int32_t dayOfMonth, int32_t d
}
// 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
// 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
#endif

View File

@ -194,6 +194,10 @@ class U_I18N_API ChineseCalendar : public Calendar {
virtual void handleComputeFields(int32_t julianDay, UErrorCode &status) 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:
virtual void add(UCalendarDateFields 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 int32_t getActualMaximum(UCalendarDateFields field, UErrorCode& status) const override;
struct Setting {
int32_t epochYear;
const TimeZone* zoneAstroCalc;

View File

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

View File

@ -40,6 +40,7 @@
#if defined( U_DEBUG_CALSVC ) || defined (U_DEBUG_CAL)
#include <stdio.h>
#endif
#include <typeinfo>
// *****************************************************************************
// class DateFormat
@ -279,9 +280,8 @@ UnicodeString&
DateFormat::format(UDate date, UnicodeString& appendTo, FieldPosition& fieldPosition) const {
if (fCalendar != nullptr) {
UErrorCode ec = U_ZERO_ERROR;
const auto* calType = fCalendar->getType();
// 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));
cal.setTime(date, ec);
if (U_SUCCESS(ec)) {
@ -309,9 +309,8 @@ DateFormat::format(UDate date, UnicodeString& appendTo, FieldPositionIterator* p
UErrorCode& status) const {
if (fCalendar != nullptr) {
UErrorCode ec = U_ZERO_ERROR;
const auto* calType = fCalendar->getType();
// 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));
cal.setTime(date, ec);
if (U_SUCCESS(ec)) {

View File

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

View File

@ -402,9 +402,8 @@ void
DateFormatSymbols::copyData(const DateFormatSymbols& other) {
UErrorCode status = U_ZERO_ERROR;
U_LOCALE_BASED(locBased, *this);
locBased.setLocaleIDs(
other.getLocale(ULOC_VALID_LOCALE, status),
other.getLocale(ULOC_ACTUAL_LOCALE, status));
locBased.setLocaleIDs(other.validLocale, other.actualLocale, status);
U_ASSERT(U_SUCCESS(status));
assignArray(fEras, fErasCount, other.fEras, other.fErasCount);
assignArray(fEraNames, fEraNamesCount, other.fEraNames, other.fEraNamesCount);
assignArray(fNarrowEras, fNarrowErasCount, other.fNarrowEras, other.fNarrowErasCount);
@ -497,6 +496,8 @@ DateFormatSymbols& DateFormatSymbols::operator=(const DateFormatSymbols& other)
DateFormatSymbols::~DateFormatSymbols()
{
dispose();
delete actualLocale;
delete validLocale;
}
void DateFormatSymbols::dispose()
@ -536,6 +537,10 @@ void DateFormatSymbols::dispose()
delete[] fStandaloneWideDayPeriods;
delete[] fStandaloneNarrowDayPeriods;
delete actualLocale;
actualLocale = nullptr;
delete validLocale;
validLocale = nullptr;
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
// are stored in a separate file
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
initField(&fEras, fErasCount, calendarSink, buildResourcePath(path, gErasTag, gNamesAbbrTag, status), status);
@ -2528,8 +2533,7 @@ DateFormatSymbols::initializeData(const Locale& locale, const char *type, UError
Locale
DateFormatSymbols::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this);
return locBased.getLocale(type, status);
return LocaleBased::getLocale(validLocale, actualLocale, type, status);
}
U_NAMESPACE_END

View File

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

View File

@ -24,6 +24,7 @@
#include "utypeinfo.h" // for 'typeid' to work
#include "unicode/utypes.h"
#include "charstr.h"
#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
@ -72,13 +73,14 @@ FieldPosition::clone() const {
Format::Format()
: UObject()
{
*validLocale = *actualLocale = 0;
}
// -------------------------------------
Format::~Format()
{
delete actualLocale;
delete validLocale;
}
// -------------------------------------
@ -97,8 +99,10 @@ Format&
Format::operator=(const Format& that)
{
if (this != &that) {
uprv_strcpy(validLocale, that.validLocale);
uprv_strcpy(actualLocale, that.actualLocale);
UErrorCode status = U_ZERO_ERROR;
U_LOCALE_BASED(locBased, *this);
locBased.setLocaleIDs(that.validLocale, that.actualLocale, status);
U_ASSERT(U_SUCCESS(status));
}
return *this;
}
@ -196,20 +200,20 @@ void Format::syntaxError(const UnicodeString& pattern,
Locale
Format::getLocale(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this);
return locBased.getLocale(type, status);
return LocaleBased::getLocale(validLocale, actualLocale, type, status);
}
const char *
Format::getLocaleID(ULocDataLocaleType type, UErrorCode& status) const {
U_LOCALE_BASED(locBased, *this);
return locBased.getLocaleID(type, status);
return LocaleBased::getLocaleID(validLocale,actualLocale, type, status);
}
void
Format::setLocaleIDs(const char* valid, const char* actual) {
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

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
ufmtval_getString(
const UFormattedValue* ufmtval,
@ -213,6 +218,9 @@ ufmtval_getString(
// defined to return memory owned by the ufmtval argument.
return readOnlyAlias.getBuffer();
}
#if U_GCC_MAJOR_MINOR >= 409
#pragma GCC diagnostic pop
#endif
U_CAPI UBool U_EXPORT2

View File

@ -147,6 +147,7 @@ UOBJECT_DEFINE_RTTI_IMPLEMENTATION(GregorianCalendar)
// in Java, -12219292800000L
//const UDate GregorianCalendar::kPapalCutover = -12219292800000L;
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 kPapalCutoverJulian = (2299161.0 - kEpochStartAsJulianDay);
@ -155,7 +156,7 @@ static const UDate kPapalCutover = (2299161.0 - kEpochStartAsJulianDay) * U_MILL
GregorianCalendar::GregorianCalendar(UErrorCode& status)
: Calendar(status),
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(kDefaultCutoverYear),
fIsGregorian(true), fInvertGregorian(false)
{
setTimeInMillis(getNow(), status);
@ -164,34 +165,22 @@ fIsGregorian(true), fInvertGregorian(false)
// -------------------------------------
GregorianCalendar::GregorianCalendar(TimeZone* zone, UErrorCode& status)
: Calendar(zone, Locale::getDefault(), status),
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fIsGregorian(true), fInvertGregorian(false)
: GregorianCalendar(zone, Locale::getDefault(), status)
{
setTimeInMillis(getNow(), status);
}
// -------------------------------------
GregorianCalendar::GregorianCalendar(const TimeZone& zone, UErrorCode& status)
: Calendar(zone, Locale::getDefault(), status),
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fIsGregorian(true), fInvertGregorian(false)
: GregorianCalendar(zone, Locale::getDefault(), status)
{
setTimeInMillis(getNow(), status);
}
// -------------------------------------
GregorianCalendar::GregorianCalendar(const Locale& aLocale, UErrorCode& status)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, status),
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fIsGregorian(true), fInvertGregorian(false)
: GregorianCalendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, status)
{
setTimeInMillis(getNow(), status);
}
// -------------------------------------
@ -200,7 +189,7 @@ GregorianCalendar::GregorianCalendar(TimeZone* zone, const Locale& aLocale,
UErrorCode& status)
: Calendar(zone, aLocale, status),
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(kDefaultCutoverYear),
fIsGregorian(true), fInvertGregorian(false)
{
setTimeInMillis(getNow(), status);
@ -212,7 +201,7 @@ GregorianCalendar::GregorianCalendar(const TimeZone& zone, const Locale& aLocale
UErrorCode& status)
: Calendar(zone, aLocale, status),
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(kDefaultCutoverYear),
fIsGregorian(true), fInvertGregorian(false)
{
setTimeInMillis(getNow(), status);
@ -224,7 +213,7 @@ GregorianCalendar::GregorianCalendar(int32_t year, int32_t month, int32_t date,
UErrorCode& status)
: Calendar(TimeZone::createDefault(), Locale::getDefault(), status),
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(kDefaultCutoverYear),
fIsGregorian(true), fInvertGregorian(false)
{
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,
int32_t hour, int32_t minute, UErrorCode& status)
: Calendar(TimeZone::createDefault(), Locale::getDefault(), status),
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fIsGregorian(true), fInvertGregorian(false)
: GregorianCalendar(year, month, date, status)
{
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);
}
@ -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,
int32_t hour, int32_t minute, int32_t second,
UErrorCode& status)
: Calendar(TimeZone::createDefault(), Locale::getDefault(), status),
fGregorianCutover(kPapalCutover),
fCutoverJulianDay(kCutoverJulianDay), fNormalizedGregorianCutover(fGregorianCutover), fGregorianCutoverYear(1582),
fIsGregorian(true), fInvertGregorian(false)
: GregorianCalendar(year, month, date, hour, minute, status)
{
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);
}
@ -601,7 +574,8 @@ int32_t GregorianCalendar::handleGetMonthLength(int32_t extendedYear, int32_t mo
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;
}
@ -871,13 +845,14 @@ GregorianCalendar::roll(UCalendarDateFields field, int32_t amount, UErrorCode& s
}
if (month == UCAL_JANUARY) {
if (woy >= 52) {
isoDoy += handleGetYearLength(isoYear);
isoDoy += handleGetYearLength(isoYear, status);
}
} else {
if (woy == 1) {
isoDoy -= handleGetYearLength(isoYear - 1);
isoDoy -= handleGetYearLength(isoYear - 1, status);
}
}
if (U_FAILURE(status)) return;
if (uprv_add32_overflow(woy, amount, &woy)) {
status = U_ILLEGAL_ARGUMENT_ERROR;
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
// week 1 of the next year, we drop the last week by
// 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) -
getFirstDayOfWeek()) % 7;
if (lastRelDow < 0) lastRelDow += 7;
@ -1186,14 +1162,10 @@ int32_t GregorianCalendar::handleGetExtendedYear(UErrorCode& status) {
int32_t year = kEpochYear;
// year field to use
int32_t yearField = UCAL_EXTENDED_YEAR;
// There are three separate fields which could be used to
// derive the proper year. Use the one most recently set.
if (fStamp[yearField] < fStamp[UCAL_YEAR])
yearField = UCAL_YEAR;
if (fStamp[yearField] < fStamp[UCAL_YEAR_WOY])
yearField = UCAL_YEAR_WOY;
UCalendarDateFields yearField = newerField(
newerField(UCAL_EXTENDED_YEAR, UCAL_YEAR), UCAL_YEAR_WOY);
// based on the "best" year field, get the year
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
}
void Grego::dayToFields(int32_t day, int32_t& year, int32_t& month,
int32_t& dom, int32_t& dow, int32_t& doy, UErrorCode& status) {
void Grego::dayToFields(int32_t day, int32_t& year, int8_t& month,
int8_t& dom, int8_t& dow, int16_t& doy, UErrorCode& status) {
year = dayToYear(day, doy, status); // one-based doy
if (U_FAILURE(status)) return;
// 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;
}
// 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.
dow = (day + 1) % 7;
dow += (dow < 0) ? (UCAL_SUNDAY + 7) : UCAL_SUNDAY;
// Common Julian/Gregorian calculation
int32_t correction = 0;
bool isLeap = isLeapYear(year);
int32_t march1 = isLeap ? 60 : 59; // zero-based DOY for March 1
if (doy >= march1) {
if (doy > march1) {
correction = isLeap ? 1 : 2;
}
month = (12 * (doy + correction) + 6) / 367; // zero-based month
dom = doy - DAYS_BEFORE[month + (isLeap ? 12 : 0)] + 1; // one-based DOM
doy++; // one-based doy
month = (12 * (doy - 1 + correction) + 6) / 367; // zero-based month
dom = doy - DAYS_BEFORE[month + (isLeap ? 12 : 0)]; // one-based DOM
}
void Grego::timeToFields(UDate time, int32_t& year, int32_t& month,
int32_t& dom, int32_t& dow, int32_t& doy, int32_t& mid, UErrorCode& status) {
int32_t Grego::dayToYear(int32_t day, 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;
double millisInDay;
double day = ClockMath::floorDivide(static_cast<double>(time), static_cast<double>(U_MILLIS_PER_DAY), &millisInDay);
mid = static_cast<int32_t>(millisInDay);
double day = ClockMath::floorDivide(time, U_MILLIS_PER_DAY, &mid);
if (day > INT32_MAX || day < INT32_MIN) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
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 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 status error code.
*/
static void dayToFields(int32_t day, int32_t& year, int32_t& month,
int32_t& dom, int32_t& dow, int32_t& doy, UErrorCode& status);
static void dayToFields(int32_t day, int32_t& year, int8_t& month,
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,
@ -223,8 +236,24 @@ class Grego {
* @param dow output parameter to receive day-of-week (1-based, 1==Sun)
* @param status error code.
*/
static inline void dayToFields(int32_t day, int32_t& year, int32_t& month,
int32_t& dom, int32_t& dow, UErrorCode& status);
static void dayToFields(int32_t day, int32_t& year, int8_t& month,
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,
@ -238,8 +267,43 @@ class Grego {
* @param mid output parameter to receive millis-in-day
* @param status error code.
*/
static void timeToFields(UDate time, int32_t& year, int32_t& month,
int32_t& dom, int32_t& dow, int32_t& doy, int32_t& mid, UErrorCode& status);
static void timeToFields(UDate time, int32_t& year, int8_t& month,
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
@ -305,12 +369,6 @@ Grego::previousMonthLength(int y, int m) {
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)
{
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)
{
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
* @internal
*/
int32_t HebrewCalendar::handleGetYearLength(int32_t eyear) const {
UErrorCode status = U_ZERO_ERROR;
int32_t len = daysInYear(eyear, status);
if (U_FAILURE(status)) {
return 12;
}
return len;
int32_t HebrewCalendar::handleGetYearLength(int32_t eyear, UErrorCode& status) const {
return daysInYear(eyear, 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
* provide a more correct or more efficient implementation than the
* 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

View File

@ -41,7 +41,6 @@ IndianCalendar* IndianCalendar::clone() const {
IndianCalendar::IndianCalendar(const Locale& aLocale, UErrorCode& 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) {
@ -129,7 +128,8 @@ int32_t IndianCalendar::handleGetMonthLength(int32_t eyear, int32_t month, UErro
*
* @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;
}
/*
@ -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;
}
/*
* 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....
@ -266,10 +254,9 @@ int32_t IndianCalendar::handleGetExtendedYear(UErrorCode& status) {
void IndianCalendar::handleComputeFields(int32_t julianDay, UErrorCode& status) {
double jdAtStartOfGregYear;
int32_t leapMonth, IndianYear, yday, IndianMonth, IndianDayOfMonth, mday;
int32_t gregorianYear; // Stores gregorian date corresponding to Julian day;
int32_t gd[3];
// Stores gregorian date corresponding to Julian day;
int32_t gregorianYear = Grego::dayToYear(julianDay - kEpochStartAsJulianDay, status);
gregorianYear = jdToGregorian(julianDay, gd, status)[0]; // Gregorian date for Julian day
if (U_FAILURE(status)) return;
IndianYear = gregorianYear - INDIAN_ERA_START; // Year in Saka era
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
* @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....

View File

@ -202,7 +202,6 @@ IslamicCalendar* IslamicCalendar::clone() const {
IslamicCalendar::IslamicCalendar(const Locale& aLocale, UErrorCode& success)
: Calendar(TimeZone::forLocaleOrDefault(aLocale), aLocale, success)
{
setTimeInMillis(getNow(), success); // Call this again now that the vtable is set up properly.
}
IslamicCalendar::~IslamicCalendar()
@ -444,15 +443,8 @@ int32_t yearLength(int32_t extendedYear, UErrorCode& status) {
* Return the number of days in the given Islamic year
* @draft ICU 2.4
*/
int32_t IslamicCalendar::handleGetYearLength(int32_t extendedYear) const {
UErrorCode status = U_ZERO_ERROR;
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;
int32_t IslamicCalendar::handleGetYearLength(int32_t extendedYear, UErrorCode& status) const {
return yearLength(extendedYear, status);
}
//-------------------------------------------------------------------------
@ -706,7 +698,8 @@ int32_t IslamicCivilCalendar::handleGetMonthLength(int32_t extendedYear, int32_t
* Return the number of days in the given Islamic year
* @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);
}
@ -872,7 +865,7 @@ int32_t IslamicUmalquraCalendar::handleGetMonthLength(int32_t extendedYear, int3
int32_t IslamicUmalquraCalendar::yearLength(int32_t extendedYear, UErrorCode& status) const {
if (extendedYear<UMALQURA_YEAR_START || extendedYear>UMALQURA_YEAR_END) {
return IslamicCivilCalendar::handleGetYearLength(extendedYear);
return IslamicCivilCalendar::handleGetYearLength(extendedYear, status);
}
int length = 0;
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
* @draft ICU 2.4
*/
int32_t IslamicUmalquraCalendar::handleGetYearLength(int32_t extendedYear) const {
UErrorCode status = U_ZERO_ERROR;
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;
int32_t IslamicUmalquraCalendar::handleGetYearLength(int32_t extendedYear, UErrorCode& status) const {
return yearLength(extendedYear, status);
}
/**

View File

@ -235,7 +235,7 @@ class U_I18N_API IslamicCalendar : public Calendar {
* Return the number of days in the given Islamic year
* @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....
@ -438,7 +438,7 @@ class U_I18N_API IslamicCivilCalendar : public IslamicCalendar {
* Return the number of days in the given Islamic year
* @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
@ -621,7 +621,7 @@ class U_I18N_API IslamicUmalquraCalendar : public IslamicCivilCalendar {
* Return the number of days in the given Islamic year
* @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

View File

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

View File

@ -104,13 +104,6 @@ public:
*/
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.
* @return return a polymorphic copy of this calendar.

View File

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

View File

@ -15,6 +15,7 @@
#include "charstr.h"
#include "cmemory.h"
#include "cstring.h"
#include "double-conversion-string-to-double.h"
#include "measunit_impl.h"
#include "resource.h"
#include "uarrsort.h"
@ -30,13 +31,15 @@
#include "unicode/ustringtrie.h"
#include "uresimp.h"
#include "util.h"
#include <limits.h>
#include <cstdlib>
U_NAMESPACE_BEGIN
namespace {
using icu::double_conversion::StringToDoubleConverter;
// TODO: Propose a new error code for this?
constexpr UErrorCode kUnitIdentifierSyntaxError = U_ILLEGAL_ARGUMENT_ERROR;
@ -467,37 +470,55 @@ void U_CALLCONV initUnitExtras(UErrorCode& status) {
class Token {
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;
}
}
enum Type {
TYPE_UNDEFINED,
TYPE_PREFIX,
// Token type for "-per-", "-", and "-and-".
TYPE_COMPOUND_PART,
// Token type for "per-".
TYPE_INITIAL_COMPOUND_PART,
TYPE_POWER_PART,
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;
}
// Calling getType() is invalid, resulting in an assertion failure, if Token
// value isn't positive.
Type getType() const {
U_ASSERT(fMatch > 0);
if (fMatch < kCompoundPartOffset) {
return TYPE_PREFIX;
}
if (fMatch < kInitialCompoundPartOffset) {
return TYPE_COMPOUND_PART;
}
if (fMatch < kPowerPartOffset) {
return TYPE_INITIAL_COMPOUND_PART;
}
if (fMatch < kSimpleUnitOffset) {
return TYPE_POWER_PART;
}
return TYPE_SIMPLE_UNIT;
}
enum Type {
TYPE_UNDEFINED,
TYPE_PREFIX,
// Token type for "-per-", "-", and "-and-".
TYPE_COMPOUND_PART,
// Token type for "per-".
TYPE_INITIAL_COMPOUND_PART,
TYPE_POWER_PART,
TYPE_SIMPLE_UNIT,
TYPE_CONSTANT_DENOMINATOR,
};
// Calling getType() is invalid, resulting in an assertion failure, if Token
// value isn't positive.
Type getType() const {
U_ASSERT(fMatch >= 0);
return this->fType;
}
// Retrieve the value of the constant denominator if the token is of type TYPE_CONSTANT_DENOMINATOR.
uint64_t getConstantDenominator() const {
U_ASSERT(getType() == TYPE_CONSTANT_DENOMINATOR);
return static_cast<uint64_t>(fMatch);
}
UMeasurePrefix getUnitPrefix() const {
U_ASSERT(getType() == TYPE_PREFIX);
@ -530,8 +551,41 @@ public:
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:
int32_t fMatch;
Token() = default;
int64_t fMatch;
Type fType = TYPE_UNDEFINED;
};
class Parser {
@ -555,6 +609,50 @@ public:
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 result;
@ -569,12 +667,19 @@ public:
while (hasNext()) {
bool sawAnd = false;
SingleUnitImpl singleUnit = nextSingleUnit(sawAnd, status);
auto singleUnitOrConstant = nextSingleUnitOrConstant(sawAnd, status);
if (U_FAILURE(status)) {
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)) {
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;
}
@ -622,6 +733,10 @@ private:
// identifier is invalid pending TODO(CLDR-13701).
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(StringPiece source)
@ -640,6 +755,10 @@ private:
// Saves the position in the fSource string for the end of the most
// recent matching token.
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:
while (fIndex < fSource.length()) {
auto result = fTrie.next(fSource.data()[fIndex++]);
@ -658,12 +777,25 @@ private:
// continue;
}
if (match < 0) {
status = kUnitIdentifierSyntaxError;
} else {
if (match >= 0) {
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);
}
/**
@ -680,10 +812,10 @@ private:
* unit", sawAnd is set to true. If not, it is left as is.
* @param status ICU error code.
*/
SingleUnitImpl nextSingleUnit(bool &sawAnd, UErrorCode &status) {
SingleUnitImpl result;
SingleUnitOrConstant nextSingleUnitOrConstant(bool &sawAnd, UErrorCode &status) {
SingleUnitImpl singleUnitResult;
if (U_FAILURE(status)) {
return result;
return {};
}
// state:
@ -695,19 +827,22 @@ private:
bool atStart = fIndex == 0;
Token token = nextToken(status);
if (U_FAILURE(status)) {
return result;
return {};
}
fJustSawPer = false;
if (atStart) {
// Identifiers optionally start with "per-".
if (token.getType() == Token::TYPE_INITIAL_COMPOUND_PART) {
U_ASSERT(token.getInitialCompoundPart() == INITIAL_COMPOUND_PART_PER);
fAfterPer = true;
result.dimensionality = -1;
fJustSawPer = true;
singleUnitResult.dimensionality = -1;
token = nextToken(status);
if (U_FAILURE(status)) {
return result;
return {};
}
}
} else {
@ -715,7 +850,7 @@ private:
// via a compound part:
if (token.getType() != Token::TYPE_COMPOUND_PART) {
status = kUnitIdentifierSyntaxError;
return result;
return {};
}
switch (token.getMatch()) {
@ -724,15 +859,16 @@ private:
// Mixed compound units not yet supported,
// TODO(CLDR-13701).
status = kUnitIdentifierSyntaxError;
return result;
return {};
}
fAfterPer = true;
result.dimensionality = -1;
fJustSawPer = true;
singleUnitResult.dimensionality = -1;
break;
case COMPOUND_PART_TIMES:
if (fAfterPer) {
result.dimensionality = -1;
singleUnitResult.dimensionality = -1;
}
break;
@ -741,7 +877,7 @@ private:
// Can't start with "-and-", and mixed compound units
// not yet supported, TODO(CLDR-13701).
status = kUnitIdentifierSyntaxError;
return result;
return {};
}
sawAnd = true;
break;
@ -749,52 +885,65 @@ private:
token = nextToken(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.
while (true) {
switch (token.getType()) {
case Token::TYPE_POWER_PART:
if (state > 0) {
status = kUnitIdentifierSyntaxError;
return result;
}
result.dimensionality *= token.getPower();
state = 1;
break;
case Token::TYPE_PREFIX:
if (state > 1) {
status = kUnitIdentifierSyntaxError;
return result;
}
result.unitPrefix = token.getUnitPrefix();
state = 2;
break;
case Token::TYPE_SIMPLE_UNIT:
result.index = token.getSimpleUnitIndex();
return result;
default:
case Token::TYPE_POWER_PART:
if (state > 0) {
status = kUnitIdentifierSyntaxError;
return result;
return {};
}
singleUnitResult.dimensionality *= token.getPower();
state = 1;
break;
case Token::TYPE_PREFIX:
if (state > 1) {
status = kUnitIdentifierSyntaxError;
return {};
}
singleUnitResult.unitPrefix = token.getUnitPrefix();
state = 2;
break;
case Token::TYPE_SIMPLE_UNIT:
singleUnitResult.index = token.getSimpleUnitIndex();
break;
default:
status = kUnitIdentifierSyntaxError;
return {};
}
if (token.getType() == Token::TYPE_SIMPLE_UNIT) {
break;
}
if (!hasNext()) {
// We ran out of tokens before finding a complete single unit.
status = kUnitIdentifierSyntaxError;
return result;
return {};
}
token = nextToken(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;
}
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.
*/
@ -1128,7 +1322,7 @@ void MeasureUnitImpl::serialize(UErrorCode &status) {
return;
}
if (this->singleUnits.length() == 0) {
if (this->singleUnits.length() == 0 && this->constantDenominator == 0) {
// Dimensionless, constructed by the default constructor.
return;
}
@ -1145,6 +1339,7 @@ void MeasureUnitImpl::serialize(UErrorCode &status) {
CharString result;
bool beforePer = true;
bool firstTimeNegativeDimension = false;
bool constantDenominatorAppended = false;
for (int32_t i = 0; i < this->singleUnits.length(); i++) {
if (beforePer && (*this->singleUnits[i]).dimensionality < 0) {
beforePer = false;
@ -1168,43 +1363,103 @@ void MeasureUnitImpl::serialize(UErrorCode &status) {
} else {
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);
constantDenominatorAppended = true;
}
} else if (result.length() != 0) {
result.append(StringPiece("-"), 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);
}
MeasureUnit MeasureUnitImpl::build(UErrorCode& status) && {
MeasureUnit MeasureUnitImpl::build(UErrorCode &status) && {
this->serialize(status);
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);
}
UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode& status) const {
UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode &status) const {
MeasureUnitImpl temp;
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;
}
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);
singleUnit.unitPrefix = prefix;
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 {
SingleUnitImpl singleUnit = SingleUnitImpl::forMeasureUnit(*this, status);
if (U_FAILURE(status)) { return 0; }
@ -1222,6 +1477,11 @@ MeasureUnit MeasureUnit::withDimensionality(int32_t dimensionality, UErrorCode&
MeasureUnit MeasureUnit::reciprocal(UErrorCode& status) const {
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);
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++) {
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;
}
return std::move(impl).build(status);
}

View File

@ -328,6 +328,14 @@ class U_I18N_API MeasureUnitImpl : public UMemory {
*/
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
// TODO(icu-units#147): revisit serialization
friend class number::impl::LongNameHandler;

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
@ -11,8 +13,10 @@
#include "unicode/messageformat2_data_model.h"
#include "unicode/messageformat2_formattable.h"
#include "unicode/messageformat2.h"
#include "unicode/normalizer2.h"
#include "unicode/unistr.h"
#include "messageformat2_allocation.h"
#include "messageformat2_checker.h"
#include "messageformat2_evaluation.h"
#include "messageformat2_macros.h"
@ -37,7 +41,7 @@ static Formattable evalLiteral(const Literal& lit) {
// The fallback for a variable name is itself.
UnicodeString str(DOLLAR);
str += var;
const Formattable* val = context.getGlobal(var, errorCode);
const Formattable* val = context.getGlobal(*this, var, errorCode);
if (U_SUCCESS(errorCode)) {
return (FormattedPlaceholder(*val, str));
}
@ -51,16 +55,16 @@ static Formattable evalLiteral(const Literal& lit) {
return FormattedPlaceholder(evalLiteral(lit), lit.quoted());
}
[[nodiscard]] FormattedPlaceholder MessageFormatter::formatOperand(const Environment& env,
const Operand& rand,
MessageContext& context,
UErrorCode &status) const {
[[nodiscard]] InternalValue* MessageFormatter::formatOperand(const Environment& env,
const Operand& rand,
MessageContext& context,
UErrorCode &status) const {
if (U_FAILURE(status)) {
return {};
}
if (rand.isNull()) {
return FormattedPlaceholder();
return create<InternalValue>(InternalValue(FormattedPlaceholder()), status);
}
if (rand.isVariable()) {
// 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:
// 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
if (env.has(var)) {
if (env.has(normalized)) {
// `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
return formatExpression(rhs.getEnv(), rhs.getExpr(), context, status);
}
// 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) {
status = U_ZERO_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
UnicodeString str(DOLLAR);
str += var;
return FormattedPlaceholder(str);
return create<InternalValue>(InternalValue(FormattedPlaceholder(str)), status);
}
return result;
return create<InternalValue>(InternalValue(std::move(result)), status);
} else {
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
// Format the operand
FormattedPlaceholder rhsVal = formatOperand(env, v, context, status);
LocalPointer<InternalValue> rhsVal(formatOperand(env, v, context, status));
if (U_FAILURE(status)) {
return {};
}
if (!rhsVal.isFallback()) {
resolvedOpt.adoptInstead(create<ResolvedFunctionOption>(ResolvedFunctionOption(k, rhsVal.asFormattable()), status));
if (U_FAILURE(status)) {
return {};
}
optionsVector->adoptElement(resolvedOpt.orphan(), status);
// Note: this means option values are "eagerly" evaluated.
// 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)) {
return {};
}
optionsVector->adoptElement(resolvedOpt.orphan(), status);
}
return FunctionOptions(std::move(*optionsVector), status);
}
// Overload that dispatches on argument type. Syntax doesn't provide for options in this case.
[[nodiscard]] FormattedPlaceholder MessageFormatter::evalFormatterCall(FormattedPlaceholder&& argument,
MessageContext& context,
UErrorCode& status) const {
[[nodiscard]] InternalValue* MessageFormatter::evalFunctionCall(FormattedPlaceholder&& argument,
MessageContext& context,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return {};
return nullptr;
}
// These cases should have been checked for already
@ -153,11 +165,11 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O
// No formatter for this type -- follow default behavior
break;
}
return evalFormatterCall(functionName,
std::move(argument),
FunctionOptions(),
context,
status);
return evalFunctionCall(functionName,
create<InternalValue>(std::move(argument), status),
FunctionOptions(),
context,
status);
}
default: {
// TODO: The array case isn't handled yet; not sure whether it's desirable
@ -167,104 +179,76 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O
}
// No formatter for this type, or it's a primitive type (which will be formatted later)
// -- just return the argument itself
return std::move(argument);
return create<InternalValue>(std::move(argument), status);
}
// Overload that dispatches on function name
[[nodiscard]] FormattedPlaceholder MessageFormatter::evalFormatterCall(const FunctionName& functionName,
FormattedPlaceholder&& argument,
FunctionOptions&& options,
MessageContext& context,
UErrorCode& status) const {
// Adopts `arg`
[[nodiscard]] InternalValue* MessageFormatter::evalFunctionCall(const FunctionName& functionName,
InternalValue* arg_,
FunctionOptions&& options,
MessageContext& context,
UErrorCode& status) const {
if (U_FAILURE(status)) {
return {};
}
DynamicErrors& errs = context.getErrors();
UnicodeString fallback(COLON);
fallback += functionName;
if (!argument.isNullOperand()) {
fallback = argument.fallback;
}
LocalPointer<InternalValue> arg(arg_);
// Look up the formatter or selector
LocalPointer<Formatter> formatterImpl(nullptr);
LocalPointer<Selector> selectorImpl(nullptr);
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 {
status = U_ZERO_ERROR;
// 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
if (errs.hasFormattingError()) {
return FormattedPlaceholder(fallback);
}
return result;
formatterImpl.adoptInstead(getFormatter(functionName, status));
U_ASSERT(U_SUCCESS(status));
}
// No formatter with this name -- set error
if (isSelector(functionName)) {
errs.setFormattingError(functionName, status);
} else {
errs.setUnknownFunction(functionName, status);
selectorImpl.adoptInstead(getSelector(context, functionName, status));
U_ASSERT(U_SUCCESS(status));
}
return FormattedPlaceholder(fallback);
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);
fallback += functionName;
return new InternalValue(FormattedPlaceholder(fallback));
} else {
return new InternalValue(FormattedPlaceholder(arg->getFallback()));
}
}
return new InternalValue(arg.orphan(),
std::move(options),
functionName,
formatterImpl.isValid() ? formatterImpl.orphan() : nullptr,
selectorImpl.isValid() ? selectorImpl.orphan() : nullptr);
}
// Formats an expression using `globalEnv` for the values of variables
[[nodiscard]] FormattedPlaceholder MessageFormatter::formatExpression(const Environment& globalEnv,
const Expression& expr,
MessageContext& context,
UErrorCode &status) const {
[[nodiscard]] InternalValue* MessageFormatter::formatExpression(const Environment& globalEnv,
const Expression& expr,
MessageContext& context,
UErrorCode &status) const {
if (U_FAILURE(status)) {
return {};
}
const Operand& rand = expr.getOperand();
// 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
if (randVal.isFallback()) {
return randVal;
}
FormattedPlaceholder maybeRand = randVal->takeArgument(status);
if (!expr.isFunctionCall()) {
if (!expr.isFunctionCall() && U_SUCCESS(status)) {
// Dispatch based on type of `randVal`
return evalFormatterCall(std::move(randVal),
context,
status);
} else {
if (maybeRand.isFallback()) {
return randVal.orphan();
}
return evalFunctionCall(std::move(maybeRand), context, status);
} else if (expr.isFunctionCall()) {
status = U_ZERO_ERROR;
const Operator* rator = expr.getOperator(status);
U_ASSERT(U_SUCCESS(status));
const FunctionName& functionName = rator->getFunctionName();
@ -273,19 +257,14 @@ FunctionOptions MessageFormatter::resolveOptions(const Environment& env, const O
FunctionOptions resolvedOptions = resolveOptions(globalEnv, options, context, status);
// Call the formatter function
// The fallback for a nullary function call is the function name
UnicodeString fallback;
if (rand.isNull()) {
fallback = UnicodeString(COLON);
fallback += functionName;
} else {
fallback = randVal.fallback;
}
return evalFormatterCall(functionName,
std::move(randVal),
std::move(resolvedOptions),
context,
status);
return evalFunctionCall(functionName,
randVal.orphan(),
std::move(resolvedOptions),
context,
status);
} else {
status = U_ZERO_ERROR;
return randVal.orphan();
}
}
@ -301,11 +280,13 @@ void MessageFormatter::formatPattern(MessageContext& context, const Environment&
// Markup is ignored
} else {
// Format the expression
FormattedPlaceholder partVal = formatExpression(globalEnv, part.contents(), context, status);
// Force full evaluation, e.g. applying default formatters to
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
// unformatted input (or formatting numbers as strings)
UnicodeString partResult = partVal.formatToString(locale, status);
result += partResult;
result += partResult.formatToString(locale, status);
// Handle formatting errors. `formatToString()` can't take a context and thus can't
// register an error directly
if (status == U_MF_FORMATTING_ERROR) {
@ -328,14 +309,14 @@ void MessageFormatter::resolveSelectors(MessageContext& context, const Environme
CHECK_ERROR(status);
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.
// (Implicit, since `res` is an out-parameter)
// 2. For each expression exp of the message's selectors
for (int32_t i = 0; i < dataModel.numSelectors(); i++) {
// 2i. Let rv be the resolved value of exp.
ResolvedSelector rv = formatSelectorExpression(env, selectors[i], context, status);
if (rv.hasSelector()) {
LocalPointer<InternalValue> rv(formatOperand(env, Operand(selectors[i]), context, status));
if (rv->canSelect()) {
// 2ii. If selection is supported for rv:
// (True if this code has been reached)
} else {
@ -344,17 +325,17 @@ void MessageFormatter::resolveSelectors(MessageContext& context, const Environme
// Append nomatch as the last element of the list res.
// Emit a Selection Error.
// (Note: in this case, rv, being a fallback, serves as `nomatch`)
#if U_DEBUG
const DynamicErrors& err = context.getErrors();
U_ASSERT(err.hasError());
U_ASSERT(rv.argument().isFallback());
#endif
DynamicErrors& err = context.getErrors();
err.setSelectorError(rv->getFunctionName(), status);
rv.adoptInstead(new InternalValue(FormattedPlaceholder(rv->getFallback())));
if (!rv.isValid()) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
}
// 2ii(a). Append rv as the last element of the list res.
// (Also fulfills 2iii)
LocalPointer<ResolvedSelector> v(create<ResolvedSelector>(std::move(rv), status));
CHECK_ERROR(status);
res.adoptElement(v.orphan(), status);
res.adoptElement(rv.orphan(), status);
}
}
@ -362,18 +343,17 @@ void MessageFormatter::resolveSelectors(MessageContext& context, const Environme
// `keys` and `matches` are vectors of strings
void MessageFormatter::matchSelectorKeys(const UVector& keys,
MessageContext& context,
ResolvedSelector&& rv,
InternalValue* rv, // Does not adopt `rv`
UVector& keysOut,
UErrorCode& status) const {
CHECK_ERROR(status);
if (!rv.hasSelector()) {
if (U_FAILURE(status)) {
// Return an empty list of matches
status = U_ZERO_ERROR;
return;
}
auto selectorImpl = rv.getSelector();
U_ASSERT(selectorImpl != nullptr);
UErrorCode savedStatus = status;
// Convert `keys` to an array
@ -400,15 +380,17 @@ void MessageFormatter::matchSelectorKeys(const UVector& keys,
int32_t prefsLen = 0;
// Call the selector
selectorImpl->selectKey(rv.takeArgument(), rv.takeOptions(),
adoptedKeys.getAlias(), keysLen, adoptedPrefs.getAlias(), prefsLen,
status);
FunctionName name = rv->getFunctionName();
rv->forceSelection(context.getErrors(),
adoptedKeys.getAlias(), keysLen,
adoptedPrefs.getAlias(), prefsLen,
status);
// Update errors
if (savedStatus != status) {
if (U_FAILURE(status)) {
status = U_ZERO_ERROR;
context.getErrors().setSelectorError(rv.getSelectorName(), status);
context.getErrors().setSelectorError(name, status);
} else {
// Ignore warnings
status = savedStatus;
@ -461,8 +443,8 @@ void MessageFormatter::resolvePreferences(MessageContext& context, UVector& res,
if (!key.isWildcard()) {
// 2ii(b)(a) Assert that key is a literal.
// (Not needed)
// 2ii(b)(b) Let `ks` be the resolved value of `key`.
ks = key.asLiteral().unquoted();
// 2ii(b)(b) Let `ks` be the resolved value of `key` in Unicode Normalization Form C.
ks = normalizeNFC(key.asLiteral().unquoted());
// 2ii(b)(c) Append `ks` as the last element of the list `keys`.
ksP.adoptInstead(create<UnicodeString>(std::move(ks), 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`.
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)
LocalPointer<UVector> matches(createUVector(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.
// (Not needed)
// 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`.
const UVector& matches = *(static_cast<UVector*>(pref[i])); // `matches` is a vector of strings
// 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.
// (Not needed)
// 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`.
matchpref = vectorFind(matches, ks);
U_ASSERT(matchpref >= 0);
@ -604,123 +586,13 @@ void MessageFormatter::sortVariants(const UVector& pref, UVector& vars, UErrorCo
// 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 {
CHECK_ERROR(status);
// See https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#pattern-selection
// Resolve Selectors
// res is a vector of FormattedPlaceholders
// res is a vector of InternalValues
LocalPointer<UVector> res(createUVector(status));
CHECK_ERROR(status);
resolveSelectors(context, env, status, *res);
@ -761,28 +633,35 @@ void MessageFormatter::formatSelectors(MessageContext& context, const Environmen
UnicodeString MessageFormatter::formatToString(const MessageArguments& arguments, UErrorCode &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
MessageContext context(arguments, *errors, status);
// Check for unresolved variable errors
checkDeclarations(context, env, status);
LocalPointer<Environment> globalEnv(env);
UnicodeString result;
if (dataModel.hasPattern()) {
formatPattern(context, *globalEnv, dataModel.getPattern(), status, result);
} else {
// Check for errors/warnings -- if so, then the result of pattern selection is the fallback value
// See https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#pattern-selection
const DynamicErrors& err = context.getErrors();
if (err.hasSyntaxError() || err.hasDataModelError()) {
result += REPLACEMENT;
if (!(errors->hasSyntaxError() || errors->hasDataModelError())) {
// Create a new environment that will store closures for all local variables
// 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);
// ...and then it's adopted to avoid leaks
LocalPointer<Environment> globalEnv(env);
if (dataModel.hasPattern()) {
formatPattern(context, *globalEnv, dataModel.getPattern(), status, result);
} else {
formatSelectors(context, *globalEnv, status, result);
// Check for errors/warnings -- if so, then the result of pattern selection is the fallback value
// See https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#pattern-selection
const DynamicErrors& err = context.getErrors();
if (err.hasSyntaxError() || err.hasDataModelError()) {
result += REPLACEMENT;
} else {
formatSelectors(context, *globalEnv, status, result);
}
}
}
// Update status according to all errors seen while formatting
if (signalErrors) {
context.checkErrors(status);
@ -813,12 +692,14 @@ void MessageFormatter::check(MessageContext& context, const Environment& localEn
// Check that variable is in scope
const VariableName& var = rand.asVariable();
UnicodeString normalized = normalizeNFC(var);
// Check local scope
if (localEnv.has(var)) {
if (localEnv.has(normalized)) {
return;
}
// Check global scope
context.getGlobal(var, status);
context.getGlobal(*this, normalized, status);
if (status == U_ILLEGAL_ARGUMENT_ERROR) {
status = U_ZERO_ERROR;
context.getErrors().setUnresolvedVariable(var, status);
@ -855,7 +736,10 @@ void MessageFormatter::checkDeclarations(MessageContext& context, Environment*&
// memoizing the value of localEnv up to this point
// 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);
}
}
@ -866,3 +750,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

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

View File

@ -3,12 +3,16 @@
#include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
#include "unicode/messageformat2.h"
#include "unicode/messageformat2_arguments.h"
#include "unicode/messageformat2_data_model_names.h"
#include "messageformat2_evaluation.h"
#include "uvector.h" // U_ASSERT
U_NAMESPACE_BEGIN
@ -22,11 +26,15 @@ namespace message2 {
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)) {
U_ASSERT(argsLen == 0 || arguments.isValid());
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];
}
}
@ -57,3 +65,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -3,12 +3,16 @@
#include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
#include "unicode/messageformat2.h"
#include "messageformat2_allocation.h"
#include "messageformat2_checker.h"
#include "messageformat2_evaluation.h"
#include "messageformat2_macros.h"
#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) {
U_ASSERT(len > 0);
for (int32_t i = 0; i < len; i++) {
@ -185,7 +197,7 @@ void Checker::checkVariants(UErrorCode& status) {
// This variant was already checked,
// so we know keys1.len == len
for (int32_t kk = 0; kk < len; kk++) {
if (!(keys[kk] == keys1[kk])) {
if (!(normalizeNFC(keys[kk]) == normalizeNFC(keys1[kk]))) {
allEqual = false;
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);
if (selectorExpr.isFunctionCall()) {
if (t.get(selectorVar) == TypeEnvironment::Type::Annotated) {
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
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
// "missing selector annotation" error
const Expression* selectors = dataModel.getSelectorsInternal();
const VariableName* selectors = dataModel.getSelectorsInternal();
for (int32_t i = 0; i < dataModel.numSelectors(); i++) {
requireAnnotated(t, selectors[i], status);
}
@ -312,3 +320,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

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

View File

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

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
@ -290,3 +292,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */
#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.
*/
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
@ -151,6 +153,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT2_ERRORS_H

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
@ -89,35 +91,54 @@ FunctionOptions::FunctionOptions(FunctionOptions&& other) {
FunctionOptions::~FunctionOptions() {
if (options != nullptr) {
delete[] options;
options = nullptr;
}
}
// ResolvedSelector
// ----------------
ResolvedSelector::ResolvedSelector(const FunctionName& fn,
Selector* sel,
FunctionOptions&& opts,
FormattedPlaceholder&& val)
: selectorName(fn), selector(sel), options(std::move(opts)), value(std::move(val)) {
U_ASSERT(sel != nullptr);
static bool containsOption(const UVector& opts, const ResolvedFunctionOption& opt) {
for (int32_t i = 0; i < opts.size(); i++) {
if (static_cast<ResolvedFunctionOption*>(opts[i])->getName()
== opt.getName()) {
return true;
}
}
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 {
selectorName = std::move(other.selectorName);
selector.adoptInstead(other.selector.orphan());
options = std::move(other.options);
value = std::move(other.value);
return *this;
if (U_FAILURE(status)) {
return {};
}
// Create a new vector consisting of the options from this `FunctionOptions`
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
// ------------------
@ -190,18 +211,210 @@ PrioritizedVariant::~PrioritizedVariant() {}
errors.checkErrors(status);
}
const Formattable* MessageContext::getGlobal(const VariableName& v, UErrorCode& errorCode) const {
return arguments.getArgument(v, errorCode);
const Formattable* MessageContext::getGlobal(const MessageFormatter& context,
const VariableName& v,
UErrorCode& errorCode) const {
return arguments.getArgument(context, v, errorCode);
}
MessageContext::MessageContext(const MessageArguments& args,
const StaticErrors& e,
UErrorCode& status) : arguments(args), errors(e, status) {}
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
U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -14,6 +14,7 @@
* \file
* \brief C++ API: Formats messages using the draft MessageFormat 2.0.
*/
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
@ -63,38 +64,6 @@ namespace message2 {
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
// -------------------------
@ -174,11 +143,15 @@ namespace message2 {
// The context contains all the information needed to process
// an entire message: arguments, formatter cache, and error list
class MessageFormatter;
class MessageContext : public UMemory {
public:
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
void checkErrors(UErrorCode& status) const;
@ -191,8 +164,47 @@ namespace message2 {
const MessageArguments& arguments; // External message arguments
// Errors accumulated during parsing/formatting
DynamicErrors errors;
}; // 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
U_NAMESPACE_END
@ -201,6 +213,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT2_EVALUATION_H

View File

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

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
@ -43,7 +45,8 @@ namespace message2 {
// Parse the pattern
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
if (errors->hasSyntaxError()) {
@ -116,6 +119,24 @@ namespace message2 {
// 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) {
CHECK_ERROR(success);
@ -132,9 +153,13 @@ namespace message2 {
.adoptFormatter(FunctionName(UnicodeString("time")), time, success)
.adoptFormatter(FunctionName(UnicodeString("number")), number, 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("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);
standardMFFunctionRegistry = standardFunctionsBuilder.build();
CHECK_ERROR(success);
@ -163,6 +188,8 @@ namespace message2 {
errors = errorsNew.orphan();
}
nfcNormalizer = Normalizer2::getNFCInstance(success);
// Note: we currently evaluate variables lazily,
// without memoization. This call is still necessary
// to check out-of-scope uses of local variables in
@ -170,7 +197,7 @@ namespace message2 {
// only be checked when arguments are known)
// Check for resolution errors
Checker(dataModel, *errors).check(success);
Checker(dataModel, *errors, *this).check(success);
}
void MessageFormatter::cleanup() noexcept {
@ -191,6 +218,7 @@ namespace message2 {
signalErrors = other.signalErrors;
errors = other.errors;
other.errors = nullptr;
nfcNormalizer = other.nfcNormalizer;
return *this;
}
@ -256,8 +284,11 @@ namespace message2 {
return formatter;
}
bool MessageFormatter::getDefaultFormatterNameByType(const UnicodeString& type, FunctionName& name) const {
U_ASSERT(hasCustomMFFunctionRegistry());
bool MessageFormatter::getDefaultFormatterNameByType(const UnicodeString& type,
FunctionName& name) const {
if (!hasCustomMFFunctionRegistry()) {
return false;
}
const MFFunctionRegistry& reg = getCustomMFFunctionRegistry();
return reg.getDefaultFormatterNameByType(type, name);
}
@ -352,3 +383,5 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_MF2 */
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -3,6 +3,8 @@
#include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
@ -85,10 +87,11 @@ MFFunctionRegistry::Builder::Builder(UErrorCode& errorCode) {
formattersByType = new Hashtable();
if (!(formatters != nullptr && selectors != nullptr && formattersByType != nullptr)) {
errorCode = U_MEMORY_ALLOCATION_ERROR;
} else {
formatters->setValueDeleter(uprv_deleteUObject);
selectors->setValueDeleter(uprv_deleteUObject);
formattersByType->setValueDeleter(uprv_deleteUObject);
}
formatters->setValueDeleter(uprv_deleteUObject);
selectors->setValueDeleter(uprv_deleteUObject);
formattersByType->setValueDeleter(uprv_deleteUObject);
}
MFFunctionRegistry::Builder::~Builder() {
@ -158,9 +161,13 @@ void MFFunctionRegistry::checkStandard() const {
checkFormatter("time");
checkFormatter("number");
checkFormatter("integer");
checkFormatter("test:function");
checkFormatter("test:format");
checkSelector("number");
checkSelector("integer");
checkSelector("string");
checkSelector("test:function");
checkSelector("test:select");
}
// Formatter/selector helpers
@ -424,14 +431,14 @@ static FormattedPlaceholder notANumber(const FormattedPlaceholder& input) {
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)) {
return {};
}
// Copying string to avoid GCC dangling-reference warning
// (although the reference is safe)
UnicodeString inputStr = input.asFormattable().getString(errorCode);
UnicodeString inputStr = input.getString(errorCode);
// Precondition: `input`'s source Formattable has type string
if (U_FAILURE(errorCode)) {
return {};
@ -463,8 +470,42 @@ static double parseNumberLiteral(const FormattedPlaceholder& input, UErrorCode&
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) {
double numberValue = parseNumberLiteral(input, errorCode);
double numberValue = parseNumberLiteral(input.asFormattable(), errorCode);
if (U_FAILURE(errorCode)) {
return notANumber(input);
}
@ -1235,6 +1276,273 @@ void StandardFunctions::TextSelector::selectKey(FormattedPlaceholder&& toFormat,
StandardFunctions::TextFactory::~TextFactory() {}
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
U_NAMESPACE_END
@ -1242,3 +1550,4 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -10,6 +10,8 @@
#if U_SHOW_CPLUSPLUS_API
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
@ -209,6 +211,60 @@ namespace message2 {
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);
@ -226,6 +282,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT2_FUNCTION_REGISTRY_INTERNAL_H

View File

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

View File

@ -3,13 +3,18 @@
#include "unicode/utypes.h"
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
#include "unicode/uniset.h"
#include "messageformat2_errors.h"
#include "messageformat2_macros.h"
#include "messageformat2_parser.h"
#include "ucln_in.h"
#include "umutex.h"
#include "uvector.h" // U_ASSERT
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`]
static bool inRange(UChar32 c, UChar32 first, UChar32 last) {
U_ASSERT(first < last);
return c >= first && c <= last;
namespace unisets {
UnicodeSet* gUnicodeSets[unisets::UNISETS_KEY_COUNT] = {};
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:
@ -113,76 +386,50 @@ static bool inRange(UChar32 c, UChar32 first, UChar32 last) {
`isWhitespace()` : `s`
*/
static bool isContentChar(UChar32 c) {
return inRange(c, 0x0001, 0x0008) // Omit NULL, HTAB and LF
|| 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);
bool Parser::isContentChar(UChar32 c) const {
return contentChars->contains(c);
}
// See `s` in the MessageFormat 2 grammar
inline bool isWhitespace(UChar32 c) {
switch (c) {
case SPACE:
case HTAB:
case CR:
case LF:
case IDEOGRAPHIC_SPACE:
return true;
default:
return false;
}
// See `bidi` in the MF2 grammar
bool Parser::isBidiControl(UChar32 c) const {
return bidiControlChars->contains(c);
}
static bool isTextChar(UChar32 c) {
return isContentChar(c)
|| isWhitespace(c)
|| c == PERIOD
|| c == AT
|| c == PIPE;
// See `ws` in the MessageFormat 2 grammar
bool Parser::isWhitespace(UChar32 c) const {
return whitespaceChars->contains(c);
}
static bool isAlpha(UChar32 c) { return inRange(c, 0x0041, 0x005A) || inRange(c, 0x0061, 0x007A); }
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);
bool Parser::isTextChar(UChar32 c) const {
return textChars->contains(c);
}
static bool isNameChar(UChar32 c) {
return isNameStart(c) || isDigit(c) || c == HYPHEN || c == PERIOD || c == 0x00B7 ||
inRange(c, 0x0300, 0x036F) || inRange(c, 0x203F, 0x2040);
bool Parser::isAlpha(UChar32 c) const {
return alphaChars->contains(c);
}
static bool isUnquotedStart(UChar32 c) {
return isNameStart(c) || isDigit(c) || c == HYPHEN || c == PERIOD || c == 0x00B7 ||
inRange(c, 0x0300, 0x036F) || inRange(c, 0x203F, 0x2040);
bool Parser::isDigit(UChar32 c) const {
return digitChars->contains(c);
}
static bool isQuotedChar(UChar32 c) {
return isContentChar(c)
|| isWhitespace(c)
|| c == PERIOD
|| c == AT
|| c == LEFT_CURLY_BRACE
|| c == RIGHT_CURLY_BRACE;
bool Parser::isNameStart(UChar32 c) const {
return nameStartChars->contains(c);
}
static bool isEscapableChar(UChar32 c) {
return c == PIPE
|| c == BACKSLASH
|| c == LEFT_CURLY_BRACE
|| c == RIGHT_CURLY_BRACE;
bool Parser::isNameChar(UChar32 c) const {
return nameChars->contains(c);
}
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
@ -203,12 +450,12 @@ static bool isAnnotationStart(UChar32 c) {
}
// 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));
}
// 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));
}
@ -347,7 +594,7 @@ option, or the optional space before an attribute.
No pre, no post.
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;
// 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 --
// then the caller is responsible for checking this case and
// setting an error if necessary.
if (!required || sawWhitespace) {
if (sawWhitespace) {
// Not an error.
return;
}
@ -380,24 +627,51 @@ void Parser::parseWhitespaceMaybeRequired(bool required, UErrorCode& errorCode)
}
}
if (!sawWhitespace && required) {
if (!sawWhitespace) {
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) {
parseWhitespaceMaybeRequired(true, errorCode);
parseOptionalBidi();
parseRequiredWS(errorCode);
parseOptionalWhitespace();
normalizedInput += SPACE;
}
/*
No pre, no post, for the same reason as `parseWhitespaceMaybeRequired()`.
*/
void Parser::parseOptionalWhitespace(UErrorCode& errorCode) {
parseWhitespaceMaybeRequired(false, errorCode);
void Parser::parseOptionalWhitespace() {
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`
@ -442,11 +716,11 @@ void Parser::parseToken(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
parseOptionalWhitespace(errorCode);
parseOptionalWhitespace();
// Establish precondition
CHECK_BOUNDS(errorCode);
parseToken(token, errorCode);
parseOptionalWhitespace(errorCode);
parseOptionalWhitespace();
// Guarantee postcondition
CHECK_BOUNDS(errorCode);
}
@ -458,12 +732,12 @@ void Parser::parseTokenWithWhitespace(const std::u16string_view& token, UErrorCo
then consumes optional whitespace again
*/
void Parser::parseTokenWithWhitespace(UChar32 c, UErrorCode& errorCode) {
// No need for error check or bounds check before parseOptionalWhitespace(errorCode)
parseOptionalWhitespace(errorCode);
// No need for error check or bounds check before parseOptionalWhitespace()
parseOptionalWhitespace();
// Establish precondition
CHECK_BOUNDS(errorCode);
parseToken(c, errorCode);
parseOptionalWhitespace(errorCode);
parseOptionalWhitespace();
// Guarantee postcondition
CHECK_BOUNDS(errorCode);
}
@ -482,11 +756,17 @@ UnicodeString Parser::parseName(UErrorCode& errorCode) {
U_ASSERT(inBounds());
if (!isNameStart(peek())) {
if (!(isNameStart(peek()) || isBidiControl(peek()))) {
ERROR(errorCode);
return name;
}
// name = [bidi] name-start *name-char [bidi]
// [bidi]
parseOptionalBidi();
// name-start *name-char
while (isNameChar(peek())) {
UChar32 c = peek();
name += c;
@ -497,6 +777,10 @@ UnicodeString Parser::parseName(UErrorCode& errorCode) {
break;
}
}
// [bidi]
parseOptionalBidi();
return name;
}
@ -510,21 +794,13 @@ VariableName Parser::parseVariableName(UErrorCode& errorCode) {
VariableName result;
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);
if (!inBounds()) {
ERROR(errorCode);
return result;
}
UnicodeString varName = parseName(errorCode);
// Set the name to "" if the variable wasn't
// declared correctly
if (!valid) {
varName.remove();
}
return VariableName(varName);
return VariableName(parseName(errorCode));
}
/*
@ -853,7 +1129,7 @@ void Parser::parseAttribute(AttributeAdder<T>& attrAdder, UErrorCode& errorCode)
// about whether whitespace precedes another
// attribute, or the '=' sign
int32_t savedIndex = index;
parseOptionalWhitespace(errorCode);
parseOptionalWhitespace();
Operand rand;
if (peek() == EQUALS) {
@ -861,19 +1137,9 @@ void Parser::parseAttribute(AttributeAdder<T>& attrAdder, UErrorCode& errorCode)
parseTokenWithWhitespace(EQUALS, errorCode);
UnicodeString rhsStr;
// Parse RHS, which is either a literal or variable
switch (peek()) {
case DOLLAR: {
rand = Operand(parseVariableName(errorCode));
break;
}
default: {
// Must be a literal
rand = Operand(parseLiteral(errorCode));
break;
}
}
U_ASSERT(!rand.isNull());
// Parse RHS, which must be a literal
// attribute = "@" identifier [o "=" o literal]
rand = Operand(parseLiteral(errorCode));
} else {
// attribute -> "@" identifier [[s] "=" [s]]
// Use null operand, which `rand` is already set to
@ -881,7 +1147,7 @@ void Parser::parseAttribute(AttributeAdder<T>& attrAdder, UErrorCode& errorCode)
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
// trailing space after the literal or variable). It's still ambiguous which
// one does apply.
parseOptionalWhitespace(status);
parseOptionalWhitespace();
// Restore precondition
CHECK_BOUNDS(status);
@ -1220,7 +1486,7 @@ Expression Parser::parseExpression(UErrorCode& status) {
// Parse opening brace
parseToken(LEFT_CURLY_BRACE, status);
// Optional whitespace after opening brace
parseOptionalWhitespace(status);
parseOptionalWhitespace();
Expression::Builder exprBuilder(status);
// Restore precondition
@ -1263,7 +1529,7 @@ Expression Parser::parseExpression(UErrorCode& status) {
// Parse optional space
// (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,
// so there can't be an error
@ -1339,7 +1605,7 @@ void Parser::parseInputDeclaration(UErrorCode& status) {
CHECK_BOUNDS(status);
parseToken(ID_INPUT, status);
parseOptionalWhitespace(status);
parseOptionalWhitespace();
// Restore precondition before calling parseExpression()
CHECK_BOUNDS(status);
@ -1400,7 +1666,7 @@ void Parser::parseDeclarations(UErrorCode& status) {
// Avoid looping infinitely
CHECK_ERROR(status);
parseOptionalWhitespace(status);
parseOptionalWhitespace();
// Restore precondition
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
// *(s key) [s]
while (peek() != LEFT_CURLY_BRACE || isWhitespace(peek())) { // Try to recover from errors
bool wasWhitespace = isWhitespace(peek());
while (peek() != LEFT_CURLY_BRACE || isWhitespace(peek()) || isBidiControl(peek())) {
bool wasWhitespace = isWhitespace(peek()) || isBidiControl(peek());
parseRequiredWhitespace(status);
if (!wasWhitespace) {
// Avoid infinite loop when parsing something like:
@ -1569,7 +1835,7 @@ Markup Parser::parseMarkup(UErrorCode& status) {
// Consume the '{'
next();
normalizedInput += LEFT_CURLY_BRACE;
parseOptionalWhitespace(status);
parseOptionalWhitespace();
bool closing = false;
switch (peek()) {
case NUMBER_SIGN: {
@ -1596,19 +1862,19 @@ Markup Parser::parseMarkup(UErrorCode& status) {
// Parse the options, which must begin with a ' '
// if present
if (inBounds() && isWhitespace(peek())) {
if (inBounds() && (isWhitespace(peek()) || isBidiControl(peek()))) {
OptionAdder<Markup::Builder> optionAdder(builder);
parseOptions(optionAdder, status);
}
// Parse the attributes, which also must begin
// with a ' '
if (inBounds() && isWhitespace(peek())) {
if (inBounds() && (isWhitespace(peek()) || isBidiControl(peek()))) {
AttributeAdder<Markup::Builder> attrAdder(builder);
parseAttributes(attrAdder, status);
}
parseOptionalWhitespace(status);
parseOptionalWhitespace();
bool standalone = false;
// Check if this is a standalone or not
@ -1656,7 +1922,7 @@ std::variant<Expression, Markup> Parser::parsePlaceholder(UErrorCode& status) {
isMarkup = true;
break;
}
if (!isWhitespace(c)){
if (!(isWhitespace(c) || isBidiControl(c))) {
break;
}
tempIndex++;
@ -1712,7 +1978,7 @@ Pattern Parser::parseSimpleMessage(UErrorCode& status) {
break;
}
// Don't loop infinitely
if (errors.hasSyntaxError()) {
if (errors.hasSyntaxError() || U_FAILURE(status)) {
break;
}
}
@ -1720,6 +1986,22 @@ Pattern Parser::parseSimpleMessage(UErrorCode& 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),
@ -1739,22 +2021,25 @@ void Parser::parseSelectors(UErrorCode& status) {
// Parse selectors
// "Backtracking" is required here. It's not clear if whitespace is
// (`[s]` selector) or (`[s]` variant)
while (isWhitespace(peek()) || peek() == LEFT_CURLY_BRACE) {
parseOptionalWhitespace(status);
while (isWhitespace(peek()) || peek() == DOLLAR) {
int32_t whitespaceStart = index;
parseRequiredWhitespace(status);
// Restore precondition
CHECK_BOUNDS(status);
if (peek() != LEFT_CURLY_BRACE) {
if (peek() != DOLLAR) {
// This is not necessarily an error, but rather,
// means the whitespace we parsed was the optional
// 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;
}
Expression expression;
expression = parseExpression(status);
VariableName var = parseVariableName(status);
empty = false;
dataModel.addSelector(std::move(expression), status);
dataModel.addSelector(std::move(var), status);
CHECK_ERROR(status);
}
@ -1770,27 +2055,29 @@ void Parser::parseSelectors(UErrorCode& status) {
} \
// Parse variants
while (isWhitespace(peek()) || isKeyStart(peek())) {
// Trailing whitespace is allowed
parseOptionalWhitespace(status);
// matcher = match-statement s variant *(o variant)
// 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()) {
return;
}
// At least one key is required
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);
parseVariant(status);
// Restore the precondition, *without* erroring out if we've
// 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
// the loop head will read off the end of the input string.
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;
// First, "look ahead" to determine if this is a simple or complex
// message. To do that, check the first non-whitespace character.
while (inBounds(index) && isWhitespace(peek())) {
while (inBounds(index) && (isWhitespace(peek()) || isBidiControl(peek()))) {
next();
}
@ -1891,10 +2182,10 @@ void Parser::parse(UParseError &parseErrorResult, UErrorCode& status) {
// Message can be empty, so we need to only look ahead
// if we know it's non-empty
if (complex) {
parseOptionalWhitespace(status);
parseOptionalWhitespace();
parseDeclarations(status);
parseBody(status);
parseOptionalWhitespace(status);
parseOptionalWhitespace();
} else {
// Simple message
// For normalization, quote the pattern
@ -1926,3 +2217,4 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */

View File

@ -10,12 +10,15 @@
#include "unicode/messageformat2_data_model.h"
#include "unicode/parseerr.h"
#include "unicode/uniset.h"
#include "messageformat2_allocation.h"
#include "messageformat2_errors.h"
#if U_SHOW_CPLUSPLUS_API
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#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)
class Parser : public UMemory {
public:
@ -82,8 +105,23 @@ namespace message2 {
UChar postContext[U_PARSE_CONTEXT_LEN];
} MessageParseError;
Parser(const UnicodeString &input, MFDataModel::Builder& dataModelBuilder, StaticErrors& e, UnicodeString& normalizedInputRef)
: source(input), index(0), errors(e), normalizedInput(normalizedInputRef), dataModel(dataModelBuilder) {
Parser(const UnicodeString &input,
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.offset = 0;
parseError.lengthBeforeCurrentLine = 0;
@ -91,6 +129,20 @@ namespace message2 {
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 setParseError(MessageParseError&, uint32_t);
void maybeAdvanceLine();
@ -100,11 +152,13 @@ namespace message2 {
void parseUnsupportedStatement(UErrorCode&);
void parseLocalDeclaration(UErrorCode&);
void parseInputDeclaration(UErrorCode&);
void parseSelectors(UErrorCode&);
void parseSelectors(UErrorCode&);
void parseVariant(UErrorCode&);
void parseWhitespaceMaybeRequired(bool, UErrorCode&);
void parseRequiredWS(UErrorCode&);
void parseRequiredWhitespace(UErrorCode&);
void parseOptionalWhitespace(UErrorCode&);
void parseOptionalBidi();
void parseOptionalWhitespace();
void parseToken(UChar32, UErrorCode&);
void parseTokenWithWhitespace(UChar32, 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 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
const UnicodeString &source;
// The current position within the input string -- counting in UChar32
@ -165,8 +231,8 @@ namespace message2 {
// The parent builder
MFDataModel::Builder &dataModel;
}; // class Parser
}; // class Parser
} // namespace message2
U_NAMESPACE_END
@ -175,6 +241,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */
#endif // MESSAGEFORMAT_PARSER_H

View File

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

View File

@ -10,6 +10,8 @@
#if U_SHOW_CPLUSPLUS_API
#if !UCONFIG_NO_NORMALIZATION
#if !UCONFIG_NO_FORMATTING
#if !UCONFIG_NO_MF2
@ -63,6 +65,8 @@ U_NAMESPACE_END
#endif /* #if !UCONFIG_NO_FORMATTING */
#endif /* #if !UCONFIG_NO_NORMALIZATION */
#endif /* U_SHOW_CPLUSPLUS_API */
#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
if (description.length() == 0) {
if (description.isEmpty()) {
// throw new IllegalArgumentException("Empty rule set description");
status = U_PARSE_ERROR;
return;
@ -177,16 +177,16 @@ NFRuleSet::NFRuleSet(RuleBasedNumberFormat *_owner, UnicodeString* descriptions,
name.setTo(UNICODE_STRING_SIMPLE("%default"));
}
if (description.length() == 0) {
if (description.isEmpty()) {
// throw new IllegalArgumentException("Empty rule set description");
status = U_PARSE_ERROR;
}
fIsPublic = name.indexOf(gPercentPercent, 2, 0) != 0;
if ( name.endsWith(gNoparse,8) ) {
if (name.endsWith(gNoparse, 8)) {
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

View File

@ -19,7 +19,6 @@
#if U_HAVE_RBNF
#include <limits>
#include "unicode/localpointer.h"
#include "unicode/rbnf.h"
#include "unicode/tblcoll.h"
@ -65,6 +64,7 @@ NFRule::~NFRule()
static const char16_t gLeftBracket = 0x005b;
static const char16_t gRightBracket = 0x005d;
static const char16_t gVerticalLine = 0x007C;
static const char16_t gColon = 0x003a;
static const char16_t gZero = 0x0030;
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)
LocalPointer<NFRule> rule2;
UnicodeString sbuf;
int32_t orElseOp = description.indexOf(gVerticalLine);
// 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
@ -194,9 +195,13 @@ NFRule::makeRules(UnicodeString& description,
rule2->radix = rule1->radix;
rule2->exponent = rule1->exponent;
// rule2's rule text omits the stuff in brackets: initialize
// its rule text and substitutions accordingly
// By default, rule2's rule text omits the stuff in brackets,
// unless it contains a | between the brackets.
// Initialize its rule text and substitutions accordingly.
sbuf.append(description, 0, brack1);
if (orElseOp >= 0) {
sbuf.append(description, orElseOp + 1, brack2 - orElseOp - 1);
}
if (brack2 + 1 < description.length()) {
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
// substitutions accordingly
sbuf.setTo(description, 0, brack1);
sbuf.append(description, brack1 + 1, brack2 - brack1 - 1);
if (orElseOp >= 0) {
sbuf.append(description, brack1 + 1, orElseOp - brack1 - 1);
}
else {
sbuf.append(description, brack1 + 1, brack2 - brack1 - 1);
}
if (brack2 + 1 < description.length()) {
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,
// stop on a slash or > sign (or at the end of the string),
// and throw an exception on any other character
int64_t ll_10 = 10;
while (p < descriptorLength) {
c = descriptor.charAt(p);
if (c >= gZero && c <= gNine) {
int32_t single_digit = static_cast<int32_t>(c - gZero);
if ((val > 0 && val > (std::numeric_limits<int64_t>::max() - single_digit) / 10) ||
(val < 0 && val < (std::numeric_limits<int64_t>::min() - single_digit) / 10)) {
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 * ll_10 + single_digit;
val = val * 10 + digit;
}
else if (c == gSlash || c == gGreaterThan) {
break;
@ -322,11 +331,17 @@ NFRule::parseRuleDescriptor(UnicodeString& description, UErrorCode& status)
if (c == gSlash) {
val = 0;
++p;
ll_10 = 10;
while (p < descriptorLength) {
c = descriptor.charAt(p);
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) {
break;
@ -400,7 +415,7 @@ NFRule::parseRuleDescriptor(UnicodeString& description, UErrorCode& status)
// finally, if the rule body begins with an apostrophe, strip it off
// (this is generally used to put whitespace at the beginning of
// a rule's rule text)
if (description.length() > 0 && description.charAt(0) == gTick) {
if (!description.isEmpty() && description.charAt(0) == gTick) {
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) {
if (!usingBytes && precision + numDigits > 16) {
if (!usingBytes && precision + numDigits >= 16) {
switchStorage();
}
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.
*/
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.
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"
// 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
MeasureUnitImpl unit;
MeasureUnitImpl perUnit;
if (unitRef.getConstantDenominator(status) != 0) {
perUnit.constantDenominator = unitRef.getConstantDenominator(status);
}
{
MeasureUnitImpl fullUnit = MeasureUnitImpl::forMeasureUnitMaybeCopy(unitRef, status);
if (U_FAILURE(status)) {
@ -1196,6 +1205,12 @@ void LongNameHandler::processPatternTimes(MeasureUnitImpl &&productUnit,
DerivedComponents derivedTimesCases(loc, "case", "times");
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
for (int32_t singleUnitIndex = 0; singleUnitIndex < productUnit.singleUnits.length();
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++) {
if (globalPlaceholder[pluralIndex] == PH_BEGINNING) {
UnicodeString tmp;

View File

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

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