Implementing sequence units and fixing bugs

This commit is contained in:
Shane F. Carr 2020-01-17 00:24:56 +01:00
parent 05ec2aea59
commit 6369aba35a
3 changed files with 272 additions and 92 deletions

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,8 @@ 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) {
@ -422,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 */
/**
@ -442,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;
}
@ -457,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 */
@ -470,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.
@ -489,6 +494,7 @@ public:
void adoptInstead(T *p) {
delete[] LocalPointerBase<T>::ptr;
LocalPointerBase<T>::ptr=p;
fLength=-1;
}
/**
* Deletes the array it owns,
@ -515,6 +521,7 @@ public:
} else {
delete[] p;
}
fLength=-1;
}
/**
* Array item access (writable).

View file

@ -312,9 +312,14 @@ struct SingleUnit {
int8_t power = 1;
UMeasureSIPrefix siPrefix = UMEASURE_SI_PREFIX_ONE;
int32_t simpleUnitIndex = 0;
StringPiece id;
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;
@ -353,6 +358,18 @@ struct SingleUnit {
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 {
@ -367,7 +384,7 @@ public:
}
}
void reciprocal() {
void takeReciprocal() {
auto temp = std::move(numerator);
numerator = std::move(denominator);
denominator = std::move(temp);
@ -377,14 +394,23 @@ public:
if (numerator.length() == 0) {
builder.append("one", status);
} else {
appendToImpl(numerator, numerator.length(), builder, status);
appendToImpl(numerator, builder, status);
}
if (denominator.length() > 0) {
builder.append("-per-", status);
appendToImpl(denominator, denominator.length(), builder, 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;
}
@ -397,12 +423,17 @@ public:
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, int32_t len, CharString& builder, UErrorCode& status) const {
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;
@ -433,17 +464,63 @@ private:
}
};
class UnitIdentifierParser {
class SequenceUnit {
public:
static UnitIdentifierParser from(StringPiece source, UErrorCode& status) {
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 UnitIdentifierParser();
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 UnitIdentifierParser();
return Parser();
}
return UnitIdentifierParser(source);
return Parser(source);
}
bool hasNext() const {
@ -474,7 +551,10 @@ public:
int32_t previ = fIndex;
SingleUnit singleUnit;
nextSingleUnit(singleUnit, sawPlus, status);
if (sawPlus) {
if (U_FAILURE(status)) {
return;
}
if (sawPlus && !result.isEmpty()) {
fIndex = previ;
break;
}
@ -497,6 +577,19 @@ public:
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;
@ -504,9 +597,9 @@ private:
bool fAfterPer = false;
UnitIdentifierParser() : fSource(""), fTrie(u"") {}
Parser() : fSource(""), fTrie(u"") {}
UnitIdentifierParser(StringPiece source)
Parser(StringPiece source)
: fSource(source), fTrie(kSerializedUnitExtrasStemTrie) {}
Token nextToken(UErrorCode& status) {
@ -547,6 +640,11 @@ private:
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)
@ -634,44 +732,22 @@ private:
MeasureUnit MeasureUnit::forIdentifier(const char* identifier, UErrorCode& status) {
UnitIdentifierParser parser = UnitIdentifierParser::from(identifier, status);
if (U_FAILURE(status)) {
// Unrecoverable error
return MeasureUnit();
}
CharString builder;
while (parser.hasNext()) {
CompoundUnit compoundUnit;
parser.nextCompoundUnit(compoundUnit, status);
if (U_FAILURE(status)) {
// Invalid syntax
return MeasureUnit();
}
if (builder.length() > 0) {
builder.append('+', status);
}
compoundUnit.appendTo(builder, status);
}
// Success
return MeasureUnit(builder.cloneData(status));
return Parser::from(identifier, status).getOnlySequenceUnit(status).build(status);
}
UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode& status) const {
const char* id = getIdentifier();
UnitIdentifierParser parser = UnitIdentifierParser::from(id, status);
Parser parser = Parser::from(id, status);
if (U_FAILURE(status)) {
// Unrecoverable error
return UMEASURE_UNIT_SINGLE;
}
CompoundUnit compoundUnit;
parser.nextCompoundUnit(compoundUnit, status);
if (compoundUnit.isSingle()) {
return UMEASURE_UNIT_SINGLE;
} else if (parser.hasNext()) {
if (parser.hasNext()) {
return UMEASURE_UNIT_SEQUENCE;
} else if (compoundUnit.isSingle()) {
return UMEASURE_UNIT_SINGLE;
} else {
return UMEASURE_UNIT_COMPOUND;
}
@ -679,65 +755,44 @@ UMeasureUnitComplexity MeasureUnit::getComplexity(UErrorCode& status) const {
UMeasureSIPrefix MeasureUnit::getSIPrefix(UErrorCode& status) const {
const char* id = getIdentifier();
return UnitIdentifierParser::from(id, status).getOnlySingleUnit(status).siPrefix;
return Parser::from(id, status).getOnlySingleUnit(status).siPrefix;
}
MeasureUnit MeasureUnit::withSIPrefix(UMeasureSIPrefix prefix, UErrorCode& status) const {
const char* id = getIdentifier();
SingleUnit singleUnit = UnitIdentifierParser::from(id, status).getOnlySingleUnit(status);
if (U_FAILURE(status)) {
return *this;
}
SingleUnit singleUnit = Parser::from(id, status).getOnlySingleUnit(status);
singleUnit.siPrefix = prefix;
CharString builder;
singleUnit.appendTo(builder, status);
return MeasureUnit(builder.cloneData(status));
return singleUnit.build(status);
}
int8_t MeasureUnit::getPower(UErrorCode& status) const {
const char* id = getIdentifier();
return UnitIdentifierParser::from(id, status).getOnlySingleUnit(status).power;
return Parser::from(id, status).getOnlySingleUnit(status).power;
}
MeasureUnit MeasureUnit::withPower(int8_t power, UErrorCode& status) const {
const char* id = getIdentifier();
SingleUnit singleUnit = UnitIdentifierParser::from(id, status).getOnlySingleUnit(status);
if (U_FAILURE(status)) {
return *this;
}
CharString builder;
SingleUnit singleUnit = Parser::from(id, status).getOnlySingleUnit(status);
singleUnit.power = power;
if (power < 0) {
builder.append("one-per-", status);
}
singleUnit.appendTo(builder, status);
return MeasureUnit(builder.cloneData(status));
return singleUnit.build(status);
}
MeasureUnit MeasureUnit::reciprocal(UErrorCode& status) const {
const char* id = getIdentifier();
CompoundUnit compoundUnit = UnitIdentifierParser::from(id, status).getOnlyCompoundUnit(status);
if (U_FAILURE(status)) {
return *this;
}
compoundUnit.reciprocal();
CharString builder;
compoundUnit.appendTo(builder, status);
return MeasureUnit(builder.cloneData(status));
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 = UnitIdentifierParser::from(id, status).getOnlyCompoundUnit(status);
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
UnitIdentifierParser otherParser = UnitIdentifierParser::from(other.getIdentifier(), status);
Parser otherParser = Parser::from(other.getIdentifier(), status);
otherParser.nextCompoundUnit(compoundUnit, status);
if (U_FAILURE(status)) {
return *this;
@ -747,16 +802,14 @@ MeasureUnit MeasureUnit::product(const MeasureUnit& other, UErrorCode& status) c
return *this;
}
CharString builder;
compoundUnit.appendTo(builder, status);
return MeasureUnit(builder.cloneData(status));
return compoundUnit.build(status);
}
LocalArray<MeasureUnit> MeasureUnit::getSingleUnits(UErrorCode& status) const {
const char* id = getIdentifier();
CompoundUnit compoundUnit = UnitIdentifierParser::from(id, status).getOnlyCompoundUnit(status);
CompoundUnit compoundUnit = Parser::from(id, status).getOnlyCompoundUnit(status);
if (U_FAILURE(status)) {
return LocalArray<MeasureUnit>::withLength(nullptr, 0);
return LocalArray<MeasureUnit>();
}
const CompoundUnit::SingleUnitList& numerator = compoundUnit.getNumeratorUnits();
@ -764,16 +817,30 @@ LocalArray<MeasureUnit> MeasureUnit::getSingleUnits(UErrorCode& status) const {
int32_t count = numerator.length() + denominator.length();
MeasureUnit* arr = new MeasureUnit[count];
CharString builder;
int32_t i = 0;
for (int32_t j = 0; j < numerator.length(); j++) {
numerator[j]->appendTo(builder.clear(), status);
arr[i++] = MeasureUnit(builder.cloneData(status));
arr[i++] = numerator[j]->build(status);
}
for (int32_t j = 0; j < denominator.length(); j++) {
builder.clear().append("one-per-", status);
denominator[j]->appendTo(builder, status);
arr[i++] = MeasureUnit(builder.cloneData(status));
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);

View file

@ -79,6 +79,7 @@ private:
void Test20332_PersonUnits();
void TestNumericTime();
void TestNumericTimeSomeSpecialFormats();
void TestInvalidIdentifiers();
void TestCompoundUnitOperations();
void verifyFormat(
const char *description,
@ -149,6 +150,11 @@ private:
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(
@ -193,6 +199,7 @@ void MeasureFormatTest::runIndexedTest(
TESTCASE_AUTO(Test20332_PersonUnits);
TESTCASE_AUTO(TestNumericTime);
TESTCASE_AUTO(TestNumericTimeSomeSpecialFormats);
TESTCASE_AUTO(TestInvalidIdentifiers);
TESTCASE_AUTO(TestCompoundUnitOperations);
TESTCASE_AUTO_END;
}
@ -3227,6 +3234,34 @@ 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");
@ -3265,12 +3300,17 @@ void MeasureFormatTest::TestCompoundUnitOperations() {
.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);
@ -3314,6 +3354,43 @@ void MeasureFormatTest::TestCompoundUnitOperations() {
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
meterSecond.withSIPrefix(UMEASURE_SI_PREFIX_CENTI, status);
status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
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);
}
@ -3447,6 +3524,35 @@ void MeasureFormatTest::verifyCompoundUnit(
}
}
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();
}