initial commit
This commit is contained in:
commit
62272807dc
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
compile_commands.json
|
||||
.cache/
|
||||
target/
|
16
Alloy.toml
Normal file
16
Alloy.toml
Normal file
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "utils"
|
||||
authors = []
|
||||
standard = "c++2b"
|
||||
version = "0.1.0"
|
||||
|
||||
[lib]
|
||||
cc-flags = ["-DNOMINMAX", "-D_HAS_CXX20=1"]
|
||||
exclude-sources = ["src/main.cc"]
|
||||
reparent-headers = true
|
||||
name = "utils"
|
||||
|
||||
[[bin]]
|
||||
cc-flags = ["-DNOMINMAX", "-g", "-gcodeview"]
|
||||
ld-flags = ["-g"]
|
||||
name = "test"
|
120
include/assert.hpp
Normal file
120
include/assert.hpp
Normal file
|
@ -0,0 +1,120 @@
|
|||
#pragma once
|
||||
|
||||
#if defined(UTIL_ASSERT_FORMAT)
|
||||
#include "print.hpp"
|
||||
#endif
|
||||
|
||||
#ifdef assert
|
||||
#pragma push_macro("assert")
|
||||
#undef assert
|
||||
#define UTIL_ASSERT_UNDEF
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include "move.hpp"
|
||||
#include "source_location.hpp"
|
||||
#include "type_traits.hpp"
|
||||
|
||||
namespace util {
|
||||
template <class From, class To>
|
||||
struct is_convertible
|
||||
: std::integral_constant<
|
||||
bool,
|
||||
(decltype(detail::test_returnable<To>(0))::value &&
|
||||
decltype(detail::test_implicitly_convertible<From, To>(0))::value) ||
|
||||
(is_void<From>::value && is_void<To>::value)> {};
|
||||
|
||||
template <typename From, typename To>
|
||||
inline constexpr bool is_convertible_v = is_convertible<From, To>::value;
|
||||
|
||||
template <typename From>
|
||||
concept boolean_testable_impl = is_convertible_v<From, bool> && requires {
|
||||
static_cast<bool>(declval<From>());
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept boolean_testable = requires(T &&t) {
|
||||
{ !static_cast<T &&>(t) } -> boolean_testable_impl;
|
||||
};
|
||||
|
||||
template <typename A, typename B>
|
||||
concept weakly_equality_comparable_with = requires (const remove_reference_t<A>& a, const remove_reference_t<B>& b) {
|
||||
{a == b} -> boolean_testable;
|
||||
{a != b} -> boolean_testable;
|
||||
{b == a} -> boolean_testable;
|
||||
{b == a} -> boolean_testable;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept equality_comparable = weakly_equality_comparable_with<T, T>;
|
||||
|
||||
template <typename A, typename B>
|
||||
concept equality_comparable_with = weakly_equality_comparable_with<A, B>;
|
||||
|
||||
template <boolean_testable B>
|
||||
inline constexpr auto assert(B&& b, const source_location& source = source_location::current()) ->void {
|
||||
if (!b) [[unlikely]] {
|
||||
|
||||
#if defined (UTIL_ASSERT_FORMAT)
|
||||
if constexpr (is_formattable_v<B>) {
|
||||
print(std::format("Assertion failed: {}\n", b));
|
||||
print(std::format("In function {} at {}:{},{}\n", source.function_name(), source.file_name(), source.line(), source.column()));
|
||||
} else {
|
||||
print(std::format("Assertion failed in function {} at {}:{},{}\n", source.function_name(), source.file_name(), source.line(), source.column()));
|
||||
}
|
||||
#else
|
||||
printf("Assertion failed in function %s at %s:%d,%d", source.function_name(), source.file_name(), source.line(), source.column());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
template <typename A, typename B>
|
||||
requires (equality_comparable_with<A, B>)
|
||||
inline constexpr auto assert_eq(A&& rhs, B&& lhs, const source_location& source = source_location::current()) ->void {
|
||||
if (rhs != lhs) [[unlikely]] {
|
||||
#if defined (UTIL_ASSERT_FORMAT)
|
||||
if constexpr (is_formattable_v<A> && is_formattable_v<B>) {
|
||||
print(std::format("Assertion failed: {} != {}\n", rhs, lhs));
|
||||
print(std::format("In function {} at {}:{},{}\n", source.function_name(), source.file_name(), source.line(), source.column()));
|
||||
} else {
|
||||
print(std::format("Assertion failed in function {} at {}:{},{}\n", source.function_name(), source.file_name(), source.line(), source.column()));
|
||||
}
|
||||
#else
|
||||
printf("Assertion failed in function %s at %s:%d,%d", source.function_name(), source.file_name(), source.line(), source.column());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
template <equality_comparable T>
|
||||
inline constexpr auto assert_eq(T&& rhs, T&& lhs, const source_location& source = source_location::current()) ->void {
|
||||
assert_eq<T,T>(FWD(rhs), FWD(lhs),source);
|
||||
}
|
||||
|
||||
|
||||
template <typename A, typename B>
|
||||
requires (equality_comparable_with<A, B>)
|
||||
inline constexpr auto assert_ne(A&& rhs, B&& lhs, const source_location& source = source_location::current()) ->void {
|
||||
if (rhs == lhs) [[unlikely]] {
|
||||
#if defined (UTIL_ASSERT_FORMAT)
|
||||
if constexpr (is_formattable_v<A> && is_formattable_v<B>) {
|
||||
print(std::format("Assertion failed: {} == {}\n", rhs, lhs));
|
||||
print(std::format("In function {} at {}:{},{}\n", source.function_name(), source.file_name(), source.line(), source.column()));
|
||||
} else {
|
||||
print(std::format("Assertion failed in function {} at {}:{},{}\n", source.function_name(), source.file_name(), source.line(), source.column()));
|
||||
}
|
||||
#else
|
||||
printf("Assertion failed in function %s at %s:%d,%d", source.function_name(), source.file_name(), source.line(), source.column());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
template <equality_comparable T>
|
||||
inline constexpr auto assert_ne(T&& rhs, T&& lhs, const source_location& source = source_location::current()) ->void {
|
||||
assert_ne<T, T>(FWD(rhs), FWD(lhs), source);
|
||||
}
|
||||
} // namespace util
|
||||
|
||||
#ifdef UTIL_ASSERT_UNDEF
|
||||
#pragma pop_macro("assert")
|
||||
#undef UTIL_ASSERT_UNDEF
|
||||
#endif
|
41
include/concepts/container.hpp
Normal file
41
include/concepts/container.hpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <ranges>
|
||||
|
||||
namespace util {
|
||||
template <typename C>
|
||||
concept reservable = requires(C &c, typename C::size_type i) {
|
||||
{c.reserve(i)};
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
concept back_insertible = requires(C &c,
|
||||
const std::ranges::range_value_t<C> &t) {
|
||||
{ std::back_inserter(c) } -> std::same_as<std::back_insert_iterator<C>>;
|
||||
{c.push_back(t)};
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
concept front_insertible = requires(C &c,
|
||||
const std::ranges::range_value_t<C> &t) {
|
||||
{ std::front_inserter(c) } -> std::same_as<std::front_insert_iterator<C>>;
|
||||
{c.push_front(t)};
|
||||
};
|
||||
|
||||
template <typename C>
|
||||
concept insertible = requires(C &c, std::ranges::iterator_t<C> i,
|
||||
const std::ranges::range_value_t<C> &t) {
|
||||
{ std::inserter(c, i) } -> std::same_as<std::insert_iterator<C>>;
|
||||
{c.insert(i, t)};
|
||||
};
|
||||
template <typename C, typename... Ts>
|
||||
concept emplaceable = requires(C &c, Ts... t) {
|
||||
{c.emplace(std::move(t...))};
|
||||
};
|
||||
|
||||
template <typename C, typename... Ts>
|
||||
concept back_emplaceable = requires(C &c, Ts... t) {
|
||||
{c.emplace_back(std::move(t...))};
|
||||
};
|
||||
} // namespace util
|
79
include/concepts/traits.hpp
Normal file
79
include/concepts/traits.hpp
Normal file
|
@ -0,0 +1,79 @@
|
|||
#pragma once
|
||||
|
||||
#include <compare>
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
namespace util {
|
||||
|
||||
template <typename T>
|
||||
concept Number = std::integral<T> || std::floating_point<T>;
|
||||
|
||||
template <auto T, auto U>
|
||||
requires std::equality_comparable_with<decltype(T), decltype(T)>
|
||||
struct is_equal : public std::integral_constant<bool, T == U> {
|
||||
};
|
||||
|
||||
template <auto T, auto U>
|
||||
inline constexpr auto is_equal_v = is_equal<T, U>::value;
|
||||
|
||||
template <auto T, auto U>
|
||||
requires std::three_way_comparable_with<decltype(T), decltype(T)>
|
||||
struct cmp {
|
||||
constexpr static std::partial_ordering ordering = T <=> U;
|
||||
};
|
||||
|
||||
template <auto T, auto U> inline constexpr auto cmp_v = cmp<T, U>::ordering;
|
||||
|
||||
template <std::partial_ordering ordering, auto T, auto U>
|
||||
concept Compare = is_equal_v<cmp_v<T, U>, ordering>;
|
||||
|
||||
template <auto T, auto U>
|
||||
concept Equal = is_equal_v<T, U>;
|
||||
|
||||
template <typename T, typename... Pack>
|
||||
concept all_same_as = (std::same_as<T, Pack> && ...);
|
||||
|
||||
template <typename T, typename U>
|
||||
concept explicit_convertible_to = requires(const T ct, T t, const U &cu) {
|
||||
{t = cu};
|
||||
{ ct.operator U() } -> std::same_as<U>;
|
||||
}
|
||||
|| std::same_as<T, U>;
|
||||
|
||||
template <typename From, typename... To>
|
||||
concept explicit_convertible_to_any_of =
|
||||
(explicit_convertible_to<std::remove_cv_t<From>, To> || ...);
|
||||
|
||||
template <typename From, typename... To>
|
||||
struct is_explicit_convertible_to_any_of
|
||||
: std::bool_constant<explicit_convertible_to_any_of<From, To...>> {};
|
||||
|
||||
template <typename T>
|
||||
concept explicit_convertible_to_integral =
|
||||
std::integral<T> || explicit_convertible_to_any_of < std::remove_cv_t<T>,
|
||||
bool, char, signed char, unsigned char, wchar_t,
|
||||
#ifdef __cpp_char8_t
|
||||
char8_t,
|
||||
#endif // __cpp_char8_t
|
||||
char16_t, char32_t, short, unsigned short, int, unsigned int, long,
|
||||
unsigned long, long long, unsigned long long > ;
|
||||
|
||||
template <typename T>
|
||||
concept explicit_convertible_to_floating_point = std::floating_point<T> ||
|
||||
explicit_convertible_to_any_of<T, float, double, long double>;
|
||||
|
||||
template <typename From, typename To>
|
||||
struct is_explicit_convertible
|
||||
: std::bool_constant<explicit_convertible_to<From, To>> {};
|
||||
|
||||
template <typename From, typename To>
|
||||
inline constexpr bool is_explicit_convertible_v =
|
||||
is_explicit_convertible<From, To>::value;
|
||||
|
||||
template <typename Fn, typename R, typename... Args>
|
||||
concept with_return_type = requires(Fn fn, Args... args) {
|
||||
{ fn(args...) } -> std::same_as<R>;
|
||||
};
|
||||
|
||||
} // namespace util
|
158
include/enum.hpp
Normal file
158
include/enum.hpp
Normal file
|
@ -0,0 +1,158 @@
|
|||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace util {
|
||||
namespace bitflag_operators {
|
||||
|
||||
template <typename E>
|
||||
concept enum_type = std::is_enum_v<E>;
|
||||
|
||||
template <enum_type E> auto operator&(const E &rhs, const E &lhs) -> E {
|
||||
return static_cast<E>(underlying_type_cast(rhs) & underlying_type_cast(lhs));
|
||||
}
|
||||
|
||||
template <enum_type E> auto operator&=(E &rhs, const E &lhs) -> E & {
|
||||
rhs = rhs & lhs;
|
||||
return rhs;
|
||||
}
|
||||
|
||||
template <enum_type E> auto operator|(const E &rhs, const E &lhs) -> E {
|
||||
return static_cast<E>(underlying_type_cast(rhs) | underlying_type_cast(lhs));
|
||||
}
|
||||
|
||||
template <enum_type E> auto operator|=(E &rhs, const E &lhs) -> E & {
|
||||
rhs = rhs | lhs;
|
||||
return rhs;
|
||||
}
|
||||
|
||||
template <enum_type E> auto operator^(const E &rhs, const E &lhs) -> E {
|
||||
return static_cast<E>(underlying_type_cast(rhs) ^ underlying_type_cast(lhs));
|
||||
}
|
||||
|
||||
template <enum_type E> auto operator^=(E &rhs, const E &lhs) -> E & {
|
||||
rhs = rhs ^ lhs;
|
||||
return rhs;
|
||||
}
|
||||
|
||||
template <enum_type E> auto operator~(const E &rhs) -> E {
|
||||
return static_cast<E>(~underlying_type_cast(rhs));
|
||||
}
|
||||
|
||||
template <enum_type E> auto is_empty(const E &value) -> bool {
|
||||
return std::to_underlying(value) == 0;
|
||||
}
|
||||
|
||||
} // namespace bitflag_operators
|
||||
|
||||
using bitflag_operators::enum_type;
|
||||
|
||||
template <typename E>
|
||||
auto underlying_type_cast(const E &value) -> std::underlying_type_t<E>
|
||||
requires std::is_enum_v<E> {
|
||||
return static_cast<std::underlying_type_t<E>>(value);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
requires std::is_enum_v<E>
|
||||
class EnumFlag {
|
||||
E flags;
|
||||
|
||||
public:
|
||||
using U = std::underlying_type_t<E>;
|
||||
using value_type = U;
|
||||
|
||||
EnumFlag() : flags(static_cast<E>(U(0))) {}
|
||||
EnumFlag(const U &value) : flags(static_cast<E>(value)) {}
|
||||
EnumFlag(const E &value) : flags(value) {}
|
||||
|
||||
auto operator=(const U &value) -> EnumFlag & {
|
||||
flags = static_cast<E>(value);
|
||||
return *this;
|
||||
}
|
||||
auto operator=(const E &value) -> EnumFlag & {
|
||||
flags = value;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto as_underlying_type() const -> U { return static_cast<U>(flags); }
|
||||
auto as_enum_type() const -> E { return flags; }
|
||||
|
||||
/// `union` is reserved
|
||||
auto unison(const EnumFlag &other) const -> EnumFlag { return this | other; }
|
||||
|
||||
auto intersection(const EnumFlag &other) const -> EnumFlag {
|
||||
return *this & other;
|
||||
}
|
||||
|
||||
auto contains(const EnumFlag &other) const -> bool {
|
||||
return intersection(other).as_underlying_type() ==
|
||||
other.as_underlying_type();
|
||||
}
|
||||
|
||||
auto intersects(const EnumFlag &other) const -> bool {
|
||||
return intersection().as_underlying_type() != 0;
|
||||
}
|
||||
|
||||
auto add(const EnumFlag &other) -> EnumFlag & {
|
||||
*this |= other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
auto remove(const EnumFlag &other) -> EnumFlag & {
|
||||
*this &= ~other;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator U() const { return as_underlying_type(); }
|
||||
operator E() const { return as_enum_type(); }
|
||||
|
||||
// auto operator==(const EnumFlag& other) const {
|
||||
// return as_underlying_type() == other.as_underlying_type();
|
||||
// }
|
||||
|
||||
auto operator<=>(const EnumFlag &other) const {
|
||||
return other.as_underlying_type() <=> other.as_underlying_type();
|
||||
}
|
||||
|
||||
// operator bool() const { return as_underlying_type() != 0; }
|
||||
|
||||
auto operator|=(const EnumFlag &other) {
|
||||
flags = static_cast<E>(as_underlying_type() | other.as_underlying_type());
|
||||
}
|
||||
|
||||
auto operator|(const EnumFlag &other) const -> EnumFlag {
|
||||
auto flag = *this;
|
||||
flag |= other;
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
auto operator&=(const EnumFlag &other) {
|
||||
flags = static_cast<E>(as_underlying_type() & other.as_underlying_type());
|
||||
}
|
||||
|
||||
auto operator&(const EnumFlag &other) const -> EnumFlag {
|
||||
auto flag = *this;
|
||||
flag &= other;
|
||||
|
||||
return flag;
|
||||
}
|
||||
|
||||
auto operator~() const -> EnumFlag {
|
||||
return static_cast<E>(~as_underlying_type());
|
||||
}
|
||||
|
||||
auto operator^=(const EnumFlag &other) {
|
||||
flags = static_cast<E>(as_underlying_type() ^ other.as_underlying_type());
|
||||
}
|
||||
|
||||
auto operator^(const EnumFlag &other) const -> EnumFlag {
|
||||
auto flag = *this;
|
||||
flag ^= other;
|
||||
|
||||
return flag;
|
||||
}
|
||||
};
|
||||
} // namespace util
|
50
include/format.hpp
Normal file
50
include/format.hpp
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include "move.hpp"
|
||||
#include "type_traits.hpp"
|
||||
#include <format>
|
||||
#include <string_view>
|
||||
|
||||
namespace util {
|
||||
|
||||
template <typename... Ts>
|
||||
inline constexpr auto format(std::string_view fmt, Ts&&... args)
|
||||
-> std::string {
|
||||
return std::vformat(fmt, std::make_format_args(std::forward<Ts>(args)...));
|
||||
}
|
||||
|
||||
template <typename T, typename Context>
|
||||
using has_formatter =
|
||||
std::is_constructible<typename Context::template formatter_type<T>>;
|
||||
|
||||
template <typename T>
|
||||
using is_formattable = has_formatter<T, std::format_context>;
|
||||
|
||||
template <typename T>
|
||||
inline constexpr bool is_formattable_v = is_formattable<T>::value;
|
||||
|
||||
template <typename T>
|
||||
concept formattable = is_formattable_v<T>;
|
||||
} // namespace util
|
||||
|
||||
#include <optional>
|
||||
|
||||
template<util::formattable F>
|
||||
struct std::formatter<std::optional<F>> : public std::formatter<F> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::optional<F>& option, FormatContext& ctx)
|
||||
-> decltype(ctx.out()) {
|
||||
auto fmt = ctx.out();
|
||||
|
||||
if (option.has_value()) {
|
||||
fmt = std::format_to(fmt, "Some(");
|
||||
ctx.advance_to(fmt);
|
||||
fmt = std::formatter<F>::format(option.value(), ctx);
|
||||
*fmt++ = ')';
|
||||
} else {
|
||||
fmt = std::format_to(fmt, "None");
|
||||
}
|
||||
|
||||
return fmt;
|
||||
}
|
||||
};
|
35
include/iterator.hpp
Normal file
35
include/iterator.hpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#pragma once
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace util {
|
||||
|
||||
template <int I, class... Ts>
|
||||
decltype(auto) get(Ts&&... ts) {
|
||||
return std::get<I>(std::forward_as_tuple(ts...));
|
||||
}
|
||||
|
||||
template <class Iterator>
|
||||
class Range {
|
||||
Iterator m_begin, m_end;
|
||||
|
||||
public:
|
||||
Range(const Iterator _begin, const Iterator _end)
|
||||
: m_begin(_begin), m_end(_end) {}
|
||||
|
||||
Range(const std::pair<const Iterator, const Iterator>& t) {
|
||||
m_begin = t.first;
|
||||
m_end = t.second;
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
Range(Args&&... args) : m_begin(get<0>(args...)), m_end(get<1>(args...)) {}
|
||||
|
||||
auto begin() -> Iterator { return m_begin; }
|
||||
auto begin() const -> const Iterator { return m_begin; }
|
||||
|
||||
auto end() -> Iterator { return m_end; }
|
||||
auto end() const -> const Iterator { return m_end; }
|
||||
};
|
||||
|
||||
} // namespace util
|
16
include/math.hpp
Normal file
16
include/math.hpp
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include <concepts>
|
||||
#include <numbers>
|
||||
|
||||
|
||||
namespace util {
|
||||
template <std::floating_point N> auto radians(N degrees) -> N {
|
||||
return (degrees * std::numbers::pi_v<N>) / static_cast<N>(180);
|
||||
}
|
||||
|
||||
template <std::floating_point N> auto degrees(N radians) -> N {
|
||||
return (radians * static_cast<N>(180)) / std::numbers::pi_v<N>;
|
||||
}
|
||||
} // namespace util
|
12
include/move.hpp
Normal file
12
include/move.hpp
Normal file
|
@ -0,0 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "type_traits.hpp"
|
||||
|
||||
// static_cast to rvalue reference
|
||||
#define MOV(...) \
|
||||
static_cast<remove_reference_t<decltype(__VA_ARGS__)>&&>(__VA_ARGS__)
|
||||
|
||||
// static_cast to identity
|
||||
// The extra && aren't necessary as discussed above, but make it more robust in case it's used with a non-reference.
|
||||
#define FWD(...) \
|
||||
static_cast<decltype(__VA_ARGS__)&&>(__VA_ARGS__)
|
13
include/option.hpp
Normal file
13
include/option.hpp
Normal file
|
@ -0,0 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
namespace util {
|
||||
template<typename T>
|
||||
class Option {
|
||||
public:
|
||||
|
||||
|
||||
private:
|
||||
bool m_has_value;
|
||||
T m_value;
|
||||
};
|
||||
}
|
24
include/print.hpp
Normal file
24
include/print.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include "format.hpp"
|
||||
|
||||
namespace util {
|
||||
|
||||
template <typename... Ts>
|
||||
inline constexpr auto print(std::string_view fmt, Ts&&... args)
|
||||
->void{
|
||||
const auto str = util::format(fmt, std::forward<Ts>(args)...);
|
||||
std::cout.write(str.c_str(), str.size());
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
inline constexpr auto println(std::string_view fmt, Ts&&... args)
|
||||
->void{
|
||||
print(fmt, std::forward<Ts>(args)...);
|
||||
std::cout.put('\n');
|
||||
}
|
||||
|
||||
}
|
83
include/ranges/collect.hpp
Normal file
83
include/ranges/collect.hpp
Normal file
|
@ -0,0 +1,83 @@
|
|||
#pragma once
|
||||
|
||||
#include "../concepts/container.hpp"
|
||||
#include <algorithm>
|
||||
#include <ranges>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace util {
|
||||
namespace rg = std::ranges;
|
||||
namespace detail {
|
||||
template <typename Collection, rg::input_range R>
|
||||
constexpr auto collect_range(R &&r) -> Collection {
|
||||
Collection c;
|
||||
if constexpr (rg::sized_range<R> && util::reservable<Collection>) {
|
||||
c.reserve(r.size());
|
||||
}
|
||||
|
||||
if constexpr (util::back_insertible<Collection>) {
|
||||
rg::move(r, std::back_inserter(c));
|
||||
} else if constexpr (util::back_emplaceable<Collection,
|
||||
decltype(*r.begin())>) {
|
||||
for (auto &&e : r) {
|
||||
c.emplace_back(std::move(e));
|
||||
}
|
||||
} else if constexpr (util::emplaceable<Collection, decltype(*r.begin())>) {
|
||||
for (auto &&e : r) {
|
||||
c.emplace(std::move(e));
|
||||
}
|
||||
}
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
template <typename Collection> struct collect_range_adaptor {
|
||||
template <rg::input_range R> constexpr auto operator()(R &&r) const {
|
||||
return collect_range<Collection>(std::forward<R>(r));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Collection, rg::viewable_range R>
|
||||
constexpr auto operator|(R &&r,
|
||||
const collect_range_adaptor<Collection> &adaptor) {
|
||||
return adaptor(std::forward<R>(r));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
namespace views {
|
||||
|
||||
template <typename Collection>
|
||||
inline detail::collect_range_adaptor<Collection> collect;
|
||||
}
|
||||
|
||||
template <typename Collection, rg::range R>
|
||||
|
||||
constexpr auto collect(R &&r) -> Collection {
|
||||
return r | views::collect<Collection>;
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
constexpr auto to_vector(Range r)
|
||||
-> std::vector<rg::range_value_t<decltype(r)>> {
|
||||
std::vector<rg::range_value_t<decltype(r)>> v;
|
||||
|
||||
if constexpr (rg::sized_range<decltype(r)>) {
|
||||
v.reserve(rg::size(r));
|
||||
}
|
||||
|
||||
std::ranges::copy(r, std::back_inserter(v));
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
constexpr auto to_unordered_set(Range r)
|
||||
-> std::unordered_set<std::ranges::range_value_t<decltype(r)>> {
|
||||
std::unordered_set<std::ranges::range_value_t<decltype(r)>> v(r.begin(),
|
||||
r.end());
|
||||
return v;
|
||||
}
|
||||
|
||||
} // namespace util
|
68
include/ranges/drop_last.hpp
Normal file
68
include/ranges/drop_last.hpp
Normal file
|
@ -0,0 +1,68 @@
|
|||
#pragma once
|
||||
#include <ranges>
|
||||
|
||||
namespace util {
|
||||
namespace rg = std::ranges;
|
||||
|
||||
template <rg::sized_range R>
|
||||
requires rg::view<R>
|
||||
class drop_last_view : public rg::view_interface<drop_last_view<R>> {
|
||||
private:
|
||||
R base_{};
|
||||
std::iter_difference_t<rg::iterator_t<R>> count_{};
|
||||
rg::iterator_t<R> iter_{std::begin(base_)};
|
||||
|
||||
public:
|
||||
drop_last_view() = default;
|
||||
|
||||
constexpr drop_last_view(R base,
|
||||
std::iter_difference_t<rg::iterator_t<R>> count)
|
||||
: base_(base), count_(count), iter_(std::begin(base_)) {}
|
||||
|
||||
constexpr R base() const & { return base_; }
|
||||
constexpr R base() && { return std::move(base_); }
|
||||
|
||||
constexpr auto begin() const { return iter_; }
|
||||
constexpr auto end() const { return std::end(base_) - count_; }
|
||||
|
||||
constexpr auto size() const requires rg::sized_range<const R> {
|
||||
const auto s = rg::size(base_);
|
||||
const auto c = static_cast<decltype(s)>(count_);
|
||||
return s < c ? 0 : s - c;
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
struct drop_last_range_adaptor_closure {
|
||||
std::size_t count_;
|
||||
constexpr drop_last_range_adaptor_closure(std::size_t count)
|
||||
: count_(count) {}
|
||||
|
||||
template <rg::viewable_range R> constexpr auto operator()(R &&r) const {
|
||||
return drop_last_view(std::forward<R>(r), count_);
|
||||
}
|
||||
};
|
||||
|
||||
struct drop_last_range_adaptor {
|
||||
template <rg::viewable_range R>
|
||||
constexpr auto operator()(R &&r,
|
||||
std::iter_difference_t<rg::iterator_t<R>> count) {
|
||||
return drop_last_view(std::forward<R>(r), count);
|
||||
}
|
||||
|
||||
constexpr auto operator()(std::size_t count) {
|
||||
return drop_last_range_adaptor_closure(count);
|
||||
}
|
||||
};
|
||||
|
||||
template <rg::viewable_range R>
|
||||
constexpr auto operator|(R &&r, drop_last_range_adaptor_closure const &a) {
|
||||
return a(std::forward<R>(r));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
namespace views {
|
||||
inline detail::drop_last_range_adaptor drop_last;
|
||||
} // namespace views
|
||||
|
||||
} // namespace util
|
91
include/ranges/enumerate.hpp
Normal file
91
include/ranges/enumerate.hpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
#pragma once
|
||||
#include "types.hpp"
|
||||
#include <ranges>
|
||||
|
||||
|
||||
namespace util {
|
||||
namespace rg = std::ranges;
|
||||
|
||||
template <rg::range R> struct enumerate_iterator_t : rg::iterator_t<R> {
|
||||
using base = rg::iterator_t<R>;
|
||||
using enumerator_type = std::size_t;
|
||||
using value_type = std::pair<enumerator_type, rg::range_value_t<R>>;
|
||||
using reference =
|
||||
std::pair<enumerator_type, std::remove_cv_t<rg::range_reference_t<R>>>;
|
||||
|
||||
enumerator_type enumerator{};
|
||||
|
||||
enumerate_iterator_t() = default;
|
||||
enumerate_iterator_t(const base &b) : base{b}, enumerator(0) {}
|
||||
|
||||
auto operator<=>(const enumerate_iterator_t &other) const {
|
||||
return static_cast<const base &>(*this) <=>
|
||||
static_cast<const base &>(other);
|
||||
}
|
||||
|
||||
auto inc_enumerator() -> void { enumerator++; }
|
||||
|
||||
auto operator++(int) -> enumerate_iterator_t {
|
||||
const auto result = static_cast<base &>(*this)++;
|
||||
inc_enumerator();
|
||||
return result;
|
||||
}
|
||||
|
||||
auto operator++() -> enumerate_iterator_t & {
|
||||
inc_enumerator();
|
||||
++static_cast<base &>(*this);
|
||||
return (*this);
|
||||
}
|
||||
|
||||
auto operator*() -> reference {
|
||||
return reference(enumerator, *static_cast<base &>(*this));
|
||||
}
|
||||
auto operator*() const -> reference {
|
||||
return reference(enumerator, *static_cast<const base &>(*this));
|
||||
}
|
||||
};
|
||||
|
||||
template <rg::range R>
|
||||
class enumerate_view : public rg::view_interface<enumerate_view<R>> {
|
||||
R base_{};
|
||||
isize enumerator{};
|
||||
enumerate_iterator_t<R> iter_{std::begin(base_)};
|
||||
|
||||
public:
|
||||
enumerate_view() = default;
|
||||
|
||||
constexpr enumerate_view(R base) : base_(base), iter_(std::begin(base_)) {}
|
||||
|
||||
constexpr R base() const & { return base_; }
|
||||
constexpr R base() && { return std::move(base_); }
|
||||
|
||||
constexpr auto begin() const { return iter_; }
|
||||
constexpr auto end() const { return std::end(base_); }
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
struct enumerate_range_adaptor_closure {
|
||||
template <rg::viewable_range R> constexpr auto operator()(R &&r) const {
|
||||
return enumerate_view(std::forward<R>(r));
|
||||
}
|
||||
};
|
||||
|
||||
struct enumerate_range_adaptor {
|
||||
template <rg::viewable_range R> constexpr auto operator()(R &&r) {
|
||||
return enumerate_view(std::forward<R>(r));
|
||||
}
|
||||
|
||||
constexpr auto operator()() { return enumerate_range_adaptor_closure(); }
|
||||
};
|
||||
|
||||
template <rg::viewable_range R>
|
||||
constexpr auto operator|(R &&r, enumerate_range_adaptor_closure const &a) {
|
||||
return a(std::forward<R>(r));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
namespace views {
|
||||
inline detail::enumerate_range_adaptor_closure enumerate;
|
||||
} // namespace views
|
||||
|
||||
} // namespace util
|
42
include/ranges/first.hpp
Normal file
42
include/ranges/first.hpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <ranges>
|
||||
#include <xutility>
|
||||
|
||||
|
||||
namespace util {
|
||||
namespace rg = std::ranges;
|
||||
namespace detail {
|
||||
|
||||
template <rg::input_range R>
|
||||
constexpr auto get_first(R &&r) -> std::optional<rg::range_value_t<R>> {
|
||||
if (rg::begin(r) == rg::end(r)) {
|
||||
return std::nullopt;
|
||||
} else {
|
||||
return *rg::begin(r);
|
||||
}
|
||||
}
|
||||
|
||||
struct first_range_adaptor {
|
||||
template <rg::input_range R> constexpr auto operator()(R &&r) const {
|
||||
return get_first(std::forward<R>(r));
|
||||
}
|
||||
};
|
||||
|
||||
template <rg::viewable_range R>
|
||||
constexpr auto operator|(R &&r, const first_range_adaptor &adaptor) {
|
||||
return adaptor(std::forward<R>(r));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
namespace views {
|
||||
|
||||
inline detail::first_range_adaptor first;
|
||||
}
|
||||
|
||||
template <rg::range R>
|
||||
constexpr auto get_first(R &&r) -> std::optional<rg::range_value_t<R>>{
|
||||
return r | views::first;
|
||||
}
|
||||
} // namespace util
|
82
include/ranges/generated.hpp
Normal file
82
include/ranges/generated.hpp
Normal file
|
@ -0,0 +1,82 @@
|
|||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <ranges>
|
||||
#include <xutility>
|
||||
|
||||
|
||||
namespace util {
|
||||
namespace rg = std::ranges;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <std::invocable F> struct generated_iterator {
|
||||
using value_type = decltype(std::declval<F>()());
|
||||
using reference = value_type &;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
|
||||
value_type value{};
|
||||
F generator{};
|
||||
|
||||
generated_iterator() = default;
|
||||
generated_iterator(const F &gen) : generator(gen), value(gen()) {}
|
||||
generated_iterator(F &&gen) : generator(std::move(gen)), value(gen()) {}
|
||||
|
||||
auto generate_new_value() -> void { value = generator(); }
|
||||
|
||||
constexpr auto operator==(const generated_iterator &other) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto operator++(int) -> generated_iterator {
|
||||
const auto result = *this;
|
||||
++(*this);
|
||||
return result;
|
||||
}
|
||||
|
||||
auto operator++() -> generated_iterator & {
|
||||
generate_new_value();
|
||||
return (*this);
|
||||
}
|
||||
|
||||
auto operator*() { return value; }
|
||||
auto operator*() const { return value; }
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
template <std::invocable F>
|
||||
class generated_view : rg::view_interface<generated_view<F>> {
|
||||
F generator;
|
||||
|
||||
public:
|
||||
generated_view() = default;
|
||||
constexpr generated_view(F &&generator) : generator(generator) {}
|
||||
|
||||
constexpr auto begin() const { return detail::generated_iterator(generator); }
|
||||
constexpr auto end() const { return std::unreachable_sentinel; }
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
struct generated_range_adaptor_closure {
|
||||
template <std::invocable F> constexpr auto operator()(F &&fn) const {
|
||||
return generated_view(std::forward<F>(fn));
|
||||
}
|
||||
};
|
||||
|
||||
struct generated_range_adaptor {
|
||||
template <std::invocable F> constexpr auto operator()(F &&r) {
|
||||
return generated_view(std::forward<F>(r));
|
||||
}
|
||||
|
||||
constexpr auto operator()() { return generated_range_adaptor_closure(); }
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
namespace views {
|
||||
|
||||
inline detail::generated_range_adaptor_closure from;
|
||||
}
|
||||
|
||||
} // namespace util
|
10
include/result.hpp
Normal file
10
include/result.hpp
Normal file
|
@ -0,0 +1,10 @@
|
|||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace util {
|
||||
class Result {};
|
||||
|
||||
using result = Result;
|
||||
using expected = Result;
|
||||
} // namespace util
|
38
include/source_location.hpp
Normal file
38
include/source_location.hpp
Normal file
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
|
||||
namespace util {
|
||||
|
||||
struct source_location {
|
||||
_NODISCARD static constexpr source_location
|
||||
current(const uint_least32_t _Line_ = __builtin_LINE(),
|
||||
const uint_least32_t _Column_ = __builtin_COLUMN(),
|
||||
const char *const _File_ = __builtin_FILE(),
|
||||
const char *const _Function_ = __builtin_FUNCTION()) noexcept {
|
||||
source_location _Result;
|
||||
_Result._Line = _Line_;
|
||||
_Result._Column = _Column_;
|
||||
_Result._File = _File_;
|
||||
_Result._Function = _Function_;
|
||||
return _Result;
|
||||
}
|
||||
|
||||
_NODISCARD_CTOR constexpr source_location() noexcept = default;
|
||||
|
||||
_NODISCARD constexpr uint_least32_t line() const noexcept { return _Line; }
|
||||
_NODISCARD constexpr uint_least32_t column() const noexcept {
|
||||
return _Column;
|
||||
}
|
||||
_NODISCARD constexpr const char *file_name() const noexcept { return _File; }
|
||||
_NODISCARD constexpr const char *function_name() const noexcept {
|
||||
return _Function;
|
||||
}
|
||||
|
||||
private:
|
||||
uint_least32_t _Line{};
|
||||
uint_least32_t _Column{};
|
||||
const char *_File = "";
|
||||
const char *_Function = "";
|
||||
};
|
||||
}
|
101
include/step_function.hpp
Normal file
101
include/step_function.hpp
Normal file
|
@ -0,0 +1,101 @@
|
|||
#pragma once
|
||||
|
||||
#include "assert.hpp"
|
||||
#include "step_function.hpp"
|
||||
#include "types.hpp"
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include "time.hpp"
|
||||
|
||||
enum class ReturnTag {
|
||||
Repeat,
|
||||
Next,
|
||||
Previous,
|
||||
Goto,
|
||||
};
|
||||
|
||||
struct Return {
|
||||
ReturnTag tag;
|
||||
union {
|
||||
isize index;
|
||||
};
|
||||
};
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class Stepped {
|
||||
|
||||
using duration_type = std::chrono::system_clock::duration;
|
||||
using step_return_type = std::pair<Return, duration_type>;
|
||||
using step_type = std::function<step_return_type()>;
|
||||
|
||||
std::vector<step_type> m_steps;
|
||||
|
||||
isize m_current_step;
|
||||
duration_type m_current_wait_duration;
|
||||
util::Delta m_delta;
|
||||
|
||||
auto inc_step() -> void;
|
||||
auto dec_step() -> void;
|
||||
|
||||
public:
|
||||
Stepped() : m_current_step(0), m_steps(), m_delta(), m_current_wait_duration(0ms) {}
|
||||
|
||||
auto add_step(const step_type& step) -> void;
|
||||
auto run() -> void;
|
||||
auto reset() -> void;
|
||||
|
||||
|
||||
};
|
||||
|
||||
/// IMPLEMENTATION
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
auto Stepped::inc_step() -> void {
|
||||
if (++m_current_step >= m_steps.size()) {
|
||||
m_current_step = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto Stepped::dec_step() -> void {
|
||||
if (--m_current_step < 0) {
|
||||
m_current_step = m_steps.size() - 1;
|
||||
}
|
||||
}
|
||||
|
||||
auto Stepped::add_step(const step_type &step) -> void {
|
||||
m_steps.push_back(step);
|
||||
}
|
||||
|
||||
auto Stepped::reset() -> void {
|
||||
m_current_step = 0;
|
||||
m_current_wait_duration = 0ms;
|
||||
}
|
||||
|
||||
auto Stepped::run() -> void {
|
||||
if (m_delta.has_elapsed(m_current_wait_duration)) {
|
||||
util::assert(m_current_step < m_steps.size());
|
||||
|
||||
auto&& [result, next_wait_duration] = m_steps[m_current_step]();
|
||||
|
||||
switch (result.tag) {
|
||||
case ReturnTag::Repeat:
|
||||
break;
|
||||
case ReturnTag::Next:
|
||||
inc_step();
|
||||
break;
|
||||
case ReturnTag::Previous:
|
||||
dec_step();
|
||||
break;
|
||||
case ReturnTag::Goto: {
|
||||
util::assert(result.index < m_steps.size() && result.index >= 0);
|
||||
m_current_step = result.index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
m_current_wait_duration = next_wait_duration;
|
||||
}
|
||||
}
|
151
include/string.hpp
Normal file
151
include/string.hpp
Normal file
|
@ -0,0 +1,151 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include <cctype>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <locale>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace util {
|
||||
#ifdef _WIN32
|
||||
inline std::string to_string(std::wstring_view wstr) {
|
||||
const auto size = WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr.length(),
|
||||
nullptr, 0, nullptr, nullptr);
|
||||
|
||||
std::string str(size, 0);
|
||||
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), wstr.length(), str.data(),
|
||||
str.length(), nullptr, nullptr);
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
inline std::wstring to_wstring(std::string_view str) {
|
||||
const auto size =
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), nullptr, 0);
|
||||
|
||||
std::wstring wstr(size, 0);
|
||||
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.data(), str.length(), wstr.data(),
|
||||
wstr.length());
|
||||
|
||||
return wstr;
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename A>
|
||||
std::basic_string<A> to_lower(std::basic_string_view<A> view) {
|
||||
std::basic_string<A> out(view.size(), '\0');
|
||||
|
||||
std::transform(
|
||||
std::begin(view), std::end(view), std::begin(out),
|
||||
[](typename std::basic_string<A>::value_type c) {
|
||||
return std::use_facet<
|
||||
std::ctype<typename std::basic_string<A>::value_type>>(
|
||||
std::locale())
|
||||
.tolower(c);
|
||||
});
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
constexpr bool string_eq_nocase(std::basic_string_view<CharT> rh,
|
||||
std::basic_string_view<CharT> lh) {
|
||||
const auto rh_lower = to_lower<CharT>(rh);
|
||||
const auto lh_lower = to_lower<CharT>(lh);
|
||||
|
||||
return rh_lower == lh_lower;
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
auto split_with(const std::basic_string<CharT> &str, CharT delimiter)
|
||||
-> std::vector<std::basic_string<CharT>> {
|
||||
std::vector<std::basic_string<CharT>> parts;
|
||||
std::basic_string<CharT> part;
|
||||
|
||||
const auto start = str.find_first_not_of(delimiter);
|
||||
const auto end = str.find_last_not_of(delimiter);
|
||||
std::basic_istringstream<CharT> stream(
|
||||
str.substr(start, end == std::string::npos ? end : end + 1));
|
||||
|
||||
while (std::getline(stream, part, delimiter))
|
||||
parts.push_back(part);
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
#include <concepts>
|
||||
|
||||
template <std::integral I>
|
||||
inline constexpr auto from_char_radix(const char &c, I radix = 10) -> I {
|
||||
I value = 0;
|
||||
|
||||
if (std::isdigit(c)) {
|
||||
value = c - '0';
|
||||
} else if (std::isalpha(c)) {
|
||||
if (std::isupper(c)) {
|
||||
value = c + 10 - 'A';
|
||||
} else {
|
||||
value = c + 10 - 'a';
|
||||
}
|
||||
}
|
||||
|
||||
return value < radix ? value : std::numeric_limits<I>::max();
|
||||
}
|
||||
|
||||
template <std::integral I>
|
||||
inline constexpr auto from_string_view_radix(std::string_view view,
|
||||
I radix = 10) -> I {
|
||||
auto negative = false;
|
||||
if (view.starts_with('-')) {
|
||||
negative = true;
|
||||
view.remove_prefix(1);
|
||||
} else if (view.starts_with('+')) {
|
||||
negative = false;
|
||||
view.remove_prefix(1);
|
||||
}
|
||||
|
||||
I value = 0;
|
||||
const auto max = std::numeric_limits<I>::max();
|
||||
|
||||
for (auto &&c : view) {
|
||||
const auto i = from_char_radix<I>(c, radix);
|
||||
|
||||
if (i >= radix) {
|
||||
return max;
|
||||
}
|
||||
|
||||
if (value > max / radix) {
|
||||
return max;
|
||||
}
|
||||
|
||||
value *= radix;
|
||||
|
||||
if (i > max - value) {
|
||||
return max;
|
||||
}
|
||||
|
||||
value += i;
|
||||
}
|
||||
|
||||
if (negative && std::is_signed_v<I>) {
|
||||
return -value;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace util
|
124
include/tagged_union.hpp
Normal file
124
include/tagged_union.hpp
Normal file
|
@ -0,0 +1,124 @@
|
|||
#pragma once
|
||||
|
||||
#include "assert.hpp"
|
||||
#include "enum.hpp"
|
||||
#include "tagged_union.hpp"
|
||||
#include "type_traits.hpp"
|
||||
#include "types.hpp"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace util {
|
||||
namespace detail {
|
||||
template <int N, typename... Ts>
|
||||
using nth_type = typename std::tuple_element<N, std::tuple<Ts...>>::type;
|
||||
|
||||
template <typename> consteval std::size_t locate(std::size_t ind) {
|
||||
return static_cast<std::size_t>(-1);
|
||||
}
|
||||
|
||||
template <typename IndexedType, typename T, typename... Ts>
|
||||
consteval std::size_t locate(std::size_t ind = 0) {
|
||||
if (std::is_same<IndexedType, T>::value) {
|
||||
return ind;
|
||||
} else {
|
||||
return locate<IndexedType, Ts...>(ind + 1);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
struct index_of
|
||||
: std::integral_constant<std::size_t, locate<T, std::tuple<Ts...>>> {};
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
inline constexpr std::size_t index_of_v = index_of<T, Ts...>::value;
|
||||
|
||||
|
||||
template <typename IndexedType, typename...> struct is_in : std::false_type {};
|
||||
|
||||
template <typename IndexedType, typename Item, typename... Items>
|
||||
struct is_in<IndexedType, Item, Items...>
|
||||
: std::integral_constant<bool, std::is_same<IndexedType, Item>::value ||
|
||||
is_in<IndexedType, Items...>::value> {};
|
||||
|
||||
|
||||
template <typename Item, typename... Items>
|
||||
concept in_types = is_in<std::size_t, Item, Items...>::value;
|
||||
|
||||
|
||||
template<typename T>
|
||||
inline constexpr auto max_all_inner(T arg) -> T {
|
||||
return arg;
|
||||
}
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
inline constexpr auto max_all_inner(T arg, Ts... args) -> decltype(max_all_inner(arg)){
|
||||
return std::max(arg, max_all_inner(args...));
|
||||
}
|
||||
|
||||
template<typename... Ts>
|
||||
inline constexpr auto max_all(Ts... args) -> usize {
|
||||
return max_all_inner(args...);
|
||||
}
|
||||
|
||||
template <typename... Ts> class variadic_union {
|
||||
std::vector<u8> m_data;
|
||||
std::optional<usize> m_type_index;
|
||||
|
||||
public:
|
||||
variadic_union() : m_data(detail::max_all(sizeof(Ts)...)), m_type_index(std::nullopt) {}
|
||||
|
||||
template<std::size_t I, typename... Us>
|
||||
variadic_union(Us&&... args) : m_data(detail::max_all(sizeof(Ts)...)), m_type_index(I) {
|
||||
*reinterpret_cast<detail::nth_type<I, Ts...>*>(m_data.data()) = detail::nth_type<I, Ts...>{std::forward<Us>(args)...};
|
||||
}
|
||||
|
||||
template<std::size_t I, typename U>
|
||||
requires (std::same_as<U, detail::nth_type<I, Ts...>>)
|
||||
variadic_union(const U& value) : m_data(detail::max_all(sizeof(Ts)...)), m_type_index(I) {
|
||||
*reinterpret_cast<detail::nth_type<I, Ts...>*>(m_data.data()) = value;
|
||||
}
|
||||
|
||||
template<std::size_t I, typename U>
|
||||
requires (std::same_as<U, detail::nth_type<I, Ts...>>)
|
||||
variadic_union(U&& value) : m_data(detail::max_all(sizeof(Ts)...)), m_type_index(I) {
|
||||
*reinterpret_cast<detail::nth_type<I, Ts...>*>(m_data.data()) = std::move(value);
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires (in_types<U, Ts...>)
|
||||
variadic_union(U&& value) : m_data(detail::max_all(sizeof(Ts)...)), m_type_index(index_of_v<U, Ts...>) {
|
||||
*reinterpret_cast<U*>(m_data.data()) = std::move(value);
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
requires (in_types<U, Ts...>)
|
||||
variadic_union(const U& value) : m_data(detail::max_all(sizeof(Ts)...)), m_type_index(index_of_v<U, Ts...>) {
|
||||
*reinterpret_cast<U*>(m_data.data()) = value;
|
||||
}
|
||||
|
||||
template<typename U>
|
||||
constexpr auto get() -> U& {
|
||||
util::assert_eq(m_type_index.value(), index_of_v<U, Ts...>);
|
||||
|
||||
return reinterpret_cast<U*>(m_data.data());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
#define NTH(Is, Ts) typename detail::nth_type<, Ts...> element_##I;
|
||||
|
||||
template <util::enum_type Tag, typename... Ts> class TaggedUnion {
|
||||
Tag tag;
|
||||
detail::variadic_union<Ts...> inner;
|
||||
public:
|
||||
template <typename U>
|
||||
requires (detail::in_types<U, Ts...>)
|
||||
TaggedUnion(const Tag& tag, const U& value) : tag(tag), inner(detail::variadic_union(value)) {}
|
||||
|
||||
};
|
||||
} // namespace util
|
164
include/thread.hpp
Normal file
164
include/thread.hpp
Normal file
|
@ -0,0 +1,164 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <Windows.h>
|
||||
|
||||
#include <TlHelp32.h>
|
||||
|
||||
namespace util {
|
||||
namespace threads {
|
||||
|
||||
auto get_all_threads(u32 pid) -> std::vector<u32>;
|
||||
|
||||
auto get_all_threads() -> std::vector<u32>;
|
||||
auto get_other_threads() -> std::vector<u32>;
|
||||
|
||||
auto suspend_threads(const std::vector<u32> &thread_ids) -> std::vector<u32>;
|
||||
|
||||
auto resume_threads(const std::vector<u32> &thread_ids) -> bool;
|
||||
|
||||
/// suspends all threads, only then executes function, and resumes threads
|
||||
/// returns `nullopt` if not all threads were suspended.
|
||||
template <typename T, typename F>
|
||||
auto with_suspended_threads(F &&fn) -> std::optional<T> {
|
||||
auto threads = get_other_threads();
|
||||
|
||||
auto suspended_threads = suspend_threads(threads);
|
||||
|
||||
const auto suspended_all_threads = suspended_threads.size() == threads.size();
|
||||
|
||||
std::optional<T> result = std::nullopt;
|
||||
if (suspended_all_threads) {
|
||||
result = fn();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace threads
|
||||
|
||||
namespace processes {
|
||||
auto get_process_id(std::string_view name) -> std::optional<u32>;
|
||||
auto get_process_id(std::wstring_view name) -> std::optional<u32>;
|
||||
} // namespace processes
|
||||
|
||||
/// impl
|
||||
|
||||
namespace threads {
|
||||
inline auto get_all_threads(u32 pid) -> std::vector<u32> {
|
||||
std::vector<u32> threads;
|
||||
const auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
||||
|
||||
if (snapshot != INVALID_HANDLE_VALUE) {
|
||||
THREADENTRY32 t;
|
||||
t.dwSize = sizeof(t);
|
||||
if (Thread32First(snapshot, &t)) {
|
||||
if (t.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) +
|
||||
sizeof(t.th32OwnerProcessID)) {
|
||||
if (t.th32OwnerProcessID == pid) {
|
||||
threads.push_back(t.th32ThreadID);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return threads;
|
||||
}
|
||||
|
||||
inline auto get_all_threads() -> std::vector<u32> {
|
||||
return get_all_threads(GetCurrentProcessId());
|
||||
}
|
||||
|
||||
inline auto get_other_threads() -> std::vector<u32> {
|
||||
auto threads = get_all_threads(GetCurrentProcessId());
|
||||
|
||||
threads.erase(
|
||||
std::remove_if(threads.begin(), threads.end(),
|
||||
[](auto &&id) { return id == GetCurrentThreadId(); }),
|
||||
threads.end());
|
||||
|
||||
return threads;
|
||||
}
|
||||
|
||||
inline auto resume_threads(const std::vector<u32> &thread_ids) -> bool {
|
||||
// return false if any threads failed to resume
|
||||
return std::count_if(thread_ids.begin(), thread_ids.end(), [](auto &&id) {
|
||||
if (auto handle = OpenThread(THREAD_SUSPEND_RESUME, false, id)) {
|
||||
const auto result = ResumeThread(handle);
|
||||
CloseHandle(handle);
|
||||
|
||||
return result != (u32)-1;
|
||||
}
|
||||
return false;
|
||||
}) == thread_ids.size();
|
||||
}
|
||||
|
||||
inline auto suspend_threads(const std::vector<u32> &thread_ids)
|
||||
-> std::vector<u32> {
|
||||
std::vector<u32> suspended_threads;
|
||||
for (auto &&id : thread_ids) {
|
||||
if (auto handle = OpenThread(THREAD_SUSPEND_RESUME, false, id)) {
|
||||
if (SuspendThread(handle) != (u32)-1) {
|
||||
suspended_threads.push_back(id);
|
||||
}
|
||||
CloseHandle(handle);
|
||||
}
|
||||
}
|
||||
return suspended_threads;
|
||||
}
|
||||
} // namespace threads
|
||||
|
||||
namespace processes {
|
||||
|
||||
inline auto get_process_id(std::string_view name) -> std::optional<u32> {
|
||||
auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
std::optional<u32> result = std::nullopt;
|
||||
|
||||
PROCESSENTRY32 process_entry;
|
||||
if (Process32First(snapshot, &process_entry)) {
|
||||
do {
|
||||
const auto process_name = std::string_view(process_entry.szExeFile);
|
||||
if (name == process_name) {
|
||||
result = process_entry.th32ProcessID;
|
||||
}
|
||||
} while (Process32Next(snapshot, &process_entry));
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
inline auto get_process_id(std::wstring_view name) -> std::optional<u32> {
|
||||
auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
std::optional<u32> result = std::nullopt;
|
||||
|
||||
PROCESSENTRY32W process_entry;
|
||||
if (Process32FirstW(snapshot, &process_entry)) {
|
||||
do {
|
||||
const auto process_name = std::wstring_view(process_entry.szExeFile);
|
||||
if (name == process_name) {
|
||||
result = process_entry.th32ProcessID;
|
||||
}
|
||||
} while (Process32NextW(snapshot, &process_entry));
|
||||
}
|
||||
|
||||
CloseHandle(snapshot);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace processes
|
||||
|
||||
} // namespace util
|
||||
#endif
|
74
include/time.hpp
Normal file
74
include/time.hpp
Normal file
|
@ -0,0 +1,74 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.hpp"
|
||||
#include <chrono>
|
||||
#include <ratio>
|
||||
|
||||
namespace util {
|
||||
using std::chrono::system_clock;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
template <typename F>
|
||||
auto time_of(F &&fn) -> std::chrono::duration<f64, std::milli> {
|
||||
const auto start = std::chrono::system_clock::now();
|
||||
fn();
|
||||
const auto end = std::chrono::system_clock::now();
|
||||
|
||||
return {end - start};
|
||||
}
|
||||
|
||||
class Delta {
|
||||
system_clock::time_point last;
|
||||
|
||||
public:
|
||||
Delta() : last(system_clock::now()) {}
|
||||
|
||||
template <typename Rep, typename Ratio>
|
||||
inline auto has_elapsed(const std::chrono::duration<Rep, Ratio> &duration)
|
||||
-> bool {
|
||||
const auto now = system_clock::now();
|
||||
|
||||
if (duration < (now - last)) {
|
||||
last = now;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Rep = usize, typename Ratio = std::milli>
|
||||
inline auto elapsed() -> std::chrono::duration<Rep, Ratio> {
|
||||
const auto now = system_clock::now();
|
||||
const auto duration =
|
||||
std::chrono::duration_cast<std::chrono::duration<Rep, Ratio>>(now -
|
||||
last);
|
||||
last = now;
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
inline auto has_elapsed_s(f64 seconds) -> bool {
|
||||
return has_elapsed(std::chrono::duration<f64>(seconds));
|
||||
}
|
||||
|
||||
inline auto has_elapsed_ms(i64 milliseconds) -> bool {
|
||||
return has_elapsed(std::chrono::duration<i64, std::milli>(milliseconds));
|
||||
}
|
||||
|
||||
inline auto elapsed_ms() -> std::chrono::duration<isize, std::milli> {
|
||||
const auto now = system_clock::now();
|
||||
const auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
system_clock::now() - last);
|
||||
last = now;
|
||||
return duration;
|
||||
}
|
||||
|
||||
inline auto elapsed_s() -> std::chrono::duration<f64> {
|
||||
const auto now = system_clock::now();
|
||||
const auto duration =
|
||||
std::chrono::duration<f64>(system_clock::now() - last);
|
||||
last = now;
|
||||
return duration;
|
||||
}
|
||||
};
|
||||
} // namespace util
|
78
include/type_traits.hpp
Normal file
78
include/type_traits.hpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
#pragma once
|
||||
|
||||
namespace util {
|
||||
|
||||
|
||||
template <typename T, T V>
|
||||
struct integral_constant {static constexpr T value = V;};
|
||||
|
||||
struct true_type : integral_constant<bool, true> {};
|
||||
struct false_type : integral_constant<bool, false> {};
|
||||
|
||||
inline constexpr bool true_type_v = true_type::value;
|
||||
inline constexpr bool false_type_v = false_type::value;
|
||||
|
||||
namespace detail {
|
||||
template <class T> struct type_identity {
|
||||
using type = T;
|
||||
}; // or use std::type_identity (since C++20)
|
||||
|
||||
template <class T> // Note that `cv void&` is a substitution failure
|
||||
auto try_add_lvalue_reference(int) -> type_identity<T &>;
|
||||
template <class T> // Handle T = cv void case
|
||||
auto try_add_lvalue_reference(...) -> type_identity<T>;
|
||||
|
||||
template <class T> auto try_add_rvalue_reference(int) -> type_identity<T &&>;
|
||||
template <class T> auto try_add_rvalue_reference(...) -> type_identity<T>;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <class T>
|
||||
struct add_lvalue_reference : decltype(detail::try_add_lvalue_reference<T>(0)) {
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct add_rvalue_reference : decltype(detail::try_add_rvalue_reference<T>(0)) {
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using add_rvalue_reference_t = typename add_rvalue_reference<T>::type;
|
||||
|
||||
template <class T> add_rvalue_reference_t<T> declval() noexcept {
|
||||
#pragma clang diagnostic push
|
||||
static_assert(false, "Calling declval is ill-formed, see N4892 [declval]/2.");
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
auto test_returnable(int)
|
||||
-> decltype(void(static_cast<T (*)()>(nullptr)), true_type{});
|
||||
template <class> auto test_returnable(...) -> false_type;
|
||||
|
||||
template <class From, class To>
|
||||
auto test_implicitly_convertible(int)
|
||||
-> decltype(void(declval<void (&)(To)>()(declval<From>())),
|
||||
true_type{});
|
||||
template <class, class>
|
||||
auto test_implicitly_convertible(...) -> false_type;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T>
|
||||
struct is_void : false_type{};
|
||||
|
||||
template<>
|
||||
struct is_void<void> : true_type {};
|
||||
|
||||
template <typename T>
|
||||
inline constexpr bool is_void_v = is_void<T>::value;
|
||||
|
||||
template< class T > struct remove_reference { typedef T type; };
|
||||
template< class T > struct remove_reference<T&> { typedef T type; };
|
||||
template< class T > struct remove_reference<T&&> { typedef T type; };
|
||||
|
||||
template< class T >
|
||||
using remove_reference_t = typename remove_reference<T>::type;
|
||||
}
|
24
include/types.hpp
Normal file
24
include/types.hpp
Normal file
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
using byte = unsigned char;
|
||||
|
||||
using u8 = uint8_t;
|
||||
using i8 = int8_t;
|
||||
|
||||
using u16 = uint16_t;
|
||||
using i16 = int16_t;
|
||||
|
||||
using u32 = uint32_t;
|
||||
using i32 = int32_t;
|
||||
|
||||
using u64 = uint64_t;
|
||||
using i64 = int64_t;
|
||||
|
||||
using usize = std::size_t;
|
||||
using isize = std::ptrdiff_t;
|
||||
|
||||
using f32 = float;
|
||||
using f64 = double;
|
50
src/main.cc
Normal file
50
src/main.cc
Normal file
|
@ -0,0 +1,50 @@
|
|||
#include "format.hpp"
|
||||
#include "print.hpp"
|
||||
#include "ranges/first.hpp"
|
||||
#include "ranges/generated.hpp"
|
||||
#include "tagged_union.hpp"
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <stdio.h>
|
||||
|
||||
#define UTIL_ASSERT_FORMAT
|
||||
#include "assert.hpp"
|
||||
#include "ranges/collect.hpp"
|
||||
#include <ranges>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
|
||||
using namespace std::string_view_literals;
|
||||
|
||||
int main(int argc, const char *argv[]) {
|
||||
printf("Hello, Alloy!\n");
|
||||
auto vec = std::vector<int>({1, 2, 3, 4, 5});
|
||||
|
||||
auto range = vec | std::views::filter([](auto &i) { return i % 2; });
|
||||
auto infinite_range = util::views::from([] {return 1;}) | std::views::take(5);
|
||||
|
||||
auto random = util::views::from([] {
|
||||
return "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY"
|
||||
[std::rand() %
|
||||
std::strlen(
|
||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXY")];
|
||||
}) |
|
||||
std::views::take(10) | util::views::collect<std::string>;
|
||||
|
||||
auto first = random | util::views::first;
|
||||
|
||||
util::println("{}", first);
|
||||
util::println("{}", random);
|
||||
|
||||
for (auto&& i : infinite_range) {
|
||||
util::print("{}\n", i);
|
||||
}
|
||||
|
||||
util::print("hello {}\n", "world");
|
||||
|
||||
util::assert_eq("asdf"sv, "nsdf"sv);
|
||||
|
||||
const auto set = util::collect<std::set<int>>(range);
|
||||
return 1;
|
||||
}
|
Loading…
Reference in a new issue