tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

messageformat2_formattable.cpp (11917B)


      1 // © 2024 and later: Unicode, Inc. and others.
      2 // License & terms of use: http://www.unicode.org/copyright.html
      3 
      4 #include "unicode/utypes.h"
      5 
      6 #if !UCONFIG_NO_NORMALIZATION
      7 
      8 #if !UCONFIG_NO_FORMATTING
      9 
     10 #if !UCONFIG_NO_MF2
     11 
     12 #include "unicode/messageformat2_formattable.h"
     13 #include "unicode/smpdtfmt.h"
     14 #include "messageformat2_allocation.h"
     15 #include "messageformat2_function_registry_internal.h"
     16 #include "messageformat2_macros.h"
     17 
     18 #include "limits.h"
     19 
     20 U_NAMESPACE_BEGIN
     21 
     22 namespace message2 {
     23 
     24    // Fallback values are enclosed in curly braces;
     25    // see https://github.com/unicode-org/message-format-wg/blob/main/spec/formatting.md#formatting-fallback-values
     26 
     27    static UnicodeString fallbackToString(const UnicodeString& s) {
     28        UnicodeString result;
     29        result += LEFT_CURLY_BRACE;
     30        result += s;
     31        result += RIGHT_CURLY_BRACE;
     32        return result;
     33    }
     34 
     35    Formattable& Formattable::operator=(Formattable other) noexcept {
     36        swap(*this, other);
     37        return *this;
     38    }
     39 
     40    Formattable::Formattable(const Formattable& other) {
     41        contents = other.contents;
     42    }
     43 
     44    Formattable Formattable::forDecimal(std::string_view number, UErrorCode &status) {
     45        Formattable f;
     46        // The relevant overload of the StringPiece constructor
     47        // casts the string length to int32_t, so we have to check
     48        // that the length makes sense
     49        if (number.size() > INT_MAX) {
     50            status = U_ILLEGAL_ARGUMENT_ERROR;
     51        } else {
     52            f.contents = icu::Formattable(StringPiece(number), status);
     53        }
     54        return f;
     55    }
     56 
     57    UFormattableType Formattable::getType() const {
     58        if (std::holds_alternative<double>(contents)) {
     59            return UFMT_DOUBLE;
     60        }
     61        if (std::holds_alternative<int64_t>(contents)) {
     62            return UFMT_INT64;
     63        }
     64        if (std::holds_alternative<UnicodeString>(contents)) {
     65            return UFMT_STRING;
     66        }
     67        if (isDecimal()) {
     68            switch (std::get_if<icu::Formattable>(&contents)->getType()) {
     69            case icu::Formattable::Type::kLong: {
     70                return UFMT_LONG;
     71            }
     72            case icu::Formattable::Type::kDouble: {
     73                return UFMT_DOUBLE;
     74            }
     75            default: {
     76                return UFMT_INT64;
     77            }
     78            }
     79        }
     80        if (isDate()) {
     81            return UFMT_DATE;
     82        }
     83        if (std::holds_alternative<const FormattableObject*>(contents)) {
     84            return UFMT_OBJECT;
     85        }
     86        return UFMT_ARRAY;
     87    }
     88 
     89    const Formattable* Formattable::getArray(int32_t& len, UErrorCode& status) const {
     90        NULL_ON_ERROR(status);
     91 
     92        if (getType() != UFMT_ARRAY) {
     93            len = 0;
     94            status = U_ILLEGAL_ARGUMENT_ERROR;
     95            return nullptr;
     96        }
     97        const std::pair<const Formattable*, int32_t>& p = *std::get_if<std::pair<const Formattable*, int32_t>>(&contents);
     98        U_ASSERT(p.first != nullptr);
     99        len = p.second;
    100        return p.first;
    101    }
    102 
    103    int64_t Formattable::getInt64(UErrorCode& status) const {
    104        if (isDecimal() && isNumeric()) {
    105            return std::get_if<icu::Formattable>(&contents)->getInt64(status);
    106        }
    107 
    108        switch (getType()) {
    109        case UFMT_LONG:
    110        case UFMT_INT64: {
    111            return *std::get_if<int64_t>(&contents);
    112        }
    113        case UFMT_DOUBLE: {
    114            return icu::Formattable(*std::get_if<double>(&contents)).getInt64(status);
    115        }
    116        default: {
    117            status = U_INVALID_FORMAT_ERROR;
    118            return 0;
    119        }
    120        }
    121    }
    122 
    123    icu::Formattable Formattable::asICUFormattable(UErrorCode& status) const {
    124        if (U_FAILURE(status)) {
    125            return {};
    126        }
    127        // Type must not be UFMT_ARRAY or UFMT_OBJECT
    128        if (getType() == UFMT_ARRAY || getType() == UFMT_OBJECT) {
    129            status = U_ILLEGAL_ARGUMENT_ERROR;
    130            return {};
    131        }
    132 
    133        if (isDecimal()) {
    134            return *std::get_if<icu::Formattable>(&contents);
    135        }
    136 
    137        switch (getType()) {
    138        case UFMT_DATE: {
    139            return icu::Formattable(*std::get_if<double>(&contents), icu::Formattable::kIsDate);
    140        }
    141        case UFMT_DOUBLE: {
    142            return icu::Formattable(*std::get_if<double>(&contents));
    143        }
    144        case UFMT_LONG: {
    145            return icu::Formattable(static_cast<int32_t>(*std::get_if<double>(&contents)));
    146        }
    147        case UFMT_INT64: {
    148            return icu::Formattable(*std::get_if<int64_t>(&contents));
    149        }
    150        case UFMT_STRING: {
    151            return icu::Formattable(*std::get_if<UnicodeString>(&contents));
    152        }
    153        default: {
    154            // Already checked for UFMT_ARRAY and UFMT_OBJECT
    155            return icu::Formattable();
    156        }
    157        }
    158    }
    159 
    160    Formattable::~Formattable() {}
    161 
    162    FormattableObject::~FormattableObject() {}
    163 
    164    FormattedMessage::~FormattedMessage() {}
    165 
    166    FormattedValue::FormattedValue(const UnicodeString& s) {
    167        type = kString;
    168        stringOutput = std::move(s);
    169    }
    170 
    171    FormattedValue::FormattedValue(number::FormattedNumber&& n) {
    172        type = kNumber;
    173        numberOutput = std::move(n);
    174    }
    175 
    176    FormattedValue& FormattedValue::operator=(FormattedValue&& other) noexcept {
    177        type = other.type;
    178        if (type == kString) {
    179            stringOutput = std::move(other.stringOutput);
    180        } else {
    181            numberOutput = std::move(other.numberOutput);
    182        }
    183        return *this;
    184    }
    185 
    186    FormattedValue::~FormattedValue() {}
    187 
    188    FormattedPlaceholder& FormattedPlaceholder::operator=(FormattedPlaceholder&& other) noexcept {
    189        type = other.type;
    190        source = other.source;
    191        if (type == kEvaluated) {
    192            formatted = std::move(other.formatted);
    193            previousOptions = std::move(other.previousOptions);
    194        }
    195        fallback = other.fallback;
    196        return *this;
    197    }
    198 
    199    const Formattable& FormattedPlaceholder::asFormattable() const {
    200        return source;
    201    }
    202 
    203    // Default formatters
    204    // ------------------
    205 
    206    number::FormattedNumber formatNumberWithDefaults(const Locale& locale, double toFormat, UErrorCode& errorCode) {
    207        return number::NumberFormatter::withLocale(locale).formatDouble(toFormat, errorCode);
    208    }
    209 
    210    number::FormattedNumber formatNumberWithDefaults(const Locale& locale, int32_t toFormat, UErrorCode& errorCode) {
    211        return number::NumberFormatter::withLocale(locale).formatInt(toFormat, errorCode);
    212    }
    213 
    214    number::FormattedNumber formatNumberWithDefaults(const Locale& locale, int64_t toFormat, UErrorCode& errorCode) {
    215        return number::NumberFormatter::withLocale(locale).formatInt(toFormat, errorCode);
    216    }
    217 
    218    number::FormattedNumber formatNumberWithDefaults(const Locale& locale, StringPiece toFormat, UErrorCode& errorCode) {
    219        return number::NumberFormatter::withLocale(locale).formatDecimal(toFormat, errorCode);
    220    }
    221 
    222    DateFormat* defaultDateTimeInstance(const Locale& locale, UErrorCode& errorCode) {
    223        NULL_ON_ERROR(errorCode);
    224        LocalPointer<DateFormat> df(DateFormat::createDateTimeInstance(DateFormat::SHORT, DateFormat::SHORT, locale));
    225        if (!df.isValid()) {
    226            errorCode = U_MEMORY_ALLOCATION_ERROR;
    227            return nullptr;
    228        }
    229        return df.orphan();
    230    }
    231 
    232    // Called when output is required and the contents are an unevaluated `Formattable`;
    233    // formats the source `Formattable` to a string with defaults, if it can be
    234    // formatted with a default formatter
    235    static FormattedPlaceholder formatWithDefaults(const Locale& locale, const FormattedPlaceholder& input, UErrorCode& status) {
    236        if (U_FAILURE(status)) {
    237            return {};
    238        }
    239 
    240        const Formattable& toFormat = input.asFormattable();
    241        // Try as decimal number first
    242        if (toFormat.isNumeric()) {
    243            // Note: the ICU Formattable has to be created here since the StringPiece
    244            // refers to state inside the Formattable; so otherwise we'll have a reference
    245            // to a temporary object
    246            icu::Formattable icuFormattable = toFormat.asICUFormattable(status);
    247            StringPiece asDecimal = icuFormattable.getDecimalNumber(status);
    248            if (U_FAILURE(status)) {
    249                return {};
    250            }
    251            if (asDecimal != nullptr) {
    252                return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, asDecimal, status)));
    253            }
    254        }
    255 
    256        UFormattableType type = toFormat.getType();
    257        switch (type) {
    258        case UFMT_DATE: {
    259            UnicodeString result;
    260            const DateInfo* dateInfo = toFormat.getDate(status);
    261            U_ASSERT(U_SUCCESS(status));
    262            formatDateWithDefaults(locale, *dateInfo, result, status);
    263            return FormattedPlaceholder(input, FormattedValue(std::move(result)));
    264        }
    265        case UFMT_DOUBLE: {
    266            double d = toFormat.getDouble(status);
    267            U_ASSERT(U_SUCCESS(status));
    268            return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, d, status)));
    269        }
    270        case UFMT_LONG: {
    271            int32_t l = toFormat.getLong(status);
    272            U_ASSERT(U_SUCCESS(status));
    273            return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, l, status)));
    274        }
    275        case UFMT_INT64: {
    276            int64_t i = toFormat.getInt64Value(status);
    277            U_ASSERT(U_SUCCESS(status));
    278            return FormattedPlaceholder(input, FormattedValue(formatNumberWithDefaults(locale, i, status)));
    279        }
    280        case UFMT_STRING: {
    281            const UnicodeString& s = toFormat.getString(status);
    282            U_ASSERT(U_SUCCESS(status));
    283            return FormattedPlaceholder(input, FormattedValue(UnicodeString(s)));
    284        }
    285        default: {
    286            // No default formatters for other types; use fallback
    287            status = U_MF_FORMATTING_ERROR;
    288            // Note: it would be better to set an internal formatting error so that a string
    289            // (e.g. the type tag) can be provided. However, this  method is called by the
    290            // public method formatToString() and thus can't take a MessageContext
    291            return FormattedPlaceholder(input.getFallback());
    292        }
    293        }
    294    }
    295 
    296    // Called when string output is required; forces output to be produced
    297    // if none is present (including formatting number output as a string)
    298    UnicodeString FormattedPlaceholder::formatToString(const Locale& locale,
    299                                                       UErrorCode& status) const {
    300        if (U_FAILURE(status)) {
    301            return {};
    302        }
    303        if (isFallback() || isNullOperand()) {
    304            return fallbackToString(fallback);
    305        }
    306 
    307        // Evaluated value: either just return the string, or format the number
    308        // as a string and return it
    309        if (isEvaluated()) {
    310            if (formatted.isString()) {
    311                return formatted.getString();
    312            } else {
    313                return formatted.getNumber().toString(status);
    314            }
    315        }
    316        // Unevaluated value: first evaluate it fully, then format
    317        UErrorCode savedStatus = status;
    318        FormattedPlaceholder evaluated = formatWithDefaults(locale, *this, status);
    319        if (status == U_MF_FORMATTING_ERROR) {
    320            U_ASSERT(evaluated.isFallback());
    321            return evaluated.getFallback();
    322        }
    323        // Ignore U_USING_DEFAULT_WARNING
    324        if (status == U_USING_DEFAULT_WARNING) {
    325            status = savedStatus;
    326        }
    327        return evaluated.formatToString(locale, status);
    328    }
    329 
    330 } // namespace message2
    331 
    332 U_NAMESPACE_END
    333 
    334 #endif /* #if !UCONFIG_NO_MF2 */
    335 
    336 #endif /* #if !UCONFIG_NO_FORMATTING */
    337 
    338 #endif /* #if !UCONFIG_NO_NORMALIZATION */