Spicy
util.h
1 // Copyright (c) 2020-2021 by the Zeek Project. See LICENSE for details.
2 
3 #pragma once
4 
5 #include <cxxabi.h>
6 
7 #include <algorithm>
8 #include <list>
9 #include <memory>
10 #include <numeric>
11 #include <optional>
12 #include <set>
13 #include <string>
14 #include <string_view>
15 #include <tuple>
16 #include <utility>
17 #include <vector>
18 
19 #include <hilti/rt/autogen/config.h>
20 #include <hilti/rt/exception.h>
21 #include <hilti/rt/filesystem.h>
22 #include <hilti/rt/result.h>
23 #include <hilti/rt/types/set_fwd.h>
24 #include <hilti/rt/types/time.h>
25 #include <hilti/rt/types/vector_fwd.h>
26 
27 namespace hilti::rt {
28 
29 void internalError(const std::string& msg) __attribute__((noreturn));
30 
31 } // namespace hilti::rt
32 
33 #undef TINYFORMAT_ERROR
34 #define TINYFORMAT_ERROR(reason) throw ::hilti::rt::FormattingError(reason)
35 #include <hilti/rt/3rdparty/tinyformat/tinyformat.h>
36 #include <hilti/rt/extension-points.h>
37 #include <hilti/rt/fmt.h>
38 
39 extern const char* __hlto_scope; // defined by linker code in HLTO file
40 
41 namespace hilti::rt {
42 
44 extern std::string version();
45 
47 extern bool isDebugVersion();
48 
50 extern void abort_with_backtrace() __attribute__((noreturn));
51 
53 inline std::string linker_scope() { return __hlto_scope; }
54 
56 extern void cannot_be_reached() __attribute__((noreturn));
57 
59 struct ResourceUsage {
60  // Note when changing this, update `resource_usage()`.
61  double user_time; //< user time since runtime initialization
62  double system_time; //< system time since runtime initialization
63  uint64_t memory_heap; //< current size of heap in bytes
64  uint64_t num_fibers; //< number of fibers currently in use
65  uint64_t max_fibers; //< high-water mark for number of fibers in use
66  uint64_t cached_fibers; //< number of fibers currently cached for reuse
67 };
68 
71 
73 extern std::optional<std::string> getenv(const std::string& name);
74 
82 
84 hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path& p);
85 
91 inline std::string_view rtrim(std::string_view s, const std::string& chars) noexcept {
92  s.remove_suffix(s.size() -
93  [](size_t pos) { return pos != std::string_view::npos ? pos + 1 : 0; }(s.find_last_not_of(chars)));
94  return s;
95 }
96 
102 inline std::string_view ltrim(std::string_view s, const std::string& chars) noexcept {
103  s.remove_prefix(std::min(s.find_first_not_of(chars), s.size()));
104  return s;
105 }
106 
113 inline std::string_view trim(std::string_view s, const std::string& chars) noexcept {
114  return ltrim(rtrim(s, chars), chars);
115 }
116 
117 namespace detail {
118 constexpr char whitespace_chars[] = " \t\f\v\n\r";
119 } // namespace detail
120 
126 inline std::string_view rtrim(std::string_view s) noexcept { return rtrim(s, detail::whitespace_chars); }
127 
133 inline std::string_view ltrim(std::string_view s) noexcept { return ltrim(s, detail::whitespace_chars); }
134 
140 inline std::string_view trim(std::string_view s) noexcept { return trim(s, detail::whitespace_chars); }
141 
148 std::vector<std::string_view> split(std::string_view s, std::string_view delim);
149 
155 std::vector<std::string_view> split(std::string_view s);
156 
163 extern std::pair<std::string, std::string> split1(std::string s);
164 
171 extern std::pair<std::string, std::string> rsplit1(std::string s);
172 
179 extern std::pair<std::string, std::string> split1(std::string s, const std::string& delim);
180 
187 extern std::pair<std::string, std::string> rsplit1(std::string s, const std::string& delim);
188 
194 std::string replace(std::string s, std::string_view o, std::string_view n);
195 
201 inline bool startsWith(const std::string& s, const std::string& prefix) { return s.find(prefix) == 0; }
202 
207 template<typename T, typename TIter = decltype(std::begin(std::declval<T>())),
208  typename = decltype(std::end(std::declval<T>()))>
209 constexpr auto enumerate(T&& iterable) {
210  struct iterator {
211  size_t i;
212  TIter iter;
213  bool operator!=(const iterator& other) const { return iter != other.iter; }
214  void operator++() {
215  ++i;
216  ++iter;
217  }
218  auto operator*() const { return std::tie(i, *iter); }
219  };
220  struct iterable_wrapper {
221  T iterable;
222  auto begin() { return iterator{0, std::begin(iterable)}; }
223  auto end() { return iterator{0, std::end(iterable)}; }
224  };
225  return iterable_wrapper{std::forward<T>(iterable)};
226 }
227 
228 /*
229  * Expands escape sequences in a UTF8 string. The following escape sequences
230  * are supported:
231  *
232  * ============ ============================
233  * Escape Result
234  * ============ ============================
235  * \\ Backslash
236  * \\n Line feed
237  * \\r Carriage return
238  * \\t Tabulator
239  * \\uXXXX 16-bit Unicode codepoint
240  * \\UXXXXXXXX 32-bit Unicode codepoint
241  * \\xXX 8-bit hex value
242  * ============ ============================
243  *
244  * @param str string to expand
245  * @return A UTF8 string with escape sequences expanded
246  */
247 std::string expandEscapes(std::string s);
248 
249 /*
250  * Escapes non-printable characters in a raw string. This produces a new
251  * string that can be reverted by expandEscapes().
252  *
253  * @param str string to escape
254  * @param escape_quotes if true, also escapes quotes characters
255  * @param use_octal use `\NNN` instead of `\XX` (needed for C++)
256  * @return escaped string
257  *
258  * \todo This is getting messy; should use enums instead of booleans.
259  */
260 std::string escapeBytes(std::string_view s, bool escape_quotes = false, bool use_octal = false);
261 
262 /*
263  * Escapes non-printable and control characters in an UTF8 string. This
264  * produces a new string that can be reverted by expandEscapes().
265  *
266  * @param str string to escape
267  * @param escape_quotes if true, also escapes quotes characters
268  * @param escape_control if false, do not escape control characters
269  * @param keep_hex if true, do not escape our custom "\xYY" escape codes
270  * @return escaped std::string
271  *
272  * \todo This is getting messy; should use enums instead of booleans.
273  */
274 std::string escapeUTF8(std::string_view s, bool escape_quotes = false, bool escape_control = true,
275  bool keep_hex = false);
276 
281 template<typename T>
282 std::string join(const T& l, const std::string& delim = "") {
283  std::string result;
284  bool first = true;
285 
286  for ( const auto& i : l ) {
287  if ( not first )
288  result += delim;
289  result += std::string(i);
290  first = false;
291  }
292 
293  return result;
294 }
295 
296 namespace detail {
297 
299 template<typename T>
300 struct is_Vector : std::false_type {};
301 
302 template<typename T, typename Allocator>
303 struct is_Vector<Vector<T, Allocator>> : std::true_type {};
304 
307 template<typename C, typename Y>
308 constexpr auto transform_result_value(const C&) {
309  using X = typename C::value_type;
310 
311  if constexpr ( std::is_same_v<C, std::vector<X>> ) {
312  return std::vector<Y>();
313  }
314  else if constexpr ( std::is_same_v<C, std::set<X>> ) {
315  return std::set<Y>();
316  }
317  else if constexpr ( is_Vector<C>::value ) {
318  // We do not preserve the allocator since a proper custom one could depend on `Y`.
319  return Vector<Y>();
320  }
321  else if constexpr ( std::is_same_v<C, Set<X>> ) {
322  return Set<Y>();
323  }
324 
325  // No default value defined for type.
326 }
327 
328 } // namespace detail
329 
331 template<typename C, typename F>
332 auto transform(const C& x, F f) {
333  using Y = typename std::result_of_t<F(typename C::value_type&)>;
334 
335  auto y = detail::transform_result_value<C, Y>(x);
336  std::transform(std::begin(x), std::end(x), std::inserter(y, std::end(y)), f);
337 
338  return y;
339 }
340 
341 class OutOfRange;
342 
365 template<class Iter, typename Result>
366 inline Iter atoi_n(Iter s, Iter e, uint8_t base, Result* result) {
367  if ( base < 2 || base > 36 )
368  throw OutOfRange("base for numerical conversion must be between 2 and 36");
369 
370  if ( s == e )
371  throw InvalidArgument("cannot decode from empty range");
372 
373  std::optional<Result> n = std::nullopt;
374  bool neg = false;
375  auto it = s;
376 
377  if ( *it == '-' ) {
378  neg = true;
379  ++it;
380  }
381  else if ( *it == '+' ) {
382  neg = false;
383  ++it;
384  }
385 
386  for ( ; it != e; ++it ) {
387  auto c = *it;
388 
389  Result d;
390  if ( c >= '0' && c < '0' + base )
391  d = c - '0';
392  else if ( c >= 'a' && c < 'a' - 10 + base )
393  d = c - 'a' + 10;
394  else if ( c >= 'A' && c < 'A' - 10 + base )
395  d = c - 'A' + 10;
396  else
397  break;
398 
399  n = n.value_or(Result()) * base + d;
400  }
401 
402  if ( ! n )
403  return s;
404 
405  s = it;
406 
407  if ( neg )
408  *result = -*n;
409  else
410  *result = *n;
411 
412  return s;
413 }
414 
418 template<typename I1, typename I2>
419 inline I1 pow(I1 base, I2 exp) {
420  I1 x = 1;
421 
422  while ( exp ) {
423  if ( exp & 1 )
424  x *= base;
425 
426  exp >>= 1;
427  base *= base;
428  }
429 
430  return x;
431 }
432 
433 // Tuple for-each, from
434 // https://stackoverflow.com/questions/40212085/type-erasure-for-objects-containing-a-stdtuple-in-c11
435 namespace detail {
436 template<typename T, typename F, std::size_t... Is>
437 constexpr auto map_tuple(T&& tup, F& f, std::index_sequence<Is...> /*unused*/) {
438  return std::make_tuple(f(std::get<Is>(std::forward<T>(tup)))...);
439 }
440 
441 template<typename T, std::size_t... Is>
442 auto join_tuple(T&& tup, std::index_sequence<Is...> /*unused*/) {
443  std::vector<std::string> x = {rt::to_string(std::get<Is>(std::forward<T>(tup)))...};
444  return join(x, ", ");
445 }
446 
447 template<typename T, std::size_t... Is>
448 auto join_tuple_for_print(T&& tup, std::index_sequence<Is...> /*unused*/) {
449  std::vector<std::string> x = {rt::to_string_for_print(std::get<Is>(std::forward<T>(tup)))...};
450  return join(x, ", ");
451 }
452 } // namespace detail
453 
455 template<typename F, std::size_t I = 0, typename... Ts>
456 void tuple_for_each(const std::tuple<Ts...>& tup, F func) {
457  if constexpr ( I == sizeof...(Ts) )
458  return;
459  else {
460  func(std::get<I>(tup));
461  tuple_for_each<F, I + 1>(tup, func);
462  }
463 }
464 
469 template<typename T, typename F, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
470 constexpr auto map_tuple(T&& tup, F f) {
471  return detail::map_tuple(std::forward<T>(tup), f, std::make_index_sequence<TupSize>{});
472 }
473 
480 template<typename T, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
481 auto join_tuple(T&& tup) {
482  return detail::join_tuple(std::forward<T>(tup), std::make_index_sequence<TupSize>{});
483 }
484 
491 template<typename T, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
492 auto join_tuple_for_print(T&& tup) {
493  return detail::join_tuple_for_print(std::forward<T>(tup), std::make_index_sequence<TupSize>{});
494 }
495 
496 template<typename>
497 struct is_tuple : std::false_type {};
498 
500 template<typename... T>
501 struct is_tuple<std::tuple<T...>> : std::true_type {};
502 
504 enum class ByteOrder : int64_t { Little, Big, Network, Host, Undef = -1 };
505 
510 extern ByteOrder systemByteOrder();
511 
512 namespace detail::adl {
513 std::string to_string(const ByteOrder& x, tag /*unused*/);
514 }
515 
528 std::string strftime(const std::string& format, const Time& time);
529 
542 Time strptime(const std::string& buf, const std::string& format);
543 
544 // RAII helper to create a temporary directory.
546 public:
548  const auto tmpdir = hilti::rt::filesystem::temp_directory_path();
549  auto template_ = (tmpdir / "hilti-rt-test-XXXXXX").native();
550  auto path = ::mkdtemp(template_.data());
551  if ( ! path )
552  throw RuntimeError("cannot create temporary directory");
553 
554  _path = path;
555  }
556 
557  TemporaryDirectory(const TemporaryDirectory& other) = delete;
558  TemporaryDirectory(TemporaryDirectory&& other) { _path = std::move(other._path); }
559 
560  ~TemporaryDirectory() {
561  if ( ! hilti::rt::filesystem::exists(_path) )
562  return;
563 
564  // Make sure we have permissions to remove the directory.
565  hilti::rt::filesystem::permissions(_path, hilti::rt::filesystem::perms::all);
566  for ( const auto& entry : hilti::rt::filesystem::recursive_directory_iterator(_path) )
567  hilti::rt::filesystem::permissions(entry, hilti::rt::filesystem::perms::all);
568 
569  std::error_code ec;
570  hilti::rt::filesystem::remove_all(_path, ec); // ignore errors
571  }
572 
573  const auto& path() const { return _path; }
574 
575  TemporaryDirectory& operator=(const TemporaryDirectory& other) = delete;
576  TemporaryDirectory& operator=(TemporaryDirectory&& other) {
577  _path = std::move(other._path);
578  return *this;
579  }
580 
581 private:
582  hilti::rt::filesystem::path _path;
583 };
584 
585 // Combine two or more hashes.
586 template<typename... Hashes>
587 constexpr std::size_t hashCombine(std::size_t hash1, std::size_t hash2, Hashes... hashes) {
588  auto result = hash1 ^ (hash2 << 1);
589 
590  if constexpr ( sizeof...(hashes) > 0 )
591  return hashCombine(result, hashes...);
592  else
593  return result;
594 }
595 
596 } // namespace hilti::rt
Definition: network.h:20
std::string to_string(T &&x)
Definition: extension-points.h:26
auto transform(const C &x, F f)
Definition: util.h:332
bool isDebugVersion()
Definition: util.cc:34
Definition: util.h:497
Definition: util.h:300
Definition: any.h:7
std::string_view rtrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:91
std::string_view ltrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:102
void internalError(const std::string &msg) __attribute__((noreturn))
Definition: logging.cc:17
Definition: optional.h:79
std::string replace(std::string s, std::string_view o, std::string_view n)
Definition: util.cc:377
hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path &p)
Definition: util.cc:102
std::pair< std::string, std::string > split1(std::string s)
Definition: util.cc:156
Definition: set.h:108
bool startsWith(const std::string &s, const std::string &prefix)
Definition: util.h:201
hilti::rt::Result< hilti::rt::filesystem::path > createTemporaryFile(const std::string &prefix="")
Definition: util.cc:84
void cannot_be_reached() __attribute__((noreturn))
Definition: util.cc:52
std::string strftime(const std::string &format, const Time &time)
Definition: util.cc:412
void tuple_for_each(const std::tuple< Ts... > &tup, F func)
Definition: util.h:456
Definition: util.h:545
constexpr auto map_tuple(T &&tup, F f)
Definition: util.h:470
std::vector< std::string_view > split(std::string_view s, std::string_view delim)
Definition: util.cc:112
Time strptime(const std::string &buf, const std::string &format)
Definition: util.cc:438
auto join_tuple_for_print(T &&tup)
Definition: util.h:492
std::optional< std::string > getenv(const std::string &name)
Definition: util.cc:77
ByteOrder
Definition: util.h:504
Definition: util.h:59
constexpr auto enumerate(T &&iterable)
Definition: util.h:209
Iter atoi_n(Iter s, Iter e, uint8_t base, Result *result)
Definition: util.h:366
std::pair< std::string, std::string > rsplit1(std::string s)
Definition: util.cc:163
std::string join(const T &l, const std::string &delim="")
Definition: util.h:282
Definition: vector.h:251
std::string version()
Definition: util.cc:22
ByteOrder systemByteOrder()
Definition: util.cc:390
auto join_tuple(T &&tup)
Definition: util.h:481
void abort_with_backtrace() __attribute__((noreturn))
Definition: util.cc:44
ResourceUsage resource_usage()
Definition: util.cc:54
std::string_view trim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:113
Definition: time.h:20
I1 pow(I1 base, I2 exp)
Definition: util.h:419
std::string linker_scope()
Definition: util.h:53
Definition: result.h:67