mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-07-10 08:51:28 +12:00
Formatting system improved
`unveil<>` renamed to `fmt_unveil<>`, now packs args to u64 imitating va_args `bijective...` removed, `cfg::enum_entry` now uses formatting system `fmt_class_string<>` added, providing type-specific "%s" handler function Added `fmt::append`, removed `fmt::narrow` (too obscure) Utilities/cfmt.h: C-style format template function (WIP) Minor formatting fixes and cleanup
This commit is contained in:
parent
662fce38bd
commit
5a36c57c57
63 changed files with 1305 additions and 469 deletions
|
@ -298,10 +298,6 @@ union alignas(16) v128
|
|||
_u64[0] = 0;
|
||||
_u64[1] = 0;
|
||||
}
|
||||
|
||||
std::string to_hex() const;
|
||||
|
||||
std::string to_xyzw() const;
|
||||
};
|
||||
|
||||
inline v128 operator |(const v128& left, const v128& right)
|
||||
|
@ -950,11 +946,13 @@ template<typename T> using atomic_le_t = atomic_t<le_t<T>>;
|
|||
|
||||
// Formatting for BE/LE data
|
||||
template<typename T, bool Se, std::size_t Align>
|
||||
struct unveil<se_t<T, Se, Align>, void>
|
||||
struct fmt_unveil<se_t<T, Se, Align>, void>
|
||||
{
|
||||
static inline auto get(const se_t<T, Se, Align>& arg)
|
||||
using type = typename fmt_unveil<T>::type;
|
||||
|
||||
static inline u64 get(const se_t<T, Se, Align>& arg)
|
||||
{
|
||||
return unveil<T>::get(arg);
|
||||
return fmt_unveil<T>::get(arg);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -242,3 +242,36 @@ struct ff_t : bf_base<T, N>
|
|||
return V;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, uint I, uint N>
|
||||
struct fmt_unveil<bf_t<T, I, N>, void>
|
||||
{
|
||||
using type = typename fmt_unveil<simple_t<T>>::type;
|
||||
|
||||
static inline u64 get(const bf_t<T, I, N>& bf)
|
||||
{
|
||||
return fmt_unveil<type>::get(bf);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename F, typename... Fields>
|
||||
struct fmt_unveil<cf_t<F, Fields...>, void>
|
||||
{
|
||||
using type = typename fmt_unveil<simple_t<typename F::type>>::type;
|
||||
|
||||
static inline u64 get(const cf_t<F, Fields...>& cf)
|
||||
{
|
||||
return fmt_unveil<type>::get(cf);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, T V, uint N>
|
||||
struct fmt_unveil<ff_t<T, V, N>, void>
|
||||
{
|
||||
using type = typename fmt_unveil<simple_t<T>>::type;
|
||||
|
||||
static inline u64 get(const ff_t<T, V, N>& ff)
|
||||
{
|
||||
return fmt_unveil<type>::get(ff);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -256,3 +256,14 @@ struct atomic_test_and_complement<bitset_t<T>, T, std::enable_if_t<std::is_enum<
|
|||
|
||||
static constexpr auto atomic_op = &_op;
|
||||
};
|
||||
|
||||
template<typename T, std::size_t BitSize>
|
||||
struct fmt_unveil<bitset_t<T, BitSize>, void>
|
||||
{
|
||||
using type = typename bitset_t<T, BitSize>::raw_type;
|
||||
|
||||
static inline u64 get(const bitset_t<T, BitSize>& value)
|
||||
{
|
||||
return fmt_unveil<type>::get(static_cast<type>(value._value()));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -85,6 +85,62 @@ bool cfg::try_to_int64(s64* out, const std::string& value, s64 min, s64 max)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, const std::string& value)
|
||||
{
|
||||
for (u64 i = 0;; i++)
|
||||
{
|
||||
std::string var;
|
||||
func(var, i);
|
||||
|
||||
if (var == value)
|
||||
{
|
||||
if (out) *out = i;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string hex;
|
||||
fmt_class_string<u64>::format(hex, i);
|
||||
if (var == hex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
const auto val = std::stoull(value, nullptr, 0);
|
||||
|
||||
if (out) *out = val;
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> cfg::try_to_enum_list(decltype(&fmt_class_string<int>::format) func)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
for (u64 i = 0;; i++)
|
||||
{
|
||||
std::string var;
|
||||
func(var, i);
|
||||
|
||||
std::string hex;
|
||||
fmt_class_string<u64>::format(hex, i);
|
||||
if (var == hex)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
result.emplace_back(std::move(var));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void cfg::encode(YAML::Emitter& out, const cfg::entry_base& rhs)
|
||||
{
|
||||
switch (rhs.get_type())
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "Utilities/types.h"
|
||||
#include "Utilities/Atomic.h"
|
||||
#include "Utilities/StrFmt.h"
|
||||
|
||||
#include <initializer_list>
|
||||
#include <exception>
|
||||
|
@ -18,6 +19,12 @@ namespace cfg
|
|||
// Convert string to signed integer
|
||||
bool try_to_int64(s64* out, const std::string& value, s64 min, s64 max);
|
||||
|
||||
// Internal hack
|
||||
bool try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, const std::string&);
|
||||
|
||||
// Internal hack
|
||||
std::vector<std::string> try_to_enum_list(decltype(&fmt_class_string<int>::format) func);
|
||||
|
||||
// Config tree entry type.
|
||||
enum class type : uint
|
||||
{
|
||||
|
@ -296,26 +303,26 @@ namespace cfg
|
|||
|
||||
std::string to_string() const override
|
||||
{
|
||||
for (std::size_t i = 0; i < sizeof(bijective<T, const char*>::map) / sizeof(bijective_pair<T, const char*>); i++)
|
||||
{
|
||||
if (bijective<T, const char*>::map[i].v1 == m_value)
|
||||
{
|
||||
return bijective<T, const char*>::map[i].v2;
|
||||
}
|
||||
}
|
||||
|
||||
return{}; // TODO: ???
|
||||
std::string result;
|
||||
fmt_class_string<T>::format(result, fmt_unveil<T>::get(m_value));
|
||||
return result; // TODO: ???
|
||||
}
|
||||
|
||||
bool from_string(const std::string& value) override
|
||||
{
|
||||
for (std::size_t i = 0; i < sizeof(bijective<T, const char*>::map) / sizeof(bijective_pair<T, const char*>); i++)
|
||||
u64 result;
|
||||
|
||||
if (try_to_enum_value(&result, &fmt_class_string<T>::format, value))
|
||||
{
|
||||
if (bijective<T, const char*>::map[i].v2 == value)
|
||||
const auto val = static_cast<std::underlying_type_t<T>>(result);
|
||||
|
||||
if (static_cast<u64>(val) != result)
|
||||
{
|
||||
m_value = bijective<T, const char*>::map[i].v1;
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_value = static_cast<T>(val);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -323,14 +330,7 @@ namespace cfg
|
|||
|
||||
std::vector<std::string> to_list() const override
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
for (std::size_t i = 0; i < sizeof(bijective<T, const char*>::map) / sizeof(bijective_pair<T, const char*>); i++)
|
||||
{
|
||||
result.emplace_back(bijective<T, const char*>::map[i].v2);
|
||||
}
|
||||
|
||||
return result;
|
||||
return try_to_enum_list(&fmt_class_string<T>::format);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -1333,3 +1333,37 @@ u64 fs::get_dir_size(const std::string& path)
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<fs::seek_mode>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](auto arg)
|
||||
{
|
||||
switch (arg)
|
||||
{
|
||||
STR_CASE(fs::seek_mode::seek_set);
|
||||
STR_CASE(fs::seek_mode::seek_cur);
|
||||
STR_CASE(fs::seek_mode::seek_end);
|
||||
}
|
||||
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<fs::error>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](auto arg)
|
||||
{
|
||||
switch (arg)
|
||||
{
|
||||
case fs::error::ok: return "OK";
|
||||
|
||||
case fs::error::inval: return "Invalid arguments";
|
||||
case fs::error::noent: return "Not found";
|
||||
case fs::error::exist: return "Already exists";
|
||||
}
|
||||
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -463,21 +463,3 @@ namespace fs
|
|||
// Error code returned
|
||||
extern thread_local error g_tls_error;
|
||||
}
|
||||
|
||||
template<>
|
||||
struct unveil<fs::error>
|
||||
{
|
||||
static inline const char* get(fs::error error)
|
||||
{
|
||||
switch (error)
|
||||
{
|
||||
case fs::error::ok: return "OK";
|
||||
|
||||
case fs::error::inval: return "Invalid arguments";
|
||||
case fs::error::noent: return "Not found";
|
||||
case fs::error::exist: return "Already exists";
|
||||
|
||||
default: throw error;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,15 +3,31 @@
|
|||
#include "StrFmt.h"
|
||||
|
||||
#include "rpcs3_version.h"
|
||||
#include <cstdarg>
|
||||
#include <string>
|
||||
|
||||
// Thread-specific log prefix provider
|
||||
thread_local std::string(*g_tls_log_prefix)() = nullptr;
|
||||
|
||||
#ifndef _MSC_VER
|
||||
constexpr DECLARE(bijective<logs::level, const char*>::map);
|
||||
#endif
|
||||
template<>
|
||||
void fmt_class_string<logs::level>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](auto lev)
|
||||
{
|
||||
switch (lev)
|
||||
{
|
||||
case logs::level::always: return "Nothing";
|
||||
case logs::level::fatal: return "Fatal";
|
||||
case logs::level::error: return "Error";
|
||||
case logs::level::todo: return "TODO";
|
||||
case logs::level::success: return "Success";
|
||||
case logs::level::warning: return "Warning";
|
||||
case logs::level::notice: return "Notice";
|
||||
case logs::level::trace: return "Trace";
|
||||
}
|
||||
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
namespace logs
|
||||
{
|
||||
|
@ -72,13 +88,10 @@ void logs::listener::add(logs::listener* _new)
|
|||
}
|
||||
}
|
||||
|
||||
void logs::channel::broadcast(const logs::channel& ch, logs::level sev, const char* fmt...)
|
||||
void logs::channel::broadcast(const logs::channel& ch, logs::level sev, const char* fmt, const fmt::supplementary_info* sup, const u64* args)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
std::string&& text = fmt::unsafe_vformat(fmt, args);
|
||||
std::string&& prefix = g_tls_log_prefix ? g_tls_log_prefix() : "";
|
||||
va_end(args);
|
||||
std::string text; fmt::raw_append(text, fmt, sup, args);
|
||||
std::string prefix(g_tls_log_prefix ? g_tls_log_prefix() : "");
|
||||
|
||||
// Prepare message information
|
||||
message msg;
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include "Platform.h"
|
||||
#include "Atomic.h"
|
||||
#include "StrFmt.h"
|
||||
|
||||
namespace logs
|
||||
{
|
||||
|
@ -67,19 +69,17 @@ namespace logs
|
|||
|
||||
// Formatting function
|
||||
template<typename... Args>
|
||||
void format(level sev, const char* fmt, const Args&... args) const
|
||||
SAFE_BUFFERS void format(level sev, const char* fmt, const Args&... args) const
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
if (sev <= enabled)
|
||||
#else
|
||||
if (__builtin_expect(sev <= enabled, 0))
|
||||
#endif
|
||||
broadcast(*this, sev, fmt, ::unveil<Args>::get(args)...);
|
||||
if (UNLIKELY(sev <= enabled))
|
||||
{
|
||||
broadcast(*this, sev, fmt, fmt::arg_type_info::get<typename fmt_unveil<Args>::type...>(), fmt::args_t<Args...>{::fmt_unveil<Args>::get(args)...});
|
||||
}
|
||||
}
|
||||
|
||||
#define GEN_LOG_METHOD(_sev)\
|
||||
template<typename... Args>\
|
||||
void _sev(const char* fmt, const Args&... args) const\
|
||||
SAFE_BUFFERS void _sev(const char* fmt, const Args&... args) const\
|
||||
{\
|
||||
return format<Args...>(level::_sev, fmt, args...);\
|
||||
}
|
||||
|
@ -95,7 +95,7 @@ namespace logs
|
|||
#undef GEN_LOG_METHOD
|
||||
private:
|
||||
// Send log message to global logger instance
|
||||
static void broadcast(const channel& ch, level sev, const char* fmt...);
|
||||
static void broadcast(const channel& ch, level sev, const char*, const fmt::supplementary_info*, const u64*);
|
||||
};
|
||||
|
||||
/* Small set of predefined channels */
|
||||
|
@ -110,22 +110,6 @@ namespace logs
|
|||
extern channel ARMv7;
|
||||
}
|
||||
|
||||
template<>
|
||||
struct bijective<logs::level, const char*>
|
||||
{
|
||||
static constexpr bijective_pair<logs::level, const char*> map[]
|
||||
{
|
||||
{ logs::level::always, "Nothing" },
|
||||
{ logs::level::fatal, "Fatal" },
|
||||
{ logs::level::error, "Error" },
|
||||
{ logs::level::todo, "TODO" },
|
||||
{ logs::level::success, "Success" },
|
||||
{ logs::level::warning, "Warning" },
|
||||
{ logs::level::notice, "Notice" },
|
||||
{ logs::level::trace, "Trace" },
|
||||
};
|
||||
};
|
||||
|
||||
// Legacy:
|
||||
|
||||
#define LOG_SUCCESS(ch, fmt, ...) logs::ch.success(fmt, ##__VA_ARGS__)
|
||||
|
|
|
@ -61,7 +61,7 @@ public:
|
|||
Show();
|
||||
}
|
||||
|
||||
force_inline void Update(const u8 thread_id, const u64 value, const wxString& msg)
|
||||
void Update(const u8 thread_id, const u64 value, const wxString& msg)
|
||||
{
|
||||
if(thread_id > m_cores) return;
|
||||
|
||||
|
|
|
@ -29,15 +29,21 @@
|
|||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define never_inline __declspec(noinline)
|
||||
#define SAFE_BUFFERS __declspec(safebuffers)
|
||||
#else
|
||||
#define never_inline __attribute__((noinline))
|
||||
#define SAFE_BUFFERS
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define force_inline __forceinline
|
||||
#define NEVER_INLINE __declspec(noinline)
|
||||
#else
|
||||
#define force_inline __attribute__((always_inline)) inline
|
||||
#define NEVER_INLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define FORCE_INLINE __forceinline
|
||||
#else
|
||||
#define FORCE_INLINE __attribute__((always_inline)) inline
|
||||
#endif
|
||||
|
||||
#if defined(__GNUG__)
|
||||
|
|
|
@ -1,73 +1,178 @@
|
|||
#include "StrFmt.h"
|
||||
#include "BEType.h"
|
||||
#include "StrUtil.h"
|
||||
#include "cfmt.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
#include <algorithm>
|
||||
|
||||
std::string v128::to_hex() const
|
||||
void fmt_class_string<const void*>::format(std::string& out, u64 arg)
|
||||
{
|
||||
return fmt::format("%016llx%016llx", _u64[1], _u64[0]);
|
||||
fmt::append(out, "%p", reinterpret_cast<const void*>(static_cast<std::uintptr_t>(arg)));
|
||||
}
|
||||
|
||||
std::string v128::to_xyzw() const
|
||||
void fmt_class_string<const char*>::format(std::string& out, u64 arg)
|
||||
{
|
||||
return fmt::format("x: %g y: %g z: %g w: %g", _f[3], _f[2], _f[1], _f[0]);
|
||||
out += reinterpret_cast<const char*>(static_cast<std::uintptr_t>(arg));
|
||||
}
|
||||
|
||||
std::string fmt::unsafe_vformat(const char* fmt, va_list _args) noexcept
|
||||
template<>
|
||||
void fmt_class_string<std::string>::format(std::string& out, u64 arg)
|
||||
{
|
||||
// Fixed stack buffer for the first attempt
|
||||
std::array<char, 4096> fixed_buf;
|
||||
out += get_object(arg).c_str(); // TODO?
|
||||
}
|
||||
|
||||
// Possibly dynamically allocated buffer for the second attempt
|
||||
std::unique_ptr<char[]> buf;
|
||||
template<>
|
||||
void fmt_class_string<std::vector<char>>::format(std::string& out, u64 arg)
|
||||
{
|
||||
const std::vector<char>& obj = get_object(arg);
|
||||
out.append(obj.cbegin(), obj.cend());
|
||||
}
|
||||
|
||||
// Pointer to the current buffer
|
||||
char* buf_addr = fixed_buf.data();
|
||||
template<>
|
||||
void fmt_class_string<char>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%hhx", static_cast<char>(arg));
|
||||
}
|
||||
|
||||
for (std::size_t buf_size = fixed_buf.size();;)
|
||||
template<>
|
||||
void fmt_class_string<uchar>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%hhx", static_cast<uchar>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<schar>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%hhx", static_cast<schar>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<short>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%hx", static_cast<short>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<ushort>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%hx", static_cast<ushort>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<int>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%x", static_cast<int>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<uint>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%x", static_cast<uint>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<long>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%lx", static_cast<long>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<ulong>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%lx", static_cast<ulong>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<llong>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%llx", static_cast<llong>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<ullong>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "0x%llx", static_cast<ullong>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<float>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%f", static_cast<float>(reinterpret_cast<f64&>(arg)));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<double>::format(std::string& out, u64 arg)
|
||||
{
|
||||
fmt::append(out, "%f", reinterpret_cast<f64&>(arg));
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<bool>::format(std::string& out, u64 arg)
|
||||
{
|
||||
out += arg ? "true" : "false"; // TODO?
|
||||
}
|
||||
|
||||
template<>
|
||||
void fmt_class_string<v128>::format(std::string& out, u64 arg)
|
||||
{
|
||||
const v128& vec = get_object(arg);
|
||||
fmt::append(out, "0x%016llx%016llx", vec._u64[1], vec._u64[0]);
|
||||
}
|
||||
|
||||
namespace fmt
|
||||
{
|
||||
struct cfmt_src;
|
||||
}
|
||||
|
||||
// Temporary implementation
|
||||
struct fmt::cfmt_src
|
||||
{
|
||||
const fmt::supplementary_info* sup;
|
||||
const u64* args;
|
||||
|
||||
bool test(std::size_t index = 0)
|
||||
{
|
||||
va_list args;
|
||||
va_copy(args, _args);
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wformat-security"
|
||||
#endif
|
||||
const std::size_t len = std::vsnprintf(buf_addr, buf_size, fmt, args);
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
va_end(args);
|
||||
|
||||
assert(len <= INT_MAX);
|
||||
|
||||
if (len < buf_size)
|
||||
for (std::size_t i = 0; i <= index; i++)
|
||||
{
|
||||
return{ buf_addr, len };
|
||||
if (!sup[i].fmt_string)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
buf.reset(buf_addr = new char[buf_size = len + 1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
T get(std::size_t index = 0)
|
||||
{
|
||||
return reinterpret_cast<const T&>(args[index]);
|
||||
}
|
||||
|
||||
void skip(std::size_t extra)
|
||||
{
|
||||
++sup += extra;
|
||||
++args += extra;
|
||||
}
|
||||
|
||||
std::size_t fmt_string(std::string& out)
|
||||
{
|
||||
const std::size_t start = out.size();
|
||||
sup->fmt_string(out, args[0]);
|
||||
return out.size() - start;
|
||||
}
|
||||
};
|
||||
|
||||
void fmt::raw_append(std::string& out, const char* fmt, const fmt::supplementary_info* sup, const u64* args) noexcept
|
||||
{
|
||||
cfmt_append(out, fmt, cfmt_src{sup, args});
|
||||
}
|
||||
|
||||
std::string fmt::unsafe_format(const char* fmt...) noexcept
|
||||
char* fmt::alloc_format(const char* fmt, const fmt::supplementary_info* sup, const u64* args) noexcept
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
auto result = unsafe_vformat(fmt, args);
|
||||
va_end(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fmt::exception_base::exception_base(const char* fmt...)
|
||||
: std::runtime_error((va_start(m_args, fmt), unsafe_vformat(fmt, m_args)))
|
||||
{
|
||||
va_end(m_args);
|
||||
std::string str;
|
||||
raw_append(str, fmt, sup, args);
|
||||
return static_cast<char*>(std::memcpy(std::malloc(str.size() + 1), str.data(), str.size() + 1));
|
||||
}
|
||||
|
||||
std::string fmt::replace_first(const std::string& src, const std::string& from, const std::string& to)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include <exception>
|
||||
#include <string>
|
||||
|
||||
|
@ -9,44 +8,224 @@
|
|||
|
||||
namespace fmt
|
||||
{
|
||||
std::string unsafe_format(const char* fmt...) noexcept;
|
||||
std::string unsafe_vformat(const char*, va_list) noexcept;
|
||||
|
||||
// Formatting function
|
||||
template<typename... Args>
|
||||
inline std::string format(const char* fmt, const Args&... args)
|
||||
static std::string format(const char*, const Args&...);
|
||||
}
|
||||
|
||||
template<typename T, typename>
|
||||
struct fmt_unveil
|
||||
{
|
||||
static_assert(sizeof(T) > 0, "fmt_unveil<>: cannot pass forward-declared object");
|
||||
|
||||
using type = T;
|
||||
|
||||
static inline u64 get(const T& arg)
|
||||
{
|
||||
return unsafe_format(fmt, ::unveil<Args>::get(args)...);
|
||||
return reinterpret_cast<std::uintptr_t>(&arg);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct fmt_unveil<T, std::enable_if_t<std::is_integral<T>::value && sizeof(T) <= 8 && alignof(T) <= 8>>
|
||||
{
|
||||
using type = T;
|
||||
|
||||
static inline u64 get(T arg)
|
||||
{
|
||||
return static_cast<T>(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct fmt_unveil<T, std::enable_if_t<std::is_floating_point<T>::value && sizeof(T) <= 8 && alignof(T) <= 8>>
|
||||
{
|
||||
using type = T;
|
||||
|
||||
// Convert FP to f64 and reinterpret (TODO?)
|
||||
static inline u64 get(f64 arg)
|
||||
{
|
||||
return reinterpret_cast<u64&>(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct fmt_unveil<T, std::enable_if_t<std::is_enum<T>::value>>
|
||||
{
|
||||
using type = T;
|
||||
|
||||
static inline u64 get(T arg)
|
||||
{
|
||||
return static_cast<std::underlying_type_t<T>>(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct fmt_unveil<T*, void>
|
||||
{
|
||||
using type = const T*;
|
||||
|
||||
static inline u64 get(const T* arg)
|
||||
{
|
||||
return reinterpret_cast<std::uintptr_t>(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, std::size_t N>
|
||||
struct fmt_unveil<T[N], void>
|
||||
{
|
||||
using type = const T*;
|
||||
|
||||
static inline u64 get(const T* arg)
|
||||
{
|
||||
return reinterpret_cast<std::uintptr_t>(arg);
|
||||
}
|
||||
};
|
||||
|
||||
template<>
|
||||
struct fmt_unveil<b8, void>
|
||||
{
|
||||
using type = bool;
|
||||
|
||||
static inline u64 get(const b8& value)
|
||||
{
|
||||
return fmt_unveil<bool>::get(value);
|
||||
}
|
||||
};
|
||||
|
||||
// String type format provider, also type classifier (format() called if an argument is formatted as "%s")
|
||||
template<typename T, typename = void>
|
||||
struct fmt_class_string
|
||||
{
|
||||
// Formatting function (must be explicitly specialized)
|
||||
static void format(std::string& out, u64 arg);
|
||||
|
||||
// Helper typedef (visible in format())
|
||||
using type = T;
|
||||
|
||||
// Helper function (converts arg to object reference)
|
||||
static SAFE_BUFFERS FORCE_INLINE const T& get_object(u64 arg)
|
||||
{
|
||||
return *reinterpret_cast<const T*>(static_cast<std::uintptr_t>(arg));
|
||||
}
|
||||
|
||||
// Helper class
|
||||
class exception_base : public std::runtime_error
|
||||
// Helper function (safely converts arg to enum value)
|
||||
static SAFE_BUFFERS FORCE_INLINE void format_enum(std::string& out, u64 arg, const char*(*get)(T value))
|
||||
{
|
||||
// Helper (there is no other room)
|
||||
va_list m_args;
|
||||
const auto value = static_cast<std::underlying_type_t<T>>(arg);
|
||||
|
||||
protected:
|
||||
// Internal formatting constructor
|
||||
exception_base(const char* fmt...);
|
||||
};
|
||||
|
||||
// Exception type derived from std::runtime_error with formatting constructor
|
||||
class exception : public exception_base
|
||||
{
|
||||
public:
|
||||
template<typename... Args>
|
||||
exception(const char* fmt, const Args&... args)
|
||||
: exception_base(fmt, ::unveil<Args>::get(args)...)
|
||||
// Check narrowing
|
||||
if (static_cast<u64>(value) == arg)
|
||||
{
|
||||
if (const char* str = get(static_cast<T>(value)))
|
||||
{
|
||||
out += str;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to underlying type formatting
|
||||
fmt_class_string<std::underlying_type_t<T>>::format(out, static_cast<u64>(value));
|
||||
}
|
||||
|
||||
// Helper constant (may be used in format_enum as lambda return value)
|
||||
static constexpr const char* unknown = nullptr;
|
||||
};
|
||||
|
||||
template<>
|
||||
struct fmt_class_string<const void*, void>
|
||||
{
|
||||
static void format(std::string& out, u64 arg);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct fmt_class_string<T*, void> : fmt_class_string<const void*, void>
|
||||
{
|
||||
// Classify all pointers as const void*
|
||||
};
|
||||
|
||||
template<>
|
||||
struct fmt_class_string<const char*, void>
|
||||
{
|
||||
static void format(std::string& out, u64 arg);
|
||||
};
|
||||
|
||||
template<>
|
||||
struct fmt_class_string<char*, void> : fmt_class_string<const char*>
|
||||
{
|
||||
// Classify char* as const char*
|
||||
};
|
||||
|
||||
namespace fmt
|
||||
{
|
||||
// Argument array type (each element generated via fmt_unveil<>)
|
||||
template<typename... Args>
|
||||
using args_t = const u64(&&)[sizeof...(Args) + 1];
|
||||
|
||||
using supplementary_info = const struct arg_type_info;
|
||||
|
||||
struct arg_type_info
|
||||
{
|
||||
decltype(&fmt_class_string<int>::format) fmt_string;
|
||||
|
||||
template<typename T>
|
||||
static constexpr arg_type_info make()
|
||||
{
|
||||
return arg_type_info
|
||||
{
|
||||
&fmt_class_string<T>::format,
|
||||
};
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
static inline const supplementary_info* get()
|
||||
{
|
||||
// Constantly initialized null-terminated list of type-specific information
|
||||
static constexpr arg_type_info result[sizeof...(Args) + 1]
|
||||
{
|
||||
make<Args>()...
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
// Narrow cast (similar to gsl::narrow) with exception message formatting
|
||||
template<typename To, typename From, typename... Args>
|
||||
inline auto narrow(const char* format_str, const From& value, const Args&... args) -> decltype(static_cast<To>(static_cast<From>(std::declval<To>())))
|
||||
// Internal formatting function
|
||||
void raw_append(std::string& out, const char*, const supplementary_info*, const u64*) noexcept;
|
||||
|
||||
// Formatting function
|
||||
template<typename... Args>
|
||||
static SAFE_BUFFERS void append(std::string& out, const char* fmt, const Args&... args)
|
||||
{
|
||||
const auto result = static_cast<To>(value);
|
||||
if (static_cast<From>(result) != value) throw fmt::exception(format_str, value, args...);
|
||||
raw_append(out, fmt, arg_type_info::get<typename fmt_unveil<Args>::type...>(), args_t<Args...>{::fmt_unveil<Args>::get(args)...});
|
||||
}
|
||||
|
||||
// Formatting function
|
||||
template<typename... Args>
|
||||
static SAFE_BUFFERS std::string format(const char* fmt, const Args&... args)
|
||||
{
|
||||
std::string result;
|
||||
append<Args...>(result, fmt, args...);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Internal helper function
|
||||
char* alloc_format(const char*, const supplementary_info*, const u64*) noexcept;
|
||||
|
||||
// Exception type with formatting constructor
|
||||
template<typename Base>
|
||||
class exception_t : public Base
|
||||
{
|
||||
using base = Base;
|
||||
|
||||
public:
|
||||
template<typename... Args>
|
||||
SAFE_BUFFERS exception_t(const char* fmt, const Args&... args)
|
||||
: base((fmt = alloc_format(fmt, arg_type_info::get<typename fmt_unveil<Args>::type...>(), args_t<Args...>{::fmt_unveil<Args>::get(args)...})))
|
||||
{
|
||||
std::free(const_cast<char*>(fmt));
|
||||
}
|
||||
};
|
||||
|
||||
// Exception type derived from std::runtime_error with formatting constructor
|
||||
using exception = exception_t<std::runtime_error>;
|
||||
}
|
||||
|
|
|
@ -863,7 +863,7 @@ bool get_x64_reg_value(x64_context* context, x64_reg_t reg, size_t d_size, size_
|
|||
return true;
|
||||
}
|
||||
|
||||
LOG_ERROR(MEMORY, "get_x64_reg_value(): invalid arguments (reg=%d, d_size=%lld, i_size=%lld)", reg, d_size, i_size);
|
||||
LOG_ERROR(MEMORY, "get_x64_reg_value(): invalid arguments (reg=%d, d_size=%lld, i_size=%lld)", (u32)reg, d_size, i_size);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -882,7 +882,7 @@ bool put_x64_reg_value(x64_context* context, x64_reg_t reg, size_t d_size, u64 v
|
|||
}
|
||||
}
|
||||
|
||||
LOG_ERROR(MEMORY, "put_x64_reg_value(): invalid destination (reg=%d, d_size=%lld, value=0x%llx)", reg, d_size, value);
|
||||
LOG_ERROR(MEMORY, "put_x64_reg_value(): invalid destination (reg=%d, d_size=%lld, value=0x%llx)", (u32)reg, d_size, value);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1062,7 +1062,7 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context)
|
|||
|
||||
if (a_size != 4 || !d_size || !i_size)
|
||||
{
|
||||
LOG_ERROR(MEMORY, "Invalid or unsupported instruction (op=%d, reg=%d, d_size=%lld, a_size=0x%llx, i_size=%lld)", op, reg, d_size, a_size, i_size);
|
||||
LOG_ERROR(MEMORY, "Invalid or unsupported instruction (op=%d, reg=%d, d_size=%lld, a_size=0x%llx, i_size=%lld)", (u32)op, (u32)reg, d_size, a_size, i_size);
|
||||
report_opcode();
|
||||
return false;
|
||||
}
|
||||
|
@ -1134,7 +1134,7 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context)
|
|||
case X64OP_STOS:
|
||||
default:
|
||||
{
|
||||
LOG_ERROR(MEMORY, "Invalid or unsupported operation (op=%d, reg=%d, d_size=%lld, i_size=%lld)", op, reg, d_size, i_size);
|
||||
LOG_ERROR(MEMORY, "Invalid or unsupported operation (op=%d, reg=%d, d_size=%lld, i_size=%lld)", (u32)op, (u32)reg, d_size, i_size);
|
||||
report_opcode();
|
||||
return false;
|
||||
}
|
||||
|
@ -1151,7 +1151,7 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context)
|
|||
// write memory using "privileged" access to avoid breaking reservation
|
||||
if (!d_size || !i_size)
|
||||
{
|
||||
LOG_ERROR(MEMORY, "Invalid or unsupported instruction (op=%d, reg=%d, d_size=%lld, a_size=0x%llx, i_size=%lld)", op, reg, d_size, a_size, i_size);
|
||||
LOG_ERROR(MEMORY, "Invalid or unsupported instruction (op=%d, reg=%d, d_size=%lld, a_size=0x%llx, i_size=%lld)", (u32)op, (u32)reg, d_size, a_size, i_size);
|
||||
report_opcode();
|
||||
return false;
|
||||
}
|
||||
|
@ -1165,7 +1165,7 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context)
|
|||
{
|
||||
if (reg - X64R_XMM0 >= 16)
|
||||
{
|
||||
LOG_ERROR(MEMORY, "X64OP_STORE: d_size=16, reg=%d", reg);
|
||||
LOG_ERROR(MEMORY, "X64OP_STORE: d_size=16, reg=%d", (u32)reg);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -1577,7 +1577,7 @@ bool handle_access_violation(u32 addr, bool is_writing, x64_context* context)
|
|||
}
|
||||
default:
|
||||
{
|
||||
LOG_ERROR(MEMORY, "Invalid or unsupported operation (op=%d, reg=%d, d_size=%lld, a_size=0x%llx, i_size=%lld)", op, reg, d_size, a_size, i_size);
|
||||
LOG_ERROR(MEMORY, "Invalid or unsupported operation (op=%d, reg=%d, d_size=%lld, a_size=0x%llx, i_size=%lld)", (u32)op, (u32)reg, d_size, a_size, i_size);
|
||||
report_opcode();
|
||||
return false;
|
||||
}
|
||||
|
|
431
Utilities/cfmt.h
Normal file
431
Utilities/cfmt.h
Normal file
|
@ -0,0 +1,431 @@
|
|||
#pragma once
|
||||
|
||||
#include "types.h"
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
/*
|
||||
C-style format parser. Appends formatted string to `out`, returns number of characters written.
|
||||
Arguments are provided via `src`. TODO
|
||||
*/
|
||||
template<typename Src, typename Size = std::size_t>
|
||||
Size cfmt_append(std::string& out, const char* fmt, Src&& src)
|
||||
{
|
||||
const std::size_t old_size = out.size();
|
||||
|
||||
out.reserve(old_size + 992);
|
||||
|
||||
struct cfmt_context
|
||||
{
|
||||
std::size_t size; // Size of current format sequence
|
||||
|
||||
u8 args; // Number of extra args used
|
||||
u8 type; // Integral type bytesize
|
||||
bool dot; // Precision enabled
|
||||
bool left;
|
||||
bool sign;
|
||||
bool space;
|
||||
bool alter;
|
||||
bool zeros;
|
||||
|
||||
uint width;
|
||||
uint prec;
|
||||
};
|
||||
|
||||
cfmt_context ctx{0};
|
||||
|
||||
// Error handling: print untouched sequence, stop further formatting
|
||||
const auto drop_sequence = [&]
|
||||
{
|
||||
out.append(fmt - ctx.size, ctx.size);
|
||||
ctx.size = -1;
|
||||
};
|
||||
|
||||
// TODO: check overflow
|
||||
const auto read_decimal = [&](uint result) -> uint
|
||||
{
|
||||
while (fmt[0] >= '0' && fmt[0] <= '9')
|
||||
{
|
||||
result = result * 10 + (fmt[0] - '0');
|
||||
fmt++, ctx.size++;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// TODO: remove this
|
||||
const auto fallback = [&]()
|
||||
{
|
||||
const std::string _fmt(fmt - ctx.size, fmt);
|
||||
|
||||
const u64 arg0 = src.template get<u64>();
|
||||
const int arg1 = ctx.args >= 1 ? src.template get<int>(1) : 0;
|
||||
const int arg2 = ctx.args >= 2 ? src.template get<int>(2) : 0;
|
||||
|
||||
if (const std::size_t _size = std::snprintf(0, 0, _fmt.c_str(), arg0, arg1, arg2))
|
||||
{
|
||||
out.resize(out.size() + _size);
|
||||
std::snprintf(&out.front() + out.size() - _size, _size + 1, _fmt.c_str(), arg0, arg1, arg2);
|
||||
}
|
||||
};
|
||||
|
||||
// Single pass over fmt string (null-terminated), TODO: check correct order
|
||||
while (const char ch = *fmt++) if (ctx.size == 0)
|
||||
{
|
||||
if (ch == '%')
|
||||
{
|
||||
ctx.size = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
out += ch;
|
||||
}
|
||||
}
|
||||
else if (ctx.size == 1 && ch == '%')
|
||||
{
|
||||
ctx = {0};
|
||||
out += ch;
|
||||
}
|
||||
else if (ctx.size == -1)
|
||||
{
|
||||
out += ch;
|
||||
}
|
||||
else switch (ctx.size++, ch)
|
||||
{
|
||||
case '-': ctx.left = true; break;
|
||||
case '+': ctx.sign = true; break;
|
||||
case ' ': ctx.space = true; break;
|
||||
case '#': ctx.alter = true; break;
|
||||
case '0': ctx.zeros = true; break;
|
||||
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
{
|
||||
ctx.width = read_decimal(ch - '0');
|
||||
break;
|
||||
}
|
||||
|
||||
case '*':
|
||||
{
|
||||
if (!src.test(++ctx.args))
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
const int warg = src.template get<int>(ctx.args);
|
||||
ctx.width = std::abs(warg);
|
||||
ctx.left |= warg < 0;
|
||||
break;
|
||||
}
|
||||
|
||||
case '.':
|
||||
{
|
||||
if (*fmt >= '0' && *fmt <= '9') // TODO: does it allow '0'?
|
||||
{
|
||||
ctx.prec = read_decimal(0);
|
||||
ctx.dot = true;
|
||||
}
|
||||
else if (*fmt == '*')
|
||||
{
|
||||
if (!src.test(++ctx.args))
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
fmt++, ctx.size++;
|
||||
const int parg = src.template get<int>(ctx.args);
|
||||
ctx.prec = parg;
|
||||
ctx.dot = parg >= 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.prec = 0;
|
||||
ctx.dot = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'h':
|
||||
{
|
||||
if (ctx.type)
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else if (fmt[0] == 'h')
|
||||
{
|
||||
fmt++, ctx.size++;
|
||||
ctx.type = sizeof(char);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.type = sizeof(short);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'l':
|
||||
{
|
||||
if (ctx.type)
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else if (fmt[0] == 'l')
|
||||
{
|
||||
fmt++, ctx.size++;
|
||||
ctx.type = sizeof(llong);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.type = sizeof(long);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'z':
|
||||
{
|
||||
if (ctx.type)
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.type = sizeof(std::size_t);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'j':
|
||||
{
|
||||
if (ctx.type)
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.type = sizeof(std::intmax_t);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 't':
|
||||
{
|
||||
if (ctx.type)
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.type = sizeof(std::ptrdiff_t);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'c':
|
||||
{
|
||||
if (ctx.type || !src.test())
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
const std::size_t start = out.size();
|
||||
out += src.template get<char>(0);
|
||||
|
||||
if (1 < ctx.width)
|
||||
{
|
||||
// Add spaces if necessary
|
||||
out.insert(start + ctx.left, ctx.width - 1, ' ');
|
||||
}
|
||||
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 's':
|
||||
{
|
||||
if (ctx.type || !src.test())
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
const std::size_t start = out.size();
|
||||
const std::size_t size1 = src.fmt_string(out);
|
||||
|
||||
if (ctx.dot && size1 > ctx.prec)
|
||||
{
|
||||
// Shrink if necessary
|
||||
out.resize(start + ctx.prec);
|
||||
}
|
||||
|
||||
// TODO: how it works if precision and width specified simultaneously?
|
||||
const std::size_t size2 = out.size() - start;
|
||||
|
||||
if (size2 < ctx.width)
|
||||
{
|
||||
// Add spaces if necessary
|
||||
out.insert(ctx.left ? out.size() : start, ctx.width - size2, ' ');
|
||||
}
|
||||
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'd':
|
||||
case 'i':
|
||||
{
|
||||
if (!src.test())
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = sizeof(int);
|
||||
}
|
||||
|
||||
fallback(); // TODO
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'o':
|
||||
{
|
||||
if (!src.test())
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = sizeof(int);
|
||||
}
|
||||
|
||||
fallback(); // TODO
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'x':
|
||||
case 'X':
|
||||
{
|
||||
if (!src.test())
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = sizeof(int);
|
||||
}
|
||||
|
||||
fallback(); // TODO
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'u':
|
||||
{
|
||||
if (!src.test())
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
if (!ctx.type)
|
||||
{
|
||||
ctx.type = sizeof(int);
|
||||
}
|
||||
|
||||
fallback(); // TODO
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'p':
|
||||
{
|
||||
if (!src.test())
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ctx.type)
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
fallback(); // TODO
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'f':
|
||||
case 'F':
|
||||
case 'e':
|
||||
case 'E':
|
||||
case 'a':
|
||||
case 'A':
|
||||
case 'g':
|
||||
case 'G':
|
||||
{
|
||||
if (!src.test())
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ctx.type)
|
||||
{
|
||||
drop_sequence();
|
||||
break;
|
||||
}
|
||||
|
||||
fallback(); // TODO
|
||||
src.skip(ctx.args);
|
||||
ctx = {0};
|
||||
break;
|
||||
}
|
||||
|
||||
case 'L': // long double, not supported
|
||||
case 'n': // writeback, not supported
|
||||
default:
|
||||
{
|
||||
drop_sequence();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle unfinished sequence
|
||||
if (ctx.size && ctx.size != -1)
|
||||
{
|
||||
fmt--, drop_sequence();
|
||||
}
|
||||
|
||||
return static_cast<Size>(out.size() - old_size);
|
||||
}
|
|
@ -27,12 +27,13 @@ namespace gsl
|
|||
enum class byte : u8;
|
||||
}
|
||||
|
||||
template<typename T1, typename T2>
|
||||
struct bijective_pair
|
||||
namespace fmt
|
||||
{
|
||||
T1 v1;
|
||||
T2 v2;
|
||||
};
|
||||
}
|
||||
|
||||
// Formatting helper, type-specific preprocessing for improving safety and functionality
|
||||
template<typename T, typename = void>
|
||||
struct fmt_unveil;
|
||||
|
||||
template<typename T, std::size_t Align = alignof(T), std::size_t Size = sizeof(T)>
|
||||
struct se_storage;
|
||||
|
@ -40,10 +41,6 @@ struct se_storage;
|
|||
template<typename T, bool Se = true, std::size_t Align = alignof(T)>
|
||||
class se_t;
|
||||
|
||||
// Specialization with static constexpr bijective_pair<T1, T2> map[] member expected
|
||||
template<typename T1, typename T2>
|
||||
struct bijective;
|
||||
|
||||
template<typename T, std::size_t Size = sizeof(T)>
|
||||
struct atomic_storage;
|
||||
|
||||
|
@ -501,40 +498,6 @@ struct multicast<void>
|
|||
}
|
||||
};
|
||||
|
||||
template<typename T1, typename T2 = const char*, typename T = T1, typename DT = T2>
|
||||
T2 bijective_find(const T& left, const DT& def = {})
|
||||
{
|
||||
for (std::size_t i = 0; i < sizeof(bijective<T1, T2>::map) / sizeof(bijective_pair<T1, T2>); i++)
|
||||
{
|
||||
if (bijective<T1, T2>::map[i].v1 == left)
|
||||
{
|
||||
return bijective<T1, T2>::map[i].v2;
|
||||
}
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
|
||||
// Formatting helper, type-specific preprocessing for improving safety and functionality
|
||||
template<typename T, typename = void>
|
||||
struct unveil
|
||||
{
|
||||
// TODO
|
||||
static inline const T& get(const T& arg)
|
||||
{
|
||||
return arg;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct unveil<T, void_t<decltype(std::declval<T>().c_str())>>
|
||||
{
|
||||
static inline const char* get(const T& arg)
|
||||
{
|
||||
return arg.c_str();
|
||||
}
|
||||
};
|
||||
|
||||
// Tagged ID type
|
||||
template<typename T = void, typename ID = u32>
|
||||
class id_value
|
||||
|
@ -560,3 +523,14 @@ public:
|
|||
return m_value;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T, typename ID>
|
||||
struct fmt_unveil<id_value<T, ID>>
|
||||
{
|
||||
using type = typename fmt_unveil<ID>::type;
|
||||
|
||||
static inline u64 get(const id_value<T, ID>& value)
|
||||
{
|
||||
return fmt_unveil<ID>::get(value);
|
||||
}
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue