winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

Regex.cpp (9463B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "Public/winget/Regex.h"
      5 #include "Public/AppInstallerErrors.h"
      6 #include "Public/AppInstallerLogging.h"
      7 #include "Public/AppInstallerLanguageUtilities.h"
      8 
      9 #define WINGET_THROW_REGEX_ERROR_IF_FAILED(_err_,_func_) \
     10     if (U_FAILURE(_err_)) \
     11     { \
     12         AICLI_LOG(Core, Error, << #_func_ " returned " << _err_); \
     13         THROW_HR(APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR); \
     14     }
     15 
     16 
     17 namespace AppInstaller::Regex
     18 {
     19     struct Expression::impl
     20     {
     21         using uregex_ptr = wil::unique_any<URegularExpression*, decltype(uregex_close), uregex_close>;
     22         using utext_ptr = wil::unique_any<UText*, decltype(utext_close), utext_close>;
     23 
     24         // Create caches the original ICU regex objects in a static map and hands out copies of them
     25         // when requested. Since we have a limited set, this is a very simple cache-all-forever pattern.
     26         static std::unique_ptr<impl> Create(std::string_view pattern, Options options)
     27         {
     28             struct key
     29             {
     30                 std::string pattern;
     31                 Options options = Options::None;
     32 
     33                 bool operator<(const key& other) const
     34                 {
     35                     if (pattern < other.pattern)
     36                     {
     37                         return true;
     38                     }
     39                     else if (pattern == other.pattern)
     40                     {
     41                         return ToIntegral(options) < ToIntegral(other.options);
     42                     }
     43                     else
     44                     {
     45                         return false;
     46                     }
     47                 }
     48             };
     49 
     50             struct statics
     51             {
     52                 std::map<key, impl> map;
     53                 wil::srwlock lock;
     54             };
     55 
     56             static statics s_regex_cache;
     57 
     58             key requested;
     59             requested.pattern = pattern;
     60             requested.options = options;
     61 
     62             {
     63                 // Attempt to find in the cache
     64                 auto sharedLock = s_regex_cache.lock.lock_shared();
     65 
     66                 auto itr = s_regex_cache.map.find(requested);
     67                 if (itr != s_regex_cache.map.end())
     68                 {
     69                     return std::make_unique<impl>(itr->second);
     70                 }
     71             }
     72 
     73             auto exclusiveLock = s_regex_cache.lock.lock_exclusive();
     74 
     75             // Check if another thread created it while we waited for the lock.
     76             auto itr = s_regex_cache.map.find(requested);
     77             if (itr != s_regex_cache.map.end())
     78             {
     79                 return std::make_unique<impl>(itr->second);
     80             }
     81             else
     82             {
     83                 return std::make_unique<impl>(s_regex_cache.map.emplace(std::move(requested), impl{ pattern, options }).first->second);
     84             }
     85         }
     86 
     87         impl(std::string_view pattern, Options options)
     88         {
     89             UErrorCode uec = U_ZERO_ERROR;
     90 
     91             utext_ptr patternUtext{ utext_openUTF8(nullptr, pattern.data(), pattern.length(), &uec) };
     92             WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_openUTF8);
     93 
     94             // For now, just handle the one option
     95             uint32_t flags = 0;
     96 
     97             if (options == Options::CaseInsensitive)
     98             {
     99                 flags = UREGEX_CASE_INSENSITIVE;
    100             }
    101 
    102             UParseError parseError{};
    103 
    104             m_regex.reset(uregex_openUText(patternUtext.get(), flags, &parseError, &uec));
    105 
    106             if (U_FAILURE(uec))
    107             {
    108                 AICLI_LOG(Core, Error, << "uregex_openUText failed with error [" << uec << "] at line " << parseError.line << ", position " << parseError.offset << '\n' << pattern);
    109                 THROW_HR(APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR);
    110             }
    111         }
    112 
    113         impl(const impl& other)
    114         {
    115             UErrorCode uec = U_ZERO_ERROR;
    116 
    117             m_regex.reset(uregex_clone(other.m_regex.get(), &uec));
    118             WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_clone);
    119         }
    120 
    121         impl& operator=(const impl& other)
    122         {
    123             *this = impl{ other };
    124             return *this;
    125         }
    126 
    127         impl(impl&&) = default;
    128         impl& operator=(impl&&) = default;
    129 
    130         ~impl() = default;
    131 
    132         bool IsMatch(std::wstring_view input) const
    133         {
    134             UErrorCode uec = U_ZERO_ERROR;
    135 
    136             SetText(input);
    137 
    138             UBool result = uregex_matches(m_regex.get(), -1, &uec);
    139             WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_matches);
    140 
    141             return !!result;
    142         }
    143 
    144         std::wstring Replace(std::wstring_view input, std::wstring_view replacement) const
    145         {
    146             UErrorCode uec = U_ZERO_ERROR;
    147 
    148             SetText(input);
    149 
    150             std::u16string_view u16replacement = Convert(replacement);
    151             utext_ptr replacementUtext{ utext_openUChars(nullptr, u16replacement.data(), u16replacement.length(), &uec) };
    152             WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_openUTF8);
    153 
    154             utext_ptr resultUText{ uregex_replaceAllUText(m_regex.get(), replacementUtext.get(), nullptr, &uec) };
    155             WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_replaceAllUText);
    156 
    157             int64_t cch = utext_nativeLength(resultUText.get());
    158             std::wstring result(static_cast<size_t>(cch), '\0');
    159 
    160             utext_extract(resultUText.get(), 0, std::numeric_limits<int64_t>::max(), reinterpret_cast<char16_t*>(&result[0]), static_cast<int32_t>(result.size()), &uec);
    161             WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_extract);
    162 
    163             return result;
    164         }
    165 
    166         void ForEach(std::wstring_view input, const std::function<bool(bool, std::wstring_view)>&f) const
    167         {
    168             UErrorCode uec = U_ZERO_ERROR;
    169 
    170             SetText(input);
    171             int32_t startPos = 0;
    172 
    173             while (uregex_findNext(m_regex.get(), &uec))
    174             {
    175                 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_findNext);
    176 
    177                 int32_t pos = uregex_start(m_regex.get(), 0, &uec);
    178                 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_start);
    179                 THROW_HR_IF(E_UNEXPECTED, pos == -1);
    180 
    181                 // First, send off the unmatched part before the match
    182                 if (pos > startPos)
    183                 {
    184                     if (!f(false, input.substr(startPos, static_cast<size_t>(pos) - startPos)))
    185                     {
    186                         return;
    187                     }
    188                 }
    189 
    190                 // Now send the matched part
    191                 int32_t end = uregex_end(m_regex.get(), 0, &uec);
    192                 WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_end);
    193                 THROW_HR_IF(E_UNEXPECTED, end == -1);
    194 
    195                 if (!f(true, input.substr(pos, static_cast<size_t>(end) - pos)))
    196                 {
    197                     return;
    198                 }
    199 
    200                 startPos = end;
    201             }
    202 
    203             WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_findNext);
    204 
    205             // Finally, send any remaining part
    206             if (input.length() > static_cast<size_t>(startPos))
    207             {
    208                 f(false, input.substr(startPos));
    209             }
    210         }
    211 
    212     private:
    213         static std::u16string_view Convert(std::wstring_view input)
    214         {
    215             static_assert(sizeof(wchar_t) == sizeof(char16_t), "wchar_t and char16_t must be the same size");
    216             return { reinterpret_cast<const char16_t*>(input.data()), input.size() };
    217         }
    218 
    219         void SetText(std::wstring_view input) const
    220         {
    221             UErrorCode uec = U_ZERO_ERROR;
    222 
    223             std::u16string_view u16 = Convert(input);
    224 
    225             uregex_setText(m_regex.get(), u16.data(), static_cast<int32_t>(u16.length()), &uec);
    226             WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_setText);
    227         }
    228 
    229         uregex_ptr m_regex;
    230     };
    231 
    232     Expression::Expression() = default;
    233 
    234     Expression::Expression(std::string_view pattern, Options options) : pImpl(impl::Create(pattern, options)) {}
    235 
    236     Expression::Expression(const Expression& other)
    237     {
    238         if (other.pImpl)
    239         {
    240             pImpl = std::make_unique<impl>(*other.pImpl);
    241         }
    242     }
    243 
    244     Expression& Expression::operator=(const Expression& other)
    245     {
    246         return *this = Expression{ other };
    247     }
    248 
    249     Expression::Expression(Expression&&) noexcept = default;
    250     Expression& Expression::operator=(Expression&&) noexcept = default;
    251 
    252     Expression::~Expression() = default;
    253 
    254     Expression::operator bool() const
    255     {
    256         return static_cast<bool>(pImpl);
    257     }
    258 
    259     bool Expression::IsMatch(std::wstring_view input) const
    260     {
    261         THROW_HR_IF(E_NOT_VALID_STATE, !pImpl);
    262         return pImpl->IsMatch(input);
    263     }
    264 
    265     std::wstring Expression::Replace(std::wstring_view input, std::wstring_view replacement) const
    266     {
    267         THROW_HR_IF(E_NOT_VALID_STATE, !pImpl);
    268         return pImpl->Replace(input, replacement);
    269     }
    270 
    271     void Expression::ForEach(std::wstring_view input, const std::function<bool(bool, std::wstring_view)>& f) const
    272     {
    273         THROW_HR_IF(E_NOT_VALID_STATE, !pImpl);
    274         return pImpl->ForEach(input, f);
    275     }
    276 }