string.h raw

   1  // Copyright (c) 2019-present The Bitcoin Core developers
   2  // Distributed under the MIT software license, see the accompanying
   3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
   4  
   5  #ifndef BITCOIN_UTIL_STRING_H
   6  #define BITCOIN_UTIL_STRING_H
   7  
   8  #include <algorithm>
   9  #include <array>
  10  #include <cstddef>
  11  #include <cstdint>
  12  #include <initializer_list>
  13  #include <locale>
  14  #include <optional>
  15  #include <span>
  16  #include <sstream>
  17  #include <string>
  18  #include <string_view>
  19  #include <vector>
  20  
  21  #include <attributes.h>
  22  
  23  namespace util {
  24  namespace detail {
  25  template <unsigned num_params>
  26  constexpr static void CheckNumFormatSpecifiers(const char* str)
  27  {
  28      unsigned count_normal{0}; // Number of "normal" specifiers, like %s
  29      unsigned count_pos{0};    // Max number in positional specifier, like %8$s
  30      for (auto it{str}; *it != '\0'; ++it) {
  31          if (*it != '%' || *++it == '%') continue; // Skip escaped %%
  32  
  33          auto add_arg = [&] {
  34              unsigned maybe_num{0};
  35              while ('0' <= *it && *it <= '9') {
  36                  maybe_num *= 10;
  37                  maybe_num += *it - '0';
  38                  ++it;
  39              }
  40  
  41              if (*it == '$') {
  42                  ++it;
  43                  // Positional specifier, like %8$s
  44                  if (maybe_num == 0) throw "Positional format specifier must have position of at least 1";
  45                  count_pos = std::max(count_pos, maybe_num);
  46              } else {
  47                  // Non-positional specifier, like %s
  48                  ++count_normal;
  49              }
  50          };
  51  
  52          // Increase argument count and consume positional specifier, if present.
  53          add_arg();
  54  
  55          // Consume flags.
  56          while (*it == '#' || *it == '0' || *it == '-' || *it == ' ' || *it == '+') ++it;
  57  
  58          auto parse_size = [&] {
  59              if (*it == '*') {
  60                  ++it;
  61                  add_arg();
  62              } else {
  63                  while ('0' <= *it && *it <= '9') ++it;
  64              }
  65          };
  66  
  67          // Consume dynamic or static width value.
  68          parse_size();
  69  
  70          // Consume dynamic or static precision value.
  71          if (*it == '.') {
  72              ++it;
  73              parse_size();
  74          }
  75  
  76          if (*it == '\0') throw "Format specifier incorrectly terminated by end of string";
  77  
  78          // Length and type in "[flags][width][.precision][length]type"
  79          // is not checked. Parsing continues with the next '%'.
  80      }
  81      if (count_normal && count_pos) throw "Format specifiers must be all positional or all non-positional!";
  82      unsigned count{count_normal | count_pos};
  83      if (num_params != count) throw "Format specifier count must match the argument count!";
  84  }
  85  } // namespace detail
  86  
  87  /**
  88   * @brief A wrapper for a compile-time partially validated format string
  89   *
  90   * This struct can be used to enforce partial compile-time validation of format
  91   * strings, to reduce the likelihood of tinyformat throwing exceptions at
  92   * run-time. Validation is partial to try and prevent the most common errors
  93   * while avoiding re-implementing the entire parsing logic.
  94   */
  95  template <unsigned num_params>
  96  struct ConstevalFormatString {
  97      const char* const fmt;
  98      consteval ConstevalFormatString(const char* str) : fmt{str} { detail::CheckNumFormatSpecifiers<num_params>(fmt); }
  99  };
 100  
 101  void ReplaceAll(std::string& in_out, const std::string& search, const std::string& substitute);
 102  
 103  /** Split a string on any char found in separators, returning a vector.
 104   *
 105   * If sep does not occur in sp, a singleton with the entirety of sp is returned.
 106   *
 107   * @param[in] include_sep Whether to include the separator at the end of the left side of the splits.
 108   *
 109   * Note that this function does not care about braces, so splitting
 110   * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
 111   *
 112   * If include_sep == true, splitting "foo(bar(1),2),3) on ','
 113   * will return:
 114   *  - foo(bar(1),
 115   *  - 2),
 116   *  - 3)
 117   */
 118  template <typename T = std::span<const char>>
 119  std::vector<T> Split(const std::span<const char>& sp, std::string_view separators, bool include_sep = false)
 120  {
 121      std::vector<T> ret;
 122      auto it = sp.begin();
 123      auto start = it;
 124      while (it != sp.end()) {
 125          if (separators.find(*it) != std::string::npos) {
 126              if (include_sep) {
 127                  ret.emplace_back(start, it + 1);
 128              } else {
 129                  ret.emplace_back(start, it);
 130              }
 131              start = it + 1;
 132          }
 133          ++it;
 134      }
 135      ret.emplace_back(start, it);
 136      return ret;
 137  }
 138  
 139  /** Split a string on every instance of sep, returning a vector.
 140   *
 141   * If sep does not occur in sp, a singleton with the entirety of sp is returned.
 142   *
 143   * Note that this function does not care about braces, so splitting
 144   * "foo(bar(1),2),3) on ',' will return {"foo(bar(1)", "2)", "3)"}.
 145   */
 146  template <typename T = std::span<const char>>
 147  std::vector<T> Split(const std::span<const char>& sp, char sep, bool include_sep = false)
 148  {
 149      return Split<T>(sp, std::string_view{&sep, 1}, include_sep);
 150  }
 151  
 152  [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, char sep)
 153  {
 154      return Split<std::string>(str, sep);
 155  }
 156  
 157  [[nodiscard]] inline std::vector<std::string> SplitString(std::string_view str, std::string_view separators)
 158  {
 159      return Split<std::string>(str, separators);
 160  }
 161  
 162  [[nodiscard]] inline std::string_view TrimStringView(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
 163  {
 164      std::string::size_type front = str.find_first_not_of(pattern);
 165      if (front == std::string::npos) {
 166          return {};
 167      }
 168      std::string::size_type end = str.find_last_not_of(pattern);
 169      return str.substr(front, end - front + 1);
 170  }
 171  
 172  [[nodiscard]] inline std::string TrimString(std::string_view str, std::string_view pattern = " \f\n\r\t\v")
 173  {
 174      return std::string(TrimStringView(str, pattern));
 175  }
 176  
 177  [[nodiscard]] inline std::string_view RemoveSuffixView(std::string_view str, std::string_view suffix)
 178  {
 179      if (str.ends_with(suffix)) {
 180          return str.substr(0, str.size() - suffix.size());
 181      }
 182      return str;
 183  }
 184  
 185  [[nodiscard]] inline std::string_view RemovePrefixView(std::string_view str, std::string_view prefix)
 186  {
 187      if (str.starts_with(prefix)) {
 188          return str.substr(prefix.size());
 189      }
 190      return str;
 191  }
 192  
 193  [[nodiscard]] inline std::string RemovePrefix(std::string_view str, std::string_view prefix)
 194  {
 195      return std::string(RemovePrefixView(str, prefix));
 196  }
 197  
 198  /**
 199   * Join all container items. Typically used to concatenate strings but accepts
 200   * containers with elements of any type.
 201   *
 202   * @param container The items to join
 203   * @param separator The separator
 204   * @param unary_op  Apply this operator to each item
 205   */
 206  template <typename C, typename S, typename UnaryOp>
 207  // NOLINTNEXTLINE(misc-no-recursion)
 208  auto Join(const C& container, const S& separator, UnaryOp unary_op)
 209  {
 210      decltype(unary_op(*container.begin())) ret;
 211      bool first{true};
 212      for (const auto& item : container) {
 213          if (!first) ret += separator;
 214          ret += unary_op(item);
 215          first = false;
 216      }
 217      return ret;
 218  }
 219  
 220  template <typename C, typename S>
 221  auto Join(const C& container, const S& separator)
 222  {
 223      return Join(container, separator, [](const auto& i) { return i; });
 224  }
 225  
 226  /**
 227   * Create an unordered multi-line list of items.
 228   */
 229  inline std::string MakeUnorderedList(const std::vector<std::string>& items)
 230  {
 231      return Join(items, "\n", [](const std::string& item) { return "- " + item; });
 232  }
 233  
 234  /**
 235   * Check if a string does not contain any embedded NUL (\0) characters
 236   */
 237  [[nodiscard]] inline bool ContainsNoNUL(std::string_view str) noexcept
 238  {
 239      for (auto c : str) {
 240          if (c == 0) return false;
 241      }
 242      return true;
 243  }
 244  
 245  /**
 246   * Locale-independent version of std::to_string
 247   */
 248  template <typename T>
 249  std::string ToString(const T& t)
 250  {
 251      std::ostringstream oss;
 252      oss.imbue(std::locale::classic());
 253      oss << t;
 254      return oss.str();
 255  }
 256  
 257  /**
 258   * Check whether a container begins with the given prefix.
 259   */
 260  template <typename T1, size_t PREFIX_LEN>
 261  [[nodiscard]] inline bool HasPrefix(const T1& obj,
 262                                  const std::array<uint8_t, PREFIX_LEN>& prefix)
 263  {
 264      return obj.size() >= PREFIX_LEN &&
 265             std::equal(std::begin(prefix), std::end(prefix), std::begin(obj));
 266  }
 267  
 268  struct LineReader {
 269      const std::span<const std::byte>::iterator start;
 270      const std::span<const std::byte>::iterator end;
 271      const size_t max_line_length;
 272      std::span<const std::byte>::iterator it;
 273  
 274      explicit LineReader(std::span<const std::byte> buffer, size_t max_line_length);
 275      explicit LineReader(std::string_view str, size_t max_line_length) : LineReader{std::as_bytes(std::span{str}), max_line_length} {}
 276  
 277      /**
 278       * Returns a string from current iterator position up to (but not including) next \n
 279       * and advances iterator to the character following the \n on success.
 280       * Will not return a line longer than max_line_length.
 281       * @returns the next string from the buffer.
 282       *          std::nullopt if end of buffer is reached without finding a \n.
 283       * @throws a std::runtime_error if max_line_length + 1 bytes are read without finding \n.
 284       */
 285      std::optional<std::string_view> ReadLine() LIFETIMEBOUND;
 286  
 287      /**
 288       * Returns string from current iterator position of specified length
 289       * if possible and advances iterator on success.
 290       * May exceed max_line_length but will not read past end of buffer.
 291       * @param[in]   len     The number of bytes to read from the buffer
 292       * @returns a string of the expected length.
 293       * @throws a std::runtime_error if there is not enough data in the buffer.
 294       */
 295      std::string_view ReadLength(size_t len) LIFETIMEBOUND;
 296  
 297      /**
 298       * Returns remaining size of bytes in buffer
 299       */
 300      size_t Remaining() const;
 301  
 302      /**
 303       * Returns number of bytes already read from buffer
 304       */
 305      size_t Consumed() const;
 306  };
 307  } // namespace util
 308  
 309  #endif // BITCOIN_UTIL_STRING_H
 310