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/exception.h>
20 #include <hilti/rt/filesystem.h>
21 #include <hilti/rt/result.h>
22 #include <hilti/rt/types/set_fwd.h>
23 #include <hilti/rt/types/time.h>
24 #include <hilti/rt/types/vector_fwd.h>
25 
38 #define HILTI_RT_ENUM_WITH_DEFAULT(name, default_, ...) \
39  struct name { \
40  enum Value : int64_t { __VA_ARGS__ }; \
41  constexpr name(int64_t value = default_) noexcept : _value(value) {} \
42  friend name Enum(Value value) { return name(value); } \
43  friend constexpr bool operator==(const name& a, const name& b) noexcept { return a.value() == b.value(); } \
44  friend constexpr bool operator!=(const name& a, const name& b) noexcept { return ! (a == b); } \
45  friend constexpr bool operator<(const name& a, const name& b) noexcept { return a.value() < b.value(); } \
46  constexpr int64_t value() const { return _value; } \
47  int64_t _value; \
48  }
49 
62 #define HILTI_RT_ENUM(name, ...) HILTI_RT_ENUM_WITH_DEFAULT(name, Undef, __VA_ARGS__)
63 
64 namespace hilti::rt {
65 
67 void internalError(const std::string& msg) __attribute__((noreturn));
68 
69 } // namespace hilti::rt
70 
71 #undef TINYFORMAT_ERROR
72 #define TINYFORMAT_ERROR(reason) throw ::hilti::rt::FormattingError(reason)
73 #include <hilti/rt/3rdparty/tinyformat/tinyformat.h>
74 #include <hilti/rt/extension-points.h>
75 #include <hilti/rt/fmt.h>
76 
77 namespace hilti::rt {
78 
80 extern std::string version();
81 
83 extern void abort_with_backtrace() __attribute__((noreturn));
84 
86 extern void cannot_be_reached() __attribute__((noreturn));
87 
89 struct ResourceUsage {
90  // Note when changing this, update `resource_usage()`.
91  double user_time; //< user time since runtime initialization
92  double system_time; //< system time since runtime initialization
93  uint64_t memory_heap; //< current size of heap in bytes
94  uint64_t num_fibers; //< number of fibers currently in use
95  uint64_t max_fibers; //< high-water mark for number of fibers in use
96  uint64_t cached_fibers; //< number of fibers currently cached for reuse
97 };
98 
101 
103 extern std::optional<std::string> getenv(const std::string& name);
104 
112 
114 hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path& p);
115 
121 inline std::string_view rtrim(std::string_view s, const std::string& chars) noexcept {
122  s.remove_suffix(s.size() -
123  [](size_t pos) { return pos != std::string_view::npos ? pos + 1 : 0; }(s.find_last_not_of(chars)));
124  return s;
125 }
126 
132 inline std::string_view ltrim(std::string_view s, const std::string& chars) noexcept {
133  s.remove_prefix(std::min(s.find_first_not_of(chars), s.size()));
134  return s;
135 }
136 
143 inline std::string_view trim(std::string_view s, const std::string& chars) noexcept {
144  return ltrim(rtrim(s, chars), chars);
145 }
146 
147 namespace detail {
148 constexpr char whitespace_chars[] = " \t\f\v\n\r";
149 } // namespace detail
150 
156 inline std::string_view rtrim(std::string_view s) noexcept { return rtrim(s, detail::whitespace_chars); }
157 
163 inline std::string_view ltrim(std::string_view s) noexcept { return ltrim(s, detail::whitespace_chars); }
164 
170 inline std::string_view trim(std::string_view s) noexcept { return trim(s, detail::whitespace_chars); }
171 
178 std::vector<std::string_view> split(std::string_view s, std::string_view delim);
179 
185 std::vector<std::string_view> split(std::string_view s);
186 
193 extern std::pair<std::string, std::string> split1(std::string s);
194 
201 extern std::pair<std::string, std::string> rsplit1(std::string s);
202 
209 extern std::pair<std::string, std::string> split1(std::string s, const std::string& delim);
210 
217 extern std::pair<std::string, std::string> rsplit1(std::string s, const std::string& delim);
218 
224 std::string replace(std::string s, std::string_view o, std::string_view n);
225 
231 bool startsWith(const std::string& s, const std::string& prefix);
232 
237 template<typename T, typename TIter = decltype(std::begin(std::declval<T>())),
238  typename = decltype(std::end(std::declval<T>()))>
239 constexpr auto enumerate(T&& iterable) {
240  struct iterator {
241  size_t i;
242  TIter iter;
243  bool operator!=(const iterator& other) const { return iter != other.iter; }
244  void operator++() {
245  ++i;
246  ++iter;
247  }
248  auto operator*() const { return std::tie(i, *iter); }
249  };
250  struct iterable_wrapper {
251  T iterable;
252  auto begin() { return iterator{0, std::begin(iterable)}; }
253  auto end() { return iterator{0, std::end(iterable)}; }
254  };
255  return iterable_wrapper{std::forward<T>(iterable)};
256 }
257 
258 /*
259  * Expands escape sequences in a UTF8 string. The following escape sequences
260  * are supported:
261  *
262  * ============ ============================
263  * Escape Result
264  * ============ ============================
265  * \\ Backslash
266  * \\n Line feed
267  * \\r Carriage return
268  * \\t Tabulator
269  * \\uXXXX 16-bit Unicode codepoint
270  * \\UXXXXXXXX 32-bit Unicode codepoint
271  * \\xXX 8-bit hex value
272  * ============ ============================
273  *
274  * @param str string to expand
275  * @return A UTF8 string with escape sequences expanded
276  */
277 std::string expandEscapes(std::string s);
278 
279 /*
280  * Escapes non-printable characters in a raw string. This produces a new
281  * string that can be reverted by expandEscapes().
282  *
283  * @param str string to escape
284  * @param escape_quotes if true, also escapes quotes characters
285  * @param use_octal use `\NNN` instead of `\XX` (needed for C++)
286  * @return escaped string
287  *
288  * \todo This is getting messy; should use enums instead of booleans.
289  */
290 std::string escapeBytes(std::string_view s, bool escape_quotes = false, bool use_octal = false);
291 
292 /*
293  * Escapes non-printable and control characters in an UTF8 string. This
294  * produces a new string that can be reverted by expandEscapes().
295  *
296  * @param str string to escape
297  * @param escape_quotes if true, also escapes quotes characters
298  * @param escape_control if false, do not escape control characters
299  * @param keep_hex if true, do not escape our custom "\xYY" escape codes
300  * @return escaped std::string
301  *
302  * \todo This is getting messy; should use enums instead of booleans.
303  */
304 std::string escapeUTF8(std::string_view s, bool escape_quotes = false, bool escape_control = true,
305  bool keep_hex = false);
306 
311 template<typename T>
312 std::string join(const T& l, const std::string& delim = "") {
313  std::string result;
314  bool first = true;
315 
316  for ( const auto& i : l ) {
317  if ( not first )
318  result += delim;
319  result += std::string(i);
320  first = false;
321  }
322 
323  return result;
324 }
325 
326 namespace detail {
327 
329 template<typename T>
330 struct is_Vector : std::false_type {};
331 
332 template<typename T, typename Allocator>
333 struct is_Vector<Vector<T, Allocator>> : std::true_type {};
334 
337 template<typename C, typename Y>
338 constexpr auto transform_result_value(const C&) {
339  using X = typename C::value_type;
340 
341  if constexpr ( std::is_same_v<C, std::vector<X>> ) {
342  return std::vector<Y>();
343  }
344  else if constexpr ( std::is_same_v<C, std::set<X>> ) {
345  return std::set<Y>();
346  }
347  else if constexpr ( is_Vector<C>::value ) {
348  // We do not preserve the allocator since a proper custom one could depend on `Y`.
349  return Vector<Y>();
350  }
351  else if constexpr ( std::is_same_v<C, Set<X>> ) {
352  return Set<Y>();
353  }
354 
355  // No default value defined for type.
356 }
357 
358 } // namespace detail
359 
361 template<typename C, typename F>
362 auto transform(const C& x, F f) {
363  using Y = typename std::invoke_result_t<F, typename C::value_type&>;
364 
365  auto y = detail::transform_result_value<C, Y>(x);
366  std::transform(std::begin(x), std::end(x), std::inserter(y, std::end(y)), f);
367 
368  return y;
369 }
370 
371 class OutOfRange;
372 
395 template<class Iter, typename Result>
396 inline Iter atoi_n(Iter s, Iter e, uint8_t base, Result* result) {
397  if ( base < 2 || base > 36 )
398  throw OutOfRange("base for numerical conversion must be between 2 and 36");
399 
400  if ( s == e )
401  throw InvalidArgument("cannot decode from empty range");
402 
403  std::optional<Result> n = std::nullopt;
404  bool neg = false;
405  auto it = s;
406 
407  if ( *it == '-' ) {
408  neg = true;
409  ++it;
410  }
411  else if ( *it == '+' ) {
412  neg = false;
413  ++it;
414  }
415 
416  for ( ; it != e; ++it ) {
417  auto c = *it;
418 
419  Result d;
420  if ( c >= '0' && c < '0' + base )
421  d = c - '0';
422  else if ( c >= 'a' && c < 'a' - 10 + base )
423  d = c - 'a' + 10;
424  else if ( c >= 'A' && c < 'A' - 10 + base )
425  d = c - 'A' + 10;
426  else
427  break;
428 
429  n = n.value_or(Result()) * base + d;
430  }
431 
432  if ( ! n )
433  return s;
434 
435  s = it;
436 
437  if ( neg )
438  *result = -*n;
439  else
440  *result = *n;
441 
442  return s;
443 }
444 
448 template<typename I1, typename I2>
449 inline I1 pow(I1 base, I2 exp) {
450  I1 x = 1;
451 
452  while ( true ) {
453  if ( exp & 1 )
454  x *= base;
455 
456  exp >>= 1;
457  if ( ! exp )
458  break;
459  base *= base;
460  }
461 
462  return x;
463 }
464 
465 // Tuple for-each, from
466 // https://stackoverflow.com/questions/40212085/type-erasure-for-objects-containing-a-stdtuple-in-c11
467 namespace detail {
468 template<typename T, typename F, std::size_t... Is>
469 constexpr auto map_tuple(T&& tup, F& f, std::index_sequence<Is...> /*unused*/) {
470  return std::make_tuple(f(std::get<Is>(std::forward<T>(tup)))...);
471 }
472 
473 template<typename T, std::size_t... Is>
474 auto join_tuple(T&& tup, std::index_sequence<Is...> /*unused*/) {
475  std::vector<std::string> x = {rt::to_string(std::get<Is>(std::forward<T>(tup)))...};
476  return join(x, ", ");
477 }
478 
479 template<typename T, std::size_t... Is>
480 auto join_tuple_for_print(T&& tup, std::index_sequence<Is...> /*unused*/) {
481  std::vector<std::string> x = {rt::to_string_for_print(std::get<Is>(std::forward<T>(tup)))...};
482  return join(x, ", ");
483 }
484 } // namespace detail
485 
487 template<typename F, std::size_t I = 0, typename... Ts>
488 void tuple_for_each(const std::tuple<Ts...>& tup, F func) {
489  if constexpr ( I == sizeof...(Ts) )
490  return;
491  else {
492  func(std::get<I>(tup));
493  tuple_for_each<F, I + 1>(tup, func);
494  }
495 }
496 
501 template<typename T, typename F, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
502 constexpr auto map_tuple(T&& tup, F f) {
503  return detail::map_tuple(std::forward<T>(tup), f, std::make_index_sequence<TupSize>{});
504 }
505 
512 template<typename T, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
513 auto join_tuple(T&& tup) {
514  return detail::join_tuple(std::forward<T>(tup), std::make_index_sequence<TupSize>{});
515 }
516 
523 template<typename T, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
524 auto join_tuple_for_print(T&& tup) {
525  return detail::join_tuple_for_print(std::forward<T>(tup), std::make_index_sequence<TupSize>{});
526 }
527 
528 template<typename>
529 struct is_tuple : std::false_type {};
530 
532 template<typename... T>
533 struct is_tuple<std::tuple<T...>> : std::true_type {};
534 
536 HILTI_RT_ENUM(ByteOrder, Little, Big, Network, Host, Undef = -1);
537 
542 extern ByteOrder systemByteOrder();
543 
544 namespace detail::adl {
545 std::string to_string(const ByteOrder& x, tag /*unused*/);
546 }
547 
560 std::string strftime(const std::string& format, const Time& time);
561 
574 Time strptime(const std::string& buf, const std::string& format);
575 
576 // RAII helper to create a temporary directory.
578 public:
580  const auto tmpdir = hilti::rt::filesystem::temp_directory_path();
581  auto template_ = (tmpdir / "hilti-rt-test-XXXXXX").native();
582  auto path = ::mkdtemp(template_.data());
583  if ( ! path )
584  throw RuntimeError("cannot create temporary directory");
585 
586  _path = path;
587  }
588 
589  TemporaryDirectory(const TemporaryDirectory& other) = delete;
590  TemporaryDirectory(TemporaryDirectory&& other) noexcept { _path = std::move(other._path); }
591 
592  ~TemporaryDirectory() {
593  // In general, ignore errors in this function.
594  std::error_code ec;
595 
596  if ( ! hilti::rt::filesystem::exists(_path, ec) )
597  return;
598 
599  // Make sure we have permissions to remove the directory.
600  hilti::rt::filesystem::permissions(_path, hilti::rt::filesystem::perms::all, ec);
601 
602  // The desugared loop contains an iterator increment which could throw (no automagic call of
603  // `std::filesystem::recursive_directory_iterator::increment`), see LWG3013 for the "fix".
604  // Ignore errors from that.
605  try {
606  for ( const auto& entry : hilti::rt::filesystem::recursive_directory_iterator(_path, ec) )
607  hilti::rt::filesystem::permissions(entry, hilti::rt::filesystem::perms::all, ec);
608  } catch ( ... ) {
609  ; // Ignore error.
610  }
611 
612  hilti::rt::filesystem::remove_all(_path, ec); // ignore errors
613  }
614 
615  const auto& path() const { return _path; }
616 
617  TemporaryDirectory& operator=(const TemporaryDirectory& other) = delete;
618  TemporaryDirectory& operator=(TemporaryDirectory&& other) noexcept {
619  _path = std::move(other._path);
620  return *this;
621  }
622 
623 private:
624  hilti::rt::filesystem::path _path;
625 };
626 
627 // Combine two or more hashes.
628 template<typename... Hashes>
629 constexpr std::size_t hashCombine(std::size_t hash1, std::size_t hash2, Hashes... hashes) {
630  auto result = hash1 ^ (hash2 << 1);
631 
632  if constexpr ( sizeof...(hashes) > 0 )
633  return hashCombine(result, hashes...);
634  else
635  return result;
636 }
637 
638 } // 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:362
Definition: util.h:529
Definition: util.h:330
Definition: any.h:7
std::string_view rtrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:121
std::string_view ltrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:132
void internalError(const std::string &msg) __attribute__((noreturn))
Definition: logging.cc:16
Definition: optional.h:79
std::string replace(std::string s, std::string_view o, std::string_view n)
Definition: util.cc:367
hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path &p)
Definition: util.cc:92
std::pair< std::string, std::string > split1(std::string s)
Definition: util.cc:146
Definition: set.h:108
bool startsWith(const std::string &s, const std::string &prefix)
Definition: util.cc:380
hilti::rt::Result< hilti::rt::filesystem::path > createTemporaryFile(const std::string &prefix="")
Definition: util.cc:74
void cannot_be_reached() __attribute__((noreturn))
Definition: util.cc:42
std::string strftime(const std::string &format, const Time &time)
Definition: util.cc:414
void tuple_for_each(const std::tuple< Ts... > &tup, F func)
Definition: util.h:488
Definition: util.h:577
constexpr auto map_tuple(T &&tup, F f)
Definition: util.h:502
std::vector< std::string_view > split(std::string_view s, std::string_view delim)
Definition: util.cc:102
Time strptime(const std::string &buf, const std::string &format)
Definition: util.cc:440
auto join_tuple_for_print(T &&tup)
Definition: util.h:524
std::optional< std::string > getenv(const std::string &name)
Definition: util.cc:67
Definition: util.h:89
constexpr auto enumerate(T &&iterable)
Definition: util.h:239
Iter atoi_n(Iter s, Iter e, uint8_t base, Result *result)
Definition: util.h:396
std::pair< std::string, std::string > rsplit1(std::string s)
Definition: util.cc:153
std::string join(const T &l, const std::string &delim="")
Definition: util.h:312
Definition: vector.h:256
std::string version()
Definition: util.cc:22
ByteOrder systemByteOrder()
Definition: util.cc:392
auto join_tuple(T &&tup)
Definition: util.h:513
void abort_with_backtrace() __attribute__((noreturn))
Definition: util.cc:34
ResourceUsage resource_usage()
Definition: util.cc:44
std::string_view trim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:143
Definition: time.h:20
I1 pow(I1 base, I2 exp)
Definition: util.h:449
Definition: result.h:67