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 void abort_with_backtrace() __attribute__((noreturn));
48 
50 inline std::string linker_scope() { return __hlto_scope; }
51 
53 extern void cannot_be_reached() __attribute__((noreturn));
54 
56 struct ResourceUsage {
57  // Note when changing this, update `resource_usage()`.
58  double user_time; //< user time since runtime initialization
59  double system_time; //< system time since runtime initialization
60  uint64_t memory_heap; //< current size of heap in bytes
61  uint64_t num_fibers; //< number of fibers currently in use
62  uint64_t max_fibers; //< high-water mark for number of fibers in use
63  uint64_t cached_fibers; //< number of fibers currently cached for reuse
64 };
65 
68 
70 extern std::optional<std::string> getenv(const std::string& name);
71 
79 
81 hilti::rt::filesystem::path normalizePath(const hilti::rt::filesystem::path& p);
82 
88 inline std::string_view rtrim(std::string_view s, const std::string& chars) noexcept {
89  s.remove_suffix(s.size() -
90  [](size_t pos) { return pos != std::string_view::npos ? pos + 1 : 0; }(s.find_last_not_of(chars)));
91  return s;
92 }
93 
99 inline std::string_view ltrim(std::string_view s, const std::string& chars) noexcept {
100  s.remove_prefix(std::min(s.find_first_not_of(chars), s.size()));
101  return s;
102 }
103 
110 inline std::string_view trim(std::string_view s, const std::string& chars) noexcept {
111  return ltrim(rtrim(s, chars), chars);
112 }
113 
114 namespace detail {
115 constexpr char whitespace_chars[] = " \t\f\v\n\r";
116 } // namespace detail
117 
123 inline std::string_view rtrim(std::string_view s) noexcept { return rtrim(s, detail::whitespace_chars); }
124 
130 inline std::string_view ltrim(std::string_view s) noexcept { return ltrim(s, detail::whitespace_chars); }
131 
137 inline std::string_view trim(std::string_view s) noexcept { return trim(s, detail::whitespace_chars); }
138 
145 std::vector<std::string_view> split(std::string_view s, std::string_view delim);
146 
152 std::vector<std::string_view> split(std::string_view s);
153 
160 extern std::pair<std::string, std::string> split1(std::string s);
161 
168 extern std::pair<std::string, std::string> rsplit1(std::string s);
169 
176 extern std::pair<std::string, std::string> split1(std::string s, const std::string& delim);
177 
184 extern std::pair<std::string, std::string> rsplit1(std::string s, const std::string& delim);
185 
191 std::string replace(std::string s, std::string_view o, std::string_view n);
192 
198 bool startsWith(const std::string& s, const std::string& prefix);
199 
204 template<typename T, typename TIter = decltype(std::begin(std::declval<T>())),
205  typename = decltype(std::end(std::declval<T>()))>
206 constexpr auto enumerate(T&& iterable) {
207  struct iterator {
208  size_t i;
209  TIter iter;
210  bool operator!=(const iterator& other) const { return iter != other.iter; }
211  void operator++() {
212  ++i;
213  ++iter;
214  }
215  auto operator*() const { return std::tie(i, *iter); }
216  };
217  struct iterable_wrapper {
218  T iterable;
219  auto begin() { return iterator{0, std::begin(iterable)}; }
220  auto end() { return iterator{0, std::end(iterable)}; }
221  };
222  return iterable_wrapper{std::forward<T>(iterable)};
223 }
224 
225 /*
226  * Expands escape sequences in a UTF8 string. The following escape sequences
227  * are supported:
228  *
229  * ============ ============================
230  * Escape Result
231  * ============ ============================
232  * \\ Backslash
233  * \\n Line feed
234  * \\r Carriage return
235  * \\t Tabulator
236  * \\uXXXX 16-bit Unicode codepoint
237  * \\UXXXXXXXX 32-bit Unicode codepoint
238  * \\xXX 8-bit hex value
239  * ============ ============================
240  *
241  * @param str string to expand
242  * @return A UTF8 string with escape sequences expanded
243  */
244 std::string expandEscapes(std::string s);
245 
246 /*
247  * Escapes non-printable characters in a raw string. This produces a new
248  * string that can be reverted by expandEscapes().
249  *
250  * @param str string to escape
251  * @param escape_quotes if true, also escapes quotes characters
252  * @param use_octal use `\NNN` instead of `\XX` (needed for C++)
253  * @return escaped string
254  *
255  * \todo This is getting messy; should use enums instead of booleans.
256  */
257 std::string escapeBytes(std::string_view s, bool escape_quotes = false, bool use_octal = false);
258 
259 /*
260  * Escapes non-printable and control characters in an UTF8 string. This
261  * produces a new string that can be reverted by expandEscapes().
262  *
263  * @param str string to escape
264  * @param escape_quotes if true, also escapes quotes characters
265  * @param escape_control if false, do not escape control characters
266  * @param keep_hex if true, do not escape our custom "\xYY" escape codes
267  * @return escaped std::string
268  *
269  * \todo This is getting messy; should use enums instead of booleans.
270  */
271 std::string escapeUTF8(std::string_view s, bool escape_quotes = false, bool escape_control = true,
272  bool keep_hex = false);
273 
278 template<typename T>
279 std::string join(const T& l, const std::string& delim = "") {
280  std::string result;
281  bool first = true;
282 
283  for ( const auto& i : l ) {
284  if ( not first )
285  result += delim;
286  result += std::string(i);
287  first = false;
288  }
289 
290  return result;
291 }
292 
293 namespace detail {
294 
296 template<typename T>
297 struct is_Vector : std::false_type {};
298 
299 template<typename T, typename Allocator>
300 struct is_Vector<Vector<T, Allocator>> : std::true_type {};
301 
304 template<typename C, typename Y>
305 constexpr auto transform_result_value(const C&) {
306  using X = typename C::value_type;
307 
308  if constexpr ( std::is_same_v<C, std::vector<X>> ) {
309  return std::vector<Y>();
310  }
311  else if constexpr ( std::is_same_v<C, std::set<X>> ) {
312  return std::set<Y>();
313  }
314  else if constexpr ( is_Vector<C>::value ) {
315  // We do not preserve the allocator since a proper custom one could depend on `Y`.
316  return Vector<Y>();
317  }
318  else if constexpr ( std::is_same_v<C, Set<X>> ) {
319  return Set<Y>();
320  }
321 
322  // No default value defined for type.
323 }
324 
325 } // namespace detail
326 
328 template<typename C, typename F>
329 auto transform(const C& x, F f) {
330  using Y = typename std::invoke_result_t<F, typename C::value_type&>;
331 
332  auto y = detail::transform_result_value<C, Y>(x);
333  std::transform(std::begin(x), std::end(x), std::inserter(y, std::end(y)), f);
334 
335  return y;
336 }
337 
338 class OutOfRange;
339 
362 template<class Iter, typename Result>
363 inline Iter atoi_n(Iter s, Iter e, uint8_t base, Result* result) {
364  if ( base < 2 || base > 36 )
365  throw OutOfRange("base for numerical conversion must be between 2 and 36");
366 
367  if ( s == e )
368  throw InvalidArgument("cannot decode from empty range");
369 
370  std::optional<Result> n = std::nullopt;
371  bool neg = false;
372  auto it = s;
373 
374  if ( *it == '-' ) {
375  neg = true;
376  ++it;
377  }
378  else if ( *it == '+' ) {
379  neg = false;
380  ++it;
381  }
382 
383  for ( ; it != e; ++it ) {
384  auto c = *it;
385 
386  Result d;
387  if ( c >= '0' && c < '0' + base )
388  d = c - '0';
389  else if ( c >= 'a' && c < 'a' - 10 + base )
390  d = c - 'a' + 10;
391  else if ( c >= 'A' && c < 'A' - 10 + base )
392  d = c - 'A' + 10;
393  else
394  break;
395 
396  n = n.value_or(Result()) * base + d;
397  }
398 
399  if ( ! n )
400  return s;
401 
402  s = it;
403 
404  if ( neg )
405  *result = -*n;
406  else
407  *result = *n;
408 
409  return s;
410 }
411 
415 template<typename I1, typename I2>
416 inline I1 pow(I1 base, I2 exp) {
417  I1 x = 1;
418 
419  while ( true ) {
420  if ( exp & 1 )
421  x *= base;
422 
423  exp >>= 1;
424  if ( ! exp )
425  break;
426  base *= base;
427  }
428 
429  return x;
430 }
431 
432 // Tuple for-each, from
433 // https://stackoverflow.com/questions/40212085/type-erasure-for-objects-containing-a-stdtuple-in-c11
434 namespace detail {
435 template<typename T, typename F, std::size_t... Is>
436 constexpr auto map_tuple(T&& tup, F& f, std::index_sequence<Is...> /*unused*/) {
437  return std::make_tuple(f(std::get<Is>(std::forward<T>(tup)))...);
438 }
439 
440 template<typename T, std::size_t... Is>
441 auto join_tuple(T&& tup, std::index_sequence<Is...> /*unused*/) {
442  std::vector<std::string> x = {rt::to_string(std::get<Is>(std::forward<T>(tup)))...};
443  return join(x, ", ");
444 }
445 
446 template<typename T, std::size_t... Is>
447 auto join_tuple_for_print(T&& tup, std::index_sequence<Is...> /*unused*/) {
448  std::vector<std::string> x = {rt::to_string_for_print(std::get<Is>(std::forward<T>(tup)))...};
449  return join(x, ", ");
450 }
451 } // namespace detail
452 
454 template<typename F, std::size_t I = 0, typename... Ts>
455 void tuple_for_each(const std::tuple<Ts...>& tup, F func) {
456  if constexpr ( I == sizeof...(Ts) )
457  return;
458  else {
459  func(std::get<I>(tup));
460  tuple_for_each<F, I + 1>(tup, func);
461  }
462 }
463 
468 template<typename T, typename F, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
469 constexpr auto map_tuple(T&& tup, F f) {
470  return detail::map_tuple(std::forward<T>(tup), f, std::make_index_sequence<TupSize>{});
471 }
472 
479 template<typename T, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
480 auto join_tuple(T&& tup) {
481  return detail::join_tuple(std::forward<T>(tup), std::make_index_sequence<TupSize>{});
482 }
483 
490 template<typename T, std::size_t TupSize = std::tuple_size_v<std::decay_t<T>>>
491 auto join_tuple_for_print(T&& tup) {
492  return detail::join_tuple_for_print(std::forward<T>(tup), std::make_index_sequence<TupSize>{});
493 }
494 
495 template<typename>
496 struct is_tuple : std::false_type {};
497 
499 template<typename... T>
500 struct is_tuple<std::tuple<T...>> : std::true_type {};
501 
503 enum class ByteOrder : int64_t { Little, Big, Network, Host, Undef = -1 };
504 
509 extern ByteOrder systemByteOrder();
510 
511 namespace detail::adl {
512 std::string to_string(const ByteOrder& x, tag /*unused*/);
513 }
514 
527 std::string strftime(const std::string& format, const Time& time);
528 
541 Time strptime(const std::string& buf, const std::string& format);
542 
543 // RAII helper to create a temporary directory.
545 public:
547  const auto tmpdir = hilti::rt::filesystem::temp_directory_path();
548  auto template_ = (tmpdir / "hilti-rt-test-XXXXXX").native();
549  auto path = ::mkdtemp(template_.data());
550  if ( ! path )
551  throw RuntimeError("cannot create temporary directory");
552 
553  _path = path;
554  }
555 
556  TemporaryDirectory(const TemporaryDirectory& other) = delete;
557  TemporaryDirectory(TemporaryDirectory&& other) noexcept { _path = std::move(other._path); }
558 
559  ~TemporaryDirectory() {
560  // In general, ignore errors in this function.
561  std::error_code ec;
562 
563  if ( ! hilti::rt::filesystem::exists(_path, ec) )
564  return;
565 
566  // Make sure we have permissions to remove the directory.
567  hilti::rt::filesystem::permissions(_path, hilti::rt::filesystem::perms::all, ec);
568 
569  // The desugared loop contains an iterator increment which could throw (no automagic call of
570  // `std::filesystem::recursive_directory_iterator::increment`), see LWG3013 for the "fix".
571  // Ignore errors from that.
572  try {
573  for ( const auto& entry : hilti::rt::filesystem::recursive_directory_iterator(_path, ec) )
574  hilti::rt::filesystem::permissions(entry, hilti::rt::filesystem::perms::all, ec);
575  } catch ( ... ) {
576  ; // Ignore error.
577  }
578 
579  hilti::rt::filesystem::remove_all(_path, ec); // ignore errors
580  }
581 
582  const auto& path() const { return _path; }
583 
584  TemporaryDirectory& operator=(const TemporaryDirectory& other) = delete;
585  TemporaryDirectory& operator=(TemporaryDirectory&& other) noexcept {
586  _path = std::move(other._path);
587  return *this;
588  }
589 
590 private:
591  hilti::rt::filesystem::path _path;
592 };
593 
594 // Combine two or more hashes.
595 template<typename... Hashes>
596 constexpr std::size_t hashCombine(std::size_t hash1, std::size_t hash2, Hashes... hashes) {
597  auto result = hash1 ^ (hash2 << 1);
598 
599  if constexpr ( sizeof...(hashes) > 0 )
600  return hashCombine(result, hashes...);
601  else
602  return result;
603 }
604 
605 } // 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:329
Definition: util.h:496
Definition: util.h:297
Definition: any.h:7
std::string_view rtrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:88
std::string_view ltrim(std::string_view s, const std::string &chars) noexcept
Definition: util.h:99
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: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:455
Definition: util.h:544
constexpr auto map_tuple(T &&tup, F f)
Definition: util.h:469
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:491
std::optional< std::string > getenv(const std::string &name)
Definition: util.cc:67
ByteOrder
Definition: util.h:503
Definition: util.h:56
constexpr auto enumerate(T &&iterable)
Definition: util.h:206
Iter atoi_n(Iter s, Iter e, uint8_t base, Result *result)
Definition: util.h:363
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:279
Definition: vector.h:251
std::string version()
Definition: util.cc:22
ByteOrder systemByteOrder()
Definition: util.cc:392
auto join_tuple(T &&tup)
Definition: util.h:480
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:110
Definition: time.h:20
I1 pow(I1 base, I2 exp)
Definition: util.h:416
std::string linker_scope()
Definition: util.h:50
Definition: result.h:67