Spicy
util.h
1 // Copyright (c) 2020-now 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 <optional>
10 #include <set>
11 #include <string>
12 #include <string_view>
13 #include <tuple>
14 #include <utility>
15 #include <vector>
16 
17 #include <hilti/rt/3rdparty/ArticleEnumClass-v2/EnumClass.h>
18 #include <hilti/rt/exception.h>
19 #include <hilti/rt/filesystem.h>
20 #include <hilti/rt/result.h>
21 #include <hilti/rt/types/set_fwd.h>
22 #include <hilti/rt/types/time.h>
23 #include <hilti/rt/types/vector_fwd.h>
24 
37 #define HILTI_RT_ENUM_WITH_DEFAULT(name, default_, ...) \
38  struct name { \
39  enum Value : int64_t { __VA_ARGS__ }; \
40  constexpr name(int64_t value = default_) noexcept : _value(value) {} \
41  friend name Enum(Value value) { return name(value); } \
42  friend constexpr bool operator==(const name& a, const name& b) noexcept { return a.value() == b.value(); } \
43  friend constexpr bool operator!=(const name& a, const name& b) noexcept { return ! (a == b); } \
44  friend constexpr bool operator<(const name& a, const name& b) noexcept { return a.value() < b.value(); } \
45  constexpr int64_t value() const { return _value; } \
46  int64_t _value; \
47  }
48 
61 #define HILTI_RT_ENUM(name, ...) HILTI_RT_ENUM_WITH_DEFAULT(name, Undef, __VA_ARGS__)
62 
63 
69 #if defined(__linux__)
70 #define HILTI_THREAD_LOCAL __thread
71 #else
72 #define HILTI_THREAD_LOCAL thread_local
73 #endif
74 
75 namespace hilti::rt {
76 
78 void internalError(std::string_view msg) __attribute__((noreturn));
79 
80 } // namespace hilti::rt
81 
82 #undef TINYFORMAT_ERROR
83 #define TINYFORMAT_ERROR(reason) throw ::hilti::rt::FormattingError(reason)
84 #include <hilti/rt/3rdparty/tinyformat/tinyformat.h>
85 #include <hilti/rt/extension-points.h>
86 #include <hilti/rt/fmt.h>
87 
88 namespace hilti::rt {
89 
91 extern std::string version();
92 
94 extern void abort_with_backtrace() __attribute__((noreturn));
95 
97 extern void cannot_be_reached() __attribute__((noreturn));
98 
101  // Note when changing this, update `resource_usage()`.
102  double user_time; //< user time since runtime initialization
103  double system_time; //< system time since runtime initialization
104  uint64_t memory_heap; //< current size of heap in bytes
105  uint64_t num_fibers; //< number of fibers currently in use
106  uint64_t max_fibers; //< high-water mark for number of fibers in use
107  uint64_t max_fiber_stack_size; //< global high-water mark for fiber stack size
108  uint64_t cached_fibers; //< number of fibers currently cached for reuse
109 };
110 
113 
115 extern std::optional<std::string> getenv(const std::string& name);
116 
124 
126 hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path& p);
127 
133 inline std::string_view rtrim(std::string_view s, const std::string& chars) noexcept {
134  s.remove_suffix(s.size() -
135  [](size_t pos) { return pos != std::string_view::npos ? pos + 1 : 0; }(s.find_last_not_of(chars)));
136  return s;
137 }
138 
144 inline std::string_view ltrim(std::string_view s, const std::string& chars) noexcept {
145  s.remove_prefix(std::min(s.find_first_not_of(chars), s.size()));
146  return s;
147 }
148 
155 inline std::string_view trim(std::string_view s, const std::string& chars) noexcept {
156  return ltrim(rtrim(s, chars), chars);
157 }
158 
159 namespace detail {
160 constexpr char whitespace_chars[] = " \t\f\v\n\r";
161 } // namespace detail
162 
168 inline std::string_view rtrim(std::string_view s) noexcept { return rtrim(s, detail::whitespace_chars); }
169 
175 inline std::string_view ltrim(std::string_view s) noexcept { return ltrim(s, detail::whitespace_chars); }
176 
182 inline std::string_view trim(std::string_view s) noexcept { return trim(s, detail::whitespace_chars); }
183 
190 std::vector<std::string_view> split(std::string_view s, std::string_view delim);
191 
197 std::vector<std::string_view> split(std::string_view s);
198 
205 extern std::pair<std::string, std::string> split1(std::string s);
206 
213 extern std::pair<std::string, std::string> rsplit1(std::string s);
214 
221 extern std::pair<std::string, std::string> split1(std::string s, const std::string& delim);
222 
229 extern std::pair<std::string, std::string> rsplit1(std::string s, const std::string& delim);
230 
236 std::string replace(std::string s, std::string_view o, std::string_view n);
237 
243 bool startsWith(std::string_view s, std::string_view prefix);
244 
250 bool endsWith(std::string_view s, std::string_view suffix);
251 
256 template<typename T, typename TIter = decltype(std::begin(std::declval<T>())),
257  typename = decltype(std::end(std::declval<T>()))>
258 constexpr auto enumerate(T&& iterable) {
259  struct iterator {
260  size_t i;
261  TIter iter;
262  bool operator!=(const iterator& other) const { return iter != other.iter; }
263  void operator++() {
264  ++i;
265  ++iter;
266  }
267  auto operator*() const { return std::tie(i, *iter); }
268  };
269  struct iterable_wrapper {
270  T iterable;
271  auto begin() { return iterator{0, std::begin(iterable)}; }
272  auto end() { return iterator{0, std::end(iterable)}; }
273  };
274  return iterable_wrapper{std::forward<T>(iterable)};
275 }
276 
277 /*
278  * Expands escape sequences in a UTF8 string. The following escape sequences
279  * are supported:
280  *
281  * ============ ============================
282  * Escape Result
283  * ============ ============================
284  * \\ Backslash
285  * \\n Line feed
286  * \\r Carriage return
287  * \\t Tabulator
288  * \\uXXXX 16-bit Unicode codepoint
289  * \\UXXXXXXXX 32-bit Unicode codepoint
290  * \\xXX 8-bit hex value
291  * ============ ============================
292  *
293  * @param str string to expand
294  * @return A UTF8 string with escape sequences expanded
295  */
296 std::string expandUTF8Escapes(std::string s);
297 
298 namespace render_style {
299 
306 enum class Bytes {
307  Default = 0,
308  EscapeQuotes = (1U << 1U),
309  UseOctal = (1U << 2U),
310  NoEscapeBackslash = (1U << 3U),
311 };
312 
321 enum class UTF8 {
322  Default = 0,
323  EscapeQuotes = (1U << 1U),
324  NoEscapeBackslash = (1U << 2U),
325  NoEscapeControl = (1U << 3U),
326  NoEscapeHex =
327  (1U << 4U),
328 };
329 
330 } // namespace render_style
331 
332 } // namespace hilti::rt
333 
334 enableEnumClassBitmask(hilti::rt::render_style::Bytes); // must be in global scope
335 enableEnumClassBitmask(hilti::rt::render_style::UTF8); // must be in global scope
336 
337 namespace hilti::rt {
338 
339 /*
340  * Escapes non-printable characters in a raw string. This produces a new
341  * string that can be reverted by expandEscapes().
342  *
343  * @param str string to escape
344  * @param escape_quotes if true, also escapes quotes characters
345  * @param use_octal use `\NNN` instead of `\XX` (needed for C++)
346  * @return escaped string
347  */
348 std::string escapeBytes(std::string_view s, bitmask<render_style::Bytes> style = render_style::Bytes::Default);
349 
350 /*
351  * Escapes non-printable and control characters in an UTF8 string. This
352  * produces a new string that can be reverted by expandEscapes().
353  *
354  * @param str string to escape
355  * @param escape_quotes if true, also escapes quotes characters
356  * @param escape_control if false, do not escape control characters
357  * @param keep_hex if true, do not escape our custom "\xYY" escape codes
358  * @return escaped std::string
359  */
360 std::string escapeUTF8(std::string_view s, bitmask<render_style::UTF8> style = render_style::UTF8::Default);
361 
366 template<typename T>
367 std::string join(const T& l, const std::string& delim = "") {
368  std::string result;
369  bool first = true;
370 
371  for ( const auto& i : l ) {
372  if ( not first )
373  result += delim;
374  result += std::string(i);
375  first = false;
376  }
377 
378  return result;
379 }
380 
381 namespace detail {
382 
384 template<typename T>
385 struct is_Vector : std::false_type {};
386 
387 template<typename T, typename Allocator>
388 struct is_Vector<Vector<T, Allocator>> : std::true_type {};
389 
392 template<typename C, typename Y>
393 constexpr auto transform_result_value(const C&) {
394  using X = typename C::value_type;
395 
396  if constexpr ( std::is_same_v<C, std::vector<X>> ) {
397  return std::vector<Y>();
398  }
399  else if constexpr ( std::is_same_v<C, std::set<X>> ) {
400  return std::set<Y>();
401  }
402  else if constexpr ( is_Vector<C>::value ) {
403  // We do not preserve the allocator since a proper custom one could depend on `Y`.
404  return Vector<Y>();
405  }
406  else if constexpr ( std::is_same_v<C, Set<X>> ) {
407  return Set<Y>();
408  }
409  else
410  return std::vector<Y>(); // fallback
411 }
412 
413 } // namespace detail
414 
416 template<typename C, typename F>
417 auto transform(const C& x, F f) {
418  using Y = typename std::invoke_result_t<F, typename C::value_type&>;
419 
420  auto y = detail::transform_result_value<C, Y>(x);
421  std::transform(std::begin(x), std::end(x), std::inserter(y, std::end(y)), f);
422 
423  return y;
424 }
425 
426 class OutOfRange;
427 
450 template<class Iter, typename Result>
451 inline Iter atoi_n(Iter s, Iter e, uint8_t base, Result* result) {
452  if ( base < 2 || base > 36 )
453  throw OutOfRange("base for numerical conversion must be between 2 and 36");
454 
455  if ( s == e )
456  throw InvalidArgument("cannot decode from empty range");
457 
458  std::optional<Result> n = std::nullopt;
459  bool neg = false;
460  auto it = s;
461 
462  if ( *it == '-' ) {
463  neg = true;
464  ++it;
465  }
466  else if ( *it == '+' ) {
467  neg = false;
468  ++it;
469  }
470 
471  for ( ; it != e; ++it ) {
472  auto c = *it;
473 
474  Result d;
475  if ( c >= '0' && c < '0' + base )
476  d = c - '0';
477  else if ( c >= 'a' && c < 'a' - 10 + base )
478  d = c - 'a' + 10;
479  else if ( c >= 'A' && c < 'A' - 10 + base )
480  d = c - 'A' + 10;
481  else
482  break;
483 
484  n = n.value_or(Result()) * base + d;
485  }
486 
487  if ( ! n )
488  return s;
489 
490  s = it;
491 
492  if ( neg )
493  *result = -*n;
494  else
495  *result = *n;
496 
497  return s;
498 }
499 
503 template<typename I1, typename I2>
504 inline I1 pow(I1 base, I2 exp) {
505  I1 x = 1;
506 
507  while ( true ) {
508  if ( exp & 1 )
509  x *= base;
510 
511  exp >>= 1;
512  if ( ! exp )
513  break;
514  base *= base;
515  }
516 
517  return x;
518 }
519 
520 // Tuple for-each, from
521 // https://stackoverflow.com/questions/40212085/type-erasure-for-objects-containing-a-stdtuple-in-c11
522 namespace detail {
523 template<typename T, typename F, std::size_t... Is>
524 constexpr auto map_tuple(T&& tup, F& f, std::index_sequence<Is...> /*unused*/) {
525  return std::make_tuple(f(std::get<Is>(std::forward<T>(tup)))...);
526 }
527 } // namespace detail
528 
530 template<typename F, std::size_t I = 0, typename... Ts>
531 void tuple_for_each(const std::tuple<Ts...>& tup, F func) {
532  if constexpr ( I == sizeof...(Ts) )
533  return;
534  else {
535  func(std::get<I>(tup));
536  tuple_for_each<F, I + 1>(tup, func);
537  }
538 }
539 
544 template<typename T, typename F, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
545 constexpr auto map_tuple(T&& tup, F f) {
546  return detail::map_tuple(std::forward<T>(tup), f, std::make_index_sequence<TupSize>{});
547 }
548 
550 HILTI_RT_ENUM(ByteOrder, Little, Big, Network, Host, Undef = -1);
551 
556 extern ByteOrder systemByteOrder();
557 
558 namespace detail::adl {
559 std::string to_string(const ByteOrder& x, tag /*unused*/);
560 }
561 
574 std::string strftime(const std::string& format, const Time& time);
575 
588 Time strptime(const std::string& buf, const std::string& format);
589 
590 // RAII helper to create a temporary directory.
592 public:
594  const auto tmpdir = hilti::rt::filesystem::temp_directory_path();
595  auto template_ = (tmpdir / "hilti-rt-test-XXXXXX").native();
596  auto path = ::mkdtemp(template_.data());
597  if ( ! path )
598  throw RuntimeError("cannot create temporary directory");
599 
600  _path = path;
601  }
602 
603  TemporaryDirectory(const TemporaryDirectory& other) = delete;
604  TemporaryDirectory(TemporaryDirectory&& other) noexcept { _path = std::move(other._path); }
605 
606  ~TemporaryDirectory() {
607  // In general, ignore errors in this function.
608  std::error_code ec;
609 
610  if ( ! hilti::rt::filesystem::exists(_path, ec) )
611  return;
612 
613  // Make sure we have permissions to remove the directory.
614  hilti::rt::filesystem::permissions(_path, hilti::rt::filesystem::perms::all, ec);
615 
616  // The desugared loop contains an iterator increment which could throw (no automagic call of
617  // `std::filesystem::recursive_directory_iterator::increment`), see LWG3013 for the "fix".
618  // Ignore errors from that.
619  try {
620  for ( const auto& entry : hilti::rt::filesystem::recursive_directory_iterator(_path, ec) )
621  hilti::rt::filesystem::permissions(entry, hilti::rt::filesystem::perms::all, ec);
622  } catch ( ... ) {
623  ; // Ignore error.
624  }
625 
626  hilti::rt::filesystem::remove_all(_path, ec); // ignore errors
627  }
628 
629  const auto& path() const { return _path; }
630 
631  TemporaryDirectory& operator=(const TemporaryDirectory& other) = delete;
632  TemporaryDirectory& operator=(TemporaryDirectory&& other) noexcept {
633  _path = std::move(other._path);
634  return *this;
635  }
636 
637 private:
638  hilti::rt::filesystem::path _path;
639 };
640 
641 // Combine two or more hashes.
642 template<typename... Hashes>
643 constexpr std::size_t hashCombine(std::size_t hash1, std::size_t hash2, Hashes... hashes) {
644  auto result = hash1 ^ (hash2 << 1);
645 
646  if constexpr ( sizeof...(hashes) > 0 )
647  return hashCombine(result, hashes...);
648  else
649  return result;
650 }
651 
652 } // namespace hilti::rt
Definition: network.h:19
Definition: result.h:71
Definition: util.h:591
Definition: vector.h:268
Definition: any.h:7
void tuple_for_each(const std::tuple< Ts... > &tup, F func)
Definition: util.h:531
auto transform(const C &x, F f)
Definition: util.h:417
std::vector< std::string_view > split(std::string_view s, std::string_view delim)
Definition: util.cc:102
constexpr auto enumerate(T &&iterable)
Definition: util.h:258
std::optional< std::string > getenv(const std::string &name)
Definition: util.cc:67
bool endsWith(std::string_view s, std::string_view suffix)
Definition: util.cc:386
hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path &p)
Definition: util.cc:92
Time strptime(const std::string &buf, const std::string &format)
Definition: util.cc:441
void internalError(std::string_view msg) __attribute__((noreturn))
Definition: logging.cc:16
hilti::rt::Result< hilti::rt::filesystem::path > createTemporaryFile(const std::string &prefix="")
Definition: util.cc:74
std::pair< std::string, std::string > split1(std::string s)
Definition: util.cc:146
std::string_view trim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:155
ResourceUsage resource_usage()
Definition: util.cc:43
std::string replace(std::string s, std::string_view o, std::string_view n)
Definition: util.cc:371
std::string strftime(const std::string &format, const Time &time)
Definition: util.cc:415
std::string version()
Definition: util.cc:21
Iter atoi_n(Iter s, Iter e, uint8_t base, Result *result)
Definition: util.h:451
I1 pow(I1 base, I2 exp)
Definition: util.h:504
std::string_view rtrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:133
void abort_with_backtrace() __attribute__((noreturn))
Definition: util.cc:33
constexpr auto map_tuple(T &&tup, F f)
Definition: util.h:545
std::pair< std::string, std::string > rsplit1(std::string s)
Definition: util.cc:153
std::string_view ltrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:144
std::string join(const T &l, const std::string &delim="")
Definition: util.h:367
std::string to_string(T &&x)
Definition: extension-points.h:26
ByteOrder systemByteOrder()
Definition: util.cc:393
bool startsWith(std::string_view s, std::string_view prefix)
Definition: util.cc:384
void cannot_be_reached() __attribute__((noreturn))
Definition: util.cc:41
Definition: util.h:100
Definition: util.h:385