Merge pull request #13 from sffc/unit-id

ICU-20920 Add new MeasureUnit functions to units-staging
This commit is contained in:
Shane F. Carr 2020-01-17 17:33:56 +01:00 committed by GitHub
commit b76e3a14b6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1694 additions and 54 deletions

View file

@ -684,26 +684,26 @@ inline H *MaybeStackHeaderAndArray<H, T, stackCapacity>::orphanOrClone(int32_t l
template<typename T, int32_t stackCapacity = 8>
class MemoryPool : public UMemory {
public:
MemoryPool() : count(0), pool() {}
MemoryPool() : fCount(0), fPool() {}
~MemoryPool() {
for (int32_t i = 0; i < count; ++i) {
delete pool[i];
for (int32_t i = 0; i < fCount; ++i) {
delete fPool[i];
}
}
MemoryPool(const MemoryPool&) = delete;
MemoryPool& operator=(const MemoryPool&) = delete;
MemoryPool(MemoryPool&& other) U_NOEXCEPT : count(other.count),
pool(std::move(other.pool)) {
other.count = 0;
MemoryPool(MemoryPool&& other) U_NOEXCEPT : fCount(other.fCount),
fPool(std::move(other.fPool)) {
other.fCount = 0;
}
MemoryPool& operator=(MemoryPool&& other) U_NOEXCEPT {
count = other.count;
pool = std::move(other.pool);
other.count = 0;
fCount = other.fCount;
fPool = std::move(other.fPool);
other.fCount = 0;
return *this;
}
@ -716,20 +716,72 @@ public:
*/
template<typename... Args>
T* create(Args&&... args) {
int32_t capacity = pool.getCapacity();
if (count == capacity &&
pool.resize(capacity == stackCapacity ? 4 * capacity : 2 * capacity,
capacity) == nullptr) {
int32_t capacity = fPool.getCapacity();
if (fCount == capacity &&
fPool.resize(capacity == stackCapacity ? 4 * capacity : 2 * capacity,
capacity) == nullptr) {
return nullptr;
}
return pool[count++] = new T(std::forward<Args>(args)...);
return fPool[fCount++] = new T(std::forward<Args>(args)...);
}
private:
int32_t count;
MaybeStackArray<T*, stackCapacity> pool;
/**
* @return Number of elements that have been allocated.
*/
int32_t count() const {
return fCount;
}
protected:
int32_t fCount;
MaybeStackArray<T*, stackCapacity> fPool;
};
/**
* An internal Vector-like implementation based on MemoryPool.
*
* To append an item to the vector, use emplaceBack.
*
* MaybeStackVector<MyType> vector;
* MyType* element = vector.emplaceBack();
* if (!element) {
* status = U_MEMORY_ALLOCATION_ERROR;
* }
* // do stuff with element
*
* To loop over the vector, use a for loop with indices:
*
* for (int32_t i = 0; i < vector.length(); i++) {
* MyType* element = vector[i];
* }
*/
template<typename T, int32_t stackCapacity = 8>
class MaybeStackVector : protected MemoryPool<T, stackCapacity> {
public:
using MemoryPool<T, stackCapacity>::MemoryPool;
using MemoryPool<T, stackCapacity>::operator=;
template<typename... Args>
T* emplaceBack(Args&&... args) {
return this->create(args...);
}
int32_t length() const {
return this->count();
}
/**
* Array item access (writable).
* No index bounds check.
* @param i array index
* @return reference to the array item
*/
T *operator[](ptrdiff_t i) const {
return this->fPool[i];
}
};
U_NAMESPACE_END
#endif /* __cplusplus */

View file

@ -378,9 +378,9 @@ public:
* @param p simple pointer to an array of T objects that is adopted
* @stable ICU 4.4
*/
explicit LocalArray(T *p=NULL) : LocalPointerBase<T>(p) {}
explicit LocalArray(T *p=nullptr) : LocalPointerBase<T>(p), fLength(p==nullptr?0:-1) {}
/**
* Constructor takes ownership and reports an error if NULL.
* Constructor takes ownership and reports an error if nullptr.
*
* This constructor is intended to be used with other-class constructors
* that may report a failure UErrorCode,
@ -389,11 +389,11 @@ public:
*
* @param p simple pointer to an array of T objects that is adopted
* @param errorCode in/out UErrorCode, set to U_MEMORY_ALLOCATION_ERROR
* if p==NULL and no other failure code had been set
* if p==nullptr and no other failure code had been set
* @stable ICU 56
*/
LocalArray(T *p, UErrorCode &errorCode) : LocalPointerBase<T>(p) {
if(p==NULL && U_SUCCESS(errorCode)) {
LocalArray(T *p, UErrorCode &errorCode) : LocalArray<T>(p) {
if(p==nullptr && U_SUCCESS(errorCode)) {
errorCode=U_MEMORY_ALLOCATION_ERROR;
}
}
@ -402,8 +402,12 @@ public:
* @param src source smart pointer
* @stable ICU 56
*/
LocalArray(LocalArray<T> &&src) U_NOEXCEPT : LocalPointerBase<T>(src.ptr) {
src.ptr=NULL;
LocalArray(LocalArray<T> &&src) U_NOEXCEPT : LocalArray<T>(src.ptr) {
src.ptr=nullptr;
}
static LocalArray<T> withLength(T *p, int32_t length) {
return LocalArray(p, length);
}
#ifndef U_HIDE_DRAFT_API
@ -418,7 +422,7 @@ public:
* @draft ICU 64
*/
explicit LocalArray(std::unique_ptr<T[]> &&p)
: LocalPointerBase<T>(p.release()) {}
: LocalArray<T>(p.release()) {}
#endif /* U_HIDE_DRAFT_API */
/**
@ -438,7 +442,8 @@ public:
LocalArray<T> &operator=(LocalArray<T> &&src) U_NOEXCEPT {
delete[] LocalPointerBase<T>::ptr;
LocalPointerBase<T>::ptr=src.ptr;
src.ptr=NULL;
src.ptr=nullptr;
fLength=src.fLength;
return *this;
}
@ -453,6 +458,7 @@ public:
*/
LocalArray<T> &operator=(std::unique_ptr<T[]> &&p) U_NOEXCEPT {
adoptInstead(p.release());
fLength=-1;
return *this;
}
#endif /* U_HIDE_DRAFT_API */
@ -466,6 +472,9 @@ public:
T *temp=LocalPointerBase<T>::ptr;
LocalPointerBase<T>::ptr=other.ptr;
other.ptr=temp;
int32_t tempLength=fLength;
fLength=other.fLength;
other.fLength=tempLength;
}
/**
* Non-member LocalArray swap function.
@ -485,6 +494,7 @@ public:
void adoptInstead(T *p) {
delete[] LocalPointerBase<T>::ptr;
LocalPointerBase<T>::ptr=p;
fLength=-1;
}
/**
* Deletes the array it owns,
@ -511,6 +521,7 @@ public:
} else {
delete[] p;
}
fLength=-1;
}
/**
* Array item access (writable).
@ -537,6 +548,15 @@ public:
return std::unique_ptr<T[]>(LocalPointerBase<T>::orphan());
}
#endif /* U_HIDE_DRAFT_API */
int32_t length() const { return fLength; }
private:
int32_t fLength = -1;
LocalArray(T *p, int32_t length) : LocalArray(p) {
fLength = length;
}
};
/**

View file

@ -97,7 +97,7 @@ uspoof.o uspoof_impl.o uspoof_build.o uspoof_conf.o smpdtfst.o \
ztrans.o zrule.o vzone.o fphdlimp.o fpositer.o ufieldpositer.o \
decNumber.o decContext.o alphaindex.o tznames.o tznames_impl.o tzgnames.o \
tzfmt.o compactdecimalformat.o gender.o region.o scriptset.o \
uregion.o reldatefmt.o quantityformatter.o measunit.o \
uregion.o reldatefmt.o quantityformatter.o measunit.o measunit_extra.o \
sharedbreakiterator.o scientificnumberformatter.o dayperiodrules.o nounit.o \
number_affixutils.o number_compact.o number_decimalquantity.o \
number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \

View file

@ -187,6 +187,7 @@
<ClCompile Include="ulistformatter.cpp" />
<ClCompile Include="measfmt.cpp" />
<ClCompile Include="measunit.cpp" />
<ClCompile Include="measunit_extra.cpp" />
<ClCompile Include="measure.cpp" />
<ClCompile Include="msgfmt.cpp" />
<ClCompile Include="nfrs.cpp" />

View file

@ -204,6 +204,9 @@
<ClCompile Include="measunit.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="measunit_extra.cpp">
<Filter>formatting</Filter>
</ClCompile>
<ClCompile Include="measure.cpp">
<Filter>formatting</Filter>
</ClCompile>

View file

@ -408,6 +408,7 @@
<ClCompile Include="ulistformatter.cpp" />
<ClCompile Include="measfmt.cpp" />
<ClCompile Include="measunit.cpp" />
<ClCompile Include="measunit_extra.cpp" />
<ClCompile Include="measure.cpp" />
<ClCompile Include="msgfmt.cpp" />
<ClCompile Include="nfrs.cpp" />

View file

@ -2006,24 +2006,62 @@ static int32_t binarySearch(
return -1;
}
MeasureUnit::MeasureUnit() {
fCurrency[0] = 0;
fTypeId = kBaseTypeIdx;
fSubTypeId = kBaseSubTypeIdx;
MeasureUnit::MeasureUnit() : MeasureUnit(kBaseTypeIdx, kBaseSubTypeIdx) {
}
MeasureUnit::MeasureUnit(int32_t typeId, int32_t subTypeId)
: fId(nullptr), fSubTypeId(subTypeId), fTypeId(typeId) {
}
MeasureUnit::MeasureUnit(const MeasureUnit &other)
: fTypeId(other.fTypeId), fSubTypeId(other.fSubTypeId) {
uprv_strcpy(fCurrency, other.fCurrency);
: fId(nullptr) {
*this = other;
}
MeasureUnit::MeasureUnit(MeasureUnit &&other) noexcept
: fId(other.fId),
fSubTypeId(other.fSubTypeId),
fTypeId(other.fTypeId) {
other.fId = nullptr;
}
MeasureUnit::MeasureUnit(char* idToAdopt)
: fId(idToAdopt), fSubTypeId(-1), fTypeId(-1) {
if (fId == nullptr) {
// Invalid; reset to the base dimensionless unit
setTo(kBaseTypeIdx, kBaseSubTypeIdx);
}
}
MeasureUnit &MeasureUnit::operator=(const MeasureUnit &other) {
if (this == &other) {
return *this;
}
uprv_free(fId);
if (other.fId) {
fId = uprv_strdup(other.fId);
if (!fId) {
// Unrecoverable allocation error; set to the default unit
*this = MeasureUnit();
return *this;
}
} else {
fId = nullptr;
}
fTypeId = other.fTypeId;
fSubTypeId = other.fSubTypeId;
return *this;
}
MeasureUnit &MeasureUnit::operator=(MeasureUnit &&other) noexcept {
if (this == &other) {
return *this;
}
uprv_free(fId);
fId = other.fId;
other.fId = nullptr;
fTypeId = other.fTypeId;
fSubTypeId = other.fSubTypeId;
uprv_strcpy(fCurrency, other.fCurrency);
return *this;
}
@ -2032,14 +2070,28 @@ MeasureUnit *MeasureUnit::clone() const {
}
MeasureUnit::~MeasureUnit() {
uprv_free(fId);
fId = nullptr;
}
const char *MeasureUnit::getType() const {
// We have a type & subtype only if fTypeId is present.
if (fTypeId == -1) {
return "";
}
return gTypes[fTypeId];
}
const char *MeasureUnit::getSubtype() const {
return fCurrency[0] == 0 ? gSubTypes[getOffset()] : fCurrency;
// We have a type & subtype only if fTypeId is present.
if (fTypeId == -1) {
return "";
}
return getIdentifier();
}
const char *MeasureUnit::getIdentifier() const {
return fId ? fId : gSubTypes[getOffset()];
}
UBool MeasureUnit::operator==(const UObject& other) const {
@ -2050,10 +2102,7 @@ UBool MeasureUnit::operator==(const UObject& other) const {
return FALSE;
}
const MeasureUnit &rhs = static_cast<const MeasureUnit&>(other);
return (
fTypeId == rhs.fTypeId
&& fSubTypeId == rhs.fSubTypeId
&& uprv_strcmp(fCurrency, rhs.fCurrency) == 0);
return uprv_strcmp(getIdentifier(), rhs.getIdentifier()) == 0;
}
int32_t MeasureUnit::getIndex() const {
@ -2189,6 +2238,10 @@ MeasureUnit MeasureUnit::resolveUnitPerUnit(
const MeasureUnit &unit, const MeasureUnit &perUnit, bool* isResolved) {
int32_t unitOffset = unit.getOffset();
int32_t perUnitOffset = perUnit.getOffset();
if (unitOffset == -1 || perUnitOffset == -1) {
*isResolved = false;
return MeasureUnit();
}
// binary search for (unitOffset, perUnitOffset)
int32_t start = 0;
@ -2242,12 +2295,18 @@ void MeasureUnit::initCurrency(const char *isoCurrency) {
fTypeId = result;
result = binarySearch(
gSubTypes, gOffsets[fTypeId], gOffsets[fTypeId + 1], isoCurrency);
if (result != -1) {
fSubTypeId = result - gOffsets[fTypeId];
} else {
uprv_strncpy(fCurrency, isoCurrency, UPRV_LENGTHOF(fCurrency));
fCurrency[3] = 0;
if (result == -1) {
fId = uprv_strdup(isoCurrency);
if (fId) {
fSubTypeId = -1;
return;
}
// malloc error: fall back to the undefined currency
result = binarySearch(
gSubTypes, gOffsets[fTypeId], gOffsets[fTypeId + 1], "XXX");
U_ASSERT(result != -1);
}
fSubTypeId = result - gOffsets[fTypeId];
}
void MeasureUnit::initNoUnit(const char *subtype) {
@ -2262,10 +2321,14 @@ void MeasureUnit::initNoUnit(const char *subtype) {
void MeasureUnit::setTo(int32_t typeId, int32_t subTypeId) {
fTypeId = typeId;
fSubTypeId = subTypeId;
fCurrency[0] = 0;
uprv_free(fId);
fId = nullptr;
}
int32_t MeasureUnit::getOffset() const {
if (fTypeId < 0 || fSubTypeId < 0) {
return -1;
}
return gOffsets[fTypeId] + fSubTypeId;
}

View file

@ -0,0 +1,852 @@
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
// Extra functions for MeasureUnit not needed for all clients.
// Separate .o file so that it can be removed for modularity.
#include "unicode/utypes.h"
#if !UCONFIG_NO_FORMATTING
// Allow implicit conversion from char16_t* to UnicodeString for this file:
// Helpful in toString methods and elsewhere.
#define UNISTR_FROM_STRING_EXPLICIT
#include "cstring.h"
#include "uassert.h"
#include "ucln_in.h"
#include "umutex.h"
#include "unicode/errorcode.h"
#include "unicode/localpointer.h"
#include "unicode/measunit.h"
#include "unicode/ucharstrie.h"
#include "unicode/ucharstriebuilder.h"
#include "cstr.h"
U_NAMESPACE_BEGIN
namespace {
// This is to ensure we only insert positive integers into the trie
constexpr int32_t kSIPrefixOffset = 64;
constexpr int32_t kCompoundPartOffset = 128;
enum CompoundPart {
COMPOUND_PART_PER = kCompoundPartOffset,
COMPOUND_PART_TIMES,
COMPOUND_PART_PLUS,
};
constexpr int32_t kPowerPartOffset = 256;
enum PowerPart {
POWER_PART_P2 = kPowerPartOffset + 2,
POWER_PART_P3,
POWER_PART_P4,
POWER_PART_P5,
POWER_PART_P6,
POWER_PART_P7,
POWER_PART_P8,
POWER_PART_P9,
POWER_PART_P10,
POWER_PART_P11,
POWER_PART_P12,
POWER_PART_P13,
POWER_PART_P14,
POWER_PART_P15,
};
constexpr int32_t kSimpleUnitOffset = 512;
const struct SIPrefixStrings {
const char* const string;
UMeasureSIPrefix value;
} gSIPrefixStrings[] = {
{ "yotta", UMEASURE_SI_PREFIX_YOTTA },
{ "zetta", UMEASURE_SI_PREFIX_ZETTA },
{ "exa", UMEASURE_SI_PREFIX_EXA },
{ "peta", UMEASURE_SI_PREFIX_PETA },
{ "tera", UMEASURE_SI_PREFIX_TERA },
{ "giga", UMEASURE_SI_PREFIX_GIGA },
{ "mega", UMEASURE_SI_PREFIX_MEGA },
{ "kilo", UMEASURE_SI_PREFIX_KILO },
{ "hecto", UMEASURE_SI_PREFIX_HECTO },
{ "deka", UMEASURE_SI_PREFIX_DEKA },
{ "deci", UMEASURE_SI_PREFIX_DECI },
{ "centi", UMEASURE_SI_PREFIX_CENTI },
{ "milli", UMEASURE_SI_PREFIX_MILLI },
{ "micro", UMEASURE_SI_PREFIX_MICRO },
{ "nano", UMEASURE_SI_PREFIX_NANO },
{ "pico", UMEASURE_SI_PREFIX_PICO },
{ "femto", UMEASURE_SI_PREFIX_FEMTO },
{ "atto", UMEASURE_SI_PREFIX_ATTO },
{ "zepto", UMEASURE_SI_PREFIX_ZEPTO },
{ "yocto", UMEASURE_SI_PREFIX_YOCTO },
};
// TODO(ICU-20920): Get this list from data
const char16_t* const gSimpleUnits[] = {
u"one", // note: expected to be index 0
u"100kilometer",
u"acre",
u"ampere",
u"arc-minute",
u"arc-second",
u"astronomical-unit",
u"atmosphere",
u"bar",
u"barrel",
u"bit",
u"british-thermal-unit",
u"bushel",
u"byte",
u"calorie",
u"carat",
u"celsius",
u"century",
u"cup",
u"cup-metric",
u"dalton",
u"day",
u"day-person",
u"decade",
u"degree",
u"dot", // (as in "dot-per-inch")
u"dunam",
u"earth-mass",
u"electronvolt",
u"em",
u"fahrenheit",
u"fathom",
u"fluid-ounce",
u"fluid-ounce-imperial",
u"foodcalorie",
u"foot",
u"furlong",
u"g-force",
u"gallon",
u"gallon-imperial",
u"generic", // (i.e., "temperature-generic")
u"gram",
u"hectare", // (note: other "are" derivatives are uncommon)
u"hertz",
u"horsepower",
u"hour",
u"inch",
u"inch-hg",
u"joule",
u"karat",
u"kelvin",
u"knot",
u"light-year",
u"liter",
u"lux",
u"meter",
u"meter-of-mercury", // (not "millimeter-of-mercury")
u"metric-ton",
u"mile",
u"mile-scandinavian",
u"minute",
u"mole",
u"month",
u"month-person",
u"nautical-mile",
u"newton",
u"ohm",
u"ounce",
u"ounce-troy",
u"parsec",
u"pascal",
u"percent",
u"permille",
u"permillion",
u"permyriad",
u"pint",
u"pint-metric",
u"pixel",
u"point",
u"pound",
u"pound-force",
u"quart",
u"radian",
u"revolution",
u"second",
u"solar-luminosity",
u"solar-mass",
u"solar-radius",
u"stone",
u"tablespoon",
u"teaspoon",
u"therm-us",
u"ton",
u"volt",
u"watt",
u"week",
u"week-person",
u"yard",
u"year",
u"year-person",
};
icu::UInitOnce gUnitExtrasInitOnce = U_INITONCE_INITIALIZER;
char16_t* kSerializedUnitExtrasStemTrie = nullptr;
UBool U_CALLCONV cleanupUnitExtras() {
uprv_free(kSerializedUnitExtrasStemTrie);
kSerializedUnitExtrasStemTrie = nullptr;
gUnitExtrasInitOnce.reset();
return TRUE;
}
void U_CALLCONV initUnitExtras(UErrorCode& status) {
ucln_i18n_registerCleanup(UCLN_I18N_UNIT_EXTRAS, cleanupUnitExtras);
UCharsTrieBuilder b(status);
if (U_FAILURE(status)) { return; }
// Add SI prefixes
for (const auto& siPrefixInfo : gSIPrefixStrings) {
UnicodeString uSIPrefix(siPrefixInfo.string, -1, US_INV);
b.add(uSIPrefix, siPrefixInfo.value + kSIPrefixOffset, status);
}
if (U_FAILURE(status)) { return; }
// Add syntax parts (compound, power prefixes)
b.add(u"-per-", COMPOUND_PART_PER, status);
b.add(u"-", COMPOUND_PART_TIMES, status);
b.add(u"+", COMPOUND_PART_PLUS, status);
b.add(u"square-", POWER_PART_P2, status);
b.add(u"cubic-", POWER_PART_P3, status);
b.add(u"p2-", POWER_PART_P2, status);
b.add(u"p3-", POWER_PART_P3, status);
b.add(u"p4-", POWER_PART_P4, status);
b.add(u"p5-", POWER_PART_P5, status);
b.add(u"p6-", POWER_PART_P6, status);
b.add(u"p7-", POWER_PART_P7, status);
b.add(u"p8-", POWER_PART_P8, status);
b.add(u"p9-", POWER_PART_P9, status);
b.add(u"p10-", POWER_PART_P10, status);
b.add(u"p11-", POWER_PART_P11, status);
b.add(u"p12-", POWER_PART_P12, status);
b.add(u"p13-", POWER_PART_P13, status);
b.add(u"p14-", POWER_PART_P14, status);
b.add(u"p15-", POWER_PART_P15, status);
if (U_FAILURE(status)) { return; }
// Add sanctioned simple units by offset
int32_t simpleUnitOffset = kSimpleUnitOffset;
for (auto simpleUnit : gSimpleUnits) {
b.add(simpleUnit, simpleUnitOffset++, status);
}
// Build the CharsTrie
// TODO: Use SLOW or FAST here?
UnicodeString result;
b.buildUnicodeString(USTRINGTRIE_BUILD_FAST, result, status);
if (U_FAILURE(status)) { return; }
// Copy the result into the global constant pointer
size_t numBytes = result.length() * sizeof(char16_t);
kSerializedUnitExtrasStemTrie = static_cast<char16_t*>(uprv_malloc(numBytes));
uprv_memcpy(kSerializedUnitExtrasStemTrie, result.getBuffer(), numBytes);
}
class Token {
public:
Token(int32_t match) : fMatch(match) {}
enum Type {
TYPE_UNDEFINED,
TYPE_SI_PREFIX,
TYPE_COMPOUND_PART,
TYPE_POWER_PART,
TYPE_ONE,
TYPE_SIMPLE_UNIT,
};
Type getType() const {
if (fMatch <= 0) {
UPRV_UNREACHABLE;
} else if (fMatch < kCompoundPartOffset) {
return TYPE_SI_PREFIX;
} else if (fMatch < kPowerPartOffset) {
return TYPE_COMPOUND_PART;
} else if (fMatch < kSimpleUnitOffset) {
return TYPE_POWER_PART;
} else if (fMatch == kSimpleUnitOffset) {
return TYPE_ONE;
} else {
return TYPE_SIMPLE_UNIT;
}
}
UMeasureSIPrefix getSIPrefix() const {
U_ASSERT(getType() == TYPE_SI_PREFIX);
return static_cast<UMeasureSIPrefix>(fMatch - kSIPrefixOffset);
}
int32_t getMatch() const {
U_ASSERT(getType() == TYPE_COMPOUND_PART);
return fMatch;
}
int8_t getPower() const {
U_ASSERT(getType() == TYPE_POWER_PART);
return static_cast<int8_t>(fMatch - kPowerPartOffset);
}
int32_t getSimpleUnitIndex() const {
U_ASSERT(getType() == TYPE_SIMPLE_UNIT);
return fMatch - kSimpleUnitOffset;
}
private:
int32_t fMatch;
};
struct SingleUnit {
int8_t power = 1;
UMeasureSIPrefix siPrefix = UMEASURE_SI_PREFIX_ONE;
int32_t simpleUnitIndex = 0;
StringPiece id = "one";
void appendTo(CharString& builder, UErrorCode& status) const {
if (simpleUnitIndex == 0) {
// Don't propagate SI prefixes and powers on one
builder.append("one", status);
return;
}
int8_t posPower = power < 0 ? -power : power;
if (posPower == 0) {
status = U_ILLEGAL_ARGUMENT_ERROR;
} else if (posPower == 1) {
// no-op
} else if (posPower == 2) {
builder.append("square-", status);
} else if (posPower == 3) {
builder.append("cubic-", status);
} else if (posPower < 10) {
builder.append('p', status);
builder.append(posPower + '0', status);
builder.append('-', status);
} else if (posPower <= 15) {
builder.append("p1", status);
builder.append('0' + (posPower % 10), status);
builder.append('-', status);
} else {
status = U_ILLEGAL_ARGUMENT_ERROR;
}
if (U_FAILURE(status)) {
return;
}
if (siPrefix != UMEASURE_SI_PREFIX_ONE) {
for (const auto& siPrefixInfo : gSIPrefixStrings) {
if (siPrefixInfo.value == siPrefix) {
builder.append(siPrefixInfo.string, status);
break;
}
}
}
if (U_FAILURE(status)) {
return;
}
builder.append(id, status);
}
char* build(UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
CharString builder;
if (power < 0) {
builder.append("one-per-", status);
}
appendTo(builder, status);
return builder.cloneData(status);
}
};
class CompoundUnit {
public:
typedef MaybeStackVector<SingleUnit, 3> SingleUnitList;
void append(SingleUnit&& singleUnit, UErrorCode& status) {
if (singleUnit.power >= 0) {
appendImpl(numerator, std::move(singleUnit), status);
} else {
appendImpl(denominator, std::move(singleUnit), status);
}
}
void takeReciprocal() {
auto temp = std::move(numerator);
numerator = std::move(denominator);
denominator = std::move(temp);
}
void appendTo(CharString& builder, UErrorCode& status) const {
if (numerator.length() == 0) {
builder.append("one", status);
} else {
appendToImpl(numerator, builder, status);
}
if (denominator.length() > 0) {
builder.append("-per-", status);
appendToImpl(denominator, builder, status);
}
}
char* build(UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
CharString builder;
appendTo(builder, status);
return builder.cloneData(status);
}
const SingleUnitList& getNumeratorUnits() const {
return numerator;
}
const SingleUnitList& getDenominatorUnits() const {
return denominator;
}
bool isSingle() const {
return numerator.length() + denominator.length() == 1;
}
bool isEmpty() const {
return numerator.length() + denominator.length() == 0;
}
private:
SingleUnitList numerator;
SingleUnitList denominator;
void appendToImpl(const SingleUnitList& unitList, CharString& builder, UErrorCode& status) const {
bool first = true;
int32_t len = unitList.length();
for (int32_t i = 0; i < len; i++) {
if (first) {
first = false;
} else {
builder.append('-', status);
}
unitList[i]->appendTo(builder, status);
}
}
void appendImpl(SingleUnitList& unitList, SingleUnit&& singleUnit, UErrorCode& status) {
// Check that the same simple unit doesn't already exist
for (int32_t i = 0; i < unitList.length(); i++) {
SingleUnit* candidate = unitList[i];
if (candidate->simpleUnitIndex == singleUnit.simpleUnitIndex
&& candidate->siPrefix == singleUnit.siPrefix) {
candidate->power += singleUnit.power;
return;
}
}
// Add a new unit
SingleUnit* destination = unitList.emplaceBack();
if (!destination) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
*destination = std::move(singleUnit);
}
};
class SequenceUnit {
public:
typedef MaybeStackVector<CompoundUnit, 3> CompoundUnitList;
void append(CompoundUnit&& compoundUnit, UErrorCode& status) {
CompoundUnit* destination = units.emplaceBack();
if (!destination) {
status = U_MEMORY_ALLOCATION_ERROR;
return;
}
*destination = std::move(compoundUnit);
}
void appendTo(CharString& builder, UErrorCode& status) const {
if (units.length() == 0) {
builder.append("one", status);
return;
}
bool isFirst = true;
for (int32_t i = 0; i < units.length(); i++) {
if (isFirst) {
isFirst = false;
} else {
builder.append('+', status);
}
units[i]->appendTo(builder, status);
}
}
char* build(UErrorCode& status) {
if (U_FAILURE(status)) {
return nullptr;
}
CharString builder;
appendTo(builder, status);
return builder.cloneData(status);
}
const CompoundUnitList& getUnits() const {
return units;
}
private:
CompoundUnitList units;
};
class Parser {
public:
static Parser from(StringPiece source, UErrorCode& status) {
if (U_FAILURE(status)) {
return Parser();
}
umtx_initOnce(gUnitExtrasInitOnce, &initUnitExtras, status);
if (U_FAILURE(status)) {
return Parser();
}
return Parser(source);
}
bool hasNext() const {
return fIndex < fSource.length();
}
SingleUnit getOnlySingleUnit(UErrorCode& status) {
bool sawPlus;
SingleUnit retval;
nextSingleUnit(retval, sawPlus, status);
if (U_FAILURE(status)) {
return retval;
}
if (sawPlus || hasNext()) {
// Expected to find only one unit in the string
status = U_ILLEGAL_ARGUMENT_ERROR;
return retval;
}
return retval;
}
void nextCompoundUnit(CompoundUnit& result, UErrorCode& status) {
bool sawPlus;
if (U_FAILURE(status)) {
return;
}
while (hasNext()) {
int32_t previ = fIndex;
SingleUnit singleUnit;
nextSingleUnit(singleUnit, sawPlus, status);
if (U_FAILURE(status)) {
return;
}
if (sawPlus && !result.isEmpty()) {
fIndex = previ;
break;
}
result.append(std::move(singleUnit), status);
}
return;
}
CompoundUnit getOnlyCompoundUnit(UErrorCode& status) {
CompoundUnit retval;
nextCompoundUnit(retval, status);
if (U_FAILURE(status)) {
return retval;
}
if (hasNext()) {
// Expected to find only one unit in the string
status = U_ILLEGAL_ARGUMENT_ERROR;
return retval;
}
return retval;
}
SequenceUnit getOnlySequenceUnit(UErrorCode& status) {
SequenceUnit retval;
while (hasNext()) {
CompoundUnit compoundUnit;
nextCompoundUnit(compoundUnit, status);
if (U_FAILURE(status)) {
return retval;
}
retval.append(std::move(compoundUnit), status);
}
return retval;
}
private:
int32_t fIndex = 0;
StringPiece fSource;
UCharsTrie fTrie;
bool fAfterPer = false;
Parser() : fSource(""), fTrie(u"") {}
Parser(StringPiece source)
: fSource(source), fTrie(kSerializedUnitExtrasStemTrie) {}
Token nextToken(UErrorCode& status) {
fTrie.reset();
int32_t match = -1;
int32_t previ = -1;
do {
fTrie.next(fSource.data()[fIndex++]);
if (fTrie.current() == USTRINGTRIE_NO_MATCH) {
break;
} else if (fTrie.current() == USTRINGTRIE_NO_VALUE) {
continue;
} else if (fTrie.current() == USTRINGTRIE_FINAL_VALUE) {
match = fTrie.getValue();
previ = fIndex;
break;
} else if (fTrie.current() == USTRINGTRIE_INTERMEDIATE_VALUE) {
match = fTrie.getValue();
previ = fIndex;
continue;
} else {
UPRV_UNREACHABLE;
}
} while (fIndex < fSource.length());
if (match < 0) {
// TODO: Make a new status code?
status = U_ILLEGAL_ARGUMENT_ERROR;
} else {
fIndex = previ;
}
return Token(match);
}
void nextSingleUnit(SingleUnit& result, bool& sawPlus, UErrorCode& status) {
sawPlus = false;
if (U_FAILURE(status)) {
return;
}
if (!hasNext()) {
// probably "one"
return;
}
// state:
// 0 = no tokens seen yet (will accept power, SI prefix, or simple unit)
// 1 = power token seen (will not accept another power token)
// 2 = SI prefix token seen (will not accept a power or SI prefix token)
int32_t state = 0;
int32_t previ = fIndex;
// Maybe read a compound part
if (fIndex != 0) {
Token token = nextToken(status);
if (U_FAILURE(status)) {
return;
}
if (token.getType() != Token::TYPE_COMPOUND_PART) {
goto fail;
}
switch (token.getMatch()) {
case COMPOUND_PART_PER:
if (fAfterPer) {
goto fail;
}
fAfterPer = true;
result.power = -1;
break;
case COMPOUND_PART_TIMES:
break;
case COMPOUND_PART_PLUS:
sawPlus = true;
fAfterPer = false;
break;
}
previ = fIndex;
}
// Read a unit
while (hasNext()) {
Token token = nextToken(status);
if (U_FAILURE(status)) {
return;
}
switch (token.getType()) {
case Token::TYPE_POWER_PART:
if (state > 0) {
goto fail;
}
result.power *= token.getPower();
previ = fIndex;
state = 1;
break;
case Token::TYPE_SI_PREFIX:
if (state > 1) {
goto fail;
}
result.siPrefix = token.getSIPrefix();
previ = fIndex;
state = 2;
break;
case Token::TYPE_ONE:
// Skip "one" and go to the next unit
return nextSingleUnit(result, sawPlus, status);
case Token::TYPE_SIMPLE_UNIT:
result.simpleUnitIndex = token.getSimpleUnitIndex();
result.id = fSource.substr(previ, fIndex - previ);
return;
default:
goto fail;
}
}
fail:
// TODO: Make a new status code?
status = U_ILLEGAL_ARGUMENT_ERROR;
return;
}
};
} // namespace
MeasureUnit MeasureUnit::forIdentifier(StringPiece identifier, UErrorCode& status) {
return Parser::from(identifier, status).getOnlySequenceUnit(status).build(status);
}
UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode& status) const {
const char* id = getIdentifier();
Parser parser = Parser::from(id, status);
if (U_FAILURE(status)) {
return UMEASURE_UNIT_SINGLE;
}
CompoundUnit compoundUnit;
parser.nextCompoundUnit(compoundUnit, status);
if (parser.hasNext()) {
return UMEASURE_UNIT_SEQUENCE;
} else if (compoundUnit.isSingle()) {
return UMEASURE_UNIT_SINGLE;
} else {
return UMEASURE_UNIT_COMPOUND;
}
}
UMeasureSIPrefix MeasureUnit::getSIPrefix(UErrorCode& status) const {
const char* id = getIdentifier();
return Parser::from(id, status).getOnlySingleUnit(status).siPrefix;
}
MeasureUnit MeasureUnit::withSIPrefix(UMeasureSIPrefix prefix, UErrorCode& status) const {
const char* id = getIdentifier();
SingleUnit singleUnit = Parser::from(id, status).getOnlySingleUnit(status);
singleUnit.siPrefix = prefix;
return singleUnit.build(status);
}
int8_t MeasureUnit::getPower(UErrorCode& status) const {
const char* id = getIdentifier();
return Parser::from(id, status).getOnlySingleUnit(status).power;
}
MeasureUnit MeasureUnit::withPower(int8_t power, UErrorCode& status) const {
const char* id = getIdentifier();
SingleUnit singleUnit = Parser::from(id, status).getOnlySingleUnit(status);
singleUnit.power = power;
return singleUnit.build(status);
}
MeasureUnit MeasureUnit::reciprocal(UErrorCode& status) const {
const char* id = getIdentifier();
CompoundUnit compoundUnit = Parser::from(id, status).getOnlyCompoundUnit(status);
compoundUnit.takeReciprocal();
return compoundUnit.build(status);
}
MeasureUnit MeasureUnit::product(const MeasureUnit& other, UErrorCode& status) const {
const char* id = getIdentifier();
CompoundUnit compoundUnit = Parser::from(id, status).getOnlyCompoundUnit(status);
if (U_FAILURE(status)) {
return *this;
}
// Append other's first CompoundUnit to compoundUnit, then assert other has only one
Parser otherParser = Parser::from(other.getIdentifier(), status);
otherParser.nextCompoundUnit(compoundUnit, status);
if (U_FAILURE(status)) {
return *this;
}
if (otherParser.hasNext()) {
status = U_ILLEGAL_ARGUMENT_ERROR;
return *this;
}
return compoundUnit.build(status);
}
LocalArray<MeasureUnit> MeasureUnit::getSingleUnits(UErrorCode& status) const {
const char* id = getIdentifier();
CompoundUnit compoundUnit = Parser::from(id, status).getOnlyCompoundUnit(status);
if (U_FAILURE(status)) {
return LocalArray<MeasureUnit>();
}
const CompoundUnit::SingleUnitList& numerator = compoundUnit.getNumeratorUnits();
const CompoundUnit::SingleUnitList& denominator = compoundUnit.getDenominatorUnits();
int32_t count = numerator.length() + denominator.length();
MeasureUnit* arr = new MeasureUnit[count];
int32_t i = 0;
for (int32_t j = 0; j < numerator.length(); j++) {
arr[i++] = numerator[j]->build(status);
}
for (int32_t j = 0; j < denominator.length(); j++) {
arr[i++] = denominator[j]->build(status);
}
return LocalArray<MeasureUnit>::withLength(arr, count);
}
LocalArray<MeasureUnit> MeasureUnit::getCompoundUnits(UErrorCode& status) const {
const char* id = getIdentifier();
SequenceUnit sequenceUnit = Parser::from(id, status).getOnlySequenceUnit(status);
if (U_FAILURE(status)) {
return LocalArray<MeasureUnit>();
}
const SequenceUnit::CompoundUnitList& unitVector = sequenceUnit.getUnits();
int32_t count = unitVector.length();
MeasureUnit* arr = new MeasureUnit[count];
for (int32_t i = 0; i < count; i++) {
arr[i] = unitVector[i]->build(status);
}
return LocalArray<MeasureUnit>::withLength(arr, count);
}
U_NAMESPACE_END
#endif /* !UNCONFIG_NO_FORMATTING */

View file

@ -26,6 +26,7 @@ as the functions are suppose to be called.
It's usually best to have child dependencies called first. */
typedef enum ECleanupI18NType {
UCLN_I18N_START = -1,
UCLN_I18N_UNIT_EXTRAS,
UCLN_I18N_NUMBER_SKELETONS,
UCLN_I18N_CURRENCY_SPACING,
UCLN_I18N_SPOOF,

View file

@ -20,6 +20,7 @@
#if !UCONFIG_NO_FORMATTING
#include "unicode/unistr.h"
#include "unicode/localpointer.h"
/**
* \file
@ -30,6 +31,199 @@ U_NAMESPACE_BEGIN
class StringEnumeration;
/**
* Enumeration for unit complexity. There are three levels:
*
* - SINGLE: A single unit, optionally with a power and/or SI prefix. Examples: hectare,
* square-kilometer, kilojoule, one-per-second.
* - COMPOUND: A unit composed of the product of multiple single units. Examples:
* meter-per-second, kilowatt-hour, kilogram-meter-per-square-second.
* - SEQUENCE: A unit composed of the sum of multiple compound units. Examples: foot+inch,
* hour+minute+second, hectare+square-meter.
*
* The complexity determines which operations are available. For example, you cannot set the power
* or SI prefix of a compound unit.
*
* @draft ICU 67
*/
enum UMeasureUnitComplexity {
/**
* A single unit, like kilojoule.
*
* @draft ICU 67
*/
UMEASURE_UNIT_SINGLE,
/**
* A compound unit, like meter-per-second.
*
* @draft ICU 67
*/
UMEASURE_UNIT_COMPOUND,
/**
* A sequence unit, like hour+minute.
*
* @draft ICU 67
*/
UMEASURE_UNIT_SEQUENCE
};
/**
* Enumeration for SI prefixes, such as "kilo".
*
* @draft ICU 67
*/
typedef enum UMeasureSIPrefix {
/**
* SI prefix: yotta, 10^24.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_YOTTA = 24,
/**
* SI prefix: zetta, 10^21.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_ZETTA = 21,
/**
* SI prefix: exa, 10^18.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_EXA = 18,
/**
* SI prefix: peta, 10^15.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_PETA = 15,
/**
* SI prefix: tera, 10^12.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_TERA = 12,
/**
* SI prefix: giga, 10^9.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_GIGA = 9,
/**
* SI prefix: mega, 10^6.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_MEGA = 6,
/**
* SI prefix: kilo, 10^3.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_KILO = 3,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_HECTO = 2,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_DEKA = 1,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_ONE = 0,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_DECI = -1,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_CENTI = -2,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_MILLI = -3,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_MICRO = -6,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_NANO = -9,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_PICO = -12,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_FEMTO = -15,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_ATTO = -18,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_ZEPTO = -21,
/**
* SI prefix: FIXME, 10^FIXME.
*
* @draft ICU 67
*/
UMEASURE_SI_PREFIX_YOCTO = -24
} UMeasureSIPrefix;
/**
* A unit such as length, mass, volume, currency, etc. A unit is
* coupled with a numeric amount to produce a Measure.
@ -52,13 +246,39 @@ class U_I18N_API MeasureUnit: public UObject {
* @stable ICU 3.0
*/
MeasureUnit(const MeasureUnit &other);
/**
* Assignment operator.
* Move constructor.
* @stable ICU 3.0
*/
MeasureUnit(MeasureUnit &&other) noexcept;
/**
* Construct a MeasureUnit from a CLDR Sequence Unit Identifier, defined in UTS 35.
* Validates and canonicalizes the identifier.
*
* <pre>
* MeasureUnit example = MeasureUnit::forIdentifier("furlong-per-nanosecond")
* </pre>
*
* @param id The CLDR Sequence Unit Identifier
* @param status Set if the identifier is invalid.
* @draft ICU 67
*/
static MeasureUnit forIdentifier(StringPiece identifier, UErrorCode& status);
/**
* Copy assignment operator.
* @stable ICU 3.0
*/
MeasureUnit &operator=(const MeasureUnit &other);
/**
* Move assignment operator.
* @stable ICU 3.0
*/
MeasureUnit &operator=(MeasureUnit &&other) noexcept;
/**
* Returns a polymorphic clone of this object. The result will
* have the same class as returned by getDynamicClassID().
@ -90,16 +310,149 @@ class U_I18N_API MeasureUnit: public UObject {
/**
* Get the type.
*
* If the unit does not have a type, the empty string is returned.
*
* @stable ICU 53
*/
const char *getType() const;
/**
* Get the sub type.
*
* If the unit does not have a subtype, the empty string is returned.
*
* @stable ICU 53
*/
const char *getSubtype() const;
/**
* Get the CLDR Sequence Unit Identifier for this MeasureUnit, as defined in UTS 35.
*
* @return The string form of this unit, owned by this MeasureUnit.
* @draft ICU 67
*/
const char* getIdentifier() const;
/**
* Compute the complexity of the unit. See UMeasureUnitComplexity for more information.
*
* @param status Set if an error occurs.
* @return The unit complexity.
* @draft ICU 67
*/
UMeasureUnitComplexity getComplexity(UErrorCode& status) const;
/**
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified SI prefix.
* For example, UMEASURE_SI_PREFIX_KILO for "kilo".
*
* There is sufficient locale data to format all standard SI prefixes.
*
* NOTE: Only works on SINGLE units. If this is a COMPOUND or SEQUENCE unit, an error will
* occur. For more information, see UMeasureUnitComplexity.
*
* @param prefix The SI prefix, from UMeasureSIPrefix.
* @param status Set if this is not a SINGLE unit or if another error occurs.
* @return A new SINGLE unit.
*/
MeasureUnit withSIPrefix(UMeasureSIPrefix prefix, UErrorCode& status) const;
/**
* Gets the current SI prefix of this SINGLE unit. For example, if the unit has the SI prefix
* "kilo", then UMEASURE_SI_PREFIX_KILO is returned.
*
* NOTE: Only works on SINGLE units. If this is a COMPOUND or SEQUENCE unit, an error will
* occur. For more information, see UMeasureUnitComplexity.
*
* @param status Set if this is not a SINGLE unit or if another error occurs.
* @return The SI prefix of this SINGLE unit, from UMeasureSIPrefix.
*/
UMeasureSIPrefix getSIPrefix(UErrorCode& status) const;
/**
* Creates a MeasureUnit which is this SINGLE unit augmented with the specified power. For
* example, if power is 2, the unit will be squared.
*
* NOTE: Only works on SINGLE units. If this is a COMPOUND or SEQUENCE unit, an error will
* occur. For more information, see UMeasureUnitComplexity.
*
* @param power The power.
* @param status Set if this is not a SINGLE unit or if another error occurs.
* @return A new SINGLE unit.
*/
MeasureUnit withPower(int8_t power, UErrorCode& status) const;
/**
* Gets the power of this MeasureUnit. For example, if the unit is square, then 2 is returned.
*
* NOTE: Only works on SINGLE units. If this is a COMPOUND or SEQUENCE unit, an error will
* occur. For more information, see UMeasureUnitComplexity.
*
* @param status Set if this is not a SINGLE unit or if another error occurs.
* @return The power of this simple unit.
*/
int8_t getPower(UErrorCode& status) const;
/**
* Gets the reciprocal of this MeasureUnit, with the numerator and denominator flipped.
*
* For example, if the receiver is "meter-per-second", the unit "second-per-meter" is returned.
*
* NOTE: Only works on SINGLE and COMPOUND units. If this is a SEQUENCE unit, an error will
* occur. For more information, see UMeasureUnitComplexity.
*
* @param status Set if this is a SEQUENCE unit or if another error occurs.
* @return The reciprocal of the target unit.
*/
MeasureUnit reciprocal(UErrorCode& status) const;
/**
* Gets the product of this unit with another unit. This is a way to build units from
* constituent parts.
*
* The numerator and denominator are preserved through this operation.
*
* For example, if the receiver is "kilowatt" and the argument is "hour-per-day", then the
* unit "kilowatt-hour-per-day" is returned.
*
* NOTE: Only works on SINGLE and COMPOUND units. If either unit (receivee and argument) is a
* SEQUENCE unit, an error will occur. For more information, see UMeasureUnitComplexity.
*
* @param status Set if this or other is a SEQUENCE unit or if another error occurs.
* @return The product of the target unit with the provided unit.
*/
MeasureUnit product(const MeasureUnit& other, UErrorCode& status) const;
/**
* Gets the list of single units contained within a compound unit.
*
* For example, given "meter-kilogram-per-second", three units will be returned: "meter",
* "kilogram", and "one-per-second".
*
* If this is a SINGLE unit, an array of length 1 will be returned.
*
* NOTE: Only works on SINGLE and COMPOUND units. If this is a SEQUENCE unit, an error will
* occur. For more information, see UMeasureUnitComplexity.
*
* @param status Set if this is a SEQUENCE unit or if another error occurs.
* @return An array of single units, owned by the caller.
*/
LocalArray<MeasureUnit> getSingleUnits(UErrorCode& status) const;
/**
* Gets the list of compound units contained within a sequence unit.
*
* For example, given "hour+minute+second", three units will be returned: "hour", "minute",
* and "second".
*
* If this is a SINGLE or COMPOUND unit, an array of length 1 will be returned.
*
* @param status Set of an error occurs.
* @return An array of compound units, owned by the caller.
*/
LocalArray<MeasureUnit> getCompoundUnits(UErrorCode& status) const;
/**
* getAvailable gets all of the available units.
* If there are too many units to fit into destCapacity then the
@ -3353,13 +3706,16 @@ class U_I18N_API MeasureUnit: public UObject {
#endif /* U_HIDE_INTERNAL_API */
private:
int32_t fTypeId;
int32_t fSubTypeId;
char fCurrency[4];
MeasureUnit(int32_t typeId, int32_t subTypeId) : fTypeId(typeId), fSubTypeId(subTypeId) {
fCurrency[0] = 0;
}
// If non-null, fId is owned by the MeasureUnit.
char* fId;
// These two ints are indices into static string lists in measunit.cpp
int16_t fSubTypeId;
int8_t fTypeId;
MeasureUnit(int32_t typeId, int32_t subTypeId);
MeasureUnit(char* idToAdopt);
void setTo(int32_t typeId, int32_t subTypeId);
int32_t getOffset() const;
static MeasureUnit *create(int typeId, int subTypeId, UErrorCode &status);

View file

@ -870,6 +870,7 @@ library: i18n
listformatter
formatting formattable_cnv regex regex_cnv translit
double_conversion number_representation number_output numberformatter numberparser
units_extra
universal_time_scale
uclean_i18n
@ -1053,6 +1054,11 @@ group: sharedbreakiterator
deps
breakiterator
group: units_extra
measunit_extra.o
deps
units
group: units
measunit.o currunit.o nounit.o
deps

View file

@ -79,6 +79,8 @@ private:
void Test20332_PersonUnits();
void TestNumericTime();
void TestNumericTimeSomeSpecialFormats();
void TestInvalidIdentifiers();
void TestCompoundUnitOperations();
void verifyFormat(
const char *description,
const MeasureFormat &fmt,
@ -138,6 +140,21 @@ private:
NumberFormat::EAlignmentFields field,
int32_t start,
int32_t end);
void verifySingleUnit(
const MeasureUnit& unit,
UMeasureSIPrefix siPrefix,
int8_t power,
const char* identifier);
void verifyCompoundUnit(
const MeasureUnit& unit,
const char* identifier,
const char** subIdentifiers,
int32_t subIdentifierCount);
void verifySequenceUnit(
const MeasureUnit& unit,
const char* identifier,
const char** subIdentifiers,
int32_t subIdentifierCount);
};
void MeasureFormatTest::runIndexedTest(
@ -182,6 +199,8 @@ void MeasureFormatTest::runIndexedTest(
TESTCASE_AUTO(Test20332_PersonUnits);
TESTCASE_AUTO(TestNumericTime);
TESTCASE_AUTO(TestNumericTimeSomeSpecialFormats);
TESTCASE_AUTO(TestInvalidIdentifiers);
TESTCASE_AUTO(TestCompoundUnitOperations);
TESTCASE_AUTO_END;
}
@ -3215,6 +3234,170 @@ void MeasureFormatTest::TestNumericTimeSomeSpecialFormats() {
verifyFormat("Danish fhoursFminutes", fmtDa, fhoursFminutes, 2, "2.03,877");
}
void MeasureFormatTest::TestInvalidIdentifiers() {
IcuTestErrorCode status(*this, "TestInvalidIdentifiers");
const char* const inputs[] = {
"kilo",
"kilokilo",
"onekilo",
"meterkilo",
"meter-kilo",
"k",
"meter-",
"meter+",
"-meter",
"+meter",
"-kilometer",
"+kilometer",
"-p2-meter",
"+p2-meter",
"+",
"-"
};
for (const auto& input : inputs) {
MeasureUnit::forIdentifier(input, status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
}
}
void MeasureFormatTest::TestCompoundUnitOperations() {
IcuTestErrorCode status(*this, "TestCompoundUnitOperations");
MeasureUnit::forIdentifier("kilometer-per-second-joule", status);
MeasureUnit kilometer = MeasureUnit::getKilometer();
MeasureUnit cubicMeter = MeasureUnit::getCubicMeter();
MeasureUnit meter = kilometer.withSIPrefix(UMEASURE_SI_PREFIX_ONE, status);
MeasureUnit centimeter1 = kilometer.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status);
MeasureUnit centimeter2 = meter.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status);
MeasureUnit cubicDecimeter = cubicMeter.withSIPrefix(UMEASURE_SI_PREFIX_DECI, status);
verifySingleUnit(kilometer, UMEASURE_SI_PREFIX_KILO, 1, "kilometer");
verifySingleUnit(meter, UMEASURE_SI_PREFIX_ONE, 1, "meter");
verifySingleUnit(centimeter1, UMEASURE_SI_PREFIX_CENTI, 1, "centimeter");
verifySingleUnit(centimeter2, UMEASURE_SI_PREFIX_CENTI, 1, "centimeter");
verifySingleUnit(cubicDecimeter, UMEASURE_SI_PREFIX_DECI, 3, "cubic-decimeter");
assertTrue("centimeter equality", centimeter1 == centimeter2);
assertTrue("kilometer inequality", centimeter1 != kilometer);
MeasureUnit squareMeter = meter.withPower(2, status);
MeasureUnit overCubicCentimeter = centimeter1.withPower(-3, status);
MeasureUnit quarticKilometer = kilometer.withPower(4, status);
MeasureUnit overQuarticKilometer1 = kilometer.withPower(-4, status);
verifySingleUnit(squareMeter, UMEASURE_SI_PREFIX_ONE, 2, "square-meter");
verifySingleUnit(overCubicCentimeter, UMEASURE_SI_PREFIX_CENTI, -3, "one-per-cubic-centimeter");
verifySingleUnit(quarticKilometer, UMEASURE_SI_PREFIX_KILO, 4, "p4-kilometer");
verifySingleUnit(overQuarticKilometer1, UMEASURE_SI_PREFIX_KILO, -4, "one-per-p4-kilometer");
assertTrue("power inequality", quarticKilometer != overQuarticKilometer1);
MeasureUnit overQuarticKilometer2 = quarticKilometer.reciprocal(status);
MeasureUnit overQuarticKilometer3 = kilometer.product(kilometer, status)
.product(kilometer, status)
.product(kilometer, status)
.reciprocal(status);
MeasureUnit overQuarticKilometer4 = meter.withPower(4, status)
.reciprocal(status)
.withSIPrefix(UMEASURE_SI_PREFIX_KILO, status);
verifySingleUnit(overQuarticKilometer2, UMEASURE_SI_PREFIX_KILO, -4, "one-per-p4-kilometer");
verifySingleUnit(overQuarticKilometer3, UMEASURE_SI_PREFIX_KILO, -4, "one-per-p4-kilometer");
verifySingleUnit(overQuarticKilometer4, UMEASURE_SI_PREFIX_KILO, -4, "one-per-p4-kilometer");
assertTrue("reciprocal equality", overQuarticKilometer1 == overQuarticKilometer2);
assertTrue("reciprocal equality", overQuarticKilometer1 == overQuarticKilometer3);
assertTrue("reciprocal equality", overQuarticKilometer1 == overQuarticKilometer4);
MeasureUnit kiloSquareSecond = MeasureUnit::getSecond()
.withPower(2, status).withSIPrefix(UMEASURE_SI_PREFIX_KILO, status);
MeasureUnit meterSecond = meter.product(kiloSquareSecond, status);
MeasureUnit cubicMeterSecond1 = meter.withPower(3, status).product(kiloSquareSecond, status);
MeasureUnit centimeterSecond1 = meter.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status).product(kiloSquareSecond, status);
MeasureUnit secondCubicMeter = kiloSquareSecond.product(meter.withPower(3, status), status);
MeasureUnit secondCentimeter = kiloSquareSecond.product(meter.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status), status);
MeasureUnit secondCentimeterPerKilometer = secondCentimeter.product(kilometer.reciprocal(status), status);
verifySingleUnit(kiloSquareSecond, UMEASURE_SI_PREFIX_KILO, 2, "square-kilosecond");
const char* meterSecondSub[] = {"meter", "square-kilosecond"};
verifyCompoundUnit(meterSecond, "meter-square-kilosecond",
meterSecondSub, UPRV_LENGTHOF(meterSecondSub));
const char* cubicMeterSecond1Sub[] = {"cubic-meter", "square-kilosecond"};
verifyCompoundUnit(cubicMeterSecond1, "cubic-meter-square-kilosecond",
cubicMeterSecond1Sub, UPRV_LENGTHOF(cubicMeterSecond1Sub));
const char* centimeterSecond1Sub[] = {"centimeter", "square-kilosecond"};
verifyCompoundUnit(centimeterSecond1, "centimeter-square-kilosecond",
centimeterSecond1Sub, UPRV_LENGTHOF(centimeterSecond1Sub));
const char* secondCubicMeterSub[] = {"square-kilosecond", "cubic-meter"};
verifyCompoundUnit(secondCubicMeter, "square-kilosecond-cubic-meter",
secondCubicMeterSub, UPRV_LENGTHOF(secondCubicMeterSub));
const char* secondCentimeterSub[] = {"square-kilosecond", "centimeter"};
verifyCompoundUnit(secondCentimeter, "square-kilosecond-centimeter",
secondCentimeterSub, UPRV_LENGTHOF(secondCentimeterSub));
const char* secondCentimeterPerKilometerSub[] = {"square-kilosecond", "centimeter", "one-per-kilometer"};
verifyCompoundUnit(secondCentimeterPerKilometer, "square-kilosecond-centimeter-per-kilometer",
secondCentimeterPerKilometerSub, UPRV_LENGTHOF(secondCentimeterPerKilometerSub));
assertTrue("order matters inequality", cubicMeterSecond1 != secondCubicMeter);
assertTrue("additional simple units inequality", secondCubicMeter != secondCentimeter);
// Don't allow get/set power or SI prefix on compound units
status.errIfFailureAndReset();
meterSecond.getPower(status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
meterSecond.withPower(3, status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
meterSecond.getSIPrefix(status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
meterSecond.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
// Test that StringPiece does not overflow
MeasureUnit kiloSquareSecond2 = MeasureUnit::forIdentifier({secondCentimeter.getIdentifier(), 17}, status);
verifySingleUnit(kiloSquareSecond2, UMEASURE_SI_PREFIX_KILO, 2, "square-kilosecond");
assertTrue("string piece equality", kiloSquareSecond == kiloSquareSecond2);
MeasureUnit footInch = MeasureUnit::forIdentifier("foot+inch", status);
MeasureUnit inchFoot = MeasureUnit::forIdentifier("inch+foot", status);
const char* footInchSub[] = {"foot", "inch"};
verifySequenceUnit(footInch, "foot+inch",
footInchSub, UPRV_LENGTHOF(footInchSub));
const char* inchFootSub[] = {"inch", "foot"};
verifySequenceUnit(inchFoot, "inch+foot",
inchFootSub, UPRV_LENGTHOF(inchFootSub));
assertTrue("order matters inequality", footInch != inchFoot);
// TODO(ICU-20920): Enable the one1 tests when the dimensionless base unit ID is updated
// MeasureUnit one1;
MeasureUnit one2 = MeasureUnit::forIdentifier("one", status);
MeasureUnit one3 = MeasureUnit::forIdentifier("", status);
MeasureUnit squareOne = one2.withPower(2, status);
MeasureUnit onePerOne = one2.reciprocal(status);
MeasureUnit squareKiloOne = squareOne.withSIPrefix(UMEASURE_SI_PREFIX_KILO, status);
MeasureUnit onePerSquareKiloOne = squareKiloOne.reciprocal(status);
MeasureUnit oneOne = MeasureUnit::forIdentifier("one-one", status);
MeasureUnit onePlusOne = MeasureUnit::forIdentifier("one+one", status);
// verifySingleUnit(one1, UMEASURE_SI_PREFIX_ONE, 1, "one");
verifySingleUnit(one2, UMEASURE_SI_PREFIX_ONE, 1, "one");
verifySingleUnit(one3, UMEASURE_SI_PREFIX_ONE, 1, "one");
verifySingleUnit(squareOne, UMEASURE_SI_PREFIX_ONE, 1, "one");
verifySingleUnit(onePerOne, UMEASURE_SI_PREFIX_ONE, -1, "one-per-one");
verifySingleUnit(squareKiloOne, UMEASURE_SI_PREFIX_ONE, 1, "one");
verifySingleUnit(onePerSquareKiloOne, UMEASURE_SI_PREFIX_ONE, -1, "one-per-one");
verifySingleUnit(oneOne, UMEASURE_SI_PREFIX_ONE, 1, "one");
verifySingleUnit(onePlusOne, UMEASURE_SI_PREFIX_ONE, 1, "one");
// assertTrue("one equality", one1 == one2);
assertTrue("one equality", one2 == one3);
assertTrue("one-per-one equality", onePerOne == onePerSquareKiloOne);
}
void MeasureFormatTest::verifyFieldPosition(
const char *description,
@ -3286,6 +3469,95 @@ void MeasureFormatTest::verifyFormat(
}
}
void MeasureFormatTest::verifySingleUnit(
const MeasureUnit& unit,
UMeasureSIPrefix siPrefix,
int8_t power,
const char* identifier) {
IcuTestErrorCode status(*this, "verifySingleUnit");
UnicodeString uid(identifier, -1, US_INV);
assertEquals(uid + ": SI prefix",
siPrefix,
unit.getSIPrefix(status));
status.errIfFailureAndReset("%s: SI prefix", identifier);
assertEquals(uid + ": Power",
static_cast<int32_t>(power),
static_cast<int32_t>(unit.getPower(status)));
status.errIfFailureAndReset("%s: Power", identifier);
assertEquals(uid + ": Identifier",
identifier,
unit.getIdentifier());
status.errIfFailureAndReset("%s: Identifier", identifier);
assertTrue(uid + ": Constructor",
unit == MeasureUnit::forIdentifier(identifier, status));
status.errIfFailureAndReset("%s: Constructor", identifier);
assertEquals(uid + ": Complexity",
UMEASURE_UNIT_SINGLE,
unit.getComplexity(status));
status.errIfFailureAndReset("%s: Complexity", identifier);
}
void MeasureFormatTest::verifyCompoundUnit(
const MeasureUnit& unit,
const char* identifier,
const char** subIdentifiers,
int32_t subIdentifierCount) {
IcuTestErrorCode status(*this, "verifyCompoundUnit");
UnicodeString uid(identifier, -1, US_INV);
assertEquals(uid + ": Identifier",
identifier,
unit.getIdentifier());
status.errIfFailureAndReset("%s: Identifier", identifier);
assertTrue(uid + ": Constructor",
unit == MeasureUnit::forIdentifier(identifier, status));
status.errIfFailureAndReset("%s: Constructor", identifier);
assertEquals(uid + ": Complexity",
UMEASURE_UNIT_COMPOUND,
unit.getComplexity(status));
status.errIfFailureAndReset("%s: Complexity", identifier);
LocalArray<MeasureUnit> subUnits = unit.getSingleUnits(status);
assertEquals(uid + ": Length", subIdentifierCount, subUnits.length());
for (int32_t i = 0;; i++) {
if (i >= subIdentifierCount || i >= subUnits.length()) break;
assertEquals(uid + ": Sub-unit #" + Int64ToUnicodeString(i),
subIdentifiers[i],
subUnits[i].getIdentifier());
assertEquals(uid + ": Sub-unit Complexity",
UMEASURE_UNIT_SINGLE,
subUnits[i].getComplexity(status));
}
}
void MeasureFormatTest::verifySequenceUnit(
const MeasureUnit& unit,
const char* identifier,
const char** subIdentifiers,
int32_t subIdentifierCount) {
IcuTestErrorCode status(*this, "verifySequenceUnit");
UnicodeString uid(identifier, -1, US_INV);
assertEquals(uid + ": Identifier",
identifier,
unit.getIdentifier());
status.errIfFailureAndReset("%s: Identifier", identifier);
assertTrue(uid + ": Constructor",
unit == MeasureUnit::forIdentifier(identifier, status));
status.errIfFailureAndReset("%s: Constructor", identifier);
assertEquals(uid + ": Complexity",
UMEASURE_UNIT_SEQUENCE,
unit.getComplexity(status));
status.errIfFailureAndReset("%s: Complexity", identifier);
LocalArray<MeasureUnit> subUnits = unit.getCompoundUnits(status);
assertEquals(uid + ": Length", subIdentifierCount, subUnits.length());
for (int32_t i = 0;; i++) {
if (i >= subIdentifierCount || i >= subUnits.length()) break;
assertEquals(uid + ": Sub-unit #" + Int64ToUnicodeString(i),
subIdentifiers[i],
subUnits[i].getIdentifier());
}
}
extern IntlTest *createMeasureFormatTest() {
return new MeasureFormatTest();
}

View file

@ -2164,6 +2164,8 @@ void NumberFormatTest::TestCurrencyUnit(void){
static const UChar BAD2[] = u"??A";
static const UChar XXX[] = u"XXX";
static const char XXX8[] = "XXX";
static const UChar XYZ[] = u"XYZ";
static const char XYZ8[] = "XYZ";
static const UChar INV[] = u"{$%";
static const char INV8[] = "{$%";
static const UChar ZZZ[] = u"zz";
@ -2180,10 +2182,16 @@ void NumberFormatTest::TestCurrencyUnit(void){
CurrencyUnit cu(USD, ec);
assertSuccess("CurrencyUnit", ec);
assertEquals("getISOCurrency()", USD, cu.getISOCurrency());
assertEquals("getSubtype()", USD8, cu.getSubtype());
// Test XYZ, a valid but non-standard currency.
// Note: Country code XY is private-use, so XYZ should remain unallocated.
CurrencyUnit extended(XYZ, ec);
assertSuccess("non-standard", ec);
assertEquals("non-standard", XYZ, extended.getISOCurrency());
assertEquals("non-standard", XYZ8, extended.getSubtype());
CurrencyUnit inv(INV, ec);
assertEquals("non-invariant", U_INVARIANT_CONVERSION_ERROR, ec);
assertEquals("non-invariant", XXX, inv.getISOCurrency());
@ -2257,15 +2265,20 @@ void NumberFormatTest::TestCurrencyUnit(void){
// Test slicing
MeasureUnit sliced1 = cu;
MeasureUnit sliced2 = cu;
MeasureUnit sliced3 = extended;
assertEquals("Subtype after slicing 1", USD8, sliced1.getSubtype());
assertEquals("Subtype after slicing 2", USD8, sliced2.getSubtype());
assertEquals("Subtype after slicing 3", XYZ8, sliced3.getSubtype());
CurrencyUnit restored1(sliced1, ec);
CurrencyUnit restored2(sliced2, ec);
CurrencyUnit restored3(sliced3, ec);
assertSuccess("Restoring from MeasureUnit", ec);
assertEquals("Subtype after restoring 1", USD8, restored1.getSubtype());
assertEquals("Subtype after restoring 2", USD8, restored2.getSubtype());
assertEquals("Subtype after restoring 3", XYZ8, restored3.getSubtype());
assertEquals("ISO Code after restoring 1", USD, restored1.getISOCurrency());
assertEquals("ISO Code after restoring 2", USD, restored2.getISOCurrency());
assertEquals("ISO Code after restoring 3", XYZ, restored3.getISOCurrency());
// Test copy constructor failure
LocalPointer<MeasureUnit> meter(MeasureUnit::createMeter(ec));