| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| /*! |
| * \file tvm/ffi/string.h |
| * \brief Runtime Bytes and String types. |
| */ |
| #ifndef TVM_FFI_STRING_H_ |
| #define TVM_FFI_STRING_H_ |
| |
| #include <tvm/ffi/base_details.h> |
| #include <tvm/ffi/error.h> |
| #include <tvm/ffi/memory.h> |
| #include <tvm/ffi/object.h> |
| #include <tvm/ffi/type_traits.h> |
| |
| #include <cctype> |
| #include <cstddef> |
| #include <cstring> |
| #include <iomanip> |
| #include <sstream> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| // Note: We place string in tvm/ffi instead of tvm/ffi/container |
| // because string itself needs special handling and is an inherent |
| // core component for return string handling. |
| // The following dependency relation holds |
| // any -> string -> object |
| |
| /// \cond Doxygen_Suppress |
| #ifdef _MSC_VER |
| #define TVM_FFI_SNPRINTF _snprintf_s |
| #pragma warning(push) |
| #pragma warning(disable : 4244) |
| #pragma warning(disable : 4127) |
| #pragma warning(disable : 4702) |
| #else |
| #define TVM_FFI_SNPRINTF snprintf |
| #endif |
| /// \endcond |
| |
| namespace tvm { |
| namespace ffi { |
| namespace details { |
| /*! |
| * \brief Base class for bytes and string objects. |
| */ |
| class BytesObjBase : public Object, public TVMFFIByteArray {}; |
| |
| /*! |
| * \brief An object representing bytes. |
| * \note We use a separate object for bytes to follow Python convention |
| * and indicate passing of raw bytes. |
| * Bytes can be converted from/to string. |
| */ |
| class BytesObj : public BytesObjBase { |
| public: |
| static constexpr const uint32_t _type_index = TypeIndex::kTVMFFIBytes; |
| static const constexpr bool _type_final = true; |
| TVM_FFI_DECLARE_OBJECT_INFO_STATIC(StaticTypeKey::kTVMFFIBytes, BytesObj, Object); |
| }; |
| |
| /*! \brief An object representing string. This is a POD type. */ |
| class StringObj : public BytesObjBase { |
| public: |
| static constexpr const uint32_t _type_index = TypeIndex::kTVMFFIStr; |
| static const constexpr bool _type_final = true; |
| TVM_FFI_DECLARE_OBJECT_INFO_STATIC(StaticTypeKey::kTVMFFIStr, StringObj, Object); |
| }; |
| |
| // String moved from std::string |
| // without having to trigger a copy |
| template <typename Base> |
| class BytesObjStdImpl : public Base { |
| public: |
| explicit BytesObjStdImpl(std::string other) : data_{std::move(other)} { |
| this->data = data_.data(); |
| this->size = data_.size(); |
| } |
| |
| private: |
| std::string data_; |
| }; |
| |
| /*! |
| * \brief Helper cell class that can be used to back small string |
| * \note Do not use directly, use String or Bytes instead |
| */ |
| class BytesBaseCell { |
| public: |
| BytesBaseCell() { |
| // initialize to none |
| data_.type_index = TypeIndex::kTVMFFINone; |
| data_.zero_padding = 0; |
| data_.v_int64 = 0; |
| } |
| |
| explicit BytesBaseCell(std::nullopt_t) { |
| data_.type_index = TypeIndex::kTVMFFINone; |
| data_.zero_padding = 0; |
| data_.v_int64 = 0; |
| } |
| |
| BytesBaseCell(const BytesBaseCell& other) : data_(other.data_) { // NOLINT(*) |
| if (data_.type_index >= TypeIndex::kTVMFFIStaticObjectBegin) { |
| details::ObjectUnsafe::IncRefObjectHandle(data_.v_obj); |
| } |
| } |
| |
| BytesBaseCell(BytesBaseCell&& other) : data_(other.data_) { // NOLINT(*) |
| other.data_.type_index = TypeIndex::kTVMFFINone; |
| } |
| |
| BytesBaseCell& operator=(const BytesBaseCell& other) { |
| BytesBaseCell(other).swap(*this); // NOLINT(*) |
| return *this; |
| } |
| |
| BytesBaseCell& operator=(BytesBaseCell&& other) noexcept { |
| BytesBaseCell(std::move(other)).swap(*this); // NOLINT(*) |
| return *this; |
| } |
| |
| ~BytesBaseCell() { |
| if (data_.type_index >= TypeIndex::kTVMFFIStaticObjectBegin) { |
| details::ObjectUnsafe::DecRefObjectHandle(data_.v_obj); |
| } |
| } |
| |
| /*! |
| * \brief Check if the cell is null |
| * \return true if the cell is null, false otherwise |
| */ |
| bool operator==(std::nullopt_t) const { return data_.type_index == TypeIndex::kTVMFFINone; } |
| |
| /*! |
| * \brief Check if the cell is not null |
| * \return true if the cell is not null, false otherwise |
| */ |
| bool operator!=(std::nullopt_t) const { return data_.type_index != TypeIndex::kTVMFFINone; } |
| |
| /*! |
| * \brief Swap this String with another string |
| * \param other The other string |
| */ |
| void swap(BytesBaseCell& other) { // NOLINT(*) |
| std::swap(data_, other.data_); |
| } |
| |
| const char* data() const noexcept { |
| if (data_.type_index < TypeIndex::kTVMFFIStaticObjectBegin) { |
| return data_.v_bytes; |
| } else { |
| // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound) |
| return TVMFFIBytesGetByteArrayPtr(data_.v_obj)->data; |
| } |
| } |
| |
| size_t size() const noexcept { |
| if (data_.type_index < TypeIndex::kTVMFFIStaticObjectBegin) { |
| return data_.small_str_len; |
| } else { |
| // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound) |
| return TVMFFIBytesGetByteArrayPtr(data_.v_obj)->size; |
| } |
| } |
| |
| template <typename LargeObj> |
| void InitFromStd(std::string&& other, int32_t large_type_index) { |
| // needs to be reset to none first for exception safety |
| data_.type_index = TypeIndex::kTVMFFINone; |
| data_.zero_padding = 0; |
| TVM_FFI_CLEAR_PTR_PADDING_IN_FFI_ANY(&data_); |
| ObjectPtr<LargeObj> ptr = make_object<BytesObjStdImpl<LargeObj>>(std::move(other)); |
| data_.v_obj = details::ObjectUnsafe::MoveObjectPtrToTVMFFIObjectPtr(std::move(ptr)); |
| data_.type_index = large_type_index; |
| } |
| |
| /*! |
| * \brief Create a new empty space for a string |
| * \param size The size of the string |
| * \param small_type_index The type index for the small string |
| * \param large_type_index The type index for the large string |
| * \note always reserve one byte for \0 compactibility |
| * \return A pointer to the empty space |
| */ |
| template <typename LargeObj> |
| char* InitSpaceForSize(size_t size, int32_t small_type_index, int32_t large_type_index) { |
| size_t kMaxSmallBytesLen = sizeof(int64_t) - 1; |
| // first zero the content, this is important for exception safety |
| data_.type_index = small_type_index; |
| data_.zero_padding = 0; |
| if (size <= kMaxSmallBytesLen) { |
| // set up the size accordingly |
| data_.small_str_len = static_cast<uint32_t>(size); |
| return data_.v_bytes; |
| } else { |
| // allocate from heap |
| ObjectPtr<LargeObj> ptr = make_inplace_array_object<LargeObj, char>(size + 1); |
| char* dest_data = reinterpret_cast<char*>(ptr.get()) + sizeof(LargeObj); |
| ptr->data = dest_data; |
| ptr->size = size; |
| TVM_FFI_CLEAR_PTR_PADDING_IN_FFI_ANY(&data_); |
| data_.v_obj = details::ObjectUnsafe::MoveObjectPtrToTVMFFIObjectPtr(std::move(ptr)); |
| // now reset the type index to str |
| data_.type_index = large_type_index; |
| return dest_data; |
| } |
| } |
| |
| void InitTypeIndex(int32_t type_index) { data_.type_index = type_index; } |
| |
| void MoveToAny(TVMFFIAny* result) { |
| *result = data_; |
| data_.type_index = TypeIndex::kTVMFFINone; |
| data_.zero_padding = 0; |
| data_.v_int64 = 0; |
| } |
| |
| TVMFFIAny CopyToTVMFFIAny() const { return data_; } |
| |
| static BytesBaseCell CopyFromAnyView(const TVMFFIAny* src) { |
| BytesBaseCell result(*src); |
| if (result.data_.type_index >= TypeIndex::kTVMFFIStaticObjectBegin) { |
| details::ObjectUnsafe::IncRefObjectHandle(result.data_.v_obj); |
| } |
| return result; |
| } |
| |
| static BytesBaseCell MoveFromAny(TVMFFIAny* src) { |
| BytesBaseCell result(*src); |
| src->type_index = TypeIndex::kTVMFFINone; |
| src->zero_padding = 0; |
| src->v_int64 = 0; |
| return result; |
| } |
| |
| private: |
| explicit BytesBaseCell(TVMFFIAny data) : data_(data) {} |
| /*! \brief internal backing data */ |
| TVMFFIAny data_; |
| }; |
| } // namespace details |
| |
| /*! |
| * \brief Managed reference of byte array. |
| */ |
| class Bytes { |
| public: |
| /*! \brief default constructor */ |
| Bytes() { data_.InitTypeIndex(TypeIndex::kTVMFFISmallBytes); } |
| /*! |
| * \brief constructor from size |
| * |
| * \param data The data pointer. |
| * \param size The size of the char array. |
| */ |
| Bytes(const char* data, size_t size) { this->InitData(data, size); } |
| /*! |
| * \brief constructor from TVMFFIByteArray |
| * |
| * \param bytes a char array. |
| */ |
| Bytes(TVMFFIByteArray bytes) { // NOLINT(*) |
| this->InitData(bytes.data, bytes.size); |
| } |
| /*! |
| * \brief constructor from std::string |
| * |
| * \param other a char array. |
| */ |
| Bytes(const std::string& other) { // NOLINT(*) |
| this->InitData(other.data(), other.size()); |
| } |
| /*! |
| * \brief constructor from std::string |
| * |
| * \param other a char array. |
| */ |
| Bytes(std::string&& other) { // NOLINT(*) |
| data_.InitFromStd<details::BytesObj>(std::move(other), TypeIndex::kTVMFFIBytes); |
| } |
| /*! |
| * \brief Swap this String with another string |
| * \param other The other string |
| */ |
| void swap(Bytes& other) { // NOLINT(*) |
| std::swap(data_, other.data_); |
| } |
| |
| template <typename T> |
| Bytes& operator=(T&& other) { |
| // copy-and-swap idiom |
| Bytes(std::forward<T>(other)).swap(*this); // NOLINT(*) |
| return *this; |
| } |
| /*! |
| * \brief Return the length of the string |
| * |
| * \return size_t string length |
| */ |
| size_t size() const { return data_.size(); } |
| /*! |
| * \brief Return the data pointer |
| * |
| * \return const char* data pointer |
| */ |
| const char* data() const { return data_.data(); } |
| /*! |
| * \brief Convert String to an std::string object |
| * |
| * \return std::string |
| */ |
| operator std::string() const { // NOLINT(google-explicit-constructor) |
| return std::string{data(), size()}; |
| } |
| |
| /*! |
| * \brief Compare two char sequence |
| * |
| * \param lhs Pointers to the char array to compare |
| * \param rhs Pointers to the char array to compare |
| * \param lhs_count Length of the char array to compare |
| * \param rhs_count Length of the char array to compare |
| * \return int zero if both char sequences compare equal. negative if this |
| * appear before other, positive otherwise. |
| */ |
| static int memncmp(const char* lhs, const char* rhs, size_t lhs_count, size_t rhs_count) { |
| if (lhs == rhs && lhs_count == rhs_count) return 0; |
| |
| for (size_t i = 0; i < lhs_count && i < rhs_count; ++i) { |
| if (lhs[i] < rhs[i]) return -1; |
| if (lhs[i] > rhs[i]) return 1; |
| } |
| if (lhs_count < rhs_count) { |
| return -1; |
| } else if (lhs_count > rhs_count) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| /*! |
| * \brief Compare two char sequence for equality |
| * |
| * \param lhs Pointers to the char array to compare |
| * \param rhs Pointers to the char array to compare |
| * \param lhs_count Length of the char array to compare |
| * \param rhs_count Length of the char array to compare |
| * |
| * \return true if the two char sequences are equal, false otherwise. |
| */ |
| static bool memequal(const void* lhs, const void* rhs, size_t lhs_count, size_t rhs_count) { |
| return lhs_count == rhs_count && (lhs == rhs || std::memcmp(lhs, rhs, lhs_count) == 0); |
| } |
| |
| private: |
| template <typename, typename> |
| friend struct TypeTraits; |
| template <typename, typename> |
| friend class Optional; |
| // internal backing cell |
| details::BytesBaseCell data_; |
| // create a new String from TVMFFIAny, must keep private |
| explicit Bytes(details::BytesBaseCell data) : data_(std::move(data)) {} |
| char* InitSpaceForSize(size_t size) { |
| return data_.InitSpaceForSize<details::BytesObj>(size, TypeIndex::kTVMFFISmallBytes, |
| TypeIndex::kTVMFFIBytes); |
| } |
| void InitData(const char* data, size_t size) { |
| char* dest_data = InitSpaceForSize(size); |
| if (size > 0) { |
| std::memcpy(dest_data, data, size); |
| } |
| // mainly to be compat with string |
| dest_data[size] = '\0'; |
| } |
| }; |
| |
| /*! |
| * \brief String container class. |
| */ |
| class String { |
| public: |
| /*! |
| * \brief avoid misuse of nullptr |
| */ |
| String(std::nullptr_t) = delete; // NOLINT(*) |
| /*! |
| * \brief constructor |
| */ |
| String() { data_.InitTypeIndex(TypeIndex::kTVMFFISmallStr); } |
| // constructors from Any |
| /*! |
| * \brief Copy constructor |
| * \param other The other string |
| */ |
| String(const String& other) = default; // NOLINT(*) |
| /*! |
| * \brief Move constructor |
| * \param other The other string |
| */ |
| String(String&& other) = default; // NOLINT(*) |
| /*! |
| * \brief Copy assignment operator |
| * \param other The other string |
| */ |
| String& operator=(const String& other) = default; // NOLINT(*) |
| /*! |
| * \brief Move assignment operator |
| * \param other The other string |
| */ |
| String& operator=(String&& other) = default; // NOLINT(*) |
| |
| /*! |
| * \brief Swap this String with another string |
| * \param other The other string |
| */ |
| void swap(String& other) noexcept { // NOLINT(*) |
| std::swap(data_, other.data_); |
| } |
| |
| /*! |
| * \brief Copy assignment operator |
| * \param other The other string |
| */ |
| String& operator=(const std::string& other) { |
| String(other).swap(*this); // NOLINT(*) |
| return *this; |
| } |
| /*! |
| * \brief Move assignment operator |
| * \param other The other string |
| */ |
| String& operator=(std::string&& other) { |
| String(std::move(other)).swap(*this); // NOLINT(*) |
| return *this; |
| } |
| |
| /*! |
| * \brief Copy assignment operator |
| * \param other The other string |
| */ |
| String& operator=(const char* other) { |
| String(other).swap(*this); // NOLINT(*) |
| return *this; |
| } |
| |
| /*! |
| * \brief constructor from raw string |
| * |
| * \param data The data pointer. |
| * \param size The size of the char array. |
| */ |
| String(const char* data, size_t size) { this->InitData(data, size); } |
| |
| /*! |
| * \brief constructor from raw string |
| * |
| * \param other a char array. |
| * \note This constructor is marked as explicit to avoid implicit conversion |
| * of nullptr value here to string, which then was used in comparison |
| */ |
| String(const char* other) { // NOLINT(*) |
| this->InitData(other, std::char_traits<char>::length(other)); |
| } |
| /*! |
| * \brief Construct a new string object |
| * \param other The std::string object to be copied |
| */ |
| String(const std::string& other) { // NOLINT(*) |
| this->InitData(other.data(), other.size()); |
| } |
| |
| /*! |
| * \brief Construct a new string object |
| * \param other The std::string object to be moved |
| */ |
| String(std::string&& other) { // NOLINT(*) |
| // exception safety, first set to none so if exception is thrown |
| // destructor works correctly |
| data_.InitFromStd<details::StringObj>(std::move(other), TypeIndex::kTVMFFIStr); |
| } |
| |
| /*! |
| * \brief constructor from TVMFFIByteArray |
| * |
| * \param other a TVMFFIByteArray. |
| */ |
| explicit String(TVMFFIByteArray other) { this->InitData(other.data, other.size); } |
| |
| /*! |
| * \brief Return the data pointer |
| * |
| * \return const char* data pointer |
| */ |
| const char* data() const noexcept { return data_.data(); } |
| |
| /*! |
| * \brief Returns a pointer to the char array in the string. |
| * |
| * \return const char* |
| */ |
| const char* c_str() const noexcept { return data(); } |
| |
| /*! |
| * \brief Return the length of the string |
| * |
| * \return size_t string length |
| */ |
| size_t size() const noexcept { return data_.size(); } |
| |
| /*! |
| * \brief Compares this String object to other |
| * |
| * \param other The String to compare with. |
| * |
| * \return zero if both char sequences compare equal. negative if this appear |
| * before other, positive otherwise. |
| */ |
| int compare(const String& other) const { |
| return Bytes::memncmp(data(), other.data(), size(), other.size()); |
| } |
| |
| /*! |
| * \brief Compares this String object to other |
| * |
| * \param other The string to compare with. |
| * |
| * \return zero if both char sequences compare equal. negative if this appear |
| * before other, positive otherwise. |
| */ |
| int compare(const std::string& other) const { |
| return Bytes::memncmp(data(), other.data(), size(), other.size()); |
| } |
| |
| /*! |
| * \brief Compares this to other |
| * |
| * \param other The character array to compare with. |
| * |
| * \return zero if both char sequences compare equal. negative if this appear |
| * before other, positive otherwise. |
| */ |
| int compare(const char* other) const { |
| const char* this_data = data(); |
| size_t this_size = size(); |
| for (size_t i = 0; i < this_size; ++i) { |
| // other is shorter than this |
| if (other[i] == '\0') return 1; |
| if (this_data[i] < other[i]) return -1; |
| if (this_data[i] > other[i]) return 1; |
| } |
| // other equals this |
| if (other[this_size] == '\0') return 0; |
| // other longer than this |
| return -1; |
| } |
| |
| /*! |
| * \brief Compares this to other |
| * |
| * \param other The TVMFFIByteArray to compare with. |
| * |
| * \return zero if both char sequences compare equal. negative if this appear |
| * before other, positive otherwise. |
| */ |
| int compare(const TVMFFIByteArray& other) const { |
| return Bytes::memncmp(data(), other.data, size(), other.size); |
| } |
| |
| /*! |
| * \brief Return the length of the string |
| * |
| * \return size_t string length |
| */ |
| size_t length() const { return size(); } |
| |
| /*! |
| * \brief Retun if the string is empty |
| * |
| * \return true if empty, false otherwise. |
| */ |
| bool empty() const { return size() == 0; } |
| |
| /*! |
| * \brief Read an element. |
| * \param pos The position at which to read the character. |
| * |
| * \return The char at position |
| */ |
| char at(size_t pos) const { |
| if (pos < size()) { |
| return data()[pos]; |
| } else { |
| throw std::out_of_range("tvm::String index out of bounds"); |
| } |
| } |
| |
| /*! \brief Value returned by find() when no match is found */ |
| static constexpr size_t npos = static_cast<size_t>(-1); |
| |
| /*! |
| * \brief Find the first occurrence of a substring |
| * \param str The substring to search for |
| * \param pos The position at which to start the search |
| * \return The position of the first character of the first match, or npos if not found |
| */ |
| size_t find(const String& str, size_t pos = 0) const { return find(str.data(), pos, str.size()); } |
| |
| /*! |
| * \brief Find the first occurrence of a substring |
| * \param str The substring to search for |
| * \param pos The position at which to start the search |
| * \return The position of the first character of the first match, or npos if not found |
| */ |
| size_t find(const char* str, size_t pos = 0) const { return find(str, pos, std::strlen(str)); } |
| |
| /*! |
| * \brief Find the first occurrence of a substring |
| * \param str The substring to search for |
| * \param pos The position at which to start the search |
| * \param count The length of the substring |
| * \return The position of the first character of the first match, or npos if not found |
| */ |
| size_t find(const char* str, size_t pos, size_t count) const { |
| return std::string_view(data(), size()).find(std::string_view(str, count), pos); |
| } |
| |
| /*! |
| * \brief Returns a substring [pos, pos+count) |
| * \param pos The position of the first character to include |
| * \param count The length of the substring (default: until end of string) |
| * \return A string containing the substring |
| */ |
| String substr(size_t pos = 0, size_t count = npos) const { |
| if (pos > size()) { |
| throw std::out_of_range("tvm::String substr index out of bounds"); |
| } |
| size_t rcount = std::min(count, size() - pos); |
| return String(data() + pos, rcount); |
| } |
| |
| /*! |
| * \brief Check if the string starts with a prefix |
| * \param prefix The prefix to check for |
| * \return true if the string starts with prefix, false otherwise |
| */ |
| bool starts_with(const String& prefix) const { return starts_with(prefix.data(), prefix.size()); } |
| |
| /*! |
| * \brief Check if the string starts with a prefix |
| * \param prefix The prefix to check for |
| * \return true if the string starts with prefix, false otherwise |
| */ |
| bool starts_with(std::string_view prefix) const { |
| return starts_with(prefix.data(), prefix.size()); |
| } |
| |
| /*! |
| * \brief Check if the string starts with a prefix |
| * \param prefix The prefix to check for |
| * \return true if the string starts with prefix, false otherwise |
| */ |
| bool starts_with(const char* prefix) const { return starts_with(prefix, std::strlen(prefix)); } |
| |
| /*! |
| * \brief Check if the string starts with a prefix |
| * \param prefix The prefix to check for |
| * \param count The length of the prefix |
| * \return true if the string starts with prefix, false otherwise |
| */ |
| bool starts_with(const char* prefix, size_t count) const { |
| if (count > size()) { |
| return false; |
| } |
| return std::memcmp(data(), prefix, count) == 0; |
| } |
| |
| /*! |
| * \brief Check if the string ends with a suffix |
| * \param suffix The suffix to check for |
| * \return true if the string ends with suffix, false otherwise |
| */ |
| bool ends_with(const String& suffix) const { return ends_with(suffix.data(), suffix.size()); } |
| |
| /*! |
| * \brief Check if the string ends with a suffix |
| * \param suffix The suffix to check for |
| * \return true if the string ends with suffix, false otherwise |
| */ |
| bool ends_with(std::string_view suffix) const { return ends_with(suffix.data(), suffix.size()); } |
| |
| /*! |
| * \brief Check if the string ends with a suffix |
| * \param suffix The suffix to check for |
| * \return true if the string ends with suffix, false otherwise |
| */ |
| bool ends_with(const char* suffix) const { return ends_with(suffix, std::strlen(suffix)); } |
| |
| /*! |
| * \brief Check if the string ends with a suffix |
| * \param suffix The suffix to check for |
| * \param count The length of the suffix |
| * \return true if the string ends with suffix, false otherwise |
| */ |
| bool ends_with(const char* suffix, size_t count) const { |
| if (count > size()) { |
| return false; |
| } |
| return std::memcmp(data() + size() - count, suffix, count) == 0; |
| } |
| |
| /*! |
| * \brief Convert String to an std::string object |
| * |
| * \return std::string |
| */ |
| operator std::string() const { // NOLINT(google-explicit-constructor) |
| return std::string{data(), size()}; |
| } |
| |
| /*! |
| * \brief Split the string by a delimiter character. |
| * \param delim The delimiter character. |
| * \return A vector of string_views pointing into this string's data. |
| * \note The returned string_views are only valid while this String is alive. |
| */ |
| std::vector<std::string_view> Split(char delim) const { |
| std::vector<std::string_view> ret; |
| const char* start = data(); |
| const char* end = start + size(); |
| for (const char* p = start; p < end; ++p) { |
| if (*p == delim) { |
| ret.emplace_back(start, static_cast<size_t>(p - start)); |
| start = p + 1; |
| } |
| } |
| ret.emplace_back(start, static_cast<size_t>(end - start)); |
| return ret; |
| } |
| |
| private: |
| template <typename, typename> |
| friend struct TypeTraits; |
| template <typename, typename> |
| friend class Optional; |
| // internal backing cell |
| details::BytesBaseCell data_; |
| // create a new String from TVMFFIAny, must keep private |
| explicit String(details::BytesBaseCell data) : data_(std::move(data)) {} |
| /*! |
| * \brief Create a new empty space for a string |
| * \param size The size of the string |
| * \return A pointer to the empty space |
| */ |
| char* InitSpaceForSize(size_t size) { |
| return data_.InitSpaceForSize<details::StringObj>(size, TypeIndex::kTVMFFISmallStr, |
| TypeIndex::kTVMFFIStr); |
| } |
| void InitData(const char* data, size_t size) { |
| char* dest_data = InitSpaceForSize(size); |
| if (size > 0) { |
| std::memcpy(dest_data, data, size); |
| } |
| dest_data[size] = '\0'; |
| } |
| /*! |
| * \brief Concatenate two char sequences |
| * |
| * \param lhs Pointers to the lhs char array |
| * \param lhs_size The size of the lhs char array |
| * \param rhs Pointers to the rhs char array |
| * \param rhs_size The size of the rhs char array |
| * |
| * \return The concatenated char sequence |
| */ |
| static String Concat(const char* lhs, size_t lhs_size, const char* rhs, size_t rhs_size) { |
| String ret; |
| // disable stringop-overflow and restrict warnings |
| // gcc may produce false positive when we enable dest_data returned from small string path |
| // Because compiler is not able to detect the condition that the path is only triggered via |
| // size < kMaxSmallStrLen and can report it as a overflow case. |
| #if (__GNUC__) && !(__clang__) |
| #pragma GCC diagnostic push |
| #pragma GCC diagnostic ignored "-Wstringop-overflow" |
| #pragma GCC diagnostic ignored "-Warray-bounds" |
| #pragma GCC diagnostic ignored "-Wrestrict" |
| #endif |
| char* dest_data = ret.InitSpaceForSize(lhs_size + rhs_size); |
| std::memcpy(dest_data, lhs, lhs_size); |
| std::memcpy(dest_data + lhs_size, rhs, rhs_size); |
| // NOLINTNEXTLINE(clang-analyzer-security.ArrayBound) |
| dest_data[lhs_size + rhs_size] = '\0'; |
| #if (__GNUC__) && !(__clang__) |
| #pragma GCC diagnostic pop |
| #endif |
| return ret; |
| } |
| // Overload + operator |
| friend String operator+(const String& lhs, const String& rhs); |
| friend String operator+(const String& lhs, const std::string& rhs); |
| friend String operator+(const std::string& lhs, const String& rhs); |
| friend String operator+(const String& lhs, const char* rhs); |
| friend String operator+(const char* lhs, const String& rhs); |
| }; |
| |
| /*! |
| * \brief Return a JSON-escaped version of the string (RFC 8259). |
| * |
| * Uses ``\\uXXXX`` for control characters, escapes ``\\/``, ``\\b``, ``\\f`` per the JSON spec. |
| * Non-ASCII bytes are passed through as-is (valid UTF-8 is preserved). |
| * |
| * \param value The input string |
| * \return The escaped string, quoted with double quotes |
| */ |
| inline String EscapeStringJSON(const String& value) { |
| std::ostringstream oss; |
| oss << '"'; |
| const char* data = value.data(); |
| const size_t size = value.size(); |
| for (size_t i = 0; i < size; ++i) { |
| switch (data[i]) { |
| /// \cond Doxygen_Suppress |
| #define TVM_FFI_ESCAPE_CHAR(pattern, val) \ |
| case pattern: \ |
| oss << (val); \ |
| break |
| TVM_FFI_ESCAPE_CHAR('\"', "\\\""); |
| TVM_FFI_ESCAPE_CHAR('\\', "\\\\"); |
| TVM_FFI_ESCAPE_CHAR('/', "\\/"); |
| TVM_FFI_ESCAPE_CHAR('\b', "\\b"); |
| TVM_FFI_ESCAPE_CHAR('\f', "\\f"); |
| TVM_FFI_ESCAPE_CHAR('\n', "\\n"); |
| TVM_FFI_ESCAPE_CHAR('\r', "\\r"); |
| TVM_FFI_ESCAPE_CHAR('\t', "\\t"); |
| #undef TVM_FFI_ESCAPE_CHAR |
| /// \endcond |
| default: { |
| uint8_t u8_val = static_cast<uint8_t>(data[i]); |
| // this is a control character, print as \uXXXX |
| if (u8_val < 0x20 || u8_val == 0x7f) { |
| char buffer[8]; |
| int size = TVM_FFI_SNPRINTF(buffer, sizeof(buffer), "\\u%04x", |
| static_cast<int32_t>(data[i]) & 0xff); |
| oss.write(buffer, size); |
| } else { |
| oss << data[i]; |
| } |
| break; |
| } |
| } |
| } |
| oss << '"'; |
| return String(oss.str()); |
| } |
| |
| /*! |
| * \brief Escape a string for JSON output. |
| * \deprecated Use EscapeStringJSON instead. |
| * \param value The input string |
| * \return The escaped string, quoted with double quotes |
| */ |
| [[deprecated("Use EscapeStringJSON instead")]] inline String EscapeString(const String& value) { |
| return EscapeStringJSON(value); |
| } |
| |
| /*! |
| * \brief Return a Python-style escaped string representation. |
| * |
| * Handles ANSI escape sequences, UTF-8 multibyte characters, and standard |
| * C escape sequences (\\n, \\t, \\r, \\\\, \\"). Uses \\xNN for control |
| * characters and \\uXXXX / \\UXXXXXXXX for non-ASCII codepoints. |
| * |
| * \param value The input string to escape. |
| * \return The escaped string, quoted with double quotes. |
| */ |
| inline String EscapedStringPy(const String& value) { |
| const char* data = value.data(); |
| const size_t length = value.size(); |
| std::ostringstream oss; |
| oss << '"'; |
| for (size_t i = 0; i < length;) { |
| unsigned char c = static_cast<unsigned char>(data[i]); |
| unsigned char d = (i + 1 < length) ? static_cast<unsigned char>(data[i + 1]) : 0; |
| // Detect ANSI escape sequences |
| if (c == '\x1b' && d == '[') { |
| size_t j = i + 2; |
| while (j < length && (std::isdigit(static_cast<unsigned char>(data[j])) || data[j] == ';')) { |
| ++j; |
| } |
| if (j < length && (data[j] == 'm' || data[j] == 'K')) { |
| oss << "\\x1b["; |
| for (i += 2; i <= j; ++i) { |
| oss << data[i]; |
| } |
| continue; |
| } |
| } |
| // Handle ASCII C escape sequences |
| switch (c) { |
| case '\n': |
| oss << "\\n"; |
| ++i; |
| continue; |
| case '\t': |
| oss << "\\t"; |
| ++i; |
| continue; |
| case '\r': |
| oss << "\\r"; |
| ++i; |
| continue; |
| case '\\': |
| oss << "\\\\"; |
| ++i; |
| continue; |
| case '\"': |
| oss << "\\\""; |
| ++i; |
| continue; |
| default: |
| break; |
| } |
| // Handle ASCII |
| if ((c & 0x80) == 0) { |
| if (c < 0x20 || c == 0x7f) { |
| // Escape control characters as \xNN |
| char buf[5]; |
| TVM_FFI_SNPRINTF(buf, sizeof(buf), "\\x%02x", static_cast<unsigned>(c)); |
| oss << buf; |
| } else { |
| oss << static_cast<char>(c); |
| } |
| ++i; |
| continue; |
| } |
| if ((c & 0xE0) == 0xC0 && i + 1 < length && (d & 0xC0) == 0x80) { |
| int32_t codepoint = ((c & 0x1F) << 6) | (d & 0x3F); |
| oss << "\\u" << std::hex << std::setw(4) << std::setfill('0') << codepoint; |
| i += 2; |
| } else if ((c & 0xF0) == 0xE0 && i + 2 < length) { |
| unsigned char e = static_cast<unsigned char>(data[i + 2]); |
| if ((d & 0xC0) == 0x80 && (e & 0xC0) == 0x80) { |
| int32_t codepoint = ((c & 0x0F) << 12) | ((d & 0x3F) << 6) | (e & 0x3F); |
| oss << "\\u" << std::hex << std::setw(4) << std::setfill('0') << codepoint; |
| i += 3; |
| } else { |
| oss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(c); |
| ++i; |
| } |
| } else if ((c & 0xF8) == 0xF0 && i + 3 < length) { |
| unsigned char e = static_cast<unsigned char>(data[i + 2]); |
| unsigned char f = static_cast<unsigned char>(data[i + 3]); |
| if ((d & 0xC0) == 0x80 && (e & 0xC0) == 0x80 && (f & 0xC0) == 0x80) { |
| int32_t codepoint = |
| ((c & 0x07) << 18) | ((d & 0x3F) << 12) | ((e & 0x3F) << 6) | (f & 0x3F); |
| oss << "\\U" << std::hex << std::setw(8) << std::setfill('0') << codepoint; |
| i += 4; |
| } else { |
| oss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(c); |
| ++i; |
| } |
| } else { |
| oss << "\\x" << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(c); |
| ++i; |
| } |
| oss.unsetf(std::ios::adjustfield | std::ios::basefield | std::ios::floatfield); |
| oss.fill(' '); |
| } |
| oss << '"'; |
| return String(oss.str()); |
| } |
| |
| /*! \brief Convert TVMFFIByteArray to std::string_view */ |
| TVM_FFI_INLINE std::string_view ToStringView(TVMFFIByteArray str) { |
| return std::string_view(str.data, str.size); |
| } |
| /// \cond Doxygen_Suppress |
| |
| template <> |
| inline constexpr bool use_default_type_traits_v<Bytes> = false; |
| |
| // specialize to enable implicit conversion from TVMFFIByteArray* |
| template <> |
| struct TypeTraits<Bytes> : public TypeTraitsBase { |
| // bytes can be union type of small bytes and object, so keep it as any |
| static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIAny; |
| |
| TVM_FFI_INLINE static void CopyToAnyView(const Bytes& src, TVMFFIAny* result) { |
| *result = src.data_.CopyToTVMFFIAny(); |
| } |
| |
| TVM_FFI_INLINE static void MoveToAny(Bytes src, TVMFFIAny* result) { |
| src.data_.MoveToAny(result); |
| } |
| |
| TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { |
| return src->type_index == TypeIndex::kTVMFFISmallBytes || |
| src->type_index == TypeIndex::kTVMFFIBytes; |
| } |
| |
| TVM_FFI_INLINE static Bytes CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { |
| return Bytes(details::BytesBaseCell::CopyFromAnyView(src)); |
| } |
| |
| TVM_FFI_INLINE static Bytes MoveFromAnyAfterCheck(TVMFFIAny* src) { |
| return Bytes(details::BytesBaseCell::MoveFromAny(src)); |
| } |
| |
| TVM_FFI_INLINE static std::optional<Bytes> TryCastFromAnyView(const TVMFFIAny* src) { |
| if (src->type_index == TypeIndex::kTVMFFIByteArrayPtr) { |
| return Bytes(*static_cast<TVMFFIByteArray*>(src->v_ptr)); |
| } |
| if (src->type_index == TypeIndex::kTVMFFISmallBytes || |
| src->type_index == TypeIndex::kTVMFFIBytes) { |
| return Bytes(details::BytesBaseCell::CopyFromAnyView(src)); |
| } |
| return std::nullopt; |
| } |
| |
| TVM_FFI_INLINE static std::string TypeStr() { return "bytes"; } |
| TVM_FFI_INLINE static std::string TypeSchema() { |
| return R"({"type":")" + std::string(StaticTypeKey::kTVMFFIBytes) + R"("})"; |
| } |
| }; |
| |
| template <> |
| inline constexpr bool use_default_type_traits_v<String> = false; |
| |
| // specialize to enable implicit conversion from const char* |
| template <> |
| struct TypeTraits<String> : public TypeTraitsBase { |
| // string can be union type of small string and object, so keep it as any |
| static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIAny; |
| |
| TVM_FFI_INLINE static void CopyToAnyView(const String& src, TVMFFIAny* result) { |
| *result = src.data_.CopyToTVMFFIAny(); |
| } |
| |
| TVM_FFI_INLINE static void MoveToAny(String src, TVMFFIAny* result) { |
| src.data_.MoveToAny(result); |
| } |
| |
| TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) { |
| return src->type_index == TypeIndex::kTVMFFISmallStr || |
| src->type_index == TypeIndex::kTVMFFIStr; |
| } |
| |
| TVM_FFI_INLINE static String CopyFromAnyViewAfterCheck(const TVMFFIAny* src) { |
| return String(details::BytesBaseCell::CopyFromAnyView(src)); |
| } |
| |
| TVM_FFI_INLINE static String MoveFromAnyAfterCheck(TVMFFIAny* src) { |
| return String(details::BytesBaseCell::MoveFromAny(src)); |
| } |
| |
| TVM_FFI_INLINE static std::optional<String> TryCastFromAnyView(const TVMFFIAny* src) { |
| if (src->type_index == TypeIndex::kTVMFFIRawStr) { |
| return String(src->v_c_str); |
| } |
| if (src->type_index == TypeIndex::kTVMFFISmallStr || src->type_index == TypeIndex::kTVMFFIStr) { |
| return String(details::BytesBaseCell::CopyFromAnyView(src)); |
| } |
| return std::nullopt; |
| } |
| |
| TVM_FFI_INLINE static std::string TypeStr() { return "str"; } |
| TVM_FFI_INLINE static std::string TypeSchema() { |
| return R"({"type":")" + std::string(StaticTypeKey::kTVMFFIStr) + R"("})"; |
| } |
| }; |
| |
| // const char*, requirement: not nullable, do not retain ownership |
| template <int N> |
| struct TypeTraits<char[N]> : public TypeTraitsBase { |
| // NOTE: only enable implicit conversion into AnyView |
| static constexpr bool storage_enabled = false; |
| |
| TVM_FFI_INLINE static void CopyToAnyView(const char src[N], TVMFFIAny* result) { |
| result->type_index = TypeIndex::kTVMFFIRawStr; |
| result->zero_padding = 0; |
| result->v_c_str = src; |
| } |
| |
| TVM_FFI_INLINE static void MoveToAny(const char src[N], TVMFFIAny* result) { |
| // when we need to move to any, convert to owned object first |
| TypeTraits<String>::MoveToAny(String(src), result); |
| } |
| }; |
| |
| template <> |
| struct TypeTraits<const char*> : public TypeTraitsBase { |
| static constexpr bool storage_enabled = false; |
| |
| TVM_FFI_INLINE static void CopyToAnyView(const char* src, TVMFFIAny* result) { |
| TVM_FFI_ICHECK_NOTNULL(src); |
| result->type_index = TypeIndex::kTVMFFIRawStr; |
| result->zero_padding = 0; |
| result->v_c_str = src; |
| } |
| |
| TVM_FFI_INLINE static void MoveToAny(const char* src, TVMFFIAny* result) { |
| // when we need to move to any, convert to owned object first |
| TypeTraits<String>::MoveToAny(String(src), result); |
| } |
| // Do not allow const char* in a container, so we do not need CheckAnyStrict |
| TVM_FFI_INLINE static std::optional<const char*> TryCastFromAnyView(const TVMFFIAny* src) { |
| if (src->type_index == TypeIndex::kTVMFFIRawStr) { |
| return static_cast<const char*>(src->v_c_str); |
| } |
| return std::nullopt; |
| } |
| |
| TVM_FFI_INLINE static std::string TypeStr() { return "const char*"; } |
| TVM_FFI_INLINE static std::string TypeSchema() { return R"({"type":"const char*"})"; } |
| }; |
| |
| // TVMFFIByteArray, requirement: not nullable, do not retain ownership |
| template <> |
| struct TypeTraits<TVMFFIByteArray*> : public TypeTraitsBase { |
| static constexpr int32_t field_static_type_index = TypeIndex::kTVMFFIByteArrayPtr; |
| static constexpr bool storage_enabled = false; |
| |
| TVM_FFI_INLINE static void CopyToAnyView(TVMFFIByteArray* src, TVMFFIAny* result) { |
| TVM_FFI_ICHECK_NOTNULL(src); |
| result->type_index = TypeIndex::kTVMFFIByteArrayPtr; |
| result->zero_padding = 0; |
| TVM_FFI_CLEAR_PTR_PADDING_IN_FFI_ANY(result); |
| result->v_ptr = src; |
| } |
| |
| TVM_FFI_INLINE static void MoveToAny(TVMFFIByteArray* src, TVMFFIAny* result) { |
| TypeTraits<Bytes>::MoveToAny(Bytes(*src), result); |
| } |
| |
| TVM_FFI_INLINE static std::optional<TVMFFIByteArray*> TryCastFromAnyView(const TVMFFIAny* src) { |
| if (src->type_index == TypeIndex::kTVMFFIByteArrayPtr) { |
| return static_cast<TVMFFIByteArray*>(src->v_ptr); |
| } |
| return std::nullopt; |
| } |
| |
| TVM_FFI_INLINE static std::string TypeStr() { return StaticTypeKey::kTVMFFIByteArrayPtr; } |
| TVM_FFI_INLINE static std::string TypeSchema() { |
| return R"({"type":")" + std::string(StaticTypeKey::kTVMFFIByteArrayPtr) + R"("})"; |
| } |
| }; |
| |
| template <> |
| inline constexpr bool use_default_type_traits_v<std::string> = false; |
| |
| template <> |
| struct TypeTraits<std::string> |
| : public FallbackOnlyTraitsBase<std::string, const char*, TVMFFIByteArray*, Bytes, String> { |
| TVM_FFI_INLINE static void CopyToAnyView(const std::string& src, TVMFFIAny* result) { |
| result->type_index = TypeIndex::kTVMFFIRawStr; |
| result->zero_padding = 0; |
| result->v_c_str = src.c_str(); |
| } |
| |
| TVM_FFI_INLINE static void MoveToAny(std::string src, TVMFFIAny* result) { |
| // when we need to move to any, convert to owned object first |
| TypeTraits<String>::MoveToAny(String(std::move(src)), result); |
| } |
| |
| TVM_FFI_INLINE static std::string TypeStr() { return "std::string"; } |
| TVM_FFI_INLINE static std::string TypeSchema() { return R"({"type":"std::string"})"; } |
| |
| TVM_FFI_INLINE static std::string ConvertFallbackValue(const char* src) { |
| return std::string(src); |
| } |
| |
| TVM_FFI_INLINE static std::string ConvertFallbackValue(TVMFFIByteArray* src) { |
| return std::string(src->data, src->size); |
| } |
| |
| // NOLINTNEXTLINE(performance-unnecessary-value-param) |
| TVM_FFI_INLINE static std::string ConvertFallbackValue(Bytes src) { |
| return src.operator std::string(); |
| } |
| |
| // NOLINTNEXTLINE(performance-unnecessary-value-param) |
| TVM_FFI_INLINE static std::string ConvertFallbackValue(String src) { |
| return src.operator std::string(); |
| } |
| }; |
| |
| inline String operator+(const String& lhs, const String& rhs) { |
| size_t lhs_size = lhs.size(); |
| size_t rhs_size = rhs.size(); |
| return String::Concat(lhs.data(), lhs_size, rhs.data(), rhs_size); |
| } |
| |
| inline String operator+(const String& lhs, const std::string& rhs) { |
| size_t lhs_size = lhs.size(); |
| size_t rhs_size = rhs.size(); |
| return String::Concat(lhs.data(), lhs_size, rhs.data(), rhs_size); |
| } |
| |
| inline String operator+(const std::string& lhs, const String& rhs) { |
| size_t lhs_size = lhs.size(); |
| size_t rhs_size = rhs.size(); |
| return String::Concat(lhs.data(), lhs_size, rhs.data(), rhs_size); |
| } |
| |
| inline String operator+(const char* lhs, const String& rhs) { |
| size_t lhs_size = std::strlen(lhs); |
| size_t rhs_size = rhs.size(); |
| return String::Concat(lhs, lhs_size, rhs.data(), rhs_size); |
| } |
| |
| inline String operator+(const String& lhs, const char* rhs) { |
| size_t lhs_size = lhs.size(); |
| size_t rhs_size = std::strlen(rhs); |
| return String::Concat(lhs.data(), lhs_size, rhs, rhs_size); |
| } |
| |
| // Overload < operator |
| inline bool operator<(std::nullptr_t, const String& rhs) = delete; |
| inline bool operator<(const String& lhs, std::nullptr_t) = delete; |
| |
| inline bool operator<(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) < 0; } |
| |
| inline bool operator<(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) > 0; } |
| |
| inline bool operator<(const String& lhs, const String& rhs) { return lhs.compare(rhs) < 0; } |
| |
| inline bool operator<(const String& lhs, const char* rhs) { return lhs.compare(rhs) < 0; } |
| |
| inline bool operator<(const char* lhs, const String& rhs) { return rhs.compare(lhs) > 0; } |
| |
| // Overload > operator |
| inline bool operator>(std::nullptr_t, const String& rhs) = delete; |
| inline bool operator>(const String& lhs, std::nullptr_t) = delete; |
| |
| inline bool operator>(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) > 0; } |
| |
| inline bool operator>(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) < 0; } |
| |
| inline bool operator>(const String& lhs, const String& rhs) { return lhs.compare(rhs) > 0; } |
| |
| inline bool operator>(const String& lhs, const char* rhs) { return lhs.compare(rhs) > 0; } |
| |
| inline bool operator>(const char* lhs, const String& rhs) { return rhs.compare(lhs) < 0; } |
| |
| // Overload <= operator |
| inline bool operator<=(std::nullptr_t, const String& rhs) = delete; |
| inline bool operator<=(const String& lhs, std::nullptr_t) = delete; |
| |
| inline bool operator<=(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) <= 0; } |
| |
| inline bool operator<=(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) >= 0; } |
| |
| inline bool operator<=(const String& lhs, const String& rhs) { return lhs.compare(rhs) <= 0; } |
| |
| inline bool operator<=(const String& lhs, const char* rhs) { return lhs.compare(rhs) <= 0; } |
| |
| inline bool operator<=(const char* lhs, const String& rhs) { return rhs.compare(lhs) >= 0; } |
| |
| // Overload >= operator |
| inline bool operator>=(std::nullptr_t, const String& rhs) = delete; |
| inline bool operator>=(const String& lhs, std::nullptr_t) = delete; |
| |
| inline bool operator>=(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) >= 0; } |
| |
| inline bool operator>=(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) <= 0; } |
| |
| inline bool operator>=(const String& lhs, const String& rhs) { return lhs.compare(rhs) >= 0; } |
| |
| inline bool operator>=(const String& lhs, const char* rhs) { return lhs.compare(rhs) >= 0; } |
| |
| inline bool operator>=(const char* lhs, const String& rhs) { return rhs.compare(lhs) <= 0; } |
| |
| // delete Overload == operator for nullptr |
| inline bool operator==(const String& lhs, std::nullptr_t) = delete; |
| inline bool operator==(std::nullptr_t, const String& rhs) = delete; |
| |
| inline bool operator==(const String& lhs, const std::string& rhs) { |
| return Bytes::memequal(lhs.data(), rhs.data(), lhs.size(), rhs.size()); |
| } |
| |
| inline bool operator==(const std::string& lhs, const String& rhs) { |
| return Bytes::memequal(lhs.data(), rhs.data(), lhs.size(), rhs.size()); |
| } |
| |
| inline bool operator==(const String& lhs, const String& rhs) { |
| return Bytes::memequal(lhs.data(), rhs.data(), lhs.size(), rhs.size()); |
| } |
| |
| inline bool operator==(const String& lhs, const char* rhs) { return lhs.compare(rhs) == 0; } |
| |
| inline bool operator==(const char* lhs, const String& rhs) { return rhs.compare(lhs) == 0; } |
| |
| // Overload != operator |
| inline bool operator!=(const String& lhs, std::nullptr_t) = delete; |
| inline bool operator!=(std::nullptr_t, const String& rhs) = delete; |
| |
| inline bool operator!=(const String& lhs, const std::string& rhs) { return lhs.compare(rhs) != 0; } |
| |
| inline bool operator!=(const std::string& lhs, const String& rhs) { return rhs.compare(lhs) != 0; } |
| |
| inline bool operator!=(const String& lhs, const String& rhs) { return lhs.compare(rhs) != 0; } |
| |
| inline bool operator!=(const String& lhs, const char* rhs) { return lhs.compare(rhs) != 0; } |
| |
| inline bool operator!=(const char* lhs, const String& rhs) { return rhs.compare(lhs) != 0; } |
| |
| inline std::ostream& operator<<(std::ostream& out, const String& input) { |
| out.write(input.data(), static_cast<std::streamsize>(input.size())); |
| return out; |
| } |
| /// \endcond |
| } // namespace ffi |
| } // namespace tvm |
| |
| /// \cond Doxygen_Suppress |
| namespace std { |
| |
| template <> |
| struct hash<::tvm::ffi::Bytes> { |
| std::size_t operator()(const ::tvm::ffi::Bytes& bytes) const { |
| return std::hash<std::string_view>()(std::string_view(bytes.data(), bytes.size())); |
| } |
| }; |
| |
| template <> |
| struct hash<::tvm::ffi::String> { |
| std::size_t operator()(const ::tvm::ffi::String& str) const { |
| return std::hash<std::string_view>()(std::string_view(str.data(), str.size())); |
| } |
| }; |
| } // namespace std |
| /// \endcond |
| #endif // TVM_FFI_STRING_H_ |