mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-02 14:50:41 -07:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 371c957025 | |||
| 83aa907b13 | |||
| 71b144918c | |||
| e618a05b08 | |||
| d0f3ffd12f | |||
| 6d18d6a496 | |||
| b0810558a7 | |||
| 6ae2e4003c | |||
| 8545f1cbdc | |||
| e0530fedad | |||
| b220cbf336 | |||
| f68a3439d3 | |||
| 6f58d9ab68 | |||
| c3428236f8 | |||
| 6856a0950c | |||
| dc7d8515d3 | |||
| e100093ead | |||
| e5808257ca | |||
| 283ec3960a | |||
| 0ec37ac6ea | |||
| 0ef0b84308 | |||
| 62c30d8895 | |||
| 4e86cb16a2 | |||
| 76f401de95 | |||
| 5947779502 | |||
| 61c97ccaab | |||
| 12d6a88c60 | |||
| 984b41a1ae | |||
| d06b8bd731 | |||
| 0dad96b876 | |||
| def296095b | |||
| 750b1fea80 | |||
| 4c87078807 | |||
| 052698afa3 | |||
| 9e2b04d5db |
+12
-11
@@ -467,6 +467,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
||||
games/pc/bi2x_hook.cpp
|
||||
|
||||
# hooks
|
||||
hooks/audio/acm.cpp
|
||||
hooks/audio/audio.cpp
|
||||
hooks/audio/buffer.cpp
|
||||
hooks/audio/util.cpp
|
||||
@@ -623,7 +624,7 @@ set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/W
|
||||
add_executable(spicetools_spice ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_spice
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
|
||||
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
target_link_libraries(spicetools_spice PUBLIC winscard)
|
||||
set_target_properties(spicetools_spice PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_spice PROPERTIES OUTPUT_NAME "spice")
|
||||
@@ -639,7 +640,7 @@ set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/W
|
||||
add_executable(spicetools_spice_laa ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_spice_laa
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
|
||||
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
target_link_libraries(spicetools_spice_laa PUBLIC winscard)
|
||||
set_target_properties(spicetools_spice_laa PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_spice_laa PROPERTIES OUTPUT_NAME "spice_laa")
|
||||
@@ -656,7 +657,7 @@ set(RESOURCE_FILES build/manifest.manifest build/manifest.rc build/icon.rc cfg/W
|
||||
add_executable(spicetools_spice_linux ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_spice_linux
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp
|
||||
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
set_target_properties(spicetools_spice_linux PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_spice_linux PROPERTIES OUTPUT_NAME "spice_linux")
|
||||
target_compile_definitions(spicetools_spice_linux PRIVATE NO_SCARD=1 PRIVATE SPICE_LINUX=1)
|
||||
@@ -674,7 +675,7 @@ add_executable(spicetools_spice64 ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
|
||||
target_link_libraries(spicetools_spice64
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
|
||||
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
target_link_libraries(spicetools_spice64 PUBLIC winscard)
|
||||
set_target_properties(spicetools_spice64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_spice64 PROPERTIES OUTPUT_NAME "spice64")
|
||||
@@ -693,7 +694,7 @@ add_executable(spicetools_spice64_linux ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
# do NOT link against: mf, mfplat, mfreadwrite; otherwise unity games will break
|
||||
target_link_libraries(spicetools_spice64_linux
|
||||
PUBLIC d3d9 ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp mfuuid strmiids dxva2
|
||||
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
set_target_properties(spicetools_spice64_linux PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_spice64_linux PROPERTIES OUTPUT_NAME "spice64_linux")
|
||||
target_compile_definitions(spicetools_spice64_linux PRIVATE SPICE64=1)
|
||||
@@ -711,7 +712,7 @@ set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.
|
||||
add_executable(spicetools_cfg WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_cfg
|
||||
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
|
||||
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
target_link_libraries(spicetools_cfg PUBLIC winscard)
|
||||
set_target_properties(spicetools_cfg PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_cfg PROPERTIES OUTPUT_NAME "spicecfg")
|
||||
@@ -729,7 +730,7 @@ set(RESOURCE_FILES cfg/manifest.manifest cfg/manifest.rc cfg/icon.rc cfg/Win32D.
|
||||
add_executable(spicetools_cfg_linux WIN32 ${SOURCE_FILES} ${RESOURCE_FILES})
|
||||
target_link_libraries(spicetools_cfg_linux
|
||||
PUBLIC ws2_32 version comctl32 shlwapi iphlpapi hid secur32 setupapi psapi winmm winhttp strmiids
|
||||
PRIVATE fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
PRIVATE fmt::fmt-header-only discord-rpc imgui hash-library minhook imm32 dwmapi CpuFeatures::cpu_features)
|
||||
set_target_properties(spicetools_cfg_linux PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_cfg_linux PROPERTIES OUTPUT_NAME "spicecfg_linux")
|
||||
target_compile_definitions(spicetools_cfg_linux PRIVATE SPICETOOLS_SPICECFG_STANDALONE=1)
|
||||
@@ -745,7 +746,7 @@ endif()
|
||||
# kbt.dll
|
||||
set(SOURCE_FILES stubs/stubs.cpp)
|
||||
add_library(spicetools_stubs_kbt SHARED ${SOURCE_FILES} stubs/stubs.def)
|
||||
target_link_libraries(spicetools_stubs_kbt PRIVATE fmt-header-only)
|
||||
target_link_libraries(spicetools_stubs_kbt PRIVATE fmt::fmt-header-only)
|
||||
set_target_properties(spicetools_stubs_kbt PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_kbt PROPERTIES OUTPUT_NAME "kbt")
|
||||
target_compile_definitions(spicetools_stubs_kbt PRIVATE STUB=1)
|
||||
@@ -756,7 +757,7 @@ endif()
|
||||
|
||||
# kbt.dll 64bit
|
||||
add_library(spicetools_stubs_kbt64 SHARED ${SOURCE_FILES} stubs/stubs.def)
|
||||
target_link_libraries(spicetools_stubs_kbt64 PRIVATE fmt-header-only)
|
||||
target_link_libraries(spicetools_stubs_kbt64 PRIVATE fmt::fmt-header-only)
|
||||
set_target_properties(spicetools_stubs_kbt64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_kbt64 PROPERTIES OUTPUT_NAME "kbt")
|
||||
target_compile_definitions(spicetools_stubs_kbt64 PRIVATE STUB=1)
|
||||
@@ -768,7 +769,7 @@ endif()
|
||||
# kld.dll
|
||||
set(SOURCE_FILES stubs/stubs.cpp)
|
||||
add_library(spicetools_stubs_kld SHARED ${SOURCE_FILES} stubs/stubs.def)
|
||||
target_link_libraries(spicetools_stubs_kld PRIVATE fmt-header-only)
|
||||
target_link_libraries(spicetools_stubs_kld PRIVATE fmt::fmt-header-only)
|
||||
set_target_properties(spicetools_stubs_kld PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_kld PROPERTIES OUTPUT_NAME "kld")
|
||||
target_compile_definitions(spicetools_stubs_kld PRIVATE STUB=1)
|
||||
@@ -779,7 +780,7 @@ endif()
|
||||
|
||||
# kld.dll 64bit
|
||||
add_library(spicetools_stubs_kld64 SHARED ${SOURCE_FILES} stubs/stubs.def)
|
||||
target_link_libraries(spicetools_stubs_kld64 PRIVATE fmt-header-only)
|
||||
target_link_libraries(spicetools_stubs_kld64 PRIVATE fmt::fmt-header-only)
|
||||
set_target_properties(spicetools_stubs_kld64 PROPERTIES PREFIX "")
|
||||
set_target_properties(spicetools_stubs_kld64 PROPERTIES OUTPUT_NAME "kld")
|
||||
target_compile_definitions(spicetools_stubs_kld64 PRIVATE STUB=1)
|
||||
|
||||
+198
-56
@@ -1,4 +1,5 @@
|
||||
#include "mdxf.h"
|
||||
#include "mdxf_poll.h"
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/ddr/io.h"
|
||||
@@ -6,63 +7,110 @@
|
||||
#include "rawinput/rawinput.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
#include <mutex>
|
||||
|
||||
// constants
|
||||
const size_t STATUS_BUFFER_SIZE = 32;
|
||||
const size_t STATUS_BUFFER_NUM_ENTRIES = 16;
|
||||
|
||||
// static stuff
|
||||
static uint8_t COUNTER = 0;
|
||||
static uint8_t HEAD_P1 = 0;
|
||||
static uint8_t HEAD_P2 = 0;
|
||||
|
||||
static uint16_t PREV_STATE_P1 = 0;
|
||||
static uint16_t PREV_STATE_P2 = 0;
|
||||
static uint64_t PREV_TIME_P1 = 0;
|
||||
static uint64_t PREV_TIME_P2 = 0;
|
||||
|
||||
static std::mutex MUTEX_P1;
|
||||
static std::mutex MUTEX_P2;
|
||||
|
||||
static bool IS_MDXF_ACTIVE = false;
|
||||
|
||||
static const uint8_t BACKFILL_INTERVAL_MS = 4;
|
||||
static const uint8_t BACKFILL_PADDING_MS = 2;
|
||||
|
||||
// buffers
|
||||
#pragma pack(push, 1)
|
||||
static struct {
|
||||
uint8_t STATUS_BUFFER_P1[7][STATUS_BUFFER_SIZE] {};
|
||||
uint8_t STATUS_BUFFER_P2[7][STATUS_BUFFER_SIZE] {};
|
||||
uint8_t STATUS_BUFFER_P1[STATUS_BUFFER_NUM_ENTRIES][STATUS_BUFFER_SIZE] {};
|
||||
uint8_t STATUS_BUFFER_P2[STATUS_BUFFER_NUM_ENTRIES][STATUS_BUFFER_SIZE] {};
|
||||
} BUFFERS {};
|
||||
#pragma pack(pop)
|
||||
|
||||
static bool STATUS_BUFFER_FREEZE = false;
|
||||
|
||||
typedef enum {
|
||||
ARKMDXP4_POLL = 0,
|
||||
EXTERNAL_POLL = 1
|
||||
} MDXFPollSource;
|
||||
|
||||
typedef uint64_t (__cdecl *ARK_GET_TICK_TIME64_T)();
|
||||
|
||||
static uint64_t arkGetTickTime64() {
|
||||
static ARK_GET_TICK_TIME64_T getTickTime64 =
|
||||
(ARK_GET_TICK_TIME64_T)GetProcAddress(avs::game::DLL_INSTANCE, "arkGetTickTime64");
|
||||
if (getTickTime64 == nullptr) {
|
||||
// this works on 32-bit versions of avs, but not on 64.
|
||||
// it's better than nothing though.
|
||||
return timeGetTime();
|
||||
static ARK_GET_TICK_TIME64_T getTickTime64 = nullptr;
|
||||
|
||||
if (!getTickTime64) {
|
||||
HMODULE h = avs::game::DLL_INSTANCE;
|
||||
if (h) {
|
||||
getTickTime64 = (ARK_GET_TICK_TIME64_T)GetProcAddress(h, "arkGetTickTime64");
|
||||
}
|
||||
}
|
||||
return getTickTime64();
|
||||
// this works on 32-bit versions of avs, but not on 64.
|
||||
// it's better than nothing though.
|
||||
return getTickTime64 ? getTickTime64() : timeGetTime();
|
||||
}
|
||||
|
||||
/*
|
||||
* Implementations
|
||||
*/
|
||||
|
||||
static bool __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *buffer, uint8_t a3, uint8_t a4) {
|
||||
static uint64_t __cdecl ac_io_mdxf_get_control_status_buffer(int node, void *out, uint8_t index, uint8_t head_in) {
|
||||
|
||||
// Default error value (matches original mask behavior)
|
||||
auto error_ret = static_cast<uint64_t>(node - 0x11) & 0xFFFFFFFFFFFFFF00;
|
||||
|
||||
// Dance Dance Revolution
|
||||
if (avs::game::is_model("MDX")) {
|
||||
// Select player-specific state
|
||||
std::mutex* mutex = nullptr;
|
||||
uint8_t* head = nullptr;
|
||||
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
|
||||
size_t size = STATUS_BUFFER_NUM_ENTRIES;
|
||||
|
||||
// get buffer index
|
||||
auto i = (COUNTER + a3) % std::size(BUFFERS.STATUS_BUFFER_P1);
|
||||
|
||||
// copy buffer
|
||||
if (node == 17 || node == 25) {
|
||||
memcpy(buffer, BUFFERS.STATUS_BUFFER_P1[i], STATUS_BUFFER_SIZE);
|
||||
mutex = &MUTEX_P1;
|
||||
head = &HEAD_P1;
|
||||
buffer = BUFFERS.STATUS_BUFFER_P1;
|
||||
} else if (node == 18 || node == 26) {
|
||||
memcpy(buffer, BUFFERS.STATUS_BUFFER_P2[i], STATUS_BUFFER_SIZE);
|
||||
mutex = &MUTEX_P2;
|
||||
head = &HEAD_P2;
|
||||
buffer = BUFFERS.STATUS_BUFFER_P2;
|
||||
} else {
|
||||
|
||||
// fill with zeros on unknown node
|
||||
memset(buffer, 0, STATUS_BUFFER_SIZE);
|
||||
return false;
|
||||
memset(out, 0, STATUS_BUFFER_SIZE);
|
||||
return error_ret;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
|
||||
if (head_in != 0xFF) {
|
||||
*head = head_in;
|
||||
}
|
||||
|
||||
// Compute ring index: walk backwards from head as index increases
|
||||
// Assumes ring buffer size is a power of two
|
||||
const size_t mask = size - 1;
|
||||
const size_t offset = static_cast<size_t>(index) & mask;
|
||||
const size_t i = (static_cast<size_t>(*head) - offset + size) & mask;
|
||||
|
||||
// Copy the chosen entry
|
||||
memcpy(out, buffer[i], STATUS_BUFFER_SIZE);
|
||||
|
||||
// Return the head value actually used
|
||||
return static_cast<uint64_t>(*head);
|
||||
}
|
||||
|
||||
// return success
|
||||
return true;
|
||||
return error_ret;
|
||||
}
|
||||
|
||||
static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2, uint8_t value) {
|
||||
@@ -104,36 +152,47 @@ static bool __cdecl ac_io_mdxf_set_output_level(unsigned int a1, unsigned int a2
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
|
||||
|
||||
// increase counter
|
||||
COUNTER = (COUNTER + 1) % std::size(BUFFERS.STATUS_BUFFER_P1);
|
||||
static bool __cdecl ac_io_mdxf_update_control_status_buffer_impl(int node, MDXFPollSource source, uint64_t current_time) {
|
||||
|
||||
// check freeze
|
||||
if (STATUS_BUFFER_FREEZE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// clear buffer
|
||||
uint8_t *buffer = nullptr;
|
||||
switch (node) {
|
||||
case 17:
|
||||
case 25:
|
||||
buffer = BUFFERS.STATUS_BUFFER_P1[COUNTER];
|
||||
break;
|
||||
case 18:
|
||||
case 26:
|
||||
buffer = BUFFERS.STATUS_BUFFER_P2[COUNTER];
|
||||
break;
|
||||
default:
|
||||
|
||||
// return failure on unknown node
|
||||
return false;
|
||||
}
|
||||
memset(buffer, 0, STATUS_BUFFER_SIZE);
|
||||
|
||||
// Dance Dance Revolution
|
||||
if (avs::game::is_model("MDX")) {
|
||||
// Marks this module as actively being used, allowing this function to be called from other sources
|
||||
if (!IS_MDXF_ACTIVE && source == ARKMDXP4_POLL) {
|
||||
IS_MDXF_ACTIVE = true;
|
||||
}
|
||||
|
||||
uint8_t (*buffer)[STATUS_BUFFER_SIZE];
|
||||
uint8_t *head = nullptr;
|
||||
uint16_t *prev_state = nullptr;
|
||||
uint64_t *prev_time = nullptr;
|
||||
std::mutex* mutex = nullptr;
|
||||
|
||||
switch (node) {
|
||||
case 17:
|
||||
case 25:
|
||||
mutex = &MUTEX_P1;
|
||||
head = &HEAD_P1;
|
||||
prev_state = &PREV_STATE_P1;
|
||||
prev_time = &PREV_TIME_P1;
|
||||
buffer = BUFFERS.STATUS_BUFFER_P1;
|
||||
break;
|
||||
case 18:
|
||||
case 26:
|
||||
mutex = &MUTEX_P2;
|
||||
head = &HEAD_P2;
|
||||
prev_state = &PREV_STATE_P2;
|
||||
prev_time = &PREV_TIME_P2;
|
||||
buffer = BUFFERS.STATUS_BUFFER_P2;
|
||||
break;
|
||||
default:
|
||||
// return failure on unknown node
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sensor Map (LDUR):
|
||||
// FOOT DOWN = bit 32-35 = byte 4, bit 0-3
|
||||
@@ -166,29 +225,112 @@ static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
|
||||
break;
|
||||
}
|
||||
|
||||
*(uint64_t*)&buffer[0x18] = arkGetTickTime64();
|
||||
uint16_t current_state;
|
||||
if (source == EXTERNAL_POLL) {
|
||||
// get buttons
|
||||
auto &buttons = games::ddr::get_buttons();
|
||||
uint8_t up_down = 0;
|
||||
uint8_t left_right = 0;
|
||||
|
||||
// get buttons
|
||||
auto &buttons = games::ddr::get_buttons();
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
|
||||
up_down |= 0xF0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) {
|
||||
up_down |= 0x0F;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) {
|
||||
left_right |= 0xF0;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) {
|
||||
left_right |= 0x0F;
|
||||
}
|
||||
current_state = (uint16_t(up_down) << 8) | left_right;
|
||||
} else {
|
||||
current_state = *prev_state;
|
||||
}
|
||||
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[0]))) {
|
||||
buffer[4] |= 0xF0;
|
||||
std::lock_guard<std::mutex> lock(*mutex);
|
||||
|
||||
const bool has_state_changed = *prev_state != current_state;
|
||||
const bool has_time_changed = *prev_time < current_time;
|
||||
|
||||
// If state hasn't changed and either the update was triggered externally or the time hasn't changed, then don't advance head pointer or write a new entry
|
||||
if (!has_state_changed && (source == EXTERNAL_POLL || !has_time_changed)) {
|
||||
return true;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[1]))) {
|
||||
buffer[4] |= 0x0F;
|
||||
|
||||
// The start and stop time cutoffs for backfilling entries. Min(..) ensures times aren't negative.
|
||||
// The stop time is just before the current_time, set by BACKFILL_PADDING_MS, which avoids the last backfilled entry being too close to current_time.
|
||||
uint64_t start_time = *prev_time;
|
||||
const uint64_t stop_time = current_time - std::min<uint64_t>(current_time, BACKFILL_PADDING_MS);
|
||||
|
||||
// Ensures the first iteration will write the first entry at current_time and not backfill to time 0ms.
|
||||
if (start_time == 0) {
|
||||
start_time = current_time - std::min<uint64_t>(current_time, BACKFILL_INTERVAL_MS);
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[2]))) {
|
||||
buffer[5] |= 0xF0;
|
||||
|
||||
// Ensures only STATUS_BUFFER_NUM_ENTRIES entries at most are backfilled
|
||||
const uint64_t max_backfill = BACKFILL_INTERVAL_MS * STATUS_BUFFER_NUM_ENTRIES;
|
||||
const uint64_t min_time = current_time - std::min<uint64_t>(current_time, max_backfill);
|
||||
if (start_time < min_time) {
|
||||
start_time = min_time;
|
||||
}
|
||||
if (GameAPI::Buttons::getState(RI_MGR, buttons.at(button_map[3]))) {
|
||||
buffer[5] |= 0x0F;
|
||||
|
||||
// Only write one entry if called externally
|
||||
if (source == EXTERNAL_POLL) {
|
||||
start_time = stop_time - 1;
|
||||
}
|
||||
|
||||
uint64_t time = start_time;
|
||||
uint16_t state = *prev_state;
|
||||
|
||||
// Backfill entries a fixed interval apart from each other between prev_time and current_time
|
||||
while (time < stop_time) {
|
||||
// Advance head pointer
|
||||
*head = (*head + 1) % STATUS_BUFFER_NUM_ENTRIES;
|
||||
uint8_t* buffer_entry = buffer[*head];
|
||||
|
||||
// Clear buffer
|
||||
memset(buffer_entry, 0, STATUS_BUFFER_SIZE);
|
||||
|
||||
time += BACKFILL_INTERVAL_MS;
|
||||
|
||||
// If the stop time is reached, then write current_time and current_state instead for this final iteration
|
||||
const bool isEdge = (time >= stop_time);
|
||||
if (isEdge) {
|
||||
state = current_state;
|
||||
time = current_time;
|
||||
}
|
||||
|
||||
// Write button state
|
||||
buffer_entry[4] = (state >> 8) & 0xFF;
|
||||
buffer_entry[5] = state & 0xFF;
|
||||
|
||||
// Write game time
|
||||
*(uint64_t*)&buffer_entry[0x18] = time;
|
||||
}
|
||||
|
||||
*prev_state = current_state;
|
||||
*prev_time = current_time;
|
||||
}
|
||||
|
||||
// return success
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool __cdecl ac_io_mdxf_update_control_status_buffer(int node) {
|
||||
return ac_io_mdxf_update_control_status_buffer_impl(node, ARKMDXP4_POLL, arkGetTickTime64());
|
||||
}
|
||||
|
||||
// Used for triggering updates of the controller states from outside arkmdxp4.dll main refresh loop (i.e. within rawinput.cpp on controller events)
|
||||
void mdxf_poll() {
|
||||
if (IS_MDXF_ACTIVE) {
|
||||
const uint64_t call_time_ms = arkGetTickTime64();
|
||||
ac_io_mdxf_update_control_status_buffer_impl(17, EXTERNAL_POLL, call_time_ms);
|
||||
ac_io_mdxf_update_control_status_buffer_impl(18, EXTERNAL_POLL, call_time_ms);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Module stuff
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// mdxf_poll.h
|
||||
#pragma once
|
||||
|
||||
// Called from rawinput thread whenever inputs have just been updated
|
||||
void mdxf_poll();
|
||||
@@ -22,7 +22,7 @@ Response::Response(uint64_t id) {
|
||||
// check for error
|
||||
auto error = document.GetParseError();
|
||||
if (error)
|
||||
log_warning("api", "response template parse error: {}", error);
|
||||
log_warning("api", "response template parse error: {}", static_cast<uint32_t>(error));
|
||||
|
||||
// set ID
|
||||
document["id"].SetUint64(id);
|
||||
|
||||
@@ -263,7 +263,7 @@ namespace avs::automap {
|
||||
int property_node_read(avs::core::node_ptr node, avs::core::node_type type, void *data, uint32_t data_size) {
|
||||
auto result = avs::core::property_node_read(node, type, data, data_size);
|
||||
if (result < 0 && property_patches_enabled(nullptr, node)) {
|
||||
log_warning("automap", "property_node_read failed ({}): {}", type, result);
|
||||
log_warning("automap", "property_node_read failed ({}): {}", static_cast<uint32_t>(type), result);
|
||||
|
||||
// probably wrong type
|
||||
memset(data, 0, data_size);
|
||||
@@ -278,7 +278,7 @@ namespace avs::automap {
|
||||
if (parent_node && parent_node != node) {
|
||||
char name_buf[256] {};
|
||||
if (avs::core::property_node_name(node, name_buf, sizeof(name_buf)) >= 0) {
|
||||
log_warning("automap", "property_node_read replace type ({}): {}", type, result);
|
||||
log_warning("automap", "property_node_read replace type ({}): {}", static_cast<uint32_t>(type), result);
|
||||
avs::core::property_node_remove(node);
|
||||
avs::core::property_node_create(nullptr, parent_node, type, name_buf, data);
|
||||
}
|
||||
|
||||
+19
-3
@@ -8,6 +8,7 @@
|
||||
#include "games/mfc/mfc.h"
|
||||
#include "hooks/avshook.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
@@ -282,6 +283,7 @@ namespace avs {
|
||||
|
||||
// read software nodes
|
||||
if (ea3_soft != nullptr) {
|
||||
log_misc("avs-ea3", "reading soft id from {}...", ea3_config_name);
|
||||
avs::core::property_node_refer(ea3_config, ea3_soft, "model",
|
||||
avs::core::NODE_TYPE_str, EA3_MODEL, 4);
|
||||
avs::core::property_node_refer(ea3_config, ea3_soft, "dest",
|
||||
@@ -293,6 +295,7 @@ namespace avs {
|
||||
avs::core::property_node_refer(ea3_config, ea3_soft, "ext",
|
||||
avs::core::NODE_TYPE_str, EA3_EXT, 11);
|
||||
} else if (fileutils::file_exists("prop/ea3-ident.xml")) {
|
||||
log_misc("avs-ea3", "reading soft id from prop/ea3-ident.xml...");
|
||||
|
||||
// read ident config
|
||||
auto ea3_ident = avs::core::config_read("prop/ea3-ident.xml");
|
||||
@@ -316,7 +319,14 @@ namespace avs {
|
||||
// clean up
|
||||
avs::core::config_destroy(ea3_ident);
|
||||
} else {
|
||||
log_fatal("avs-ea3", "node not found in '{}': /ea3/soft", ea3_config_name);
|
||||
// <ea3><soft> node missing error
|
||||
log_warning("avs-ea3", "soft id (datecode) not found in prop XML files");
|
||||
log_warning("avs-ea3", "get fixed prop files and try again - you have incomplete data");
|
||||
log_warning("avs-ea3", "for experts only:");
|
||||
log_warning("avs-ea3", " * add the missing <soft>...</soft> node to {}", ea3_config_name);
|
||||
log_warning("avs-ea3", " * alternatively, provide ea3-ident.xml with <ea3_conf><soft>...");
|
||||
|
||||
log_fatal("avs-ea3", "datecode missing in '{}'; 'prop/ea3-ident.xml' also missing", ea3_config_name);
|
||||
}
|
||||
|
||||
// set account id (`EA3_PCBID` is valid if and only if `/ea3/id` is present)
|
||||
@@ -484,8 +494,13 @@ namespace avs {
|
||||
std::ostringstream init_code;
|
||||
init_code << EA3_MODEL;
|
||||
init_code << EA3_DEST;
|
||||
init_code << EA3_SPEC;
|
||||
init_code << EA3_REV;
|
||||
if (strcmp(EA3_MODEL, "MDX") == 0 && strcmp(EA3_SPEC, "I") == 0) {
|
||||
init_code << "G";
|
||||
init_code << "B";
|
||||
} else {
|
||||
init_code << EA3_SPEC;
|
||||
init_code << EA3_REV;
|
||||
}
|
||||
init_code << EA3_EXT;
|
||||
std::string init_code_str = init_code.str();
|
||||
|
||||
@@ -563,6 +578,7 @@ namespace avs {
|
||||
soft_id_code << EA3_EXT;
|
||||
std::string soft_id_code_str = soft_id_code.str();
|
||||
log_info("avs-ea3", "soft id code: {}", soft_id_code_str);
|
||||
deferredlogs::set_softid(soft_id_code_str);
|
||||
|
||||
// set soft ID code
|
||||
avs::core::avs_std_setenv("/env/profile/soft_id_code", soft_id_code_str.c_str());
|
||||
|
||||
@@ -82,18 +82,23 @@ public:
|
||||
return this->index != 0xFF;
|
||||
}
|
||||
|
||||
inline void clearBindings() {
|
||||
device_identifier = "";
|
||||
index = 0xFF;
|
||||
inline void resetValues() {
|
||||
setSensitivity(1.f);
|
||||
setDeadzone(0.f);
|
||||
invert = false;
|
||||
smoothing = false;
|
||||
deadzone_mirror = false;
|
||||
setMultiplier(1);
|
||||
setRelativeMode(false);
|
||||
setDelayBufferDepth(0);
|
||||
}
|
||||
|
||||
inline void clearBindings() {
|
||||
device_identifier = "";
|
||||
index = 0xFF;
|
||||
resetValues();
|
||||
}
|
||||
|
||||
inline const std::string &getName() const {
|
||||
return this->name;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ Config::Config() {
|
||||
this->firstFillConfigFile();
|
||||
break;
|
||||
default:
|
||||
log_warning("cfg", "Unknown XML error reading config: {}", configLoadError);
|
||||
log_warning("cfg", "Unknown XML error reading config: {}", static_cast<uint32_t>(configLoadError));
|
||||
break;
|
||||
}
|
||||
} while (configLoadError != tinyxml2::XMLError::XML_SUCCESS);
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
static auto format_as(OptionType f) {
|
||||
return fmt::underlying(f);
|
||||
}
|
||||
|
||||
void Option::value_add(std::string new_value) {
|
||||
|
||||
// put in primary slot if possible
|
||||
|
||||
@@ -48,7 +48,7 @@ namespace cfg {
|
||||
// check parse error
|
||||
auto error = doc.GetParseError();
|
||||
if (error) {
|
||||
log_warning("ScreenResize", "config parse error: {}", error);
|
||||
log_warning("ScreenResize", "config parse error: {}", static_cast<uint32_t>(error));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
# Run manually to reformat a file:
|
||||
# clang-format -i --style=file <file>
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
IndentPPDirectives: AfterHash
|
||||
IndentCaseLabels: false
|
||||
AlwaysBreakTemplateDeclarations: false
|
||||
DerivePointerAlignment: false
|
||||
@@ -1,37 +0,0 @@
|
||||
.vscode/
|
||||
.vs/
|
||||
|
||||
*.iml
|
||||
.idea/
|
||||
.externalNativeBuild/
|
||||
.gradle/
|
||||
gradle/
|
||||
gradlew*
|
||||
local.properties
|
||||
build/
|
||||
support/.cxx
|
||||
|
||||
bin/
|
||||
/_CPack_Packages
|
||||
/CMakeScripts
|
||||
/doc/doxyxml
|
||||
/doc/html
|
||||
/doc/node_modules
|
||||
virtualenv
|
||||
/Testing
|
||||
/install_manifest.txt
|
||||
*~
|
||||
*.a
|
||||
*.so*
|
||||
*.xcodeproj
|
||||
*.zip
|
||||
cmake_install.cmake
|
||||
CPack*.cmake
|
||||
fmt-*.cmake
|
||||
CTestTestfile.cmake
|
||||
CMakeCache.txt
|
||||
CMakeFiles
|
||||
FMT.build
|
||||
Makefile
|
||||
run-msbuild.bat
|
||||
fmt.pc
|
||||
+229
-105
@@ -1,15 +1,17 @@
|
||||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
cmake_minimum_required(VERSION 3.8...3.28)
|
||||
|
||||
# Fallback for using newer policies on CMake <3.12.
|
||||
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||
if (${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
# Determine if fmt is built as a subproject (using add_subdirectory)
|
||||
# or if it is the master project.
|
||||
if (NOT DEFINED FMT_MASTER_PROJECT)
|
||||
set(FMT_MASTER_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
# NOTE: source vs current_source detection is unreliable
|
||||
# this heuristic is more generally applicable esp w.r.t FetchContent
|
||||
if (NOT DEFINED PROJECT_NAME)
|
||||
set(FMT_MASTER_PROJECT ON)
|
||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||
endif ()
|
||||
@@ -17,22 +19,108 @@ endif ()
|
||||
|
||||
# Joins arguments and places the results in ${result_var}.
|
||||
function(join result_var)
|
||||
set(result )
|
||||
set(result "")
|
||||
foreach (arg ${ARGN})
|
||||
set(result "${result}${arg}")
|
||||
endforeach ()
|
||||
set(${result_var} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# DEPRECATED! Should be merged into add_module_library.
|
||||
function(enable_module target)
|
||||
if (MSVC)
|
||||
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
|
||||
target_compile_options(${target}
|
||||
PRIVATE /interface /ifcOutput ${BMI}
|
||||
INTERFACE /reference fmt=${BMI})
|
||||
if(NOT CMAKE_GENERATOR STREQUAL "Ninja")
|
||||
set(BMI_DIR "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
file(TO_NATIVE_PATH "${BMI_DIR}/${target}.ifc" BMI)
|
||||
target_compile_options(${target}
|
||||
PRIVATE /interface /ifcOutput ${BMI}
|
||||
INTERFACE /reference fmt=${BMI})
|
||||
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
||||
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
||||
endif()
|
||||
endif ()
|
||||
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
||||
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
||||
endfunction()
|
||||
|
||||
set(FMT_USE_CMAKE_MODULES FALSE)
|
||||
if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.28 AND
|
||||
CMAKE_GENERATOR STREQUAL "Ninja")
|
||||
set(FMT_USE_CMAKE_MODULES TRUE)
|
||||
endif ()
|
||||
|
||||
# Adds a library compiled with C++20 module support.
|
||||
# `enabled` is a CMake variables that specifies if modules are enabled.
|
||||
# If modules are disabled `add_module_library` falls back to creating a
|
||||
# non-modular library.
|
||||
#
|
||||
# Usage:
|
||||
# add_module_library(<name> [sources...] FALLBACK [sources...] [IF enabled])
|
||||
function(add_module_library name)
|
||||
cmake_parse_arguments(AML "" "IF" "FALLBACK" ${ARGN})
|
||||
set(sources ${AML_UNPARSED_ARGUMENTS})
|
||||
|
||||
add_library(${name})
|
||||
set_target_properties(${name} PROPERTIES LINKER_LANGUAGE CXX)
|
||||
|
||||
if (NOT ${${AML_IF}})
|
||||
# Create a non-modular library.
|
||||
target_sources(${name} PRIVATE ${AML_FALLBACK})
|
||||
set_target_properties(${name} PROPERTIES CXX_SCAN_FOR_MODULES OFF)
|
||||
return()
|
||||
endif ()
|
||||
|
||||
# Modules require C++20.
|
||||
target_compile_features(${name} PUBLIC cxx_std_20)
|
||||
if (CMAKE_COMPILER_IS_GNUCXX)
|
||||
target_compile_options(${name} PUBLIC -fmodules-ts)
|
||||
endif ()
|
||||
|
||||
if (FMT_USE_CMAKE_MODULES)
|
||||
target_sources(${name} PUBLIC FILE_SET fmt TYPE CXX_MODULES
|
||||
FILES ${sources})
|
||||
else()
|
||||
# `std` is affected by CMake options and may be higher than C++20.
|
||||
get_target_property(std ${name} CXX_STANDARD)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(pcms)
|
||||
foreach (src ${sources})
|
||||
get_filename_component(pcm ${src} NAME_WE)
|
||||
set(pcm ${pcm}.pcm)
|
||||
|
||||
# Propagate -fmodule-file=*.pcm to targets that link with this library.
|
||||
target_compile_options(
|
||||
${name} PUBLIC -fmodule-file=${CMAKE_CURRENT_BINARY_DIR}/${pcm})
|
||||
|
||||
# Use an absolute path to prevent target_link_libraries prepending -l
|
||||
# to it.
|
||||
set(pcms ${pcms} ${CMAKE_CURRENT_BINARY_DIR}/${pcm})
|
||||
add_custom_command(
|
||||
OUTPUT ${pcm}
|
||||
COMMAND ${CMAKE_CXX_COMPILER}
|
||||
-std=c++${std} -x c++-module --precompile -c
|
||||
-o ${pcm} ${CMAKE_CURRENT_SOURCE_DIR}/${src}
|
||||
"-I$<JOIN:$<TARGET_PROPERTY:${name},INCLUDE_DIRECTORIES>,;-I>"
|
||||
# Required by the -I generator expression above.
|
||||
COMMAND_EXPAND_LISTS
|
||||
DEPENDS ${src})
|
||||
endforeach ()
|
||||
|
||||
# Add .pcm files as sources to make sure they are built before the library.
|
||||
set(sources)
|
||||
foreach (pcm ${pcms})
|
||||
get_filename_component(pcm_we ${pcm} NAME_WE)
|
||||
set(obj ${pcm_we}.o)
|
||||
# Use an absolute path to prevent target_link_libraries prepending -l.
|
||||
set(sources ${sources} ${pcm} ${CMAKE_CURRENT_BINARY_DIR}/${obj})
|
||||
add_custom_command(
|
||||
OUTPUT ${obj}
|
||||
COMMAND ${CMAKE_CXX_COMPILER} $<TARGET_PROPERTY:${name},COMPILE_OPTIONS>
|
||||
-c -o ${obj} ${pcm}
|
||||
DEPENDS ${pcm})
|
||||
endforeach ()
|
||||
endif ()
|
||||
target_sources(${name} PRIVATE ${sources})
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
include(CMakeParseArguments)
|
||||
@@ -79,28 +167,28 @@ option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
||||
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
||||
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
|
||||
option(FMT_OS "Include OS-specific APIs." ON)
|
||||
option(FMT_MODULE "Build a module instead of a traditional library." OFF)
|
||||
option(FMT_SYSTEM_HEADERS "Expose headers with marking them as system." OFF)
|
||||
option(FMT_UNICODE "Enable Unicode support." ON)
|
||||
|
||||
set(FMT_CAN_MODULE OFF)
|
||||
if (CMAKE_CXX_STANDARD GREATER 17 AND
|
||||
# msvc 16.10-pre4
|
||||
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
|
||||
set(FMT_CAN_MODULE ON)
|
||||
endif ()
|
||||
if (NOT FMT_CAN_MODULE)
|
||||
set(FMT_MODULE OFF)
|
||||
message(STATUS "Module support is disabled.")
|
||||
endif ()
|
||||
if (FMT_TEST AND FMT_MODULE)
|
||||
# The tests require {fmt} to be compiled as traditional library
|
||||
message(STATUS "Testing is incompatible with build mode 'module'.")
|
||||
endif ()
|
||||
set(FMT_SYSTEM_HEADERS_ATTRIBUTE "")
|
||||
if (FMT_SYSTEM_HEADERS)
|
||||
set(FMT_SYSTEM_HEADERS_ATTRIBUTE SYSTEM)
|
||||
endif ()
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "MSDOS")
|
||||
set(FMT_TEST OFF)
|
||||
message(STATUS "MSDOS is incompatible with gtest")
|
||||
endif ()
|
||||
|
||||
# Get version from core.h
|
||||
file(READ include/fmt/core.h core_h)
|
||||
if (NOT core_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])")
|
||||
message(FATAL_ERROR "Cannot get FMT_VERSION from core.h.")
|
||||
# Get version from base.h
|
||||
file(READ include/fmt/base.h base_h)
|
||||
if (NOT base_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])")
|
||||
message(FATAL_ERROR "Cannot get FMT_VERSION from base.h.")
|
||||
endif ()
|
||||
# Use math to skip leading zeros if any.
|
||||
math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
@@ -108,29 +196,19 @@ math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.
|
||||
${CPACK_PACKAGE_VERSION_PATCH})
|
||||
message(STATUS "Version: ${FMT_VERSION}")
|
||||
message(STATUS "{fmt} version: ${FMT_VERSION}")
|
||||
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
|
||||
endif ()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||
|
||||
include(cxx14)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(JoinPaths)
|
||||
|
||||
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
|
||||
if (${index} GREATER -1)
|
||||
# Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
|
||||
# compatibility with older CMake versions.
|
||||
set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
|
||||
endif ()
|
||||
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
|
||||
|
||||
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
|
||||
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
|
||||
"Preset for the export of private symbols")
|
||||
@@ -151,7 +229,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
-Wcast-align
|
||||
-Wctor-dtor-privacy -Wdisabled-optimization
|
||||
-Winvalid-pch -Woverloaded-virtual
|
||||
-Wconversion -Wswitch-enum -Wundef
|
||||
-Wconversion -Wundef
|
||||
-Wno-ctor-dtor-privacy -Wno-format-nonliteral)
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||
@@ -164,7 +242,13 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
endif ()
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
|
||||
-Wnull-dereference -Wduplicated-cond)
|
||||
-Wduplicated-cond)
|
||||
# Workaround for GCC regression
|
||||
# [12/13/14/15 regression] New (since gcc 12) false positive null-dereference in vector.resize
|
||||
# https://gcc.gnu.org/bugzilla/show_bug.cgi?id=108860
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS 12.0)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wnull-dereference)
|
||||
endif ()
|
||||
endif ()
|
||||
set(WERROR_FLAG -Werror)
|
||||
endif ()
|
||||
@@ -204,18 +288,6 @@ if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*")
|
||||
endif ()
|
||||
|
||||
set(strtod_l_headers stdlib.h)
|
||||
if (APPLE)
|
||||
set(strtod_l_headers ${strtod_l_headers} xlocale.h)
|
||||
endif ()
|
||||
|
||||
include(CheckSymbolExists)
|
||||
if (WIN32)
|
||||
check_symbol_exists(_strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
|
||||
else ()
|
||||
check_symbol_exists(strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
|
||||
endif ()
|
||||
|
||||
function(add_headers VAR)
|
||||
set(headers ${${VAR}})
|
||||
foreach (header ${ARGN})
|
||||
@@ -225,29 +297,22 @@ function(add_headers VAR)
|
||||
endfunction()
|
||||
|
||||
# Define the fmt library, its includes and the needed defines.
|
||||
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
|
||||
format-inl.h locale.h os.h ostream.h printf.h ranges.h
|
||||
set(FMT_HEADERS)
|
||||
add_headers(FMT_HEADERS args.h base.h chrono.h color.h compile.h core.h format.h
|
||||
format-inl.h os.h ostream.h printf.h ranges.h std.h
|
||||
xchar.h)
|
||||
if (FMT_MODULE)
|
||||
set(FMT_SOURCES src/fmt.cc)
|
||||
elseif (FMT_OS)
|
||||
set(FMT_SOURCES src/format.cc src/os.cc)
|
||||
else()
|
||||
set(FMT_SOURCES src/format.cc)
|
||||
endif ()
|
||||
set(FMT_SOURCES src/format.cc)
|
||||
|
||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS} README.rst ChangeLog.rst)
|
||||
add_module_library(fmt src/fmt.cc FALLBACK
|
||||
${FMT_SOURCES} ${FMT_HEADERS} README.md ChangeLog.md
|
||||
IF FMT_MODULE)
|
||||
add_library(fmt::fmt ALIAS fmt)
|
||||
|
||||
if (HAVE_STRTOD_L)
|
||||
target_compile_definitions(fmt PUBLIC FMT_LOCALE)
|
||||
endif ()
|
||||
|
||||
if (MINGW)
|
||||
check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
|
||||
if (${FMT_HAS_MBIG_OBJ})
|
||||
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
|
||||
endif()
|
||||
if (FMT_MODULE)
|
||||
enable_module(fmt)
|
||||
elseif (FMT_OS)
|
||||
target_sources(fmt PRIVATE src/os.cc)
|
||||
else()
|
||||
target_compile_definitions(fmt PRIVATE FMT_OS=0)
|
||||
endif ()
|
||||
|
||||
if (FMT_WERROR)
|
||||
@@ -256,13 +321,14 @@ endif ()
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
if (FMT_MODULE)
|
||||
enable_module(fmt)
|
||||
|
||||
if (cxx_std_11 IN_LIST CMAKE_CXX_COMPILE_FEATURES)
|
||||
target_compile_features(fmt PUBLIC cxx_std_11)
|
||||
else ()
|
||||
message(WARNING "Feature cxx_std_11 is unknown for the CXX compiler")
|
||||
endif ()
|
||||
|
||||
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
|
||||
target_include_directories(fmt PUBLIC
|
||||
target_include_directories(fmt ${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
@@ -270,7 +336,16 @@ set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.")
|
||||
|
||||
set_target_properties(fmt PROPERTIES
|
||||
VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
|
||||
DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}")
|
||||
PUBLIC_HEADER "${FMT_HEADERS}"
|
||||
DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}"
|
||||
|
||||
# Workaround for Visual Studio 2017:
|
||||
# Ensure the .pdb is created with the same name and in the same directory
|
||||
# as the .lib. Newer VS versions already do this by default, but there is no
|
||||
# harm in setting it for those too. Ignored by other generators.
|
||||
COMPILE_PDB_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
|
||||
COMPILE_PDB_NAME "fmt"
|
||||
COMPILE_PDB_NAME_DEBUG "fmt${FMT_DEBUG_POSTFIX}")
|
||||
|
||||
# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target
|
||||
# property because it's not set by default.
|
||||
@@ -280,25 +355,30 @@ if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
endif ()
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND
|
||||
NOT EMSCRIPTEN)
|
||||
# Fix rpmlint warning:
|
||||
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
|
||||
target_link_libraries(fmt -Wl,--as-needed)
|
||||
endif ()
|
||||
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
|
||||
target_compile_definitions(fmt PRIVATE FMT_LIB_EXPORT INTERFACE FMT_SHARED)
|
||||
endif ()
|
||||
if (FMT_SAFE_DURATION_CAST)
|
||||
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
add_library(fmt-header-only INTERFACE)
|
||||
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
||||
|
||||
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
if (NOT MSVC)
|
||||
# Unicode is always supported on compilers other than MSVC.
|
||||
elseif (FMT_UNICODE)
|
||||
# Unicode support requires compiling with /utf-8.
|
||||
target_compile_options(fmt PUBLIC $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
|
||||
target_compile_options(fmt-header-only INTERFACE $<$<AND:$<COMPILE_LANGUAGE:CXX>,$<CXX_COMPILER_ID:MSVC>>:/utf-8>)
|
||||
else ()
|
||||
target_compile_definitions(fmt PUBLIC FMT_UNICODE=0)
|
||||
endif ()
|
||||
|
||||
target_include_directories(fmt-header-only INTERFACE
|
||||
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||
target_compile_features(fmt-header-only INTERFACE cxx_std_11)
|
||||
|
||||
target_include_directories(fmt-header-only
|
||||
${FMT_SYSTEM_HEADERS_ATTRIBUTE} BEFORE INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
@@ -318,7 +398,7 @@ if (FMT_INSTALL)
|
||||
"Installation directory for libraries, a relative path that "
|
||||
"will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
||||
|
||||
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
|
||||
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE STRING
|
||||
"Installation directory for pkgconfig (.pc) files, a relative "
|
||||
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
|
||||
"absolute path.")
|
||||
@@ -343,11 +423,20 @@ if (FMT_INSTALL)
|
||||
|
||||
set(INSTALL_TARGETS fmt fmt-header-only)
|
||||
|
||||
set(INSTALL_FILE_SET)
|
||||
if (FMT_USE_CMAKE_MODULES)
|
||||
set(INSTALL_FILE_SET FILE_SET fmt DESTINATION "${FMT_INC_DIR}/fmt")
|
||||
endif()
|
||||
|
||||
# Install the library and headers.
|
||||
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
|
||||
install(TARGETS ${INSTALL_TARGETS}
|
||||
COMPONENT fmt_core
|
||||
EXPORT ${targets_export_name}
|
||||
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
PUBLIC_HEADER DESTINATION "${FMT_INC_DIR}/fmt"
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
${INSTALL_FILE_SET})
|
||||
|
||||
# Use a namespace because CMake provides better diagnostics for namespaced
|
||||
# imported targets.
|
||||
@@ -355,20 +444,56 @@ if (FMT_INSTALL)
|
||||
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
|
||||
|
||||
# Install version, config and target files.
|
||||
install(
|
||||
FILES ${project_config} ${version_config}
|
||||
DESTINATION ${FMT_CMAKE_DIR})
|
||||
install(FILES ${project_config} ${version_config}
|
||||
DESTINATION ${FMT_CMAKE_DIR}
|
||||
COMPONENT fmt_core)
|
||||
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
||||
NAMESPACE fmt::)
|
||||
NAMESPACE fmt::
|
||||
COMPONENT fmt_core)
|
||||
|
||||
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
|
||||
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
|
||||
install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt")
|
||||
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
|
||||
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}"
|
||||
COMPONENT fmt_core)
|
||||
endif ()
|
||||
|
||||
function(add_doc_target)
|
||||
find_program(DOXYGEN doxygen
|
||||
PATHS "$ENV{ProgramFiles}/doxygen/bin"
|
||||
"$ENV{ProgramFiles\(x86\)}/doxygen/bin")
|
||||
if (NOT DOXYGEN)
|
||||
message(STATUS "Target 'doc' disabled because doxygen not found")
|
||||
return ()
|
||||
endif ()
|
||||
|
||||
find_program(MKDOCS mkdocs)
|
||||
if (NOT MKDOCS)
|
||||
message(STATUS "Target 'doc' disabled because mkdocs not found")
|
||||
return ()
|
||||
endif ()
|
||||
|
||||
set(sources )
|
||||
foreach (source api.md index.md syntax.md get-started.md fmt.css fmt.js)
|
||||
set(sources ${sources} doc/${source})
|
||||
endforeach()
|
||||
|
||||
add_custom_target(
|
||||
doc
|
||||
COMMAND
|
||||
${CMAKE_COMMAND}
|
||||
-E env PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/support/python
|
||||
${MKDOCS} build -f ${CMAKE_CURRENT_SOURCE_DIR}/support/mkdocs.yml
|
||||
# MkDocs requires the site dir to be outside of the doc dir.
|
||||
--site-dir ${CMAKE_CURRENT_BINARY_DIR}/doc-html
|
||||
--no-directory-urls
|
||||
SOURCES ${sources})
|
||||
|
||||
include(GNUInstallDirs)
|
||||
install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/doc-html/
|
||||
DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/doc/fmt
|
||||
COMPONENT fmt_doc OPTIONAL)
|
||||
endfunction()
|
||||
|
||||
if (FMT_DOC)
|
||||
add_subdirectory(doc)
|
||||
add_doc_target()
|
||||
endif ()
|
||||
|
||||
if (FMT_TEST)
|
||||
@@ -398,13 +523,12 @@ if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
|
||||
string(REPLACE "*" ".*" line "${line}")
|
||||
set(ignored_files ${ignored_files} "${line}$" "${line}/")
|
||||
endforeach ()
|
||||
set(ignored_files ${ignored_files}
|
||||
/.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees)
|
||||
set(ignored_files ${ignored_files} /.git /build/doxyxml .vagrant)
|
||||
|
||||
set(CPACK_SOURCE_GENERATOR ZIP)
|
||||
set(CPACK_SOURCE_IGNORE_FILES ${ignored_files})
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION})
|
||||
set(CPACK_PACKAGE_NAME fmt)
|
||||
set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst)
|
||||
set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.md)
|
||||
include(CPack)
|
||||
endif ()
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
Contributing to {fmt}
|
||||
=====================
|
||||
|
||||
By submitting a pull request or a patch, you represent that you have the right
|
||||
to license your contribution to the {fmt} project owners and the community,
|
||||
agree that your contributions are licensed under the {fmt} license, and agree
|
||||
to future changes to the licensing.
|
||||
|
||||
All C++ code must adhere to [Google C++ Style Guide](
|
||||
https://google.github.io/styleguide/cppguide.html) with the following
|
||||
exceptions:
|
||||
|
||||
* Exceptions are permitted
|
||||
* snake_case should be used instead of UpperCamelCase for function and type
|
||||
names
|
||||
|
||||
All documentation must adhere to the [Google Developer Documentation Style
|
||||
Guide](https://developers.google.com/style).
|
||||
|
||||
Thanks for contributing!
|
||||
+3299
File diff suppressed because it is too large
Load Diff
-4238
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2012 - present, Victor Zverovich
|
||||
Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
Vendored
+486
@@ -0,0 +1,486 @@
|
||||
<img src="https://user-images.githubusercontent.com/576385/156254208-f5b743a9-88cf-439d-b0c0-923d53e8d551.png" alt="{fmt}" width="25%"/>
|
||||
|
||||
[](https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux)
|
||||
[](https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos)
|
||||
[](https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows)
|
||||
[](https://bugs.chromium.org/p/oss-fuzz/issues/list?\%0Acolspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\%0ASummary&q=proj%3Dfmt&can=1)
|
||||
[](https://www.bestpractices.dev/projects/8880)
|
||||
[](https://securityscorecards.dev/viewer/?uri=github.com/fmtlib/fmt)
|
||||
[](https://stackoverflow.com/questions/tagged/fmt)
|
||||
|
||||
**{fmt}** is an open-source formatting library providing a fast and safe
|
||||
alternative to C stdio and C++ iostreams.
|
||||
|
||||
If you like this project, please consider donating to one of the funds
|
||||
that help victims of the war in Ukraine: <https://www.stopputin.net/>.
|
||||
|
||||
[Documentation](https://fmt.dev)
|
||||
|
||||
[Cheat Sheets](https://hackingcpp.com/cpp/libs/fmt.html)
|
||||
|
||||
Q&A: ask questions on [StackOverflow with the tag
|
||||
fmt](https://stackoverflow.com/questions/tagged/fmt).
|
||||
|
||||
Try {fmt} in [Compiler Explorer](https://godbolt.org/z/8Mx1EW73v).
|
||||
|
||||
# Features
|
||||
|
||||
- Simple [format API](https://fmt.dev/latest/api/) with positional
|
||||
arguments for localization
|
||||
- Implementation of [C++20
|
||||
std::format](https://en.cppreference.com/w/cpp/utility/format) and
|
||||
[C++23 std::print](https://en.cppreference.com/w/cpp/io/print)
|
||||
- [Format string syntax](https://fmt.dev/latest/syntax/) similar
|
||||
to Python\'s
|
||||
[format](https://docs.python.org/3/library/stdtypes.html#str.format)
|
||||
- Fast IEEE 754 floating-point formatter with correct rounding,
|
||||
shortness and round-trip guarantees using the
|
||||
[Dragonbox](https://github.com/jk-jeon/dragonbox) algorithm
|
||||
- Portable Unicode support
|
||||
- Safe [printf
|
||||
implementation](https://fmt.dev/latest/api/#printf-formatting)
|
||||
including the POSIX extension for positional arguments
|
||||
- Extensibility: [support for user-defined
|
||||
types](https://fmt.dev/latest/api/#formatting-user-defined-types)
|
||||
- High performance: faster than common standard library
|
||||
implementations of `(s)printf`, iostreams, `to_string` and
|
||||
`to_chars`, see [Speed tests](#speed-tests) and [Converting a
|
||||
hundred million integers to strings per
|
||||
second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html)
|
||||
- Small code size both in terms of source code with the minimum
|
||||
configuration consisting of just three files, `base.h`, `format.h`
|
||||
and `format-inl.h`, and compiled code; see [Compile time and code
|
||||
bloat](#compile-time-and-code-bloat)
|
||||
- Reliability: the library has an extensive set of
|
||||
[tests](https://github.com/fmtlib/fmt/tree/master/test) and is
|
||||
[continuously fuzzed](https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1)
|
||||
- Safety: the library is fully type-safe, errors in format strings can
|
||||
be reported at compile time, automatic memory management prevents
|
||||
buffer overflow errors
|
||||
- Ease of use: small self-contained code base, no external
|
||||
dependencies, permissive MIT
|
||||
[license](https://github.com/fmtlib/fmt/blob/master/LICENSE)
|
||||
- [Portability](https://fmt.dev/latest/#portability) with
|
||||
consistent output across platforms and support for older compilers
|
||||
- Clean warning-free codebase even on high warning levels such as
|
||||
`-Wall -Wextra -pedantic`
|
||||
- Locale independence by default
|
||||
- Optional header-only configuration enabled with the
|
||||
`FMT_HEADER_ONLY` macro
|
||||
|
||||
See the [documentation](https://fmt.dev) for more details.
|
||||
|
||||
# Examples
|
||||
|
||||
**Print to stdout** ([run](https://godbolt.org/z/Tevcjh))
|
||||
|
||||
``` c++
|
||||
#include <fmt/base.h>
|
||||
|
||||
int main() {
|
||||
fmt::print("Hello, world!\n");
|
||||
}
|
||||
```
|
||||
|
||||
**Format a string** ([run](https://godbolt.org/z/oK8h33))
|
||||
|
||||
``` c++
|
||||
std::string s = fmt::format("The answer is {}.", 42);
|
||||
// s == "The answer is 42."
|
||||
```
|
||||
|
||||
**Format a string using positional arguments**
|
||||
([run](https://godbolt.org/z/Yn7Txe))
|
||||
|
||||
``` c++
|
||||
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
|
||||
// s == "I'd rather be happy than right."
|
||||
```
|
||||
|
||||
**Print dates and times** ([run](https://godbolt.org/z/c31ExdY3W))
|
||||
|
||||
``` c++
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
int main() {
|
||||
auto now = std::chrono::system_clock::now();
|
||||
fmt::print("Date and time: {}\n", now);
|
||||
fmt::print("Time: {:%H:%M}\n", now);
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
Date and time: 2023-12-26 19:10:31.557195597
|
||||
Time: 19:10
|
||||
|
||||
**Print a container** ([run](https://godbolt.org/z/MxM1YqjE7))
|
||||
|
||||
``` c++
|
||||
#include <vector>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
int main() {
|
||||
std::vector<int> v = {1, 2, 3};
|
||||
fmt::print("{}\n", v);
|
||||
}
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
[1, 2, 3]
|
||||
|
||||
**Check a format string at compile time**
|
||||
|
||||
``` c++
|
||||
std::string s = fmt::format("{:d}", "I am not a number");
|
||||
```
|
||||
|
||||
This gives a compile-time error in C++20 because `d` is an invalid
|
||||
format specifier for a string.
|
||||
|
||||
**Write a file from a single thread**
|
||||
|
||||
``` c++
|
||||
#include <fmt/os.h>
|
||||
|
||||
int main() {
|
||||
auto out = fmt::output_file("guide.txt");
|
||||
out.print("Don't {}", "Panic");
|
||||
}
|
||||
```
|
||||
|
||||
This can be [5 to 9 times faster than
|
||||
fprintf](http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html).
|
||||
|
||||
**Print with colors and text styles**
|
||||
|
||||
``` c++
|
||||
#include <fmt/color.h>
|
||||
|
||||
int main() {
|
||||
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
|
||||
"Hello, {}!\n", "world");
|
||||
fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
|
||||
fmt::emphasis::underline, "Olá, {}!\n", "Mundo");
|
||||
fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
|
||||
"你好{}!\n", "世界");
|
||||
}
|
||||
```
|
||||
|
||||
Output on a modern terminal with Unicode support:
|
||||
|
||||

|
||||
|
||||
# Benchmarks
|
||||
|
||||
## Speed tests
|
||||
|
||||
| Library | Method | Run Time, s |
|
||||
|-------------------|---------------|-------------|
|
||||
| libc | printf | 0.91 |
|
||||
| libc++ | std::ostream | 2.49 |
|
||||
| {fmt} 9.1 | fmt::print | 0.74 |
|
||||
| Boost Format 1.80 | boost::format | 6.26 |
|
||||
| Folly Format | folly::format | 1.87 |
|
||||
|
||||
{fmt} is the fastest of the benchmarked methods, \~20% faster than
|
||||
`printf`.
|
||||
|
||||
The above results were generated by building `tinyformat_test.cpp` on
|
||||
macOS 12.6.1 with `clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT`, and
|
||||
taking the best of three runs. In the test, the format string
|
||||
`"%0.10f:%04d:%+g:%s:%p:%c:%%\n"` or equivalent is filled 2,000,000
|
||||
times with output sent to `/dev/null`; for further details refer to the
|
||||
[source](https://github.com/fmtlib/format-benchmark/blob/master/src/tinyformat-test.cc).
|
||||
|
||||
{fmt} is up to 20-30x faster than `std::ostringstream` and `sprintf` on
|
||||
IEEE754 `float` and `double` formatting
|
||||
([dtoa-benchmark](https://github.com/fmtlib/dtoa-benchmark)) and faster
|
||||
than [double-conversion](https://github.com/google/double-conversion)
|
||||
and [ryu](https://github.com/ulfjack/ryu):
|
||||
|
||||
[](https://fmt.dev/unknown_mac64_clang12.0.html)
|
||||
|
||||
## Compile time and code bloat
|
||||
|
||||
The script [bloat-test.py][test] from [format-benchmark][bench] tests compile
|
||||
time and code bloat for nontrivial projects. It generates 100 translation units
|
||||
and uses `printf()` or its alternative five times in each to simulate a
|
||||
medium-sized project. The resulting executable size and compile time (Apple
|
||||
clang version 15.0.0 (clang-1500.1.0.2.5), macOS Sonoma, best of three) is shown
|
||||
in the following tables.
|
||||
|
||||
[test]: https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py
|
||||
[bench]: https://github.com/fmtlib/format-benchmark
|
||||
|
||||
**Optimized build (-O3)**
|
||||
|
||||
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|
||||
|---------------|-----------------|----------------------|--------------------|
|
||||
| printf | 1.6 | 54 | 50 |
|
||||
| IOStreams | 25.9 | 98 | 84 |
|
||||
| fmt 83652df | 4.8 | 54 | 50 |
|
||||
| tinyformat | 29.1 | 161 | 136 |
|
||||
| Boost Format | 55.0 | 530 | 317 |
|
||||
|
||||
{fmt} is fast to compile and is comparable to `printf` in terms of per-call
|
||||
binary size (within a rounding error on this system).
|
||||
|
||||
**Non-optimized build**
|
||||
|
||||
| Method | Compile Time, s | Executable size, KiB | Stripped size, KiB |
|
||||
|---------------|-----------------|----------------------|--------------------|
|
||||
| printf | 1.4 | 54 | 50 |
|
||||
| IOStreams | 23.4 | 92 | 68 |
|
||||
| {fmt} 83652df | 4.4 | 89 | 85 |
|
||||
| tinyformat | 24.5 | 204 | 161 |
|
||||
| Boost Format | 36.4 | 831 | 462 |
|
||||
|
||||
`libc`, `lib(std)c++`, and `libfmt` are all linked as shared libraries
|
||||
to compare formatting function overhead only. Boost Format is a
|
||||
header-only library so it doesn\'t provide any linkage options.
|
||||
|
||||
## Running the tests
|
||||
|
||||
Please refer to [Building the
|
||||
library](https://fmt.dev/latest/get-started/#building-from-source) for
|
||||
instructions on how to build the library and run the unit tests.
|
||||
|
||||
Benchmarks reside in a separate repository,
|
||||
[format-benchmarks](https://github.com/fmtlib/format-benchmark), so to
|
||||
run the benchmarks you first need to clone this repository and generate
|
||||
Makefiles with CMake:
|
||||
|
||||
$ git clone --recursive https://github.com/fmtlib/format-benchmark.git
|
||||
$ cd format-benchmark
|
||||
$ cmake .
|
||||
|
||||
Then you can run the speed test:
|
||||
|
||||
$ make speed-test
|
||||
|
||||
or the bloat test:
|
||||
|
||||
$ make bloat-test
|
||||
|
||||
# Migrating code
|
||||
|
||||
[clang-tidy](https://clang.llvm.org/extra/clang-tidy/) v18 provides the
|
||||
[modernize-use-std-print](https://clang.llvm.org/extra/clang-tidy/checks/modernize/use-std-print.html)
|
||||
check that is capable of converting occurrences of `printf` and
|
||||
`fprintf` to `fmt::print` if configured to do so. (By default it
|
||||
converts to `std::print`.)
|
||||
|
||||
# Notable projects using this library
|
||||
|
||||
- [0 A.D.](https://play0ad.com/): a free, open-source, cross-platform
|
||||
real-time strategy game
|
||||
- [AMPL/MP](https://github.com/ampl/mp): an open-source library for
|
||||
mathematical programming
|
||||
- [Apple's FoundationDB](https://github.com/apple/foundationdb): an open-source,
|
||||
distributed, transactional key-value store
|
||||
- [Aseprite](https://github.com/aseprite/aseprite): animated sprite
|
||||
editor & pixel art tool
|
||||
- [AvioBook](https://www.aviobook.aero/en): a comprehensive aircraft
|
||||
operations suite
|
||||
- [Blizzard Battle.net](https://battle.net/): an online gaming
|
||||
platform
|
||||
- [Celestia](https://celestia.space/): real-time 3D visualization of
|
||||
space
|
||||
- [Ceph](https://ceph.com/): a scalable distributed storage system
|
||||
- [ccache](https://ccache.dev/): a compiler cache
|
||||
- [ClickHouse](https://github.com/ClickHouse/ClickHouse): an
|
||||
analytical database management system
|
||||
- [ContextVision](https://www.contextvision.com/): medical imaging software
|
||||
- [Contour](https://github.com/contour-terminal/contour/): a modern
|
||||
terminal emulator
|
||||
- [CUAUV](https://cuauv.org/): Cornell University\'s autonomous
|
||||
underwater vehicle
|
||||
- [Drake](https://drake.mit.edu/): a planning, control, and analysis
|
||||
toolbox for nonlinear dynamical systems (MIT)
|
||||
- [Envoy](https://github.com/envoyproxy/envoy): C++ L7 proxy and
|
||||
communication bus (Lyft)
|
||||
- [FiveM](https://fivem.net/): a modification framework for GTA V
|
||||
- [fmtlog](https://github.com/MengRao/fmtlog): a performant
|
||||
fmtlib-style logging library with latency in nanoseconds
|
||||
- [Folly](https://github.com/facebook/folly): Facebook open-source
|
||||
library
|
||||
- [GemRB](https://gemrb.org/): a portable open-source implementation
|
||||
of Bioware's Infinity Engine
|
||||
- [Grand Mountain
|
||||
Adventure](https://store.steampowered.com/app/1247360/Grand_Mountain_Adventure/):
|
||||
a beautiful open-world ski & snowboarding game
|
||||
- [HarpyWar/pvpgn](https://github.com/pvpgn/pvpgn-server): Player vs
|
||||
Player Gaming Network with tweaks
|
||||
- [KBEngine](https://github.com/kbengine/kbengine): an open-source
|
||||
MMOG server engine
|
||||
- [Keypirinha](https://keypirinha.com/): a semantic launcher for
|
||||
Windows
|
||||
- [Kodi](https://kodi.tv/) (formerly xbmc): home theater software
|
||||
- [Knuth](https://kth.cash/): high-performance Bitcoin full-node
|
||||
- [libunicode](https://github.com/contour-terminal/libunicode/): a
|
||||
modern C++17 Unicode library
|
||||
- [MariaDB](https://mariadb.org/): relational database management
|
||||
system
|
||||
- [Microsoft Verona](https://github.com/microsoft/verona): research
|
||||
programming language for concurrent ownership
|
||||
- [MongoDB](https://mongodb.com/): distributed document database
|
||||
- [MongoDB Smasher](https://github.com/duckie/mongo_smasher): a small
|
||||
tool to generate randomized datasets
|
||||
- [OpenSpace](https://openspaceproject.com/): an open-source
|
||||
astrovisualization framework
|
||||
- [PenUltima Online (POL)](https://www.polserver.com/): an MMO server,
|
||||
compatible with most Ultima Online clients
|
||||
- [PyTorch](https://github.com/pytorch/pytorch): an open-source
|
||||
machine learning library
|
||||
- [quasardb](https://www.quasardb.net/): a distributed,
|
||||
high-performance, associative database
|
||||
- [Quill](https://github.com/odygrd/quill): asynchronous low-latency
|
||||
logging library
|
||||
- [QKW](https://github.com/ravijanjam/qkw): generalizing aliasing to
|
||||
simplify navigation, and execute complex multi-line terminal
|
||||
command sequences
|
||||
- [redis-cerberus](https://github.com/HunanTV/redis-cerberus): a Redis
|
||||
cluster proxy
|
||||
- [redpanda](https://vectorized.io/redpanda): a 10x faster Kafka®
|
||||
replacement for mission-critical systems written in C++
|
||||
- [rpclib](http://rpclib.net/): a modern C++ msgpack-RPC server and
|
||||
client library
|
||||
- [Salesforce Analytics
|
||||
Cloud](https://www.salesforce.com/analytics-cloud/overview/):
|
||||
business intelligence software
|
||||
- [Scylla](https://www.scylladb.com/): a Cassandra-compatible NoSQL
|
||||
data store that can handle 1 million transactions per second on a
|
||||
single server
|
||||
- [Seastar](http://www.seastar-project.org/): an advanced, open-source
|
||||
C++ framework for high-performance server applications on modern
|
||||
hardware
|
||||
- [spdlog](https://github.com/gabime/spdlog): super fast C++ logging
|
||||
library
|
||||
- [Stellar](https://www.stellar.org/): financial platform
|
||||
- [Touch Surgery](https://www.touchsurgery.com/): surgery simulator
|
||||
- [TrinityCore](https://github.com/TrinityCore/TrinityCore):
|
||||
open-source MMORPG framework
|
||||
- [🐙 userver framework](https://userver.tech/): open-source
|
||||
asynchronous framework with a rich set of abstractions and database
|
||||
drivers
|
||||
- [Windows Terminal](https://github.com/microsoft/terminal): the new
|
||||
Windows terminal
|
||||
|
||||
[More\...](https://github.com/search?q=fmtlib&type=Code)
|
||||
|
||||
If you are aware of other projects using this library, please let me
|
||||
know by [email](mailto:victor.zverovich@gmail.com) or by submitting an
|
||||
[issue](https://github.com/fmtlib/fmt/issues).
|
||||
|
||||
# Motivation
|
||||
|
||||
So why yet another formatting library?
|
||||
|
||||
There are plenty of methods for doing this task, from standard ones like
|
||||
the printf family of function and iostreams to Boost Format and
|
||||
FastFormat libraries. The reason for creating a new library is that
|
||||
every existing solution that I found either had serious issues or
|
||||
didn\'t provide all the features I needed.
|
||||
|
||||
## printf
|
||||
|
||||
The good thing about `printf` is that it is pretty fast and readily
|
||||
available being a part of the C standard library. The main drawback is
|
||||
that it doesn\'t support user-defined types. `printf` also has safety
|
||||
issues although they are somewhat mitigated with [\_\_attribute\_\_
|
||||
((format (printf,
|
||||
\...))](https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html) in
|
||||
GCC. There is a POSIX extension that adds positional arguments required
|
||||
for
|
||||
[i18n](https://en.wikipedia.org/wiki/Internationalization_and_localization)
|
||||
to `printf` but it is not a part of C99 and may not be available on some
|
||||
platforms.
|
||||
|
||||
## iostreams
|
||||
|
||||
The main issue with iostreams is best illustrated with an example:
|
||||
|
||||
``` c++
|
||||
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
|
||||
```
|
||||
|
||||
which is a lot of typing compared to printf:
|
||||
|
||||
``` c++
|
||||
printf("%.2f\n", 1.23456);
|
||||
```
|
||||
|
||||
Matthew Wilson, the author of FastFormat, called this \"chevron hell\".
|
||||
iostreams don\'t support positional arguments by design.
|
||||
|
||||
The good part is that iostreams support user-defined types and are safe
|
||||
although error handling is awkward.
|
||||
|
||||
## Boost Format
|
||||
|
||||
This is a very powerful library that supports both `printf`-like format
|
||||
strings and positional arguments. Its main drawback is performance.
|
||||
According to various benchmarks, it is much slower than other methods
|
||||
considered here. Boost Format also has excessive build times and severe
|
||||
code bloat issues (see [Benchmarks](#benchmarks)).
|
||||
|
||||
## FastFormat
|
||||
|
||||
This is an interesting library that is fast, safe and has positional
|
||||
arguments. However, it has significant limitations, citing its author:
|
||||
|
||||
> Three features that have no hope of being accommodated within the
|
||||
> current design are:
|
||||
>
|
||||
> - Leading zeros (or any other non-space padding)
|
||||
> - Octal/hexadecimal encoding
|
||||
> - Runtime width/alignment specification
|
||||
|
||||
It is also quite big and has a heavy dependency, on STLSoft, which might be
|
||||
too restrictive for use in some projects.
|
||||
|
||||
## Boost Spirit.Karma
|
||||
|
||||
This is not a formatting library but I decided to include it here for
|
||||
completeness. As iostreams, it suffers from the problem of mixing
|
||||
verbatim text with arguments. The library is pretty fast, but slower on
|
||||
integer formatting than `fmt::format_to` with format string compilation
|
||||
on Karma\'s own benchmark, see [Converting a hundred million integers to
|
||||
strings per
|
||||
second](http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html).
|
||||
|
||||
# License
|
||||
|
||||
{fmt} is distributed under the MIT
|
||||
[license](https://github.com/fmtlib/fmt/blob/master/LICENSE).
|
||||
|
||||
# Documentation License
|
||||
|
||||
The [Format String Syntax](https://fmt.dev/latest/syntax/) section
|
||||
in the documentation is based on the one from Python [string module
|
||||
documentation](https://docs.python.org/3/library/string.html#module-string).
|
||||
For this reason, the documentation is distributed under the Python
|
||||
Software Foundation license available in
|
||||
[doc/python-license.txt](https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt).
|
||||
It only applies if you distribute the documentation of {fmt}.
|
||||
|
||||
# Maintainers
|
||||
|
||||
The {fmt} library is maintained by Victor Zverovich
|
||||
([vitaut](https://github.com/vitaut)) with contributions from many other
|
||||
people. See
|
||||
[Contributors](https://github.com/fmtlib/fmt/graphs/contributors) and
|
||||
[Releases](https://github.com/fmtlib/fmt/releases) for some of the
|
||||
names. Let us know if your contribution is not listed or mentioned
|
||||
incorrectly and we\'ll make it right.
|
||||
|
||||
# Security Policy
|
||||
|
||||
To report a security issue, please disclose it at [security
|
||||
advisory](https://github.com/fmtlib/fmt/security/advisories/new).
|
||||
|
||||
This project is maintained by a team of volunteers on a
|
||||
reasonable-effort basis. As such, please give us at least *90* days to
|
||||
work on a fix before public exposure.
|
||||
Vendored
-524
@@ -1,524 +0,0 @@
|
||||
{fmt}
|
||||
=====
|
||||
|
||||
.. image:: https://github.com/fmtlib/fmt/workflows/linux/badge.svg
|
||||
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Alinux
|
||||
|
||||
.. image:: https://github.com/fmtlib/fmt/workflows/macos/badge.svg
|
||||
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Amacos
|
||||
|
||||
.. image:: https://github.com/fmtlib/fmt/workflows/windows/badge.svg
|
||||
:target: https://github.com/fmtlib/fmt/actions?query=workflow%3Awindows
|
||||
|
||||
.. image:: https://ci.appveyor.com/api/projects/status/ehjkiefde6gucy1v
|
||||
:target: https://ci.appveyor.com/project/vitaut/fmt
|
||||
|
||||
.. image:: https://oss-fuzz-build-logs.storage.googleapis.com/badges/fmt.svg
|
||||
:alt: fmt is continuously fuzzed at oss-fuzz
|
||||
:target: https://bugs.chromium.org/p/oss-fuzz/issues/list?\
|
||||
colspec=ID%20Type%20Component%20Status%20Proj%20Reported%20Owner%20\
|
||||
Summary&q=proj%3Dfmt&can=1
|
||||
|
||||
.. image:: https://img.shields.io/badge/stackoverflow-fmt-blue.svg
|
||||
:alt: Ask questions at StackOverflow with the tag fmt
|
||||
:target: https://stackoverflow.com/questions/tagged/fmt
|
||||
|
||||
**{fmt}** is an open-source formatting library providing a fast and safe
|
||||
alternative to C stdio and C++ iostreams.
|
||||
|
||||
If you like this project, please consider donating to the BYSOL
|
||||
Foundation that helps victims of political repressions in Belarus:
|
||||
https://bysol.org/en/bs/general/.
|
||||
|
||||
`Documentation <https://fmt.dev>`__
|
||||
|
||||
Q&A: ask questions on `StackOverflow with the tag fmt
|
||||
<https://stackoverflow.com/questions/tagged/fmt>`_.
|
||||
|
||||
Try {fmt} in `Compiler Explorer <https://godbolt.org/z/Eq5763>`_.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
* Simple `format API <https://fmt.dev/latest/api.html>`_ with positional arguments
|
||||
for localization
|
||||
* Implementation of `C++20 std::format
|
||||
<https://en.cppreference.com/w/cpp/utility/format>`__
|
||||
* `Format string syntax <https://fmt.dev/latest/syntax.html>`_ similar to Python's
|
||||
`format <https://docs.python.org/3/library/stdtypes.html#str.format>`_
|
||||
* Fast IEEE 754 floating-point formatter with correct rounding, shortness and
|
||||
round-trip guarantees
|
||||
* Safe `printf implementation
|
||||
<https://fmt.dev/latest/api.html#printf-formatting>`_ including the POSIX
|
||||
extension for positional arguments
|
||||
* Extensibility: `support for user-defined types
|
||||
<https://fmt.dev/latest/api.html#formatting-user-defined-types>`_
|
||||
* High performance: faster than common standard library implementations of
|
||||
``(s)printf``, iostreams, ``to_string`` and ``to_chars``, see `Speed tests`_
|
||||
and `Converting a hundred million integers to strings per second
|
||||
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_
|
||||
* Small code size both in terms of source code with the minimum configuration
|
||||
consisting of just three files, ``core.h``, ``format.h`` and ``format-inl.h``,
|
||||
and compiled code; see `Compile time and code bloat`_
|
||||
* Reliability: the library has an extensive set of `tests
|
||||
<https://github.com/fmtlib/fmt/tree/master/test>`_ and is `continuously fuzzed
|
||||
<https://bugs.chromium.org/p/oss-fuzz/issues/list?colspec=ID%20Type%20
|
||||
Component%20Status%20Proj%20Reported%20Owner%20Summary&q=proj%3Dfmt&can=1>`_
|
||||
* Safety: the library is fully type safe, errors in format strings can be
|
||||
reported at compile time, automatic memory management prevents buffer overflow
|
||||
errors
|
||||
* Ease of use: small self-contained code base, no external dependencies,
|
||||
permissive MIT `license
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_
|
||||
* `Portability <https://fmt.dev/latest/index.html#portability>`_ with
|
||||
consistent output across platforms and support for older compilers
|
||||
* Clean warning-free codebase even on high warning levels such as
|
||||
``-Wall -Wextra -pedantic``
|
||||
* Locale-independence by default
|
||||
* Optional header-only configuration enabled with the ``FMT_HEADER_ONLY`` macro
|
||||
|
||||
See the `documentation <https://fmt.dev>`_ for more details.
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
**Print to stdout** (`run <https://godbolt.org/z/Tevcjh>`_)
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/core.h>
|
||||
|
||||
int main() {
|
||||
fmt::print("Hello, world!\n");
|
||||
}
|
||||
|
||||
**Format a string** (`run <https://godbolt.org/z/oK8h33>`_)
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::string s = fmt::format("The answer is {}.", 42);
|
||||
// s == "The answer is 42."
|
||||
|
||||
**Format a string using positional arguments** (`run <https://godbolt.org/z/Yn7Txe>`_)
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::string s = fmt::format("I'd rather be {1} than {0}.", "right", "happy");
|
||||
// s == "I'd rather be happy than right."
|
||||
|
||||
**Print chrono durations** (`run <https://godbolt.org/z/K8s4Mc>`_)
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
int main() {
|
||||
using namespace std::literals::chrono_literals;
|
||||
fmt::print("Default format: {} {}\n", 42s, 100ms);
|
||||
fmt::print("strftime-like format: {:%H:%M:%S}\n", 3h + 15min + 30s);
|
||||
}
|
||||
|
||||
Output::
|
||||
|
||||
Default format: 42s 100ms
|
||||
strftime-like format: 03:15:30
|
||||
|
||||
**Print a container** (`run <https://godbolt.org/z/MjsY7c>`_)
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <vector>
|
||||
#include <fmt/ranges.h>
|
||||
|
||||
int main() {
|
||||
std::vector<int> v = {1, 2, 3};
|
||||
fmt::print("{}\n", v);
|
||||
}
|
||||
|
||||
Output::
|
||||
|
||||
[1, 2, 3]
|
||||
|
||||
**Check a format string at compile time**
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::string s = fmt::format(FMT_STRING("{:d}"), "I am not a number");
|
||||
|
||||
This gives a compile-time error because ``d`` is an invalid format specifier for
|
||||
a string.
|
||||
|
||||
**Write a file from a single thread**
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/os.h>
|
||||
|
||||
int main() {
|
||||
auto out = fmt::output_file("guide.txt");
|
||||
out.print("Don't {}", "Panic");
|
||||
}
|
||||
|
||||
This can be `5 to 9 times faster than fprintf
|
||||
<http://www.zverovich.net/2020/08/04/optimal-file-buffer-size.html>`_.
|
||||
|
||||
**Print with colors and text styles**
|
||||
|
||||
.. code:: c++
|
||||
|
||||
#include <fmt/color.h>
|
||||
|
||||
int main() {
|
||||
fmt::print(fg(fmt::color::crimson) | fmt::emphasis::bold,
|
||||
"Hello, {}!\n", "world");
|
||||
fmt::print(fg(fmt::color::floral_white) | bg(fmt::color::slate_gray) |
|
||||
fmt::emphasis::underline, "Hello, {}!\n", "мир");
|
||||
fmt::print(fg(fmt::color::steel_blue) | fmt::emphasis::italic,
|
||||
"Hello, {}!\n", "世界");
|
||||
}
|
||||
|
||||
Output on a modern terminal:
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/
|
||||
576385/88485597-d312f600-cf2b-11ea-9cbe-61f535a86e28.png
|
||||
|
||||
Benchmarks
|
||||
----------
|
||||
|
||||
Speed tests
|
||||
~~~~~~~~~~~
|
||||
|
||||
================= ============= ===========
|
||||
Library Method Run Time, s
|
||||
================= ============= ===========
|
||||
libc printf 1.04
|
||||
libc++ std::ostream 3.05
|
||||
{fmt} 6.1.1 fmt::print 0.75
|
||||
Boost Format 1.67 boost::format 7.24
|
||||
Folly Format folly::format 2.23
|
||||
================= ============= ===========
|
||||
|
||||
{fmt} is the fastest of the benchmarked methods, ~35% faster than ``printf``.
|
||||
|
||||
The above results were generated by building ``tinyformat_test.cpp`` on macOS
|
||||
10.14.6 with ``clang++ -O3 -DNDEBUG -DSPEED_TEST -DHAVE_FORMAT``, and taking the
|
||||
best of three runs. In the test, the format string ``"%0.10f:%04d:%+g:%s:%p:%c:%%\n"``
|
||||
or equivalent is filled 2,000,000 times with output sent to ``/dev/null``; for
|
||||
further details refer to the `source
|
||||
<https://github.com/fmtlib/format-benchmark/blob/master/tinyformat_test.cpp>`_.
|
||||
|
||||
{fmt} is up to 20-30x faster than ``std::ostringstream`` and ``sprintf`` on
|
||||
floating-point formatting (`dtoa-benchmark <https://github.com/fmtlib/dtoa-benchmark>`_)
|
||||
and faster than `double-conversion <https://github.com/google/double-conversion>`_ and
|
||||
`ryu <https://github.com/ulfjack/ryu>`_:
|
||||
|
||||
.. image:: https://user-images.githubusercontent.com/576385/
|
||||
95684665-11719600-0ba8-11eb-8e5b-972ff4e49428.png
|
||||
:target: https://fmt.dev/unknown_mac64_clang12.0.html
|
||||
|
||||
Compile time and code bloat
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The script `bloat-test.py
|
||||
<https://github.com/fmtlib/format-benchmark/blob/master/bloat-test.py>`_
|
||||
from `format-benchmark <https://github.com/fmtlib/format-benchmark>`_
|
||||
tests compile time and code bloat for nontrivial projects.
|
||||
It generates 100 translation units and uses ``printf()`` or its alternative
|
||||
five times in each to simulate a medium sized project. The resulting
|
||||
executable size and compile time (Apple LLVM version 8.1.0 (clang-802.0.42),
|
||||
macOS Sierra, best of three) is shown in the following tables.
|
||||
|
||||
**Optimized build (-O3)**
|
||||
|
||||
============= =============== ==================== ==================
|
||||
Method Compile Time, s Executable size, KiB Stripped size, KiB
|
||||
============= =============== ==================== ==================
|
||||
printf 2.6 29 26
|
||||
printf+string 16.4 29 26
|
||||
iostreams 31.1 59 55
|
||||
{fmt} 19.0 37 34
|
||||
Boost Format 91.9 226 203
|
||||
Folly Format 115.7 101 88
|
||||
============= =============== ==================== ==================
|
||||
|
||||
As you can see, {fmt} has 60% less overhead in terms of resulting binary code
|
||||
size compared to iostreams and comes pretty close to ``printf``. Boost Format
|
||||
and Folly Format have the largest overheads.
|
||||
|
||||
``printf+string`` is the same as ``printf`` but with extra ``<string>``
|
||||
include to measure the overhead of the latter.
|
||||
|
||||
**Non-optimized build**
|
||||
|
||||
============= =============== ==================== ==================
|
||||
Method Compile Time, s Executable size, KiB Stripped size, KiB
|
||||
============= =============== ==================== ==================
|
||||
printf 2.2 33 30
|
||||
printf+string 16.0 33 30
|
||||
iostreams 28.3 56 52
|
||||
{fmt} 18.2 59 50
|
||||
Boost Format 54.1 365 303
|
||||
Folly Format 79.9 445 430
|
||||
============= =============== ==================== ==================
|
||||
|
||||
``libc``, ``lib(std)c++`` and ``libfmt`` are all linked as shared libraries to
|
||||
compare formatting function overhead only. Boost Format is a
|
||||
header-only library so it doesn't provide any linkage options.
|
||||
|
||||
Running the tests
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
Please refer to `Building the library`__ for the instructions on how to build
|
||||
the library and run the unit tests.
|
||||
|
||||
__ https://fmt.dev/latest/usage.html#building-the-library
|
||||
|
||||
Benchmarks reside in a separate repository,
|
||||
`format-benchmarks <https://github.com/fmtlib/format-benchmark>`_,
|
||||
so to run the benchmarks you first need to clone this repository and
|
||||
generate Makefiles with CMake::
|
||||
|
||||
$ git clone --recursive https://github.com/fmtlib/format-benchmark.git
|
||||
$ cd format-benchmark
|
||||
$ cmake .
|
||||
|
||||
Then you can run the speed test::
|
||||
|
||||
$ make speed-test
|
||||
|
||||
or the bloat test::
|
||||
|
||||
$ make bloat-test
|
||||
|
||||
Migrating code
|
||||
--------------
|
||||
|
||||
`clang-tidy-fmt <https://github.com/mikecrowe/clang-tidy-fmt>`_ provides clang
|
||||
tidy checks for converting occurrences of ``printf`` and ``fprintf`` to
|
||||
``fmt::print``.
|
||||
|
||||
Projects using this library
|
||||
---------------------------
|
||||
|
||||
* `0 A.D. <https://play0ad.com/>`_: a free, open-source, cross-platform
|
||||
real-time strategy game
|
||||
|
||||
* `2GIS <https://2gis.ru/>`_: free business listings with a city map
|
||||
|
||||
* `AMPL/MP <https://github.com/ampl/mp>`_:
|
||||
an open-source library for mathematical programming
|
||||
|
||||
* `Aseprite <https://github.com/aseprite/aseprite>`_:
|
||||
animated sprite editor & pixel art tool
|
||||
|
||||
* `AvioBook <https://www.aviobook.aero/en>`_: a comprehensive aircraft
|
||||
operations suite
|
||||
|
||||
* `Blizzard Battle.net <https://battle.net/>`_: an online gaming platform
|
||||
|
||||
* `Celestia <https://celestia.space/>`_: real-time 3D visualization of space
|
||||
|
||||
* `Ceph <https://ceph.com/>`_: a scalable distributed storage system
|
||||
|
||||
* `ccache <https://ccache.dev/>`_: a compiler cache
|
||||
|
||||
* `ClickHouse <https://github.com/ClickHouse/ClickHouse>`_: analytical database
|
||||
management system
|
||||
|
||||
* `CUAUV <https://cuauv.org/>`_: Cornell University's autonomous underwater
|
||||
vehicle
|
||||
|
||||
* `Drake <https://drake.mit.edu/>`_: a planning, control, and analysis toolbox
|
||||
for nonlinear dynamical systems (MIT)
|
||||
|
||||
* `Envoy <https://lyft.github.io/envoy/>`_: C++ L7 proxy and communication bus
|
||||
(Lyft)
|
||||
|
||||
* `FiveM <https://fivem.net/>`_: a modification framework for GTA V
|
||||
|
||||
* `fmtlog <https://github.com/MengRao/fmtlog>`_: a performant fmtlib-style
|
||||
logging library with latency in nanoseconds
|
||||
|
||||
* `Folly <https://github.com/facebook/folly>`_: Facebook open-source library
|
||||
|
||||
* `HarpyWar/pvpgn <https://github.com/pvpgn/pvpgn-server>`_:
|
||||
Player vs Player Gaming Network with tweaks
|
||||
|
||||
* `KBEngine <https://github.com/kbengine/kbengine>`_: an open-source MMOG server
|
||||
engine
|
||||
|
||||
* `Keypirinha <https://keypirinha.com/>`_: a semantic launcher for Windows
|
||||
|
||||
* `Kodi <https://kodi.tv/>`_ (formerly xbmc): home theater software
|
||||
|
||||
* `Knuth <https://kth.cash/>`_: high-performance Bitcoin full-node
|
||||
|
||||
* `Microsoft Verona <https://github.com/microsoft/verona>`_:
|
||||
research programming language for concurrent ownership
|
||||
|
||||
* `MongoDB <https://mongodb.com/>`_: distributed document database
|
||||
|
||||
* `MongoDB Smasher <https://github.com/duckie/mongo_smasher>`_: a small tool to
|
||||
generate randomized datasets
|
||||
|
||||
* `OpenSpace <https://openspaceproject.com/>`_: an open-source
|
||||
astrovisualization framework
|
||||
|
||||
* `PenUltima Online (POL) <https://www.polserver.com/>`_:
|
||||
an MMO server, compatible with most Ultima Online clients
|
||||
|
||||
* `PyTorch <https://github.com/pytorch/pytorch>`_: an open-source machine
|
||||
learning library
|
||||
|
||||
* `quasardb <https://www.quasardb.net/>`_: a distributed, high-performance,
|
||||
associative database
|
||||
|
||||
* `Quill <https://github.com/odygrd/quill>`_: asynchronous low-latency logging library
|
||||
|
||||
* `QKW <https://github.com/ravijanjam/qkw>`_: generalizing aliasing to simplify
|
||||
navigation, and executing complex multi-line terminal command sequences
|
||||
|
||||
* `redis-cerberus <https://github.com/HunanTV/redis-cerberus>`_: a Redis cluster
|
||||
proxy
|
||||
|
||||
* `redpanda <https://vectorized.io/redpanda>`_: a 10x faster Kafka® replacement
|
||||
for mission critical systems written in C++
|
||||
|
||||
* `rpclib <http://rpclib.net/>`_: a modern C++ msgpack-RPC server and client
|
||||
library
|
||||
|
||||
* `Salesforce Analytics Cloud
|
||||
<https://www.salesforce.com/analytics-cloud/overview/>`_:
|
||||
business intelligence software
|
||||
|
||||
* `Scylla <https://www.scylladb.com/>`_: a Cassandra-compatible NoSQL data store
|
||||
that can handle 1 million transactions per second on a single server
|
||||
|
||||
* `Seastar <http://www.seastar-project.org/>`_: an advanced, open-source C++
|
||||
framework for high-performance server applications on modern hardware
|
||||
|
||||
* `spdlog <https://github.com/gabime/spdlog>`_: super fast C++ logging library
|
||||
|
||||
* `Stellar <https://www.stellar.org/>`_: financial platform
|
||||
|
||||
* `Touch Surgery <https://www.touchsurgery.com/>`_: surgery simulator
|
||||
|
||||
* `TrinityCore <https://github.com/TrinityCore/TrinityCore>`_: open-source
|
||||
MMORPG framework
|
||||
|
||||
* `Windows Terminal <https://github.com/microsoft/terminal>`_: the new Windows
|
||||
terminal
|
||||
|
||||
`More... <https://github.com/search?q=fmtlib&type=Code>`_
|
||||
|
||||
If you are aware of other projects using this library, please let me know
|
||||
by `email <mailto:victor.zverovich@gmail.com>`_ or by submitting an
|
||||
`issue <https://github.com/fmtlib/fmt/issues>`_.
|
||||
|
||||
Motivation
|
||||
----------
|
||||
|
||||
So why yet another formatting library?
|
||||
|
||||
There are plenty of methods for doing this task, from standard ones like
|
||||
the printf family of function and iostreams to Boost Format and FastFormat
|
||||
libraries. The reason for creating a new library is that every existing
|
||||
solution that I found either had serious issues or didn't provide
|
||||
all the features I needed.
|
||||
|
||||
printf
|
||||
~~~~~~
|
||||
|
||||
The good thing about ``printf`` is that it is pretty fast and readily available
|
||||
being a part of the C standard library. The main drawback is that it
|
||||
doesn't support user-defined types. ``printf`` also has safety issues although
|
||||
they are somewhat mitigated with `__attribute__ ((format (printf, ...))
|
||||
<https://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html>`_ in GCC.
|
||||
There is a POSIX extension that adds positional arguments required for
|
||||
`i18n <https://en.wikipedia.org/wiki/Internationalization_and_localization>`_
|
||||
to ``printf`` but it is not a part of C99 and may not be available on some
|
||||
platforms.
|
||||
|
||||
iostreams
|
||||
~~~~~~~~~
|
||||
|
||||
The main issue with iostreams is best illustrated with an example:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
std::cout << std::setprecision(2) << std::fixed << 1.23456 << "\n";
|
||||
|
||||
which is a lot of typing compared to printf:
|
||||
|
||||
.. code:: c++
|
||||
|
||||
printf("%.2f\n", 1.23456);
|
||||
|
||||
Matthew Wilson, the author of FastFormat, called this "chevron hell". iostreams
|
||||
don't support positional arguments by design.
|
||||
|
||||
The good part is that iostreams support user-defined types and are safe although
|
||||
error handling is awkward.
|
||||
|
||||
Boost Format
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This is a very powerful library which supports both ``printf``-like format
|
||||
strings and positional arguments. Its main drawback is performance. According to
|
||||
various, benchmarks it is much slower than other methods considered here. Boost
|
||||
Format also has excessive build times and severe code bloat issues (see
|
||||
`Benchmarks`_).
|
||||
|
||||
FastFormat
|
||||
~~~~~~~~~~
|
||||
|
||||
This is an interesting library which is fast, safe and has positional arguments.
|
||||
However, it has significant limitations, citing its author:
|
||||
|
||||
Three features that have no hope of being accommodated within the
|
||||
current design are:
|
||||
|
||||
* Leading zeros (or any other non-space padding)
|
||||
* Octal/hexadecimal encoding
|
||||
* Runtime width/alignment specification
|
||||
|
||||
It is also quite big and has a heavy dependency, STLSoft, which might be too
|
||||
restrictive for using it in some projects.
|
||||
|
||||
Boost Spirit.Karma
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This is not really a formatting library but I decided to include it here for
|
||||
completeness. As iostreams, it suffers from the problem of mixing verbatim text
|
||||
with arguments. The library is pretty fast, but slower on integer formatting
|
||||
than ``fmt::format_to`` with format string compilation on Karma's own benchmark,
|
||||
see `Converting a hundred million integers to strings per second
|
||||
<http://www.zverovich.net/2020/06/13/fast-int-to-string-revisited.html>`_.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
{fmt} is distributed under the MIT `license
|
||||
<https://github.com/fmtlib/fmt/blob/master/LICENSE.rst>`_.
|
||||
|
||||
Documentation License
|
||||
---------------------
|
||||
|
||||
The `Format String Syntax <https://fmt.dev/latest/syntax.html>`_
|
||||
section in the documentation is based on the one from Python `string module
|
||||
documentation <https://docs.python.org/3/library/string.html#module-string>`_.
|
||||
For this reason the documentation is distributed under the Python Software
|
||||
Foundation license available in `doc/python-license.txt
|
||||
<https://raw.github.com/fmtlib/fmt/master/doc/python-license.txt>`_.
|
||||
It only applies if you distribute the documentation of {fmt}.
|
||||
|
||||
Maintainers
|
||||
-----------
|
||||
|
||||
The {fmt} library is maintained by Victor Zverovich (`vitaut
|
||||
<https://github.com/vitaut>`_) and Jonathan Müller (`foonathan
|
||||
<https://github.com/foonathan>`_) with contributions from many other people.
|
||||
See `Contributors <https://github.com/fmtlib/fmt/graphs/contributors>`_ and
|
||||
`Releases <https://github.com/fmtlib/fmt/releases>`_ for some of the names.
|
||||
Let us know if your contribution is not listed or mentioned incorrectly and
|
||||
we'll make it right.
|
||||
+85
-97
@@ -1,4 +1,4 @@
|
||||
// Formatting library for C++ - dynamic format arguments
|
||||
// Formatting library for C++ - dynamic argument lists
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
@@ -8,34 +8,39 @@
|
||||
#ifndef FMT_ARGS_H_
|
||||
#define FMT_ARGS_H_
|
||||
|
||||
#include <functional> // std::reference_wrapper
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <vector>
|
||||
#ifndef FMT_MODULE
|
||||
# include <functional> // std::reference_wrapper
|
||||
# include <memory> // std::unique_ptr
|
||||
# include <vector>
|
||||
#endif
|
||||
|
||||
#include "core.h"
|
||||
#include "format.h" // std_string_view
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T> struct is_reference_wrapper : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
|
||||
|
||||
template <typename T> const T& unwrap(const T& v) { return v; }
|
||||
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
|
||||
template <typename T> auto unwrap(const T& v) -> const T& { return v; }
|
||||
template <typename T>
|
||||
auto unwrap(const std::reference_wrapper<T>& v) -> const T& {
|
||||
return static_cast<const T&>(v);
|
||||
}
|
||||
|
||||
class dynamic_arg_list {
|
||||
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
|
||||
// templates it doesn't complain about inability to deduce single translation
|
||||
// unit for placing vtable. So storage_node_base is made a fake template.
|
||||
template <typename = void> struct node {
|
||||
virtual ~node() = default;
|
||||
std::unique_ptr<node<>> next;
|
||||
};
|
||||
// node is defined outside dynamic_arg_list to workaround a C2504 bug in MSVC
|
||||
// 2022 (v17.10.0).
|
||||
//
|
||||
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
|
||||
// templates it doesn't complain about inability to deduce single translation
|
||||
// unit for placing vtable. So node is made a fake template.
|
||||
template <typename = void> struct node {
|
||||
virtual ~node() = default;
|
||||
std::unique_ptr<node<>> next;
|
||||
};
|
||||
|
||||
class dynamic_arg_list {
|
||||
template <typename T> struct typed_node : node<> {
|
||||
T value;
|
||||
|
||||
@@ -50,7 +55,7 @@ class dynamic_arg_list {
|
||||
std::unique_ptr<node<>> head_;
|
||||
|
||||
public:
|
||||
template <typename T, typename Arg> const T& push(const Arg& arg) {
|
||||
template <typename T, typename Arg> auto push(const Arg& arg) -> const T& {
|
||||
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
|
||||
auto& value = new_node->value;
|
||||
new_node->next = std::move(head_);
|
||||
@@ -61,28 +66,18 @@ class dynamic_arg_list {
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
\rst
|
||||
A dynamic version of `fmt::format_arg_store`.
|
||||
It's equipped with a storage to potentially temporary objects which lifetimes
|
||||
could be shorter than the format arguments object.
|
||||
|
||||
It can be implicitly converted into `~fmt::basic_format_args` for passing
|
||||
into type-erased formatting functions such as `~fmt::vformat`.
|
||||
\endrst
|
||||
* A dynamic list of formatting arguments with storage.
|
||||
*
|
||||
* It can be implicitly converted into `fmt::basic_format_args` for passing
|
||||
* into type-erased formatting functions such as `fmt::vformat`.
|
||||
*/
|
||||
template <typename Context>
|
||||
class dynamic_format_arg_store
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
// Workaround a GCC template argument substitution bug.
|
||||
: public basic_format_args<Context>
|
||||
#endif
|
||||
{
|
||||
FMT_EXPORT template <typename Context> class dynamic_format_arg_store {
|
||||
private:
|
||||
using char_type = typename Context::char_type;
|
||||
|
||||
template <typename T> struct need_copy {
|
||||
static constexpr detail::type mapped_type =
|
||||
detail::mapped_type_constant<T, Context>::value;
|
||||
detail::mapped_type_constant<T, char_type>::value;
|
||||
|
||||
enum {
|
||||
value = !(detail::is_reference_wrapper<T>::value ||
|
||||
@@ -95,10 +90,10 @@ class dynamic_format_arg_store
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using stored_type = conditional_t<detail::is_string<T>::value &&
|
||||
!has_formatter<T, Context>::value &&
|
||||
!detail::is_reference_wrapper<T>::value,
|
||||
std::basic_string<char_type>, T>;
|
||||
using stored_t = conditional_t<
|
||||
std::is_convertible<T, std::basic_string<char_type>>::value &&
|
||||
!detail::is_reference_wrapper<T>::value,
|
||||
std::basic_string<char_type>, T>;
|
||||
|
||||
// Storage of basic_format_arg must be contiguous.
|
||||
std::vector<basic_format_arg<Context>> data_;
|
||||
@@ -110,78 +105,72 @@ class dynamic_format_arg_store
|
||||
|
||||
friend class basic_format_args<Context>;
|
||||
|
||||
unsigned long long get_types() const {
|
||||
return detail::is_unpacked_bit | data_.size() |
|
||||
(named_info_.empty()
|
||||
? 0ULL
|
||||
: static_cast<unsigned long long>(detail::has_named_args_bit));
|
||||
}
|
||||
|
||||
const basic_format_arg<Context>* data() const {
|
||||
auto data() const -> const basic_format_arg<Context>* {
|
||||
return named_info_.empty() ? data_.data() : data_.data() + 1;
|
||||
}
|
||||
|
||||
template <typename T> void emplace_arg(const T& arg) {
|
||||
data_.emplace_back(detail::make_arg<Context>(arg));
|
||||
data_.emplace_back(arg);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
|
||||
if (named_info_.empty()) {
|
||||
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
|
||||
data_.insert(data_.begin(), {zero_ptr, 0});
|
||||
}
|
||||
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
|
||||
if (named_info_.empty())
|
||||
data_.insert(data_.begin(), basic_format_arg<Context>(nullptr, 0));
|
||||
data_.emplace_back(detail::unwrap(arg.value));
|
||||
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
|
||||
data->pop_back();
|
||||
};
|
||||
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
|
||||
guard{&data_, pop_one};
|
||||
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
|
||||
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
|
||||
data_[0] = {named_info_.data(), named_info_.size()};
|
||||
guard.release();
|
||||
}
|
||||
|
||||
public:
|
||||
constexpr dynamic_format_arg_store() = default;
|
||||
|
||||
operator basic_format_args<Context>() const {
|
||||
return basic_format_args<Context>(data(), static_cast<int>(data_.size()),
|
||||
!named_info_.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Adds an argument into the dynamic store for later passing to a formatting
|
||||
function.
|
||||
|
||||
Note that custom types and string types (but not string views) are copied
|
||||
into the store dynamically allocating memory if necessary.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(42);
|
||||
store.push_back("abc");
|
||||
store.push_back(1.5f);
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
\endrst
|
||||
*/
|
||||
* Adds an argument into the dynamic store for later passing to a formatting
|
||||
* function.
|
||||
*
|
||||
* Note that custom types and string types (but not string views) are copied
|
||||
* into the store dynamically allocating memory if necessary.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
* store.push_back(42);
|
||||
* store.push_back("abc");
|
||||
* store.push_back(1.5f);
|
||||
* std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
*/
|
||||
template <typename T> void push_back(const T& arg) {
|
||||
if (detail::const_check(need_copy<T>::value))
|
||||
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
|
||||
emplace_arg(dynamic_args_.push<stored_t<T>>(arg));
|
||||
else
|
||||
emplace_arg(detail::unwrap(arg));
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Adds a reference to the argument into the dynamic store for later passing to
|
||||
a formatting function.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
char band[] = "Rolling Stones";
|
||||
store.push_back(std::cref(band));
|
||||
band[9] = 'c'; // Changing str affects the output.
|
||||
std::string result = fmt::vformat("{}", store);
|
||||
// result == "Rolling Scones"
|
||||
\endrst
|
||||
*/
|
||||
* Adds a reference to the argument into the dynamic store for later passing
|
||||
* to a formatting function.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
* char band[] = "Rolling Stones";
|
||||
* store.push_back(std::cref(band));
|
||||
* band[9] = 'c'; // Changing str affects the output.
|
||||
* std::string result = fmt::vformat("{}", store);
|
||||
* // result == "Rolling Scones"
|
||||
*/
|
||||
template <typename T> void push_back(std::reference_wrapper<T> arg) {
|
||||
static_assert(
|
||||
need_copy<T>::value,
|
||||
@@ -190,41 +179,40 @@ class dynamic_format_arg_store
|
||||
}
|
||||
|
||||
/**
|
||||
Adds named argument into the dynamic store for later passing to a formatting
|
||||
function. ``std::reference_wrapper`` is supported to avoid copying of the
|
||||
argument. The name is always copied into the store.
|
||||
*/
|
||||
* Adds named argument into the dynamic store for later passing to a
|
||||
* formatting function. `std::reference_wrapper` is supported to avoid
|
||||
* copying of the argument. The name is always copied into the store.
|
||||
*/
|
||||
template <typename T>
|
||||
void push_back(const detail::named_arg<char_type, T>& arg) {
|
||||
const char_type* arg_name =
|
||||
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
|
||||
if (detail::const_check(need_copy<T>::value)) {
|
||||
emplace_arg(
|
||||
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
|
||||
fmt::arg(arg_name, dynamic_args_.push<stored_t<T>>(arg.value)));
|
||||
} else {
|
||||
emplace_arg(fmt::arg(arg_name, arg.value));
|
||||
}
|
||||
}
|
||||
|
||||
/** Erase all elements from the store */
|
||||
/// Erase all elements from the store.
|
||||
void clear() {
|
||||
data_.clear();
|
||||
named_info_.clear();
|
||||
dynamic_args_ = detail::dynamic_arg_list();
|
||||
dynamic_args_ = {};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Reserves space to store at least *new_cap* arguments including
|
||||
*new_cap_named* named arguments.
|
||||
\endrst
|
||||
*/
|
||||
/// Reserves space to store at least `new_cap` arguments including
|
||||
/// `new_cap_named` named arguments.
|
||||
void reserve(size_t new_cap, size_t new_cap_named) {
|
||||
FMT_ASSERT(new_cap >= new_cap_named,
|
||||
"Set of arguments includes set of named arguments");
|
||||
"set of arguments includes set of named arguments");
|
||||
data_.reserve(new_cap);
|
||||
named_info_.reserve(new_cap_named);
|
||||
}
|
||||
|
||||
/// Returns the number of elements in the store.
|
||||
auto size() const noexcept -> size_t { return data_.size(); }
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
+3010
File diff suppressed because it is too large
Load Diff
+1634
-647
File diff suppressed because it is too large
Load Diff
+311
-301
@@ -10,15 +10,8 @@
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// __declspec(deprecated) is broken in some MSVC versions.
|
||||
#if FMT_MSC_VER
|
||||
# define FMT_DEPRECATED_NONMSVC
|
||||
#else
|
||||
# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
enum class color : uint32_t {
|
||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||
@@ -185,19 +178,23 @@ enum class terminal_color : uint8_t {
|
||||
|
||||
enum class emphasis : uint8_t {
|
||||
bold = 1,
|
||||
italic = 1 << 1,
|
||||
underline = 1 << 2,
|
||||
strikethrough = 1 << 3
|
||||
faint = 1 << 1,
|
||||
italic = 1 << 2,
|
||||
underline = 1 << 3,
|
||||
blink = 1 << 4,
|
||||
reverse = 1 << 5,
|
||||
conceal = 1 << 6,
|
||||
strikethrough = 1 << 7,
|
||||
};
|
||||
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
// Using the name "rgb" makes some editors show the color in a tooltip.
|
||||
struct rgb {
|
||||
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
|
||||
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||
FMT_CONSTEXPR rgb(uint32_t hex)
|
||||
constexpr rgb() : r(0), g(0), b(0) {}
|
||||
constexpr rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||
constexpr rgb(uint32_t hex)
|
||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
|
||||
FMT_CONSTEXPR rgb(color hex)
|
||||
constexpr rgb(color hex)
|
||||
: r((uint32_t(hex) >> 16) & 0xFF),
|
||||
g((uint32_t(hex) >> 8) & 0xFF),
|
||||
b(uint32_t(hex) & 0xFF) {}
|
||||
@@ -206,422 +203,435 @@ struct rgb {
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
// A bit-packed variant of an RGB color, a terminal color, or unset color.
|
||||
// see text_style for the bit-packing scheme.
|
||||
struct color_type {
|
||||
FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {}
|
||||
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true),
|
||||
value{} {
|
||||
value.rgb_color = static_cast<uint32_t>(rgb_color);
|
||||
constexpr color_type() noexcept = default;
|
||||
constexpr color_type(color rgb_color) noexcept
|
||||
: value_(static_cast<uint32_t>(rgb_color) | (1 << 24)) {}
|
||||
constexpr color_type(rgb rgb_color) noexcept
|
||||
: color_type(static_cast<color>(
|
||||
(static_cast<uint32_t>(rgb_color.r) << 16) |
|
||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b)) {}
|
||||
constexpr color_type(terminal_color term_color) noexcept
|
||||
: value_(static_cast<uint32_t>(term_color) | (3 << 24)) {}
|
||||
|
||||
constexpr auto is_terminal_color() const noexcept -> bool {
|
||||
return (value_ & (1 << 25)) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} {
|
||||
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
|
||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
|
||||
|
||||
constexpr auto value() const noexcept -> uint32_t {
|
||||
return value_ & 0xFFFFFF;
|
||||
}
|
||||
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(),
|
||||
value{} {
|
||||
value.term_color = static_cast<uint8_t>(term_color);
|
||||
}
|
||||
bool is_rgb;
|
||||
union color_union {
|
||||
uint8_t term_color;
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
|
||||
constexpr color_type(uint32_t value) noexcept : value_(value) {}
|
||||
|
||||
uint32_t value_ = 0;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
/** A text style consisting of foreground and background colors and emphasis. */
|
||||
/// A text style consisting of foreground and background colors and emphasis.
|
||||
class text_style {
|
||||
// The information is packed as follows:
|
||||
// ┌──┐
|
||||
// │ 0│─┐
|
||||
// │..│ ├── foreground color value
|
||||
// │23│─┘
|
||||
// ├──┤
|
||||
// │24│─┬── discriminator for the above value. 00 if unset, 01 if it's
|
||||
// │25│─┘ an RGB color, or 11 if it's a terminal color (10 is unused)
|
||||
// ├──┤
|
||||
// │26│──── overflow bit, always zero (see below)
|
||||
// ├──┤
|
||||
// │27│─┐
|
||||
// │..│ │
|
||||
// │50│ │
|
||||
// ├──┤ │
|
||||
// │51│ ├── background color (same format as the foreground color)
|
||||
// │52│ │
|
||||
// ├──┤ │
|
||||
// │53│─┘
|
||||
// ├──┤
|
||||
// │54│─┐
|
||||
// │..│ ├── emphases
|
||||
// │61│─┘
|
||||
// ├──┤
|
||||
// │62│─┬── unused
|
||||
// │63│─┘
|
||||
// └──┘
|
||||
// The overflow bits are there to make operator|= efficient.
|
||||
// When ORing, we must throw if, for either the foreground or background,
|
||||
// one style specifies a terminal color and the other specifies any color
|
||||
// (terminal or RGB); in other words, if one discriminator is 11 and the
|
||||
// other is 11 or 01.
|
||||
//
|
||||
// We do that check by adding the styles. Consider what adding does to each
|
||||
// possible pair of discriminators:
|
||||
// 00 + 00 = 000
|
||||
// 01 + 00 = 001
|
||||
// 11 + 00 = 011
|
||||
// 01 + 01 = 010
|
||||
// 11 + 01 = 100 (!!)
|
||||
// 11 + 11 = 110 (!!)
|
||||
// In the last two cases, the ones we want to catch, the third bit——the
|
||||
// overflow bit——is set. Bingo.
|
||||
//
|
||||
// We must take into account the possible carry bit from the bits
|
||||
// before the discriminator. The only potentially problematic case is
|
||||
// 11 + 00 = 011 (a carry bit would make it 100, not good!), but a carry
|
||||
// bit is impossible in that case, because 00 (unset color) means the
|
||||
// 24 bits that precede the discriminator are all zero.
|
||||
//
|
||||
// This test can be applied to both colors simultaneously.
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems(em) {}
|
||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) noexcept
|
||||
: style_(static_cast<uint64_t>(em) << 54) {}
|
||||
|
||||
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
FMT_THROW(format_error("can't OR a terminal color"));
|
||||
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
FMT_THROW(format_error("can't OR a terminal color"));
|
||||
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
FMT_CONSTEXPR auto operator|=(text_style rhs) -> text_style& {
|
||||
if (((style_ + rhs.style_) & ((1ULL << 26) | (1ULL << 53))) != 0)
|
||||
report_error("can't OR a terminal color");
|
||||
style_ |= rhs.style_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style operator|(text_style lhs,
|
||||
const text_style& rhs) {
|
||||
friend FMT_CONSTEXPR auto operator|(text_style lhs, text_style rhs)
|
||||
-> text_style {
|
||||
return lhs |= rhs;
|
||||
}
|
||||
|
||||
FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=(
|
||||
const text_style& rhs) {
|
||||
return and_assign(rhs);
|
||||
FMT_CONSTEXPR auto operator==(text_style rhs) const noexcept -> bool {
|
||||
return style_ == rhs.style_;
|
||||
}
|
||||
|
||||
FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style
|
||||
operator&(text_style lhs, const text_style& rhs) {
|
||||
return lhs.and_assign(rhs);
|
||||
FMT_CONSTEXPR auto operator!=(text_style rhs) const noexcept -> bool {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
|
||||
return set_foreground_color;
|
||||
FMT_CONSTEXPR auto has_foreground() const noexcept -> bool {
|
||||
return (style_ & (1 << 24)) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
|
||||
return set_background_color;
|
||||
FMT_CONSTEXPR auto has_background() const noexcept -> bool {
|
||||
return (style_ & (1ULL << 51)) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
|
||||
return static_cast<uint8_t>(ems) != 0;
|
||||
FMT_CONSTEXPR auto has_emphasis() const noexcept -> bool {
|
||||
return (style_ >> 54) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR auto get_foreground() const noexcept -> detail::color_type {
|
||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||
return foreground_color;
|
||||
return style_ & 0x3FFFFFF;
|
||||
}
|
||||
FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR auto get_background() const noexcept -> detail::color_type {
|
||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||
return background_color;
|
||||
return (style_ >> 27) & 0x3FFFFFF;
|
||||
}
|
||||
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR auto get_emphasis() const noexcept -> emphasis {
|
||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||
return ems;
|
||||
return static_cast<emphasis>(style_ >> 54);
|
||||
}
|
||||
|
||||
private:
|
||||
FMT_CONSTEXPR text_style(bool is_foreground,
|
||||
detail::color_type text_color) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems() {
|
||||
if (is_foreground) {
|
||||
foreground_color = text_color;
|
||||
set_foreground_color = true;
|
||||
} else {
|
||||
background_color = text_color;
|
||||
set_background_color = true;
|
||||
}
|
||||
}
|
||||
FMT_CONSTEXPR text_style(uint64_t style) noexcept : style_(style) {}
|
||||
|
||||
// DEPRECATED!
|
||||
FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
FMT_THROW(format_error("can't AND a terminal color"));
|
||||
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
friend FMT_CONSTEXPR auto fg(detail::color_type foreground) noexcept
|
||||
-> text_style;
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
FMT_THROW(format_error("can't AND a terminal color"));
|
||||
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
friend FMT_CONSTEXPR auto bg(detail::color_type background) noexcept
|
||||
-> text_style;
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
detail::color_type foreground_color;
|
||||
detail::color_type background_color;
|
||||
bool set_foreground_color;
|
||||
bool set_background_color;
|
||||
emphasis ems;
|
||||
uint64_t style_ = 0;
|
||||
};
|
||||
|
||||
/** Creates a text style from the foreground (text) color. */
|
||||
FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return text_style(true, foreground);
|
||||
/// Creates a text style from the foreground (text) color.
|
||||
FMT_CONSTEXPR inline auto fg(detail::color_type foreground) noexcept
|
||||
-> text_style {
|
||||
return foreground.value_;
|
||||
}
|
||||
|
||||
/** Creates a text style from the background color. */
|
||||
FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT {
|
||||
return text_style(false, background);
|
||||
/// Creates a text style from the background color.
|
||||
FMT_CONSTEXPR inline auto bg(detail::color_type background) noexcept
|
||||
-> text_style {
|
||||
return static_cast<uint64_t>(background.value_) << 27;
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR inline text_style operator|(emphasis lhs,
|
||||
emphasis rhs) FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR inline auto operator|(emphasis lhs, emphasis rhs) noexcept
|
||||
-> text_style {
|
||||
return text_style(lhs) | rhs;
|
||||
}
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||
const char* esc) FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR ansi_color_escape(color_type text_color,
|
||||
const char* esc) noexcept {
|
||||
// If we have a terminal color, we need to output another escape code
|
||||
// sequence.
|
||||
if (!text_color.is_rgb) {
|
||||
if (text_color.is_terminal_color()) {
|
||||
bool is_background = esc == string_view("\x1b[48;2;");
|
||||
uint32_t value = text_color.value.term_color;
|
||||
uint32_t value = text_color.value();
|
||||
// Background ASCII codes are the same as the foreground ones but with
|
||||
// 10 more.
|
||||
if (is_background) value += 10u;
|
||||
|
||||
size_t index = 0;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
buffer[size++] = static_cast<Char>('\x1b');
|
||||
buffer[size++] = static_cast<Char>('[');
|
||||
|
||||
if (value >= 100u) {
|
||||
buffer[index++] = static_cast<Char>('1');
|
||||
buffer[size++] = static_cast<Char>('1');
|
||||
value %= 100u;
|
||||
}
|
||||
buffer[index++] = static_cast<Char>('0' + value / 10u);
|
||||
buffer[index++] = static_cast<Char>('0' + value % 10u);
|
||||
buffer[size++] = static_cast<Char>('0' + value / 10u);
|
||||
buffer[size++] = static_cast<Char>('0' + value % 10u);
|
||||
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
buffer[index++] = static_cast<Char>('\0');
|
||||
buffer[size++] = static_cast<Char>('m');
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
buffer[i] = static_cast<Char>(esc[i]);
|
||||
}
|
||||
rgb color(text_color.value.rgb_color);
|
||||
rgb color(text_color.value());
|
||||
to_esc(color.r, buffer + 7, ';');
|
||||
to_esc(color.g, buffer + 11, ';');
|
||||
to_esc(color.b, buffer + 15, 'm');
|
||||
buffer[19] = static_cast<Char>(0);
|
||||
size = 19;
|
||||
}
|
||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
|
||||
uint8_t em_codes[4] = {};
|
||||
uint8_t em_bits = static_cast<uint8_t>(em);
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
|
||||
em_codes[3] = 9;
|
||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) noexcept {
|
||||
uint8_t em_codes[num_emphases] = {};
|
||||
if (has_emphasis(em, emphasis::bold)) em_codes[0] = 1;
|
||||
if (has_emphasis(em, emphasis::faint)) em_codes[1] = 2;
|
||||
if (has_emphasis(em, emphasis::italic)) em_codes[2] = 3;
|
||||
if (has_emphasis(em, emphasis::underline)) em_codes[3] = 4;
|
||||
if (has_emphasis(em, emphasis::blink)) em_codes[4] = 5;
|
||||
if (has_emphasis(em, emphasis::reverse)) em_codes[5] = 7;
|
||||
if (has_emphasis(em, emphasis::conceal)) em_codes[6] = 8;
|
||||
if (has_emphasis(em, emphasis::strikethrough)) em_codes[7] = 9;
|
||||
|
||||
size_t index = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
buffer[size++] = static_cast<Char>('\x1b');
|
||||
buffer[size++] = static_cast<Char>('[');
|
||||
|
||||
for (size_t i = 0; i < num_emphases; ++i) {
|
||||
if (!em_codes[i]) continue;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
buffer[size++] = static_cast<Char>('0' + em_codes[i]);
|
||||
buffer[size++] = static_cast<Char>(';');
|
||||
}
|
||||
buffer[index++] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
|
||||
|
||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
||||
FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT {
|
||||
return buffer + std::char_traits<Char>::length(buffer);
|
||||
buffer[size - 1] = static_cast<Char>('m');
|
||||
}
|
||||
FMT_CONSTEXPR operator const Char*() const noexcept { return buffer; }
|
||||
|
||||
FMT_CONSTEXPR auto begin() const noexcept -> const Char* { return buffer; }
|
||||
FMT_CONSTEXPR auto end() const noexcept -> const Char* {
|
||||
return buffer + size;
|
||||
}
|
||||
|
||||
private:
|
||||
Char buffer[7u + 3u * 4u + 1u];
|
||||
static constexpr size_t num_emphases = 8;
|
||||
Char buffer[7u + 4u * num_emphases] = {};
|
||||
size_t size = 0;
|
||||
|
||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
||||
char delimiter) FMT_NOEXCEPT {
|
||||
char delimiter) noexcept {
|
||||
out[0] = static_cast<Char>('0' + c / 100);
|
||||
out[1] = static_cast<Char>('0' + c / 10 % 10);
|
||||
out[2] = static_cast<Char>('0' + c % 10);
|
||||
out[3] = static_cast<Char>(delimiter);
|
||||
}
|
||||
static FMT_CONSTEXPR auto has_emphasis(emphasis em, emphasis mask) noexcept
|
||||
-> bool {
|
||||
return static_cast<uint8_t>(em) & static_cast<uint8_t>(mask);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
||||
detail::color_type foreground) FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR auto make_foreground_color(color_type foreground) noexcept
|
||||
-> ansi_color_escape<Char> {
|
||||
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
||||
detail::color_type background) FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR auto make_background_color(color_type background) noexcept
|
||||
-> ansi_color_escape<Char> {
|
||||
return ansi_color_escape<Char>(background, "\x1b[48;2;");
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT {
|
||||
FMT_CONSTEXPR auto make_emphasis(emphasis em) noexcept
|
||||
-> ansi_color_escape<Char> {
|
||||
return ansi_color_escape<Char>(em);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
std::fputs(chars, stream);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
std::fputws(chars, stream);
|
||||
}
|
||||
|
||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs("\x1b[0m", stream);
|
||||
}
|
||||
|
||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(L"\x1b[0m", stream);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||
template <typename Char> inline void reset_color(buffer<Char>& buffer) {
|
||||
auto reset_color = string_view("\x1b[0m");
|
||||
buffer.append(reset_color.begin(), reset_color.end());
|
||||
}
|
||||
|
||||
template <typename T> struct styled_arg : view {
|
||||
const T& value;
|
||||
text_style style;
|
||||
styled_arg(const T& v, text_style s) : value(v), style(s) {}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
bool has_style = false;
|
||||
void vformat_to(buffer<Char>& buf, text_style ts, basic_string_view<Char> fmt,
|
||||
basic_format_args<buffered_context<Char>> args) {
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
||||
auto emphasis = make_emphasis<Char>(ts.get_emphasis());
|
||||
buf.append(emphasis.begin(), emphasis.end());
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
|
||||
auto foreground = make_foreground_color<Char>(ts.get_foreground());
|
||||
buf.append(foreground.begin(), foreground.end());
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
auto background = detail::make_background_color<Char>(ts.get_background());
|
||||
auto background = make_background_color<Char>(ts.get_background());
|
||||
buf.append(background.begin(), background.end());
|
||||
}
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
if (has_style) detail::reset_color<Char>(buf);
|
||||
vformat_to(buf, fmt, args);
|
||||
if (ts != text_style()) reset_color<Char>(buf);
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, to_string_view(format), args);
|
||||
buf.push_back(Char(0));
|
||||
detail::fputs(buf.data(), f);
|
||||
inline void vprint(FILE* f, text_style ts, string_view fmt, format_args args) {
|
||||
auto buf = memory_buffer();
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
print(f, FMT_STRING("{}"), string_view(buf.begin(), buf.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats a string and prints it to the specified file stream using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
\endrst
|
||||
* Formats a string and prints it to the specified file stream using ANSI
|
||||
* escape sequences to specify text formatting.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
* "Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
vprint(f, ts, format_str,
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
template <typename... T>
|
||||
void print(FILE* f, text_style ts, format_string<T...> fmt, T&&... args) {
|
||||
vprint(f, ts, fmt.str, vargs<T...>{{args...}});
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
specify text formatting.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
\endrst
|
||||
* Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
* specify text formatting.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
* "Elapsed time: {0:.2f} seconds", 1.23);
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(const text_style& ts, const S& format_str, const Args&... args) {
|
||||
return print(stdout, ts, format_str, args...);
|
||||
template <typename... T>
|
||||
void print(text_style ts, format_string<T...> fmt, T&&... args) {
|
||||
return print(stdout, ts, fmt, std::forward<T>(args)...);
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const text_style& ts, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, to_string_view(format_str), args);
|
||||
inline auto vformat(text_style ts, string_view fmt, format_args args)
|
||||
-> std::string {
|
||||
auto buf = memory_buffer();
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
return fmt::to_string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
|
||||
**Example**::
|
||||
|
||||
#include <fmt/color.h>
|
||||
std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"The answer is {}", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
return fmt::vformat(ts, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string with the given text_style and writes the output to ``out``.
|
||||
* Formats arguments and returns the result as a string using ANSI escape
|
||||
* sequences to specify text formatting.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* ```
|
||||
* #include <fmt/color.h>
|
||||
* std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
* "The answer is {}", 42);
|
||||
* ```
|
||||
*/
|
||||
template <typename OutputIt, typename Char,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
|
||||
OutputIt vformat_to(
|
||||
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, ts, format_str, args);
|
||||
return detail::get_iterator(buf);
|
||||
template <typename... T>
|
||||
inline auto format(text_style ts, format_string<T...> fmt, T&&... args)
|
||||
-> std::string {
|
||||
return fmt::vformat(ts, fmt.str, vargs<T...>{{args...}});
|
||||
}
|
||||
|
||||
/// Formats a string with the given text_style and writes the output to `out`.
|
||||
template <typename OutputIt,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
|
||||
auto vformat_to(OutputIt out, text_style ts, string_view fmt, format_args args)
|
||||
-> OutputIt {
|
||||
auto&& buf = detail::get_buffer<char>(out);
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments with the given text_style, writes the result to the output
|
||||
iterator ``out`` and returns the iterator past the end of the output range.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::vector<char> out;
|
||||
fmt::format_to(std::back_inserter(out),
|
||||
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
|
||||
detail::is_string<S>::value>
|
||||
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
|
||||
Args&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
return vformat_to(out, ts, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
* Formats arguments with the given text style, writes the result to the output
|
||||
* iterator `out` and returns the iterator past the end of the output range.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* std::vector<char> out;
|
||||
* fmt::format_to(std::back_inserter(out),
|
||||
* fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
|
||||
*/
|
||||
template <typename OutputIt, typename... T,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, char>::value)>
|
||||
inline auto format_to(OutputIt out, text_style ts, format_string<T...> fmt,
|
||||
T&&... args) -> OutputIt {
|
||||
return vformat_to(out, ts, fmt.str, vargs<T...>{{args...}});
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
template <typename T, typename Char>
|
||||
struct formatter<detail::styled_arg<T>, Char> : formatter<T, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const detail::styled_arg<T>& arg, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
const auto& ts = arg.style;
|
||||
auto out = ctx.out();
|
||||
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
||||
out = detail::copy<Char>(emphasis.begin(), emphasis.end(), out);
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
auto foreground =
|
||||
detail::make_foreground_color<Char>(ts.get_foreground());
|
||||
out = detail::copy<Char>(foreground.begin(), foreground.end(), out);
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
auto background =
|
||||
detail::make_background_color<Char>(ts.get_background());
|
||||
out = detail::copy<Char>(background.begin(), background.end(), out);
|
||||
}
|
||||
out = formatter<T, Char>::format(arg.value, ctx);
|
||||
if (has_style) {
|
||||
auto reset_color = string_view("\x1b[0m");
|
||||
out = detail::copy<Char>(reset_color.begin(), reset_color.end(), out);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an argument that will be formatted using ANSI escape sequences,
|
||||
* to be used in a formatting function.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print("Elapsed time: {0:.2f} seconds",
|
||||
* fmt::styled(1.23, fmt::fg(fmt::color::green) |
|
||||
* fmt::bg(fmt::color::blue)));
|
||||
*/
|
||||
template <typename T>
|
||||
FMT_CONSTEXPR auto styled(const T& value, text_style ts)
|
||||
-> detail::styled_arg<remove_cvref_t<T>> {
|
||||
return detail::styled_arg<remove_cvref_t<T>>{value, ts};
|
||||
}
|
||||
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COLOR_H_
|
||||
|
||||
+250
-300
@@ -8,134 +8,14 @@
|
||||
#ifndef FMT_COMPILE_H_
|
||||
#define FMT_COMPILE_H_
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <iterator> // std::back_inserter
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// An output iterator that counts the number of objects written to it and
|
||||
// discards them.
|
||||
class counting_iterator {
|
||||
private:
|
||||
size_t count_;
|
||||
|
||||
public:
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
using _Unchecked_type = counting_iterator; // Mark iterator as checked.
|
||||
|
||||
struct value_type {
|
||||
template <typename T> void operator=(const T&) {}
|
||||
};
|
||||
|
||||
counting_iterator() : count_(0) {}
|
||||
|
||||
size_t count() const { return count_; }
|
||||
|
||||
counting_iterator& operator++() {
|
||||
++count_;
|
||||
return *this;
|
||||
}
|
||||
counting_iterator operator++(int) {
|
||||
auto it = *this;
|
||||
++*this;
|
||||
return it;
|
||||
}
|
||||
|
||||
friend counting_iterator operator+(counting_iterator it, difference_type n) {
|
||||
it.count_ += static_cast<size_t>(n);
|
||||
return it;
|
||||
}
|
||||
|
||||
value_type operator*() const { return {}; }
|
||||
};
|
||||
|
||||
template <typename Char, typename InputIt>
|
||||
inline counting_iterator copy_str(InputIt begin, InputIt end,
|
||||
counting_iterator it) {
|
||||
return it + (end - begin);
|
||||
}
|
||||
|
||||
template <typename OutputIt> class truncating_iterator_base {
|
||||
protected:
|
||||
OutputIt out_;
|
||||
size_t limit_;
|
||||
size_t count_ = 0;
|
||||
|
||||
truncating_iterator_base() : out_(), limit_(0) {}
|
||||
|
||||
truncating_iterator_base(OutputIt out, size_t limit)
|
||||
: out_(out), limit_(limit) {}
|
||||
|
||||
public:
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using value_type = typename std::iterator_traits<OutputIt>::value_type;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
using _Unchecked_type =
|
||||
truncating_iterator_base; // Mark iterator as checked.
|
||||
|
||||
OutputIt base() const { return out_; }
|
||||
size_t count() const { return count_; }
|
||||
};
|
||||
|
||||
// An output iterator that truncates the output and counts the number of objects
|
||||
// written to it.
|
||||
template <typename OutputIt,
|
||||
typename Enable = typename std::is_void<
|
||||
typename std::iterator_traits<OutputIt>::value_type>::type>
|
||||
class truncating_iterator;
|
||||
|
||||
template <typename OutputIt>
|
||||
class truncating_iterator<OutputIt, std::false_type>
|
||||
: public truncating_iterator_base<OutputIt> {
|
||||
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
|
||||
|
||||
public:
|
||||
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
|
||||
|
||||
truncating_iterator() = default;
|
||||
|
||||
truncating_iterator(OutputIt out, size_t limit)
|
||||
: truncating_iterator_base<OutputIt>(out, limit) {}
|
||||
|
||||
truncating_iterator& operator++() {
|
||||
if (this->count_++ < this->limit_) ++this->out_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
truncating_iterator operator++(int) {
|
||||
auto it = *this;
|
||||
++*this;
|
||||
return it;
|
||||
}
|
||||
|
||||
value_type& operator*() const {
|
||||
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename OutputIt>
|
||||
class truncating_iterator<OutputIt, std::true_type>
|
||||
: public truncating_iterator_base<OutputIt> {
|
||||
public:
|
||||
truncating_iterator() = default;
|
||||
|
||||
truncating_iterator(OutputIt out, size_t limit)
|
||||
: truncating_iterator_base<OutputIt>(out, limit) {}
|
||||
|
||||
template <typename T> truncating_iterator& operator=(T val) {
|
||||
if (this->count_++ < this->limit_) *this->out_++ = val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
truncating_iterator& operator++() { return *this; }
|
||||
truncating_iterator& operator++(int) { return *this; }
|
||||
truncating_iterator& operator*() { return *this; }
|
||||
};
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
// A compile-time string which is compiled into fast formatting code.
|
||||
class compiled_string {};
|
||||
@@ -144,64 +24,99 @@ template <typename S>
|
||||
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Converts a string literal *s* into a format string that will be parsed at
|
||||
compile time and converted into efficient formatting code. Requires C++17
|
||||
``constexpr if`` compiler support.
|
||||
|
||||
**Example**::
|
||||
|
||||
// Converts 42 into std::string using the most efficient method and no
|
||||
// runtime format string processing.
|
||||
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||
\endrst
|
||||
* Converts a string literal `s` into a format string that will be parsed at
|
||||
* compile time and converted into efficient formatting code. Requires C++17
|
||||
* `constexpr if` compiler support.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* // Converts 42 into std::string using the most efficient method and no
|
||||
* // runtime format string processing.
|
||||
* std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||
*/
|
||||
#ifdef __cpp_if_constexpr
|
||||
# define FMT_COMPILE(s) \
|
||||
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
# define FMT_COMPILE(s) FMT_STRING_IMPL(s, fmt::compiled_string)
|
||||
#else
|
||||
# define FMT_COMPILE(s) FMT_STRING(s)
|
||||
#endif
|
||||
|
||||
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
template <typename Char, size_t N, fixed_string<Char, N> Str>
|
||||
struct udl_compiled_string : compiled_string {
|
||||
using char_type = Char;
|
||||
constexpr operator basic_string_view<char_type>() const {
|
||||
return {Str.data, N - 1};
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Converts a string literal into a format string that will be parsed at
|
||||
* compile time and converted into efficient formatting code. Requires support
|
||||
* for class types in constant template parameters (a C++20 feature).
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* // Converts 42 into std::string using the most efficient method and no
|
||||
* // runtime format string processing.
|
||||
* using namespace fmt::literals;
|
||||
* std::string s = fmt::format("{}"_cf, 42);
|
||||
*/
|
||||
#if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
inline namespace literals {
|
||||
template <detail::fixed_string Str> constexpr auto operator""_cf() {
|
||||
return FMT_COMPILE(Str.data);
|
||||
}
|
||||
} // namespace literals
|
||||
#endif
|
||||
|
||||
FMT_END_EXPORT
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T, typename... Tail>
|
||||
const T& first(const T& value, const Tail&...) {
|
||||
constexpr auto first(const T& value, const Tail&...) -> const T& {
|
||||
return value;
|
||||
}
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
template <typename... Args> struct type_list {};
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
template <typename... T> struct type_list {};
|
||||
|
||||
// Returns a reference to the argument at index N from [first, rest...].
|
||||
template <int N, typename T, typename... Args>
|
||||
constexpr const auto& get([[maybe_unused]] const T& first,
|
||||
[[maybe_unused]] const Args&... rest) {
|
||||
constexpr auto get([[maybe_unused]] const T& first,
|
||||
[[maybe_unused]] const Args&... rest) -> const auto& {
|
||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||
if constexpr (N == 0)
|
||||
return first;
|
||||
else
|
||||
return get<N - 1>(rest...);
|
||||
return detail::get<N - 1>(rest...);
|
||||
}
|
||||
|
||||
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
template <int N, typename T, typename... Args, typename Char>
|
||||
constexpr auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
|
||||
if constexpr (is_static_named_arg<T>()) {
|
||||
if (name == T::name) return N;
|
||||
}
|
||||
if constexpr (sizeof...(Args) > 0)
|
||||
return get_arg_index_by_name<N + 1, Args...>(name);
|
||||
(void)name; // Workaround an MSVC bug about "unused" parameter.
|
||||
return -1;
|
||||
}
|
||||
# endif
|
||||
|
||||
template <typename... Args, typename Char>
|
||||
FMT_CONSTEXPR auto get_arg_index_by_name(basic_string_view<Char> name) -> int {
|
||||
# if FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
if constexpr (sizeof...(Args) > 0)
|
||||
return get_arg_index_by_name<0, Args...>(name);
|
||||
# endif
|
||||
(void)name;
|
||||
return -1;
|
||||
}
|
||||
|
||||
template <typename Char, typename... Args>
|
||||
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
|
||||
type_list<Args...>) {
|
||||
constexpr auto get_arg_index_by_name(basic_string_view<Char> name,
|
||||
type_list<Args...>) -> int {
|
||||
return get_arg_index_by_name<Args...>(name);
|
||||
}
|
||||
|
||||
template <int N, typename> struct get_type_impl;
|
||||
|
||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
|
||||
using type =
|
||||
remove_cvref_t<decltype(detail::get<N>(std::declval<Args>()...))>;
|
||||
};
|
||||
|
||||
template <int N, typename T>
|
||||
@@ -213,8 +128,8 @@ template <typename Char> struct text {
|
||||
basic_string_view<Char> data;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||
template <typename OutputIt, typename... T>
|
||||
constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
|
||||
return write<Char>(out, data);
|
||||
}
|
||||
};
|
||||
@@ -223,8 +138,8 @@ template <typename Char>
|
||||
struct is_compiled_format<text<Char>> : std::true_type {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||
size_t size) {
|
||||
constexpr auto make_text(basic_string_view<Char> s, size_t pos, size_t size)
|
||||
-> text<Char> {
|
||||
return {{&s[pos], size}};
|
||||
}
|
||||
|
||||
@@ -232,16 +147,17 @@ template <typename Char> struct code_unit {
|
||||
Char value;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||
return write<Char>(out, value);
|
||||
template <typename OutputIt, typename... T>
|
||||
constexpr auto format(OutputIt out, const T&...) const -> OutputIt {
|
||||
*out++ = value;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
// This ensures that the argument type is convertible to `const T&`.
|
||||
template <typename T, int N, typename... Args>
|
||||
constexpr const T& get_arg_checked(const Args&... args) {
|
||||
const auto& arg = get<N>(args...);
|
||||
constexpr auto get_arg_checked(const Args&... args) -> const T& {
|
||||
const auto& arg = detail::get<N>(args...);
|
||||
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
|
||||
return arg.value;
|
||||
} else {
|
||||
@@ -253,12 +169,18 @@ template <typename Char>
|
||||
struct is_compiled_format<code_unit<Char>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument N.
|
||||
template <typename Char, typename T, int N> struct field {
|
||||
template <typename Char, typename V, int N> struct field {
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
return write<Char>(out, get_arg_checked<T, N>(args...));
|
||||
template <typename OutputIt, typename... T>
|
||||
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
|
||||
const V& arg = get_arg_checked<V, N>(args...);
|
||||
if constexpr (std::is_convertible<V, basic_string_view<Char>>::value) {
|
||||
auto s = basic_string_view<Char>(arg);
|
||||
return copy<Char>(s.begin(), s.end(), out);
|
||||
} else {
|
||||
return write<Char>(out, arg);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -271,10 +193,10 @@ template <typename Char> struct runtime_named_field {
|
||||
basic_string_view<Char> name;
|
||||
|
||||
template <typename OutputIt, typename T>
|
||||
constexpr static bool try_format_argument(
|
||||
constexpr static auto try_format_argument(
|
||||
OutputIt& out,
|
||||
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
|
||||
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
|
||||
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) -> bool {
|
||||
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
|
||||
if (arg_name == arg.name) {
|
||||
out = write<Char>(out, arg.value);
|
||||
@@ -284,11 +206,11 @@ template <typename Char> struct runtime_named_field {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
template <typename OutputIt, typename... T>
|
||||
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
|
||||
bool found = (try_format_argument(out, name, args) || ...);
|
||||
if (!found) {
|
||||
throw format_error("argument with specified name is not found");
|
||||
FMT_THROW(format_error("argument with specified name is not found"));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
@@ -298,17 +220,17 @@ template <typename Char>
|
||||
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument N and has format specifiers.
|
||||
template <typename Char, typename T, int N> struct spec_field {
|
||||
template <typename Char, typename V, int N> struct spec_field {
|
||||
using char_type = Char;
|
||||
formatter<T, Char> fmt;
|
||||
formatter<V, Char> fmt;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr FMT_INLINE OutputIt format(OutputIt out,
|
||||
const Args&... args) const {
|
||||
template <typename OutputIt, typename... T>
|
||||
constexpr FMT_INLINE auto format(OutputIt out, const T&... args) const
|
||||
-> OutputIt {
|
||||
const auto& vargs =
|
||||
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
|
||||
basic_format_context<OutputIt, Char> ctx(out, vargs);
|
||||
return fmt.format(get_arg_checked<T, N>(args...), ctx);
|
||||
return fmt.format(get_arg_checked<V, N>(args...), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -320,8 +242,8 @@ template <typename L, typename R> struct concat {
|
||||
R rhs;
|
||||
using char_type = typename L::char_type;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
template <typename OutputIt, typename... T>
|
||||
constexpr auto format(OutputIt out, const T&... args) const -> OutputIt {
|
||||
out = lhs.format(out, args...);
|
||||
return rhs.format(out, args...);
|
||||
}
|
||||
@@ -331,14 +253,14 @@ template <typename L, typename R>
|
||||
struct is_compiled_format<concat<L, R>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R>
|
||||
constexpr concat<L, R> make_concat(L lhs, R rhs) {
|
||||
constexpr auto make_concat(L lhs, R rhs) -> concat<L, R> {
|
||||
return {lhs, rhs};
|
||||
}
|
||||
|
||||
struct unknown_format {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
||||
constexpr auto parse_text(basic_string_view<Char> str, size_t pos) -> size_t {
|
||||
for (size_t size = str.size(); pos != size; ++pos) {
|
||||
if (str[pos] == '{' || str[pos] == '}') break;
|
||||
}
|
||||
@@ -346,13 +268,12 @@ constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
||||
}
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str);
|
||||
constexpr auto compile_format_string(S fmt);
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename T, typename S>
|
||||
constexpr auto parse_tail(T head, S format_str) {
|
||||
if constexpr (POS !=
|
||||
basic_string_view<typename S::char_type>(format_str).size()) {
|
||||
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
|
||||
constexpr auto parse_tail(T head, S fmt) {
|
||||
if constexpr (POS != basic_string_view<typename S::char_type>(fmt).size()) {
|
||||
constexpr auto tail = compile_format_string<Args, POS, ID>(fmt);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
||||
unknown_format>())
|
||||
return tail;
|
||||
@@ -369,48 +290,51 @@ template <typename T, typename Char> struct parse_specs_result {
|
||||
int next_arg_id;
|
||||
};
|
||||
|
||||
constexpr int manual_indexing_id = -1;
|
||||
enum { manual_indexing_id = -1 };
|
||||
|
||||
template <typename T, typename Char>
|
||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||
size_t pos, int next_arg_id) {
|
||||
constexpr auto parse_specs(basic_string_view<Char> str, size_t pos,
|
||||
int next_arg_id) -> parse_specs_result<T, Char> {
|
||||
str.remove_prefix(pos);
|
||||
auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id);
|
||||
auto ctx =
|
||||
compile_parse_context<Char>(str, max_value<int>(), nullptr, next_arg_id);
|
||||
auto f = formatter<T, Char>();
|
||||
auto end = f.parse(ctx);
|
||||
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
|
||||
return {f, pos + fmt::detail::to_unsigned(end - str.data()),
|
||||
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
|
||||
}
|
||||
|
||||
template <typename Char> struct arg_id_handler {
|
||||
arg_id_kind kind;
|
||||
arg_ref<Char> arg_id;
|
||||
|
||||
constexpr int operator()() {
|
||||
constexpr auto on_auto() -> int {
|
||||
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
|
||||
return 0;
|
||||
}
|
||||
constexpr int operator()(int id) {
|
||||
constexpr auto on_index(int id) -> int {
|
||||
kind = arg_id_kind::index;
|
||||
arg_id = arg_ref<Char>(id);
|
||||
return 0;
|
||||
}
|
||||
constexpr int operator()(basic_string_view<Char> id) {
|
||||
constexpr auto on_name(basic_string_view<Char> id) -> int {
|
||||
kind = arg_id_kind::name;
|
||||
arg_id = arg_ref<Char>(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr void on_error(const char* message) { throw format_error(message); }
|
||||
};
|
||||
|
||||
template <typename Char> struct parse_arg_id_result {
|
||||
arg_id_kind kind;
|
||||
arg_ref<Char> arg_id;
|
||||
const Char* arg_id_end;
|
||||
};
|
||||
|
||||
template <int ID, typename Char>
|
||||
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
|
||||
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
|
||||
auto handler = arg_id_handler<Char>{arg_id_kind::none, arg_ref<Char>{}};
|
||||
auto arg_id_end = parse_arg_id(begin, end, handler);
|
||||
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
|
||||
return parse_arg_id_result<Char>{handler.kind, handler.arg_id, arg_id_end};
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void> struct field_type {
|
||||
@@ -424,43 +348,48 @@ struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
|
||||
|
||||
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
|
||||
typename S>
|
||||
constexpr auto parse_replacement_field_then_tail(S format_str) {
|
||||
constexpr auto parse_replacement_field_then_tail(S fmt) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr auto str = basic_string_view<char_type>(format_str);
|
||||
constexpr auto str = basic_string_view<char_type>(fmt);
|
||||
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
|
||||
if constexpr (c == '}') {
|
||||
return parse_tail<Args, END_POS + 1, NEXT_ID>(
|
||||
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
|
||||
format_str);
|
||||
} else if constexpr (c == ':') {
|
||||
field<char_type, typename field_type<T>::type, ARG_INDEX>(), fmt);
|
||||
} else if constexpr (c != ':') {
|
||||
FMT_THROW(format_error("expected ':'"));
|
||||
} else {
|
||||
constexpr auto result = parse_specs<typename field_type<T>::type>(
|
||||
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
|
||||
return parse_tail<Args, result.end, result.next_arg_id>(
|
||||
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
|
||||
result.fmt},
|
||||
format_str);
|
||||
if constexpr (result.end >= str.size() || str[result.end] != '}') {
|
||||
FMT_THROW(format_error("expected '}'"));
|
||||
return 0;
|
||||
} else {
|
||||
return parse_tail<Args, result.end + 1, result.next_arg_id>(
|
||||
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
|
||||
result.fmt},
|
||||
fmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compiles a non-empty format string and returns the compiled representation
|
||||
// or unknown_format() on unrecognized input.
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str) {
|
||||
constexpr auto compile_format_string(S fmt) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr auto str = basic_string_view<char_type>(format_str);
|
||||
constexpr auto str = basic_string_view<char_type>(fmt);
|
||||
if constexpr (str[POS] == '{') {
|
||||
if constexpr (POS + 1 == str.size())
|
||||
throw format_error("unmatched '{' in format string");
|
||||
FMT_THROW(format_error("unmatched '{' in format string"));
|
||||
if constexpr (str[POS + 1] == '{') {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
|
||||
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
|
||||
static_assert(ID != manual_indexing_id,
|
||||
"cannot switch from manual to automatic argument indexing");
|
||||
constexpr auto next_id =
|
||||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
|
||||
POS + 1, ID, next_id>(
|
||||
format_str);
|
||||
POS + 1, ID, next_id>(fmt);
|
||||
} else {
|
||||
constexpr auto arg_id_result =
|
||||
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
|
||||
@@ -468,92 +397,87 @@ constexpr auto compile_format_string(S format_str) {
|
||||
constexpr char_type c =
|
||||
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
|
||||
static_assert(c == '}' || c == ':', "missing '}' in format string");
|
||||
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
|
||||
if constexpr (arg_id_result.kind == arg_id_kind::index) {
|
||||
static_assert(
|
||||
ID == manual_indexing_id || ID == 0,
|
||||
"cannot switch from automatic to manual argument indexing");
|
||||
constexpr auto arg_index = arg_id_result.arg_id.val.index;
|
||||
constexpr auto arg_index = arg_id_result.arg_id.index;
|
||||
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
|
||||
Args, arg_id_end_pos,
|
||||
arg_index, manual_indexing_id>(
|
||||
format_str);
|
||||
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
|
||||
fmt);
|
||||
} else if constexpr (arg_id_result.kind == arg_id_kind::name) {
|
||||
constexpr auto arg_index =
|
||||
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
|
||||
if constexpr (arg_index != invalid_arg_index) {
|
||||
get_arg_index_by_name(arg_id_result.arg_id.name, Args{});
|
||||
if constexpr (arg_index >= 0) {
|
||||
constexpr auto next_id =
|
||||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||
return parse_replacement_field_then_tail<
|
||||
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
|
||||
arg_index, next_id>(format_str);
|
||||
} else {
|
||||
if constexpr (c == '}') {
|
||||
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
||||
format_str);
|
||||
} else if constexpr (c == ':') {
|
||||
return unknown_format(); // no type info for specs parsing
|
||||
}
|
||||
arg_index, next_id>(fmt);
|
||||
} else if constexpr (c == '}') {
|
||||
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||
runtime_named_field<char_type>{arg_id_result.arg_id.name}, fmt);
|
||||
} else if constexpr (c == ':') {
|
||||
return unknown_format(); // no type info for specs parsing
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if constexpr (str[POS] == '}') {
|
||||
if constexpr (POS + 1 == str.size())
|
||||
throw format_error("unmatched '}' in format string");
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
FMT_THROW(format_error("unmatched '}' in format string"));
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), fmt);
|
||||
} else {
|
||||
constexpr auto end = parse_text(str, POS + 1);
|
||||
if constexpr (end - POS > 1) {
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||
format_str);
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS), fmt);
|
||||
} else {
|
||||
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
|
||||
format_str);
|
||||
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]}, fmt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
constexpr auto compile(S format_str) {
|
||||
constexpr auto str = basic_string_view<typename S::char_type>(format_str);
|
||||
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||
constexpr auto compile(S fmt) {
|
||||
constexpr auto str = basic_string_view<typename S::char_type>(fmt);
|
||||
if constexpr (str.size() == 0) {
|
||||
return detail::make_text(str, 0, 0);
|
||||
} else {
|
||||
constexpr auto result =
|
||||
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
|
||||
format_str);
|
||||
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(fmt);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#endif // __cpp_if_constexpr
|
||||
#endif // defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
#if defined(__cpp_if_constexpr) && defined(__cpp_return_type_deduction)
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
template <typename CompiledFormat, typename... T,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
FMT_INLINE FMT_CONSTEXPR_STRING auto format(const CompiledFormat& cf,
|
||||
const T&... args)
|
||||
-> std::basic_string<Char> {
|
||||
auto s = std::basic_string<Char>();
|
||||
cf.format(std::back_inserter(s), args...);
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
template <typename OutputIt, typename CompiledFormat, typename... T,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
constexpr FMT_INLINE auto format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const T&... args) -> OutputIt {
|
||||
return cf.format(out, args...);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||
Args&&... args) {
|
||||
template <typename S, typename... T,
|
||||
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||
FMT_INLINE FMT_CONSTEXPR_STRING auto format(const S&, T&&... args)
|
||||
-> std::basic_string<typename S::char_type> {
|
||||
if constexpr (std::is_same<typename S::char_type, char>::value) {
|
||||
constexpr auto str = basic_string_view<typename S::char_type>(S());
|
||||
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
|
||||
@@ -566,73 +490,99 @@ FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||
}
|
||||
}
|
||||
}
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
constexpr auto compiled = detail::compile<T...>(S());
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||
detail::unknown_format>()) {
|
||||
return format(static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||
std::forward<Args>(args)...);
|
||||
return fmt::format(
|
||||
static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||
std::forward<T>(args)...);
|
||||
} else {
|
||||
return format(compiled, std::forward<Args>(args)...);
|
||||
return fmt::format(compiled, std::forward<T>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||
FMT_CONSTEXPR auto format_to(OutputIt out, const S&, T&&... args) -> OutputIt {
|
||||
constexpr auto compiled = detail::compile<T...>(S());
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||
detail::unknown_format>()) {
|
||||
return format_to(out,
|
||||
static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||
std::forward<Args>(args)...);
|
||||
return fmt::format_to(
|
||||
out, static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||
std::forward<T>(args)...);
|
||||
} else {
|
||||
return format_to(out, compiled, std::forward<Args>(args)...);
|
||||
return fmt::format_to(out, compiled, std::forward<T>(args)...);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const S& format_str, Args&&... args) {
|
||||
auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), format_str,
|
||||
std::forward<Args>(args)...);
|
||||
return {it.base(), it.count()};
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||
auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
using traits = detail::fixed_buffer_traits;
|
||||
auto buf = detail::iterator_buffer<OutputIt, char, traits>(out, n);
|
||||
fmt::format_to(std::back_inserter(buf), fmt, std::forward<T>(args)...);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
size_t formatted_size(const S& format_str, const Args&... args) {
|
||||
return format_to(detail::counting_iterator(), format_str, args...).count();
|
||||
template <typename S, typename... T,
|
||||
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||
FMT_CONSTEXPR20 auto formatted_size(const S& fmt, T&&... args) -> size_t {
|
||||
auto buf = detail::counting_buffer<>();
|
||||
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
|
||||
return buf.count();
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
void print(std::FILE* f, const S& format_str, const Args&... args) {
|
||||
memory_buffer buffer;
|
||||
format_to(std::back_inserter(buffer), format_str, args...);
|
||||
detail::print(f, {buffer.data(), buffer.size()});
|
||||
template <typename S, typename... T,
|
||||
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||
void print(std::FILE* f, const S& fmt, T&&... args) {
|
||||
auto buf = memory_buffer();
|
||||
fmt::format_to(appender(buf), fmt, std::forward<T>(args)...);
|
||||
detail::print(f, {buf.data(), buf.size()});
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
void print(const S& format_str, const Args&... args) {
|
||||
print(stdout, format_str, args...);
|
||||
template <typename S, typename... T,
|
||||
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||
void print(const S& fmt, T&&... args) {
|
||||
print(stdout, fmt, std::forward<T>(args)...);
|
||||
}
|
||||
|
||||
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
inline namespace literals {
|
||||
template <detail::fixed_string Str>
|
||||
constexpr detail::udl_compiled_string<
|
||||
remove_cvref_t<decltype(Str.data[0])>,
|
||||
sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
|
||||
operator""_cf() {
|
||||
return {};
|
||||
}
|
||||
} // namespace literals
|
||||
#endif
|
||||
template <size_t N> class static_format_result {
|
||||
private:
|
||||
char data[N];
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
public:
|
||||
template <typename S, typename... T,
|
||||
FMT_ENABLE_IF(is_compiled_string<S>::value)>
|
||||
explicit FMT_CONSTEXPR static_format_result(const S& fmt, T&&... args) {
|
||||
*fmt::format_to(data, fmt, std::forward<T>(args)...) = '\0';
|
||||
}
|
||||
|
||||
auto str() const -> fmt::string_view { return {data, N - 1}; }
|
||||
auto c_str() const -> const char* { return data; }
|
||||
};
|
||||
|
||||
/**
|
||||
* Formats arguments according to the format string `fmt_str` and produces
|
||||
* a string of the exact required size at compile time. Both the format string
|
||||
* and the arguments must be compile-time expressions.
|
||||
*
|
||||
* The resulting string can be accessed as a C string via `c_str()` or as
|
||||
* a `fmt::string_view` via `str()`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* // Produces the static string "42" at compile time.
|
||||
* static constexpr auto result = FMT_STATIC_FORMAT("{}", 42);
|
||||
* const char* s = result.c_str();
|
||||
*/
|
||||
#define FMT_STATIC_FORMAT(fmt_str, ...) \
|
||||
fmt::static_format_result< \
|
||||
fmt::formatted_size(FMT_COMPILE(fmt_str), __VA_ARGS__) + 1>( \
|
||||
FMT_COMPILE(fmt_str), __VA_ARGS__)
|
||||
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COMPILE_H_
|
||||
|
||||
+4
-2966
File diff suppressed because it is too large
Load Diff
+1183
-1859
File diff suppressed because it is too large
Load Diff
+3626
-2073
File diff suppressed because it is too large
Load Diff
@@ -1,2 +0,0 @@
|
||||
#include "xchar.h"
|
||||
#warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead
|
||||
+177
-265
@@ -8,30 +8,34 @@
|
||||
#ifndef FMT_OS_H_
|
||||
#define FMT_OS_H_
|
||||
|
||||
#include <cerrno>
|
||||
#include <clocale> // locale_t
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib> // strtod_l
|
||||
#include <system_error> // std::system_error
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <cerrno>
|
||||
# include <cstddef>
|
||||
# include <cstdio>
|
||||
# include <system_error> // std::system_error
|
||||
|
||||
# if FMT_HAS_INCLUDE(<xlocale.h>)
|
||||
# include <xlocale.h> // LC_NUMERIC_MASK on macOS
|
||||
# endif
|
||||
#endif // FMT_MODULE
|
||||
|
||||
#ifndef FMT_USE_FCNTL
|
||||
// UWP doesn't provide _pipe.
|
||||
#if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
#else
|
||||
# define FMT_USE_FCNTL 0
|
||||
# if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
# endif
|
||||
# if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
(!defined(WINAPI_FAMILY) || \
|
||||
(WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP)) && \
|
||||
!defined(__wasm__)
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
# else
|
||||
# define FMT_USE_FCNTL 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
@@ -45,6 +49,7 @@
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_HAS_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) ::call
|
||||
@@ -70,144 +75,88 @@
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
/**
|
||||
\rst
|
||||
A reference to a null-terminated string. It can be constructed from a C
|
||||
string or ``std::string``.
|
||||
|
||||
You can use one of the following type aliases for common character types:
|
||||
|
||||
+---------------+-----------------------------+
|
||||
| Type | Definition |
|
||||
+===============+=============================+
|
||||
| cstring_view | basic_cstring_view<char> |
|
||||
+---------------+-----------------------------+
|
||||
| wcstring_view | basic_cstring_view<wchar_t> |
|
||||
+---------------+-----------------------------+
|
||||
|
||||
This class is most useful as a parameter type to allow passing
|
||||
different types of strings to a function, for example::
|
||||
|
||||
template <typename... Args>
|
||||
std::string format(cstring_view format_str, const Args & ... args);
|
||||
|
||||
format("{}", 42);
|
||||
format(std::string("{}"), 42);
|
||||
\endrst
|
||||
* A reference to a null-terminated string. It can be constructed from a C
|
||||
* string or `std::string`.
|
||||
*
|
||||
* You can use one of the following type aliases for common character types:
|
||||
*
|
||||
* +---------------+-----------------------------+
|
||||
* | Type | Definition |
|
||||
* +===============+=============================+
|
||||
* | cstring_view | basic_cstring_view<char> |
|
||||
* +---------------+-----------------------------+
|
||||
* | wcstring_view | basic_cstring_view<wchar_t> |
|
||||
* +---------------+-----------------------------+
|
||||
*
|
||||
* This class is most useful as a parameter type for functions that wrap C APIs.
|
||||
*/
|
||||
template <typename Char> class basic_cstring_view {
|
||||
private:
|
||||
const Char* data_;
|
||||
|
||||
public:
|
||||
/** Constructs a string reference object from a C string. */
|
||||
/// Constructs a string reference object from a C string.
|
||||
basic_cstring_view(const Char* s) : data_(s) {}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a string reference from an ``std::string`` object.
|
||||
\endrst
|
||||
*/
|
||||
/// Constructs a string reference from an `std::string` object.
|
||||
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
|
||||
|
||||
/** Returns the pointer to a C string. */
|
||||
const Char* c_str() const { return data_; }
|
||||
/// Returns the pointer to a C string.
|
||||
auto c_str() const -> const Char* { return data_; }
|
||||
};
|
||||
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
|
||||
template <typename Char> struct formatter<std::error_code, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
out = detail::write_bytes(out, ec.category().name(),
|
||||
basic_format_specs<Char>());
|
||||
out = detail::write<Char>(out, Char(':'));
|
||||
out = detail::write<Char>(out, ec.value());
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
FMT_API const std::error_category& system_category() FMT_NOEXCEPT;
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
// A converter from UTF-16 to UTF-8.
|
||||
// It is only provided for Windows since other systems support UTF-8 natively.
|
||||
class utf16_to_utf8 {
|
||||
private:
|
||||
memory_buffer buffer_;
|
||||
|
||||
public:
|
||||
utf16_to_utf8() {}
|
||||
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
|
||||
operator string_view() const { return string_view(&buffer_[0], size()); }
|
||||
size_t size() const { return buffer_.size() - 1; }
|
||||
const char* c_str() const { return &buffer_[0]; }
|
||||
std::string str() const { return std::string(&buffer_[0], size()); }
|
||||
|
||||
// Performs conversion returning a system error code instead of
|
||||
// throwing exception on conversion error. This method may still throw
|
||||
// in case of memory allocation error.
|
||||
FMT_API int convert(basic_string_view<wchar_t> s);
|
||||
};
|
||||
FMT_API const std::error_category& system_category() noexcept;
|
||||
|
||||
namespace detail {
|
||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||
const char* message) FMT_NOEXCEPT;
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
const char* message) noexcept;
|
||||
}
|
||||
|
||||
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
||||
FMT_API std::system_error vwindows_error(int error_code, string_view fmt,
|
||||
format_args args);
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a :class:`std::system_error` object with the description
|
||||
of the form
|
||||
|
||||
.. parsed-literal::
|
||||
*<message>*: *<system-message>*
|
||||
|
||||
where *<message>* is the formatted message and *<system-message>* is the
|
||||
system message corresponding to the error code.
|
||||
*error_code* is a Windows error code as given by ``GetLastError``.
|
||||
If *error_code* is not a valid error code such as -1, the system message
|
||||
will look like "error -1".
|
||||
|
||||
**Example**::
|
||||
|
||||
// This throws a system_error with the description
|
||||
// cannot open file 'madeup': The system cannot find the file specified.
|
||||
// or similar (system message may vary).
|
||||
const char *filename = "madeup";
|
||||
LPOFSTRUCT of = LPOFSTRUCT();
|
||||
HFILE file = OpenFile(filename, &of, OF_READ);
|
||||
if (file == HFILE_ERROR) {
|
||||
throw fmt::windows_error(GetLastError(),
|
||||
"cannot open file '{}'", filename);
|
||||
}
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
std::system_error windows_error(int error_code, string_view message,
|
||||
const Args&... args) {
|
||||
return vwindows_error(error_code, message, fmt::make_format_args(args...));
|
||||
* Constructs a `std::system_error` object with the description of the form
|
||||
*
|
||||
* <message>: <system-message>
|
||||
*
|
||||
* where `<message>` is the formatted message and `<system-message>` is the
|
||||
* system message corresponding to the error code.
|
||||
* `error_code` is a Windows error code as given by `GetLastError`.
|
||||
* If `error_code` is not a valid error code such as -1, the system message
|
||||
* will look like "error -1".
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* // This throws a system_error with the description
|
||||
* // cannot open file 'foo': The system cannot find the file specified.
|
||||
* // or similar (system message may vary) if the file doesn't exist.
|
||||
* const char *filename = "foo";
|
||||
* LPOFSTRUCT of = LPOFSTRUCT();
|
||||
* HFILE file = OpenFile(filename, &of, OF_READ);
|
||||
* if (file == HFILE_ERROR) {
|
||||
* throw fmt::windows_error(GetLastError(),
|
||||
* "cannot open file '{}'", filename);
|
||||
* }
|
||||
*/
|
||||
template <typename... T>
|
||||
auto windows_error(int error_code, string_view message, const T&... args)
|
||||
-> std::system_error {
|
||||
return vwindows_error(error_code, message, vargs<T...>{{args...}});
|
||||
}
|
||||
|
||||
// Reports a Windows error without throwing an exception.
|
||||
// Can be used to report errors from destructors.
|
||||
FMT_API void report_windows_error(int error_code,
|
||||
const char* message) FMT_NOEXCEPT;
|
||||
FMT_API void report_windows_error(int error_code, const char* message) noexcept;
|
||||
#else
|
||||
inline const std::error_category& system_category() FMT_NOEXCEPT {
|
||||
inline auto system_category() noexcept -> const std::error_category& {
|
||||
return std::system_category();
|
||||
}
|
||||
#endif // _WIN32
|
||||
@@ -215,8 +164,8 @@ inline const std::error_category& system_category() FMT_NOEXCEPT {
|
||||
// std::system is not available on some platforms such as iOS (#2248).
|
||||
#ifdef __OSX__
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
void say(const S& format_str, Args&&... args) {
|
||||
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
|
||||
void say(const S& fmt, Args&&... args) {
|
||||
std::system(format("say \"{}\"", format(fmt, args...)).c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -227,24 +176,24 @@ class buffered_file {
|
||||
|
||||
friend class file;
|
||||
|
||||
explicit buffered_file(FILE* f) : file_(f) {}
|
||||
inline explicit buffered_file(FILE* f) : file_(f) {}
|
||||
|
||||
public:
|
||||
buffered_file(const buffered_file&) = delete;
|
||||
void operator=(const buffered_file&) = delete;
|
||||
|
||||
// Constructs a buffered_file object which doesn't represent any file.
|
||||
buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
|
||||
inline buffered_file() noexcept : file_(nullptr) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~buffered_file() FMT_NOEXCEPT;
|
||||
FMT_API ~buffered_file() noexcept;
|
||||
|
||||
public:
|
||||
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
inline buffered_file(buffered_file&& other) noexcept : file_(other.file_) {
|
||||
other.file_ = nullptr;
|
||||
}
|
||||
|
||||
buffered_file& operator=(buffered_file&& other) {
|
||||
inline auto operator=(buffered_file&& other) -> buffered_file& {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = nullptr;
|
||||
@@ -258,36 +207,35 @@ class buffered_file {
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE* get() const FMT_NOEXCEPT { return file_; }
|
||||
inline auto get() const noexcept -> FILE* { return file_; }
|
||||
|
||||
// We place parentheses around fileno to workaround a bug in some versions
|
||||
// of MinGW that define fileno as a macro.
|
||||
FMT_API int(fileno)() const;
|
||||
FMT_API auto descriptor() const -> int;
|
||||
|
||||
void vprint(string_view format_str, format_args args) {
|
||||
fmt::vprint(file_, format_str, args);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(string_view format_str, const Args&... args) {
|
||||
vprint(format_str, fmt::make_format_args(args...));
|
||||
template <typename... T>
|
||||
inline void print(string_view fmt, const T&... args) {
|
||||
fmt::vargs<T...> vargs = {{args...}};
|
||||
detail::is_locking<T...>() ? fmt::vprint_buffered(file_, fmt, vargs)
|
||||
: fmt::vprint(file_, fmt, vargs);
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
|
||||
// A file. Closed file is represented by a file object with descriptor -1.
|
||||
// Methods that are not declared with FMT_NOEXCEPT may throw
|
||||
// Methods that are not declared with noexcept may throw
|
||||
// fmt::system_error in case of failure. Note that some errors such as
|
||||
// closing the file multiple times will cause a crash on Windows rather
|
||||
// than an exception. You can get standard behavior by overriding the
|
||||
// invalid parameter handler with _set_invalid_parameter_handler.
|
||||
class file {
|
||||
class FMT_API file {
|
||||
private:
|
||||
int fd_; // File descriptor.
|
||||
|
||||
// Constructs a file object with a given descriptor.
|
||||
explicit file(int fd) : fd_(fd) {}
|
||||
|
||||
friend struct pipe;
|
||||
|
||||
public:
|
||||
// Possible values for the oflag argument to the constructor.
|
||||
enum {
|
||||
@@ -300,19 +248,19 @@ class file {
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
file() FMT_NOEXCEPT : fd_(-1) {}
|
||||
inline file() noexcept : fd_(-1) {}
|
||||
|
||||
// Opens a file and constructs a file object representing this file.
|
||||
FMT_API file(cstring_view path, int oflag);
|
||||
file(cstring_view path, int oflag);
|
||||
|
||||
public:
|
||||
file(const file&) = delete;
|
||||
void operator=(const file&) = delete;
|
||||
|
||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
||||
inline file(file&& other) noexcept : fd_(other.fd_) { other.fd_ = -1; }
|
||||
|
||||
// Move assignment is not noexcept because close may throw.
|
||||
file& operator=(file&& other) {
|
||||
inline auto operator=(file&& other) -> file& {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
@@ -320,54 +268,65 @@ class file {
|
||||
}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~file() FMT_NOEXCEPT;
|
||||
~file() noexcept;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||
inline auto descriptor() const noexcept -> int { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
void close();
|
||||
|
||||
// Returns the file size. The size has signed type for consistency with
|
||||
// stat::st_size.
|
||||
FMT_API long long size() const;
|
||||
auto size() const -> long long;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
FMT_API size_t read(void* buffer, size_t count);
|
||||
auto read(void* buffer, size_t count) -> size_t;
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
FMT_API size_t write(const void* buffer, size_t count);
|
||||
auto write(const void* buffer, size_t count) -> size_t;
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
FMT_API static file dup(int fd);
|
||||
static auto dup(int fd) -> file;
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd);
|
||||
void dup2(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd, std::error_code& ec) FMT_NOEXCEPT;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
FMT_API static void pipe(file& read_end, file& write_end);
|
||||
void dup2(int fd, std::error_code& ec) noexcept;
|
||||
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
FMT_API buffered_file fdopen(const char* mode);
|
||||
auto fdopen(const char* mode) -> buffered_file;
|
||||
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Opens a file and constructs a file object representing this file by
|
||||
// wcstring_view filename. Windows only.
|
||||
static file open_windows_file(wcstring_view path, int oflag);
|
||||
# endif
|
||||
};
|
||||
|
||||
struct FMT_API pipe {
|
||||
file read_end;
|
||||
file write_end;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
pipe();
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
auto getpagesize() -> long;
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
struct buffer_size {
|
||||
buffer_size() = default;
|
||||
constexpr buffer_size() = default;
|
||||
size_t value = 0;
|
||||
buffer_size operator=(size_t val) const {
|
||||
FMT_CONSTEXPR auto operator=(size_t val) const -> buffer_size {
|
||||
auto bs = buffer_size();
|
||||
bs.value = val;
|
||||
return bs;
|
||||
@@ -378,7 +337,7 @@ struct ostream_params {
|
||||
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
|
||||
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
|
||||
|
||||
ostream_params() {}
|
||||
constexpr ostream_params() {}
|
||||
|
||||
template <typename... T>
|
||||
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
|
||||
@@ -390,126 +349,79 @@ struct ostream_params {
|
||||
: ostream_params(params...) {
|
||||
this->buffer_size = bs.value;
|
||||
}
|
||||
|
||||
// Intel has a bug that results in failure to deduce a constructor
|
||||
// for empty parameter packs.
|
||||
# if defined(__INTEL_COMPILER) && __INTEL_COMPILER < 2000
|
||||
ostream_params(int new_oflag) : oflag(new_oflag) {}
|
||||
ostream_params(detail::buffer_size bs) : buffer_size(bs.value) {}
|
||||
# endif
|
||||
};
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
} // namespace detail
|
||||
|
||||
static constexpr detail::buffer_size buffer_size;
|
||||
FMT_INLINE_VARIABLE constexpr auto buffer_size = detail::buffer_size();
|
||||
|
||||
/** A fast output stream which is not thread-safe. */
|
||||
class FMT_API ostream final : private detail::buffer<char> {
|
||||
/// A fast buffered output stream for writing from a single thread. Writing from
|
||||
/// multiple threads without external synchronization may result in a data race.
|
||||
class ostream : private detail::buffer<char> {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
void flush() {
|
||||
FMT_API ostream(cstring_view path, const detail::ostream_params& params);
|
||||
|
||||
FMT_API static void grow(buffer<char>& buf, size_t);
|
||||
|
||||
public:
|
||||
FMT_API ostream(ostream&& other) noexcept;
|
||||
FMT_API ~ostream();
|
||||
|
||||
operator writer() {
|
||||
detail::buffer<char>& buf = *this;
|
||||
return buf;
|
||||
}
|
||||
|
||||
inline void flush() {
|
||||
if (size() == 0) return;
|
||||
file_.write(data(), size());
|
||||
file_.write(data(), size() * sizeof(data()[0]));
|
||||
clear();
|
||||
}
|
||||
|
||||
void grow(size_t) override;
|
||||
|
||||
ostream(cstring_view path, const detail::ostream_params& params)
|
||||
: file_(path, params.oflag) {
|
||||
set(new char[params.buffer_size], params.buffer_size);
|
||||
}
|
||||
|
||||
public:
|
||||
ostream(ostream&& other)
|
||||
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
|
||||
file_(std::move(other.file_)) {
|
||||
other.clear();
|
||||
other.set(nullptr, 0);
|
||||
}
|
||||
~ostream() {
|
||||
flush();
|
||||
delete[] data();
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
friend ostream output_file(cstring_view path, T... params);
|
||||
friend auto output_file(cstring_view path, T... params) -> ostream;
|
||||
|
||||
void close() {
|
||||
inline void close() {
|
||||
flush();
|
||||
file_.close();
|
||||
}
|
||||
|
||||
/**
|
||||
Formats ``args`` according to specifications in ``fmt`` and writes the
|
||||
output to the file.
|
||||
*/
|
||||
/// Formats `args` according to specifications in `fmt` and writes the
|
||||
/// output to the file.
|
||||
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
||||
vformat_to(detail::buffer_appender<char>(*this), fmt,
|
||||
fmt::make_format_args(args...));
|
||||
vformat_to(appender(*this), fmt.str, vargs<T...>{{args...}});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Opens a file for writing. Supported parameters passed in *params*:
|
||||
|
||||
* ``<integer>``: Flags passed to `open
|
||||
<https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
|
||||
(``file::WRONLY | file::CREATE`` by default)
|
||||
* ``buffer_size=<integer>``: Output buffer size
|
||||
|
||||
**Example**::
|
||||
|
||||
auto out = fmt::output_file("guide.txt");
|
||||
out.print("Don't {}", "Panic");
|
||||
\endrst
|
||||
* Opens a file for writing. Supported parameters passed in `params`:
|
||||
*
|
||||
* - `<integer>`: Flags passed to [open](
|
||||
* https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html)
|
||||
* (`file::WRONLY | file::CREATE | file::TRUNC` by default)
|
||||
* - `buffer_size=<integer>`: Output buffer size
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* auto out = fmt::output_file("guide.txt");
|
||||
* out.print("Don't {}", "Panic");
|
||||
*/
|
||||
template <typename... T>
|
||||
inline ostream output_file(cstring_view path, T... params) {
|
||||
inline auto output_file(cstring_view path, T... params) -> ostream {
|
||||
return {path, detail::ostream_params(params...)};
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
// A "C" numeric locale.
|
||||
class locale {
|
||||
private:
|
||||
# ifdef _WIN32
|
||||
using locale_t = _locale_t;
|
||||
|
||||
static void freelocale(locale_t loc) { _free_locale(loc); }
|
||||
|
||||
static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
|
||||
return _strtod_l(nptr, endptr, loc);
|
||||
}
|
||||
# endif
|
||||
|
||||
locale_t locale_;
|
||||
|
||||
public:
|
||||
using type = locale_t;
|
||||
locale(const locale&) = delete;
|
||||
void operator=(const locale&) = delete;
|
||||
|
||||
locale() {
|
||||
# ifndef _WIN32
|
||||
locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr));
|
||||
# else
|
||||
locale_ = _create_locale(LC_NUMERIC, "C");
|
||||
# endif
|
||||
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
|
||||
}
|
||||
~locale() { freelocale(locale_); }
|
||||
|
||||
type get() const { return locale_; }
|
||||
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char*& str) const {
|
||||
char* end = nullptr;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
using Locale FMT_DEPRECATED_ALIAS = locale;
|
||||
#endif // FMT_LOCALE
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OS_H_
|
||||
|
||||
+122
-136
@@ -8,93 +8,53 @@
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include <ostream>
|
||||
#ifndef FMT_MODULE
|
||||
# include <fstream> // std::filebuf
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
#ifdef _WIN32
|
||||
# ifdef __GLIBCXX__
|
||||
# include <ext/stdio_filebuf.h>
|
||||
# include <ext/stdio_sync_filebuf.h>
|
||||
# endif
|
||||
# include <io.h>
|
||||
#endif
|
||||
|
||||
#include "chrono.h" // formatbuf
|
||||
|
||||
#ifdef _MSVC_STL_UPDATE
|
||||
# define FMT_MSVC_STL_UPDATE _MSVC_STL_UPDATE
|
||||
#elif defined(_MSC_VER) && _MSC_VER < 1912 // VS 15.5
|
||||
# define FMT_MSVC_STL_UPDATE _MSVC_LANG
|
||||
#else
|
||||
# define FMT_MSVC_STL_UPDATE 0
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char> class basic_printf_parse_context;
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
using int_type = typename std::basic_streambuf<Char>::int_type;
|
||||
using traits_type = typename std::basic_streambuf<Char>::traits_type;
|
||||
|
||||
buffer<Char>& buffer_;
|
||||
|
||||
public:
|
||||
formatbuf(buffer<Char>& buf) : buffer_(buf) {}
|
||||
|
||||
protected:
|
||||
// The put-area is actually always empty. This makes the implementation
|
||||
// simpler and has the advantage that the streambuf and the buffer are always
|
||||
// in sync and sputc never writes into uninitialized memory. The obvious
|
||||
// disadvantage is that each call to sputc always results in a (virtual) call
|
||||
// to overflow. There is no disadvantage here for sputn since this always
|
||||
// results in a call to xsputn.
|
||||
|
||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof()))
|
||||
buffer_.push_back(static_cast<Char>(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
|
||||
buffer_.append(s, s + count);
|
||||
return count;
|
||||
}
|
||||
// Generate a unique explicit instantiation in every translation unit using a
|
||||
// tag type in an anonymous namespace.
|
||||
namespace {
|
||||
struct file_access_tag {};
|
||||
} // namespace
|
||||
template <typename Tag, typename BufType, FILE* BufType::*FileMemberPtr>
|
||||
class file_access {
|
||||
friend auto get_file(BufType& obj) -> FILE* { return obj.*FileMemberPtr; }
|
||||
};
|
||||
|
||||
struct converter {
|
||||
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
|
||||
};
|
||||
|
||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
void_t<> operator<<(converter);
|
||||
};
|
||||
|
||||
// Hide insertion operators for built-in types.
|
||||
template <typename Char, typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
|
||||
template <typename Char, typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||
// std::ostream).
|
||||
template <typename T, typename Char> class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
void_t<>>::value>
|
||||
test(int);
|
||||
|
||||
template <typename> static std::false_type test(...);
|
||||
|
||||
using result = decltype(test<T>(0));
|
||||
|
||||
public:
|
||||
is_streamable() = default;
|
||||
|
||||
static const bool value = result::value;
|
||||
};
|
||||
#if FMT_MSVC_STL_UPDATE
|
||||
template class file_access<file_access_tag, std::filebuf,
|
||||
&std::filebuf::_Myfile>;
|
||||
auto get_file(std::filebuf&) -> FILE*;
|
||||
#endif
|
||||
|
||||
// Write the content of buf to os.
|
||||
// It is a separate function rather than a part of vprint to simplify testing.
|
||||
template <typename Char>
|
||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
using unsigned_streamsize = make_unsigned_t<std::streamsize>;
|
||||
unsigned_streamsize size = buf.size();
|
||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||
do {
|
||||
@@ -105,77 +65,103 @@ void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
formatbuf<Char> format_buf(buf);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
#endif
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
buf.try_resize(buf.size());
|
||||
}
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
: private formatter<basic_string_view<Char>, Char> {
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
return formatter<basic_string_view<Char>, Char>::parse(ctx);
|
||||
}
|
||||
template <typename ParseCtx,
|
||||
FMT_ENABLE_IF(std::is_same<
|
||||
ParseCtx, basic_printf_parse_context<Char>>::value)>
|
||||
auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
}
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
||||
}
|
||||
template <typename T> struct streamed_view {
|
||||
const T& value;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
struct basic_ostream_formatter : formatter<basic_string_view<Char>, Char> {
|
||||
void set_debug_format() = delete;
|
||||
|
||||
template <typename T, typename Context>
|
||||
auto format(const T& value, Context& ctx) const -> decltype(ctx.out()) {
|
||||
auto buffer = basic_memory_buffer<Char>();
|
||||
auto&& formatbuf = detail::formatbuf<std::basic_streambuf<Char>>(buffer);
|
||||
auto&& output = std::basic_ostream<Char>(&formatbuf);
|
||||
output.imbue(std::locale::classic()); // The default is always unlocalized.
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
return formatter<basic_string_view<Char>, Char>::format(
|
||||
{buffer.data(), buffer.size()}, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
using ostream_formatter = basic_ostream_formatter<char>;
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<detail::streamed_view<T>, Char>
|
||||
: basic_ostream_formatter<Char> {
|
||||
template <typename Context>
|
||||
auto format(detail::streamed_view<T> view, Context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return basic_ostream_formatter<Char>::format(view.value, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a view that formats `value` via an ostream `operator<<`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print("Current thread id: {}\n",
|
||||
* fmt::streamed(std::this_thread::get_id()));
|
||||
*/
|
||||
template <typename T>
|
||||
constexpr auto streamed(const T& value) -> detail::streamed_view<T> {
|
||||
return {value};
|
||||
}
|
||||
|
||||
inline void vprint(std::ostream& os, string_view fmt, format_args args) {
|
||||
auto buffer = memory_buffer();
|
||||
detail::vformat_to(buffer, fmt, args);
|
||||
FILE* f = nullptr;
|
||||
#if FMT_MSVC_STL_UPDATE && FMT_USE_RTTI
|
||||
if (auto* buf = dynamic_cast<std::filebuf*>(os.rdbuf()))
|
||||
f = detail::get_file(*buf);
|
||||
#elif defined(_WIN32) && defined(__GLIBCXX__) && FMT_USE_RTTI
|
||||
auto* rdbuf = os.rdbuf();
|
||||
if (auto* sfbuf = dynamic_cast<__gnu_cxx::stdio_sync_filebuf<char>*>(rdbuf))
|
||||
f = sfbuf->file();
|
||||
else if (auto* fbuf = dynamic_cast<__gnu_cxx::stdio_filebuf<char>*>(rdbuf))
|
||||
f = fbuf->file();
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
if (f) {
|
||||
int fd = _fileno(f);
|
||||
if (_isatty(fd)) {
|
||||
os.flush();
|
||||
if (detail::write_console(fd, {buffer.data(), buffer.size()})) return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
detail::ignore_unused(f);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
* Prints formatted data to the stream `os`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::print(cerr, "Don't {}!", "panic");
|
||||
*/
|
||||
FMT_MODULE_EXPORT
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||
vprint(os, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
FMT_EXPORT template <typename... T>
|
||||
void print(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
fmt::vargs<T...> vargs = {{args...}};
|
||||
if (detail::const_check(detail::use_utf8)) return vprint(os, fmt.str, vargs);
|
||||
auto buffer = memory_buffer();
|
||||
detail::vformat_to(buffer, fmt.str, vargs);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
FMT_EXPORT template <typename... T>
|
||||
void println(std::ostream& os, format_string<T...> fmt, T&&... args) {
|
||||
fmt::print(os, FMT_STRING("{}\n"),
|
||||
fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
||||
|
||||
+330
-414
@@ -8,74 +8,114 @@
|
||||
#ifndef FMT_PRINTF_H_
|
||||
#define FMT_PRINTF_H_
|
||||
|
||||
#include <algorithm> // std::max
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <ostream>
|
||||
#ifndef FMT_MODULE
|
||||
# include <algorithm> // std::find
|
||||
# include <limits> // std::numeric_limits
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
template <typename Char>
|
||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||
template <typename Char> class basic_printf_context {
|
||||
private:
|
||||
basic_appender<Char> out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
|
||||
static_assert(std::is_same<Char, char>::value ||
|
||||
std::is_same<Char, wchar_t>::value,
|
||||
"Unsupported code unit type.");
|
||||
|
||||
public:
|
||||
using char_type = Char;
|
||||
enum { builtin_types = 1 };
|
||||
|
||||
/// Constructs a `printf_context` object. References to the arguments are
|
||||
/// stored in the context object so make sure they have appropriate lifetimes.
|
||||
basic_printf_context(basic_appender<Char> out,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: out_(out), args_(args) {}
|
||||
|
||||
auto out() -> basic_appender<Char> { return out_; }
|
||||
void advance_to(basic_appender<Char>) {}
|
||||
|
||||
auto locale() -> locale_ref { return {}; }
|
||||
|
||||
auto arg(int id) const -> basic_format_arg<basic_printf_context> {
|
||||
return args_.get(id);
|
||||
}
|
||||
};
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// Return the result via the out param to workaround gcc bug 77539.
|
||||
template <bool IS_CONSTEXPR, typename T, typename Ptr = const T*>
|
||||
FMT_CONSTEXPR auto find(Ptr first, Ptr last, T value, Ptr& out) -> bool {
|
||||
for (out = first; out != last; ++out) {
|
||||
if (*out == value) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <>
|
||||
inline auto find<false, char>(const char* first, const char* last, char value,
|
||||
const char*& out) -> bool {
|
||||
out =
|
||||
static_cast<const char*>(memchr(first, value, to_unsigned(last - first)));
|
||||
return out != nullptr;
|
||||
}
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned> struct int_checker {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
unsigned max = max_value<int>();
|
||||
return value <= max;
|
||||
template <bool IS_SIGNED> struct int_checker {
|
||||
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||
return value <= to_unsigned(max_value<int>());
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
inline static auto fits_in_int(bool) -> bool { return true; }
|
||||
};
|
||||
|
||||
template <> struct int_checker<true> {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
template <typename T> static auto fits_in_int(T value) -> bool {
|
||||
return value >= (std::numeric_limits<int>::min)() &&
|
||||
value <= max_value<int>();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
inline static auto fits_in_int(int) -> bool { return true; }
|
||||
};
|
||||
|
||||
class printf_precision_handler {
|
||||
public:
|
||||
struct printf_precision_handler {
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
int operator()(T value) {
|
||||
auto operator()(T value) -> int {
|
||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
return (std::max)(static_cast<int>(value), 0);
|
||||
report_error("number is too big");
|
||||
return max_of(static_cast<int>(value), 0);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
int operator()(T) {
|
||||
FMT_THROW(format_error("precision is not integer"));
|
||||
auto operator()(T) -> int {
|
||||
report_error("precision is not integer");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An argument visitor that returns true iff arg is a zero integer.
|
||||
class is_zero_int {
|
||||
public:
|
||||
struct is_zero_int {
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
bool operator()(T value) {
|
||||
auto operator()(T value) -> bool {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
bool operator()(T) {
|
||||
auto operator()(T) -> bool {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
|
||||
|
||||
template <> struct make_unsigned_or_bool<bool> { using type = bool; };
|
||||
template <> struct make_unsigned_or_bool<bool> {
|
||||
using type = bool;
|
||||
};
|
||||
|
||||
template <typename T, typename Context> class arg_converter {
|
||||
private:
|
||||
@@ -98,24 +138,19 @@ template <typename T, typename Context> class arg_converter {
|
||||
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
|
||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<int>(static_cast<target_type>(value)));
|
||||
} else {
|
||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
||||
}
|
||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||
if (is_signed)
|
||||
arg_ = static_cast<int>(static_cast<target_type>(value));
|
||||
else
|
||||
arg_ = static_cast<unsigned>(static_cast<unsigned_type>(value));
|
||||
} else {
|
||||
if (is_signed) {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
|
||||
} else {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
}
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
if (is_signed)
|
||||
arg_ = static_cast<long long>(value);
|
||||
else
|
||||
arg_ = static_cast<typename make_unsigned_or_bool<U>::type>(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +164,7 @@ template <typename T, typename Context> class arg_converter {
|
||||
// unsigned).
|
||||
template <typename T, typename Context, typename Char>
|
||||
void convert_arg(basic_format_arg<Context>& arg, Char type) {
|
||||
visit_format_arg(arg_converter<T, Context>(arg, type), arg);
|
||||
arg.visit(arg_converter<T, Context>(arg, type));
|
||||
}
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
@@ -142,8 +177,7 @@ template <typename Context> class char_converter {
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
void operator()(T value) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename Context::char_type>(value));
|
||||
arg_ = static_cast<typename Context::char_type>(value);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
@@ -153,261 +187,163 @@ template <typename Context> class char_converter {
|
||||
// An argument visitor that return a pointer to a C string if argument is a
|
||||
// string or null otherwise.
|
||||
template <typename Char> struct get_cstring {
|
||||
template <typename T> const Char* operator()(T) { return nullptr; }
|
||||
const Char* operator()(const Char* s) { return s; }
|
||||
template <typename T> auto operator()(T) -> const Char* { return nullptr; }
|
||||
auto operator()(const Char* s) -> const Char* { return s; }
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
template <typename Char> class printf_width_handler {
|
||||
class printf_width_handler {
|
||||
private:
|
||||
using format_specs = basic_format_specs<Char>;
|
||||
|
||||
format_specs& specs_;
|
||||
|
||||
public:
|
||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
||||
inline explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
unsigned operator()(T value) {
|
||||
auto operator()(T value) -> unsigned {
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (detail::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
specs_.set_align(align::left);
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = max_value<int>();
|
||||
if (width > int_max) FMT_THROW(format_error("number is too big"));
|
||||
unsigned int_max = to_unsigned(max_value<int>());
|
||||
if (width > int_max) report_error("number is too big");
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
unsigned operator()(T) {
|
||||
FMT_THROW(format_error("width is not integer"));
|
||||
auto operator()(T) -> unsigned {
|
||||
report_error("width is not integer");
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// The ``printf`` argument formatter.
|
||||
template <typename OutputIt, typename Char>
|
||||
// Workaround for a bug with the XL compiler when initializing
|
||||
// printf_arg_formatter's base class.
|
||||
template <typename Char>
|
||||
auto make_arg_formatter(basic_appender<Char> iter, format_specs& s)
|
||||
-> arg_formatter<Char> {
|
||||
return {iter, s, locale_ref()};
|
||||
}
|
||||
|
||||
// The `printf` argument formatter.
|
||||
template <typename Char>
|
||||
class printf_arg_formatter : public arg_formatter<Char> {
|
||||
private:
|
||||
using base = arg_formatter<Char>;
|
||||
using context_type = basic_printf_context<OutputIt, Char>;
|
||||
using format_specs = basic_format_specs<Char>;
|
||||
using context_type = basic_printf_context<Char>;
|
||||
|
||||
context_type& context_;
|
||||
|
||||
OutputIt write_null_pointer(bool is_string = false) {
|
||||
void write_null_pointer(bool is_string = false) {
|
||||
auto s = this->specs;
|
||||
s.type = 0;
|
||||
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||
s.set_type(presentation_type::none);
|
||||
write_bytes<Char>(this->out, is_string ? "(null)" : "(nil)", s);
|
||||
}
|
||||
|
||||
template <typename T> void write(T value) {
|
||||
detail::write<Char>(this->out, value, this->specs, this->locale);
|
||||
}
|
||||
|
||||
public:
|
||||
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
|
||||
: base{iter, s, locale_ref()}, context_(ctx) {}
|
||||
printf_arg_formatter(basic_appender<Char> iter, format_specs& s,
|
||||
context_type& ctx)
|
||||
: base(make_arg_formatter(iter, s)), context_(ctx) {}
|
||||
|
||||
OutputIt operator()(monostate value) { return base::operator()(value); }
|
||||
void operator()(monostate value) { write(value); }
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
||||
OutputIt operator()(T value) {
|
||||
void operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
||||
// std::is_same instead.
|
||||
if (std::is_same<T, Char>::value) {
|
||||
format_specs fmt_specs = this->specs;
|
||||
if (fmt_specs.type && fmt_specs.type != 'c')
|
||||
return (*this)(static_cast<int>(value));
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// ignored for non-numeric types
|
||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||
fmt_specs.align = align::right;
|
||||
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
||||
if (!std::is_same<T, Char>::value) {
|
||||
write(value);
|
||||
return;
|
||||
}
|
||||
return base::operator()(value);
|
||||
format_specs s = this->specs;
|
||||
if (s.type() != presentation_type::none &&
|
||||
s.type() != presentation_type::chr) {
|
||||
return (*this)(static_cast<int>(value));
|
||||
}
|
||||
s.set_sign(sign::none);
|
||||
s.clear_alt();
|
||||
s.set_fill(' '); // Ignore '0' flag for char types.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// ignored for non-numeric types
|
||||
if (s.align() == align::none || s.align() == align::numeric)
|
||||
s.set_align(align::right);
|
||||
detail::write<Char>(this->out, static_cast<Char>(value), s);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
OutputIt operator()(T value) {
|
||||
return base::operator()(value);
|
||||
void operator()(T value) {
|
||||
write(value);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated C string. */
|
||||
OutputIt operator()(const char* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != 'p');
|
||||
void operator()(const char* value) {
|
||||
if (value)
|
||||
write(value);
|
||||
else
|
||||
write_null_pointer(this->specs.type() != presentation_type::pointer);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated wide C string. */
|
||||
OutputIt operator()(const wchar_t* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != 'p');
|
||||
void operator()(const wchar_t* value) {
|
||||
if (value)
|
||||
write(value);
|
||||
else
|
||||
write_null_pointer(this->specs.type() != presentation_type::pointer);
|
||||
}
|
||||
|
||||
OutputIt operator()(basic_string_view<Char> value) {
|
||||
return base::operator()(value);
|
||||
void operator()(basic_string_view<Char> value) { write(value); }
|
||||
|
||||
void operator()(const void* value) {
|
||||
if (value)
|
||||
write(value);
|
||||
else
|
||||
write_null_pointer();
|
||||
}
|
||||
|
||||
/** Formats a pointer. */
|
||||
OutputIt operator()(const void* value) {
|
||||
return value ? base::operator()(value) : write_null_pointer();
|
||||
}
|
||||
|
||||
/** Formats an argument of a custom (user-defined) type. */
|
||||
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
handle.format(context_.parse_context(), context_);
|
||||
return this->out;
|
||||
void operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
auto parse_ctx = parse_context<Char>({});
|
||||
handle.format(parse_ctx, context_);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
Context(buffer_appender<Char>(buf), format, args).format();
|
||||
}
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
// For printing into memory_buffer.
|
||||
template <typename Char, typename Context>
|
||||
FMT_DEPRECATED void printf(detail::buffer<Char>& buf,
|
||||
basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
return detail::vprintf(buf, format, args);
|
||||
}
|
||||
using detail::vprintf;
|
||||
|
||||
template <typename T> struct printf_formatter {
|
||||
printf_formatter() = delete;
|
||||
|
||||
template <typename ParseContext>
|
||||
auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const T&, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
This template formats data and writes the output through an output iterator.
|
||||
*/
|
||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
public:
|
||||
/** The character type for the output. */
|
||||
using char_type = Char;
|
||||
using iterator = OutputIt;
|
||||
using format_arg = basic_format_arg<basic_printf_context>;
|
||||
using parse_context_type = basic_printf_parse_context<Char>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
private:
|
||||
using format_specs = basic_format_specs<char_type>;
|
||||
|
||||
OutputIt out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
parse_context_type parse_ctx_;
|
||||
|
||||
static void parse_flags(format_specs& specs, const Char*& it,
|
||||
const Char* end);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||
// argument.
|
||||
format_arg get_arg(int arg_index = -1);
|
||||
|
||||
// Parses argument index, flags and width and returns the argument index.
|
||||
int parse_header(const Char*& it, const Char* end, format_specs& specs);
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``printf_context`` object. References to the arguments are
|
||||
stored in the context object so make sure they have appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out, basic_string_view<char_type> format_str,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: out_(out), args_(args), parse_ctx_(format_str) {}
|
||||
|
||||
OutputIt out() { return out_; }
|
||||
void advance_to(OutputIt it) { out_ = it; }
|
||||
|
||||
detail::locale_ref locale() { return {}; }
|
||||
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
|
||||
parse_context_type& parse_context() { return parse_ctx_; }
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
parse_ctx_.on_error(message);
|
||||
}
|
||||
|
||||
/** Formats stored arguments and writes the output to the range. */
|
||||
OutputIt format();
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
void basic_printf_context<OutputIt, Char>::parse_flags(format_specs& specs,
|
||||
const Char*& it,
|
||||
const Char* end) {
|
||||
template <typename Char>
|
||||
void parse_flags(format_specs& specs, const Char*& it, const Char* end) {
|
||||
for (; it != end; ++it) {
|
||||
switch (*it) {
|
||||
case '-':
|
||||
specs.align = align::left;
|
||||
break;
|
||||
case '+':
|
||||
specs.sign = sign::plus;
|
||||
break;
|
||||
case '0':
|
||||
specs.fill[0] = '0';
|
||||
break;
|
||||
case '-': specs.set_align(align::left); break;
|
||||
case '+': specs.set_sign(sign::plus); break;
|
||||
case '0': specs.set_fill('0'); break;
|
||||
case ' ':
|
||||
if (specs.sign != sign::plus) {
|
||||
specs.sign = sign::space;
|
||||
}
|
||||
if (specs.sign() != sign::plus) specs.set_sign(sign::space);
|
||||
break;
|
||||
case '#':
|
||||
specs.alt = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
case '#': specs.set_alt(); break;
|
||||
default: return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
typename basic_printf_context<OutputIt, Char>::format_arg
|
||||
basic_printf_context<OutputIt, Char>::get_arg(int arg_index) {
|
||||
if (arg_index < 0)
|
||||
arg_index = parse_ctx_.next_arg_id();
|
||||
else
|
||||
parse_ctx_.check_arg_id(--arg_index);
|
||||
return detail::get_arg(*this, arg_index);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
||||
const Char* end,
|
||||
format_specs& specs) {
|
||||
template <typename Char, typename GetArg>
|
||||
auto parse_header(const Char*& it, const Char* end, format_specs& specs,
|
||||
GetArg get_arg) -> int {
|
||||
int arg_index = -1;
|
||||
char_type c = *it;
|
||||
Char c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
detail::error_handler eh;
|
||||
int value = parse_nonnegative_int(it, end, eh);
|
||||
int value = parse_nonnegative_int(it, end, -1);
|
||||
if (it != end && *it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value;
|
||||
arg_index = value != -1 ? value : max_value<int>();
|
||||
} else {
|
||||
if (c == '0') specs.fill[0] = '0';
|
||||
if (c == '0') specs.set_fill('0');
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
if (value == -1) report_error("number is too big");
|
||||
specs.width = value;
|
||||
return arg_index;
|
||||
}
|
||||
@@ -417,89 +353,127 @@ int basic_printf_context<OutputIt, Char>::parse_header(const Char*& it,
|
||||
// Parse width.
|
||||
if (it != end) {
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
detail::error_handler eh;
|
||||
specs.width = parse_nonnegative_int(it, end, eh);
|
||||
specs.width = parse_nonnegative_int(it, end, -1);
|
||||
if (specs.width == -1) report_error("number is too big");
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
specs.width = static_cast<int>(visit_format_arg(
|
||||
detail::printf_width_handler<char_type>(specs), get_arg()));
|
||||
specs.width = static_cast<int>(
|
||||
get_arg(-1).visit(detail::printf_width_handler(specs)));
|
||||
}
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char>
|
||||
OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
auto out = this->out();
|
||||
const Char* start = parse_ctx_.begin();
|
||||
const Char* end = parse_ctx_.end();
|
||||
inline auto parse_printf_presentation_type(char c, type t, bool& upper)
|
||||
-> presentation_type {
|
||||
using pt = presentation_type;
|
||||
constexpr auto integral_set = sint_set | uint_set | bool_set | char_set;
|
||||
switch (c) {
|
||||
case 'd': return in(t, integral_set) ? pt::dec : pt::none;
|
||||
case 'o': return in(t, integral_set) ? pt::oct : pt::none;
|
||||
case 'X': upper = true; FMT_FALLTHROUGH;
|
||||
case 'x': return in(t, integral_set) ? pt::hex : pt::none;
|
||||
case 'E': upper = true; FMT_FALLTHROUGH;
|
||||
case 'e': return in(t, float_set) ? pt::exp : pt::none;
|
||||
case 'F': upper = true; FMT_FALLTHROUGH;
|
||||
case 'f': return in(t, float_set) ? pt::fixed : pt::none;
|
||||
case 'G': upper = true; FMT_FALLTHROUGH;
|
||||
case 'g': return in(t, float_set) ? pt::general : pt::none;
|
||||
case 'A': upper = true; FMT_FALLTHROUGH;
|
||||
case 'a': return in(t, float_set) ? pt::hexfloat : pt::none;
|
||||
case 'c': return in(t, integral_set) ? pt::chr : pt::none;
|
||||
case 's': return in(t, string_set | cstring_set) ? pt::string : pt::none;
|
||||
case 'p': return in(t, pointer_set | cstring_set) ? pt::pointer : pt::none;
|
||||
default: return pt::none;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
using iterator = basic_appender<Char>;
|
||||
auto out = iterator(buf);
|
||||
auto context = basic_printf_context<Char>(out, args);
|
||||
auto parse_ctx = parse_context<Char>(format);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||
// argument.
|
||||
auto get_arg = [&](int arg_index) {
|
||||
if (arg_index < 0)
|
||||
arg_index = parse_ctx.next_arg_id();
|
||||
else
|
||||
parse_ctx.check_arg_id(--arg_index);
|
||||
auto arg = context.arg(arg_index);
|
||||
if (!arg) report_error("argument not found");
|
||||
return arg;
|
||||
};
|
||||
|
||||
const Char* start = parse_ctx.begin();
|
||||
const Char* end = parse_ctx.end();
|
||||
auto it = start;
|
||||
while (it != end) {
|
||||
if (!detail::find<false, Char>(it, end, '%', it)) {
|
||||
it = end; // detail::find leaves it == nullptr if it doesn't find '%'
|
||||
if (!find<false, Char>(it, end, '%', it)) {
|
||||
it = end; // find leaves it == nullptr if it doesn't find '%'.
|
||||
break;
|
||||
}
|
||||
char_type c = *it++;
|
||||
Char c = *it++;
|
||||
if (it != end && *it == c) {
|
||||
out = detail::write(
|
||||
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
out = detail::write(out, basic_string_view<Char>(
|
||||
start, detail::to_unsigned(it - 1 - start)));
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - 1 - start)));
|
||||
|
||||
format_specs specs;
|
||||
specs.align = align::right;
|
||||
auto specs = format_specs();
|
||||
specs.set_align(align::right);
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
int arg_index = parse_header(it, end, specs);
|
||||
if (arg_index == 0) on_error("argument not found");
|
||||
int arg_index = parse_header(it, end, specs, get_arg);
|
||||
if (arg_index == 0) report_error("argument not found");
|
||||
|
||||
// Parse precision.
|
||||
if (it != end && *it == '.') {
|
||||
++it;
|
||||
c = it != end ? *it : 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
detail::error_handler eh;
|
||||
specs.precision = parse_nonnegative_int(it, end, eh);
|
||||
specs.precision = parse_nonnegative_int(it, end, 0);
|
||||
} else if (c == '*') {
|
||||
++it;
|
||||
specs.precision = static_cast<int>(
|
||||
visit_format_arg(detail::printf_precision_handler(), get_arg()));
|
||||
specs.precision =
|
||||
static_cast<int>(get_arg(-1).visit(printf_precision_handler()));
|
||||
} else {
|
||||
specs.precision = 0;
|
||||
}
|
||||
}
|
||||
|
||||
format_arg arg = get_arg(arg_index);
|
||||
auto arg = get_arg(arg_index);
|
||||
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
||||
// specified, the '0' flag is ignored
|
||||
if (specs.precision >= 0 && arg.is_integral())
|
||||
specs.fill[0] =
|
||||
' '; // Ignore '0' flag for non-numeric types or if '-' present.
|
||||
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
|
||||
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
|
||||
if (specs.precision >= 0 && is_integral_type(arg.type())) {
|
||||
// Ignore '0' for non-numeric types or if '-' present.
|
||||
specs.set_fill(' ');
|
||||
}
|
||||
if (specs.precision >= 0 && arg.type() == type::cstring_type) {
|
||||
auto str = arg.visit(get_cstring<Char>());
|
||||
auto str_end = str + specs.precision;
|
||||
auto nul = std::find(str, str_end, Char());
|
||||
arg = detail::make_arg<basic_printf_context>(basic_string_view<Char>(
|
||||
str,
|
||||
detail::to_unsigned(nul != str_end ? nul - str : specs.precision)));
|
||||
auto sv = basic_string_view<Char>(
|
||||
str, to_unsigned(nul != str_end ? nul - str : specs.precision));
|
||||
arg = sv;
|
||||
}
|
||||
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
||||
specs.alt = false;
|
||||
if (specs.fill[0] == '0') {
|
||||
if (arg.is_arithmetic() && specs.align != align::left)
|
||||
specs.align = align::numeric;
|
||||
else
|
||||
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-'
|
||||
// flag is also present.
|
||||
if (specs.alt() && arg.visit(is_zero_int())) specs.clear_alt();
|
||||
if (specs.fill_unit<Char>() == '0') {
|
||||
if (is_arithmetic_type(arg.type()) && specs.align() != align::left) {
|
||||
specs.set_align(align::numeric);
|
||||
} else {
|
||||
// Ignore '0' flag for non-numeric types or if '-' flag is also present.
|
||||
specs.set_fill(' ');
|
||||
}
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
c = it != end ? *it++ : 0;
|
||||
char_type t = it != end ? *it : 0;
|
||||
using detail::convert_arg;
|
||||
Char t = it != end ? *it : 0;
|
||||
switch (c) {
|
||||
case 'h':
|
||||
if (t == 'h') {
|
||||
@@ -519,190 +493,132 @@ OutputIt basic_printf_context<OutputIt, Char>::format() {
|
||||
convert_arg<long>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'j':
|
||||
convert_arg<intmax_t>(arg, t);
|
||||
break;
|
||||
case 'z':
|
||||
convert_arg<size_t>(arg, t);
|
||||
break;
|
||||
case 't':
|
||||
convert_arg<std::ptrdiff_t>(arg, t);
|
||||
break;
|
||||
case 'j': convert_arg<intmax_t>(arg, t); break;
|
||||
case 'z': convert_arg<size_t>(arg, t); break;
|
||||
case 't': convert_arg<std::ptrdiff_t>(arg, t); break;
|
||||
case 'L':
|
||||
// printf produces garbage when 'L' is omitted for long double, no
|
||||
// need to do the same.
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
convert_arg<void>(arg, c);
|
||||
default: --it; convert_arg<void>(arg, c);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (it == end) FMT_THROW(format_error("invalid format string"));
|
||||
specs.type = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
if (it == end) report_error("invalid format string");
|
||||
char type = static_cast<char>(*it++);
|
||||
if (is_integral_type(arg.type())) {
|
||||
// Normalize type.
|
||||
switch (specs.type) {
|
||||
switch (type) {
|
||||
case 'i':
|
||||
case 'u':
|
||||
specs.type = 'd';
|
||||
break;
|
||||
case 'u': type = 'd'; break;
|
||||
case 'c':
|
||||
visit_format_arg(detail::char_converter<basic_printf_context>(arg),
|
||||
arg);
|
||||
arg.visit(char_converter<basic_printf_context<Char>>(arg));
|
||||
break;
|
||||
}
|
||||
}
|
||||
bool upper = false;
|
||||
specs.set_type(parse_printf_presentation_type(type, arg.type(), upper));
|
||||
if (specs.type() == presentation_type::none)
|
||||
report_error("invalid format specifier");
|
||||
if (upper) specs.set_upper();
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
out = visit_format_arg(
|
||||
detail::printf_arg_formatter<OutputIt, Char>(out, specs, *this), arg);
|
||||
arg.visit(printf_arg_formatter<Char>(out, specs, context));
|
||||
}
|
||||
return detail::write(
|
||||
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
|
||||
write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
template <typename Char>
|
||||
using basic_printf_context_t =
|
||||
basic_printf_context<detail::buffer_appender<Char>, Char>;
|
||||
|
||||
using printf_context = basic_printf_context_t<char>;
|
||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||
using printf_context = basic_printf_context<char>;
|
||||
using wprintf_context = basic_printf_context<wchar_t>;
|
||||
|
||||
using printf_args = basic_format_args<printf_context>;
|
||||
using wprintf_args = basic_format_args<wprintf_context>;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::printf_args`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
inline auto make_printf_args(const T&... args)
|
||||
-> format_arg_store<printf_context, T...> {
|
||||
return {args...};
|
||||
/// Constructs an `format_arg_store` object that contains references to
|
||||
/// arguments and can be implicitly converted to `printf_args`.
|
||||
template <typename Char = char, typename... T>
|
||||
inline auto make_printf_args(T&... args)
|
||||
-> decltype(fmt::make_format_args<basic_printf_context<Char>>(args...)) {
|
||||
return fmt::make_format_args<basic_printf_context<Char>>(args...);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
inline auto make_wprintf_args(const T&... args)
|
||||
-> format_arg_store<wprintf_context, T...> {
|
||||
return {args...};
|
||||
}
|
||||
template <typename Char> struct vprintf_args {
|
||||
using type = basic_format_args<basic_printf_context<Char>>;
|
||||
};
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline auto vsprintf(
|
||||
const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
template <typename Char>
|
||||
inline auto vsprintf(basic_string_view<Char> fmt,
|
||||
typename vprintf_args<Char>::type args)
|
||||
-> std::basic_string<Char> {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(fmt), args);
|
||||
return to_string(buffer);
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vprintf(buf, fmt, args);
|
||||
return {buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... T,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vsprintf(to_string_view(fmt), fmt::make_format_args<context>(args...));
|
||||
* Formats `args` according to specifications in `fmt` and returns the result
|
||||
* as as string.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
*/
|
||||
template <typename... T>
|
||||
inline auto sprintf(string_view fmt, const T&... args) -> std::string {
|
||||
return vsprintf(fmt, make_printf_args(args...));
|
||||
}
|
||||
template <typename... T>
|
||||
FMT_DEPRECATED auto sprintf(basic_string_view<wchar_t> fmt, const T&... args)
|
||||
-> std::wstring {
|
||||
return vsprintf(fmt, make_printf_args<wchar_t>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline auto vfprintf(
|
||||
std::FILE* f, const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(fmt), args);
|
||||
size_t size = buffer.size();
|
||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||
template <typename Char>
|
||||
auto vfprintf(std::FILE* f, basic_string_view<Char> fmt,
|
||||
typename vprintf_args<Char>::type args) -> int {
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vprintf(buf, fmt, args);
|
||||
size_t size = buf.size();
|
||||
return std::fwrite(buf.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
: static_cast<int>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the file *f*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
* Formats `args` according to specifications in `fmt` and writes the output
|
||||
* to `f`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||
*/
|
||||
template <typename S, typename... T, typename Char = char_t<S>>
|
||||
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(f, to_string_view(fmt), fmt::make_format_args<context>(args...));
|
||||
template <typename... T>
|
||||
inline auto fprintf(std::FILE* f, string_view fmt, const T&... args) -> int {
|
||||
return vfprintf(f, fmt, make_printf_args(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline auto vprintf(
|
||||
const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
return vfprintf(stdout, to_string_view(fmt), args);
|
||||
template <typename... T>
|
||||
FMT_DEPRECATED auto fprintf(std::FILE* f, basic_string_view<wchar_t> fmt,
|
||||
const T&... args) -> int {
|
||||
return vfprintf(f, fmt, make_printf_args<wchar_t>(args...));
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to ``stdout``.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
* Formats `args` according to specifications in `fmt` and writes the output
|
||||
* to `stdout`.
|
||||
*
|
||||
* **Example**:
|
||||
*
|
||||
* fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
*/
|
||||
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
inline auto printf(const S& fmt, const T&... args) -> int {
|
||||
return vprintf(to_string_view(fmt),
|
||||
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
|
||||
template <typename... T>
|
||||
inline auto printf(string_view fmt, const T&... args) -> int {
|
||||
return vfprintf(stdout, fmt, make_printf_args(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline auto vfprintf(
|
||||
std::basic_ostream<Char>& os, const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(fmt), args);
|
||||
os.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(cerr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... T, typename Char = char_t<S>>
|
||||
inline auto fprintf(std::basic_ostream<Char>& os, const S& fmt,
|
||||
const T&... args) -> int {
|
||||
return vfprintf(os, to_string_view(fmt),
|
||||
fmt::make_format_args<basic_printf_context_t<Char>>(args...));
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
||||
|
||||
+674
-286
File diff suppressed because it is too large
Load Diff
+727
@@ -0,0 +1,727 @@
|
||||
// Formatting library for C++ - formatters for standard library types
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_STD_H_
|
||||
#define FMT_STD_H_
|
||||
|
||||
#include "format.h"
|
||||
#include "ostream.h"
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <atomic>
|
||||
# include <bitset>
|
||||
# include <complex>
|
||||
# include <exception>
|
||||
# include <functional> // std::reference_wrapper
|
||||
# include <memory>
|
||||
# include <thread>
|
||||
# include <type_traits>
|
||||
# include <typeinfo> // std::type_info
|
||||
# include <utility> // std::make_index_sequence
|
||||
|
||||
// Check FMT_CPLUSPLUS to suppress a bogus warning in MSVC.
|
||||
# if FMT_CPLUSPLUS >= 201703L
|
||||
# if FMT_HAS_INCLUDE(<filesystem>) && \
|
||||
(!defined(FMT_CPP_LIB_FILESYSTEM) || FMT_CPP_LIB_FILESYSTEM != 0)
|
||||
# include <filesystem>
|
||||
# endif
|
||||
# if FMT_HAS_INCLUDE(<variant>)
|
||||
# include <variant>
|
||||
# endif
|
||||
# if FMT_HAS_INCLUDE(<optional>)
|
||||
# include <optional>
|
||||
# endif
|
||||
# endif
|
||||
// Use > instead of >= in the version check because <source_location> may be
|
||||
// available after C++17 but before C++20 is marked as implemented.
|
||||
# if FMT_CPLUSPLUS > 201703L && FMT_HAS_INCLUDE(<source_location>)
|
||||
# include <source_location>
|
||||
# endif
|
||||
# if FMT_CPLUSPLUS > 202002L && FMT_HAS_INCLUDE(<expected>)
|
||||
# include <expected>
|
||||
# endif
|
||||
#endif // FMT_MODULE
|
||||
|
||||
#if FMT_HAS_INCLUDE(<version>)
|
||||
# include <version>
|
||||
#endif
|
||||
|
||||
// GCC 4 does not support FMT_HAS_INCLUDE.
|
||||
#if FMT_HAS_INCLUDE(<cxxabi.h>) || defined(__GLIBCXX__)
|
||||
# include <cxxabi.h>
|
||||
// Android NDK with gabi++ library on some architectures does not implement
|
||||
// abi::__cxa_demangle().
|
||||
# ifndef __GABIXX_CXXABI_H__
|
||||
# define FMT_HAS_ABI_CXA_DEMANGLE
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef FMT_CPP_LIB_FILESYSTEM
|
||||
// Use the provided definition.
|
||||
#elif defined(__cpp_lib_filesystem)
|
||||
# define FMT_CPP_LIB_FILESYSTEM __cpp_lib_filesystem
|
||||
#else
|
||||
# define FMT_CPP_LIB_FILESYSTEM 0
|
||||
#endif
|
||||
|
||||
#ifdef FMT_CPP_LIB_VARIANT
|
||||
// Use the provided definition.
|
||||
#elif defined(__cpp_lib_variant)
|
||||
# define FMT_CPP_LIB_VARIANT __cpp_lib_variant
|
||||
#else
|
||||
# define FMT_CPP_LIB_VARIANT 0
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
#if FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
template <typename Char, typename PathChar>
|
||||
auto get_path_string(const std::filesystem::path& p,
|
||||
const std::basic_string<PathChar>& native) {
|
||||
if constexpr (std::is_same_v<Char, char> && std::is_same_v<PathChar, wchar_t>)
|
||||
return to_utf8<wchar_t>(native, to_utf8_error_policy::replace);
|
||||
else
|
||||
return p.string<Char>();
|
||||
}
|
||||
|
||||
template <typename Char, typename PathChar>
|
||||
void write_escaped_path(basic_memory_buffer<Char>& quoted,
|
||||
const std::filesystem::path& p,
|
||||
const std::basic_string<PathChar>& native) {
|
||||
if constexpr (std::is_same_v<Char, char> &&
|
||||
std::is_same_v<PathChar, wchar_t>) {
|
||||
auto buf = basic_memory_buffer<wchar_t>();
|
||||
write_escaped_string<wchar_t>(std::back_inserter(buf), native);
|
||||
bool valid = to_utf8<wchar_t>::convert(quoted, {buf.data(), buf.size()});
|
||||
FMT_ASSERT(valid, "invalid utf16");
|
||||
} else if constexpr (std::is_same_v<Char, PathChar>) {
|
||||
write_escaped_string<std::filesystem::path::value_type>(
|
||||
std::back_inserter(quoted), native);
|
||||
} else {
|
||||
write_escaped_string<Char>(std::back_inserter(quoted), p.string<Char>());
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
#if defined(__cpp_lib_expected) || FMT_CPP_LIB_VARIANT
|
||||
|
||||
template <typename Char, typename OutputIt, typename T, typename FormatContext>
|
||||
auto write_escaped_alternative(OutputIt out, const T& v, FormatContext& ctx)
|
||||
-> OutputIt {
|
||||
if constexpr (has_to_string_view<T>::value)
|
||||
return write_escaped_string<Char>(out, detail::to_string_view(v));
|
||||
if constexpr (std::is_same_v<T, Char>) return write_escaped_char(out, v);
|
||||
|
||||
formatter<std::remove_cv_t<T>, Char> underlying;
|
||||
maybe_set_debug_format(underlying, true);
|
||||
return underlying.format(v, ctx);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if FMT_CPP_LIB_VARIANT
|
||||
|
||||
template <typename> struct is_variant_like_ : std::false_type {};
|
||||
template <typename... Types>
|
||||
struct is_variant_like_<std::variant<Types...>> : std::true_type {};
|
||||
|
||||
template <typename Variant, typename Char> class is_variant_formattable {
|
||||
template <size_t... Is>
|
||||
static auto check(std::index_sequence<Is...>) -> std::conjunction<
|
||||
is_formattable<std::variant_alternative_t<Is, Variant>, Char>...>;
|
||||
|
||||
public:
|
||||
static constexpr bool value = decltype(check(
|
||||
std::make_index_sequence<std::variant_size<Variant>::value>()))::value;
|
||||
};
|
||||
|
||||
#endif // FMT_CPP_LIB_VARIANT
|
||||
|
||||
#if FMT_USE_RTTI
|
||||
inline auto normalize_libcxx_inline_namespaces(string_view demangled_name_view,
|
||||
char* begin) -> string_view {
|
||||
// Normalization of stdlib inline namespace names.
|
||||
// libc++ inline namespaces.
|
||||
// std::__1::* -> std::*
|
||||
// std::__1::__fs::* -> std::*
|
||||
// libstdc++ inline namespaces.
|
||||
// std::__cxx11::* -> std::*
|
||||
// std::filesystem::__cxx11::* -> std::filesystem::*
|
||||
if (demangled_name_view.starts_with("std::")) {
|
||||
char* to = begin + 5; // std::
|
||||
for (const char *from = to, *end = begin + demangled_name_view.size();
|
||||
from < end;) {
|
||||
// This is safe, because demangled_name is NUL-terminated.
|
||||
if (from[0] == '_' && from[1] == '_') {
|
||||
const char* next = from + 1;
|
||||
while (next < end && *next != ':') next++;
|
||||
if (next[0] == ':' && next[1] == ':') {
|
||||
from = next + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*to++ = *from++;
|
||||
}
|
||||
demangled_name_view = {begin, detail::to_unsigned(to - begin)};
|
||||
}
|
||||
return demangled_name_view;
|
||||
}
|
||||
|
||||
template <class OutputIt>
|
||||
auto normalize_msvc_abi_name(string_view abi_name_view, OutputIt out)
|
||||
-> OutputIt {
|
||||
const string_view demangled_name(abi_name_view);
|
||||
for (size_t i = 0; i < demangled_name.size(); ++i) {
|
||||
auto sub = demangled_name;
|
||||
sub.remove_prefix(i);
|
||||
if (sub.starts_with("enum ")) {
|
||||
i += 4;
|
||||
continue;
|
||||
}
|
||||
if (sub.starts_with("class ") || sub.starts_with("union ")) {
|
||||
i += 5;
|
||||
continue;
|
||||
}
|
||||
if (sub.starts_with("struct ")) {
|
||||
i += 6;
|
||||
continue;
|
||||
}
|
||||
if (*sub.begin() != ' ') *out++ = *sub.begin();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto write_demangled_name(OutputIt out, const std::type_info& ti) -> OutputIt {
|
||||
# ifdef FMT_HAS_ABI_CXA_DEMANGLE
|
||||
int status = 0;
|
||||
size_t size = 0;
|
||||
std::unique_ptr<char, void (*)(void*)> demangled_name_ptr(
|
||||
abi::__cxa_demangle(ti.name(), nullptr, &size, &status), &free);
|
||||
|
||||
string_view demangled_name_view;
|
||||
if (demangled_name_ptr) {
|
||||
demangled_name_view = normalize_libcxx_inline_namespaces(
|
||||
demangled_name_ptr.get(), demangled_name_ptr.get());
|
||||
} else {
|
||||
demangled_name_view = string_view(ti.name());
|
||||
}
|
||||
return detail::write_bytes<char>(out, demangled_name_view);
|
||||
# elif FMT_MSC_VERSION && defined(_MSVC_STL_UPDATE)
|
||||
return normalize_msvc_abi_name(ti.name(), out);
|
||||
# elif FMT_MSC_VERSION && defined(_LIBCPP_VERSION)
|
||||
const string_view demangled_name = ti.name();
|
||||
std::string name_copy(demangled_name.size(), '\0');
|
||||
// normalize_msvc_abi_name removes class, struct, union etc that MSVC has in
|
||||
// front of types
|
||||
name_copy.erase(normalize_msvc_abi_name(demangled_name, name_copy.begin()),
|
||||
name_copy.end());
|
||||
// normalize_libcxx_inline_namespaces removes the inline __1, __2, etc
|
||||
// namespaces libc++ uses for ABI versioning On MSVC ABI + libc++
|
||||
// environments, we need to eliminate both of them.
|
||||
const string_view normalized_name =
|
||||
normalize_libcxx_inline_namespaces(name_copy, name_copy.data());
|
||||
return detail::write_bytes<char>(out, normalized_name);
|
||||
# else
|
||||
return detail::write_bytes<char>(out, string_view(ti.name()));
|
||||
# endif
|
||||
}
|
||||
|
||||
#endif // FMT_USE_RTTI
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_flip : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_flip<T, void_t<decltype(std::declval<T>().flip())>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T> struct is_bit_reference_like {
|
||||
static constexpr bool value = std::is_convertible<T, bool>::value &&
|
||||
std::is_nothrow_assignable<T, bool>::value &&
|
||||
has_flip<T>::value;
|
||||
};
|
||||
|
||||
// Workaround for libc++ incompatibility with C++ standard.
|
||||
// According to the Standard, `bitset::operator[] const` returns bool.
|
||||
#if defined(_LIBCPP_VERSION) && !defined(FMT_IMPORT_STD)
|
||||
template <typename C>
|
||||
struct is_bit_reference_like<std::__bit_const_reference<C>> {
|
||||
static constexpr bool value = true;
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_format_as : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_format_as<T, void_t<decltype(format_as(std::declval<const T&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_format_as_member : std::false_type {};
|
||||
template <typename T>
|
||||
struct has_format_as_member<
|
||||
T, void_t<decltype(formatter<T>::format_as(std::declval<const T&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T, typename Deleter>
|
||||
auto ptr(const std::unique_ptr<T, Deleter>& p) -> const void* {
|
||||
return p.get();
|
||||
}
|
||||
template <typename T> auto ptr(const std::shared_ptr<T>& p) -> const void* {
|
||||
return p.get();
|
||||
}
|
||||
|
||||
#if FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
template <typename Char> struct formatter<std::filesystem::path, Char> {
|
||||
private:
|
||||
format_specs specs_;
|
||||
detail::arg_ref<Char> width_ref_;
|
||||
bool debug_ = false;
|
||||
char path_type_ = 0;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
|
||||
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_align(it, end, specs_);
|
||||
if (it == end) return it;
|
||||
|
||||
Char c = *it;
|
||||
if ((c >= '0' && c <= '9') || c == '{')
|
||||
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
|
||||
if (it != end && *it == '?') {
|
||||
debug_ = true;
|
||||
++it;
|
||||
}
|
||||
if (it != end && (*it == 'g')) path_type_ = detail::to_ascii(*it++);
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::filesystem::path& p, FormatContext& ctx) const {
|
||||
auto specs = specs_;
|
||||
auto path_string =
|
||||
!path_type_ ? p.native()
|
||||
: p.generic_string<std::filesystem::path::value_type>();
|
||||
|
||||
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
|
||||
ctx);
|
||||
if (!debug_) {
|
||||
auto s = detail::get_path_string<Char>(p, path_string);
|
||||
return detail::write(ctx.out(), basic_string_view<Char>(s), specs);
|
||||
}
|
||||
auto quoted = basic_memory_buffer<Char>();
|
||||
detail::write_escaped_path(quoted, p, path_string);
|
||||
return detail::write(ctx.out(),
|
||||
basic_string_view<Char>(quoted.data(), quoted.size()),
|
||||
specs);
|
||||
}
|
||||
};
|
||||
|
||||
class path : public std::filesystem::path {
|
||||
public:
|
||||
auto display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{}"), base);
|
||||
}
|
||||
auto system_string() const -> std::string { return string(); }
|
||||
|
||||
auto generic_display_string() const -> std::string {
|
||||
const std::filesystem::path& base = *this;
|
||||
return fmt::format(FMT_STRING("{:g}"), base);
|
||||
}
|
||||
auto generic_system_string() const -> std::string { return generic_string(); }
|
||||
};
|
||||
|
||||
#endif // FMT_CPP_LIB_FILESYSTEM
|
||||
|
||||
template <size_t N, typename Char>
|
||||
struct formatter<std::bitset<N>, Char>
|
||||
: nested_formatter<basic_string_view<Char>, Char> {
|
||||
private:
|
||||
// This is a functor because C++11 doesn't support generic lambdas.
|
||||
struct writer {
|
||||
const std::bitset<N>& bs;
|
||||
|
||||
template <typename OutputIt>
|
||||
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
|
||||
for (auto pos = N; pos > 0; --pos)
|
||||
out = detail::write<Char>(out, bs[pos - 1] ? Char('1') : Char('0'));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
template <typename FormatContext>
|
||||
auto format(const std::bitset<N>& bs, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return this->write_padded(ctx, writer{bs});
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct formatter<std::thread::id, Char> : basic_ostream_formatter<Char> {};
|
||||
|
||||
#ifdef __cpp_lib_optional
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::optional<T>, Char,
|
||||
std::enable_if_t<is_formattable<T, Char>::value>> {
|
||||
private:
|
||||
formatter<std::remove_cv_t<T>, Char> underlying_;
|
||||
static constexpr basic_string_view<Char> optional =
|
||||
detail::string_literal<Char, 'o', 'p', 't', 'i', 'o', 'n', 'a', 'l',
|
||||
'('>{};
|
||||
static constexpr basic_string_view<Char> none =
|
||||
detail::string_literal<Char, 'n', 'o', 'n', 'e'>{};
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) {
|
||||
detail::maybe_set_debug_format(underlying_, true);
|
||||
return underlying_.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::optional<T>& opt, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
if (!opt) return detail::write<Char>(ctx.out(), none);
|
||||
|
||||
auto out = ctx.out();
|
||||
out = detail::write<Char>(out, optional);
|
||||
ctx.advance_to(out);
|
||||
out = underlying_.format(*opt, ctx);
|
||||
return detail::write(out, ')');
|
||||
}
|
||||
};
|
||||
#endif // __cpp_lib_optional
|
||||
|
||||
#ifdef __cpp_lib_expected
|
||||
template <typename T, typename E, typename Char>
|
||||
struct formatter<std::expected<T, E>, Char,
|
||||
std::enable_if_t<(std::is_void<T>::value ||
|
||||
is_formattable<T, Char>::value) &&
|
||||
is_formattable<E, Char>::value>> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::expected<T, E>& value, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
|
||||
if (value.has_value()) {
|
||||
out = detail::write<Char>(out, "expected(");
|
||||
if constexpr (!std::is_void<T>::value)
|
||||
out = detail::write_escaped_alternative<Char>(out, *value, ctx);
|
||||
} else {
|
||||
out = detail::write<Char>(out, "unexpected(");
|
||||
out = detail::write_escaped_alternative<Char>(out, value.error(), ctx);
|
||||
}
|
||||
*out++ = ')';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
#endif // __cpp_lib_expected
|
||||
|
||||
#ifdef __cpp_lib_source_location
|
||||
template <> struct formatter<std::source_location> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<>& ctx) { return ctx.begin(); }
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::source_location& loc, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
out = detail::write(out, loc.file_name());
|
||||
out = detail::write(out, ':');
|
||||
out = detail::write<char>(out, loc.line());
|
||||
out = detail::write(out, ':');
|
||||
out = detail::write<char>(out, loc.column());
|
||||
out = detail::write(out, ": ");
|
||||
out = detail::write(out, loc.function_name());
|
||||
return out;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
#if FMT_CPP_LIB_VARIANT
|
||||
|
||||
template <typename T> struct is_variant_like {
|
||||
static constexpr bool value = detail::is_variant_like_<T>::value;
|
||||
};
|
||||
|
||||
template <typename Char> struct formatter<std::monostate, Char> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::monostate&, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return detail::write<Char>(ctx.out(), "monostate");
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Variant, typename Char>
|
||||
struct formatter<Variant, Char,
|
||||
std::enable_if_t<std::conjunction_v<
|
||||
is_variant_like<Variant>,
|
||||
detail::is_variant_formattable<Variant, Char>>>> {
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const Variant& value, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
|
||||
out = detail::write<Char>(out, "variant(");
|
||||
FMT_TRY {
|
||||
std::visit(
|
||||
[&](const auto& v) {
|
||||
out = detail::write_escaped_alternative<Char>(out, v, ctx);
|
||||
},
|
||||
value);
|
||||
}
|
||||
FMT_CATCH(const std::bad_variant_access&) {
|
||||
detail::write<Char>(out, "valueless by exception");
|
||||
}
|
||||
*out++ = ')';
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // FMT_CPP_LIB_VARIANT
|
||||
|
||||
template <> struct formatter<std::error_code> {
|
||||
private:
|
||||
format_specs specs_;
|
||||
detail::arg_ref<char> width_ref_;
|
||||
bool debug_ = false;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR void set_debug_format(bool set = true) { debug_ = set; }
|
||||
|
||||
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
|
||||
auto it = ctx.begin(), end = ctx.end();
|
||||
if (it == end) return it;
|
||||
|
||||
it = detail::parse_align(it, end, specs_);
|
||||
|
||||
char c = *it;
|
||||
if (it != end && ((c >= '0' && c <= '9') || c == '{'))
|
||||
it = detail::parse_width(it, end, specs_, width_ref_, ctx);
|
||||
|
||||
if (it != end && *it == '?') {
|
||||
debug_ = true;
|
||||
++it;
|
||||
}
|
||||
if (it != end && *it == 's') {
|
||||
specs_.set_type(presentation_type::string);
|
||||
++it;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR20 auto format(const std::error_code& ec,
|
||||
FormatContext& ctx) const -> decltype(ctx.out()) {
|
||||
auto specs = specs_;
|
||||
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width, width_ref_,
|
||||
ctx);
|
||||
auto buf = memory_buffer();
|
||||
if (specs_.type() == presentation_type::string) {
|
||||
buf.append(ec.message());
|
||||
} else {
|
||||
buf.append(string_view(ec.category().name()));
|
||||
buf.push_back(':');
|
||||
detail::write<char>(appender(buf), ec.value());
|
||||
}
|
||||
auto quoted = memory_buffer();
|
||||
auto str = string_view(buf.data(), buf.size());
|
||||
if (debug_) {
|
||||
detail::write_escaped_string<char>(std::back_inserter(quoted), str);
|
||||
str = string_view(quoted.data(), quoted.size());
|
||||
}
|
||||
return detail::write<char>(ctx.out(), str, specs);
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_RTTI
|
||||
template <> struct formatter<std::type_info> {
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
auto format(const std::type_info& ti, Context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return detail::write_demangled_name(ctx.out(), ti);
|
||||
}
|
||||
};
|
||||
#endif // FMT_USE_RTTI
|
||||
|
||||
template <typename T>
|
||||
struct formatter<
|
||||
T, char,
|
||||
typename std::enable_if<std::is_base_of<std::exception, T>::value>::type> {
|
||||
private:
|
||||
bool with_typename_ = false;
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(parse_context<>& ctx) -> const char* {
|
||||
auto it = ctx.begin();
|
||||
auto end = ctx.end();
|
||||
if (it == end || *it == '}') return it;
|
||||
if (*it == 't') {
|
||||
++it;
|
||||
with_typename_ = FMT_USE_RTTI != 0;
|
||||
}
|
||||
return it;
|
||||
}
|
||||
|
||||
template <typename Context>
|
||||
auto format(const std::exception& ex, Context& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
#if FMT_USE_RTTI
|
||||
if (with_typename_) {
|
||||
out = detail::write_demangled_name(out, typeid(ex));
|
||||
*out++ = ':';
|
||||
*out++ = ' ';
|
||||
}
|
||||
#endif
|
||||
return detail::write_bytes<char>(out, string_view(ex.what()));
|
||||
}
|
||||
};
|
||||
|
||||
// We can't use std::vector<bool, Allocator>::reference and
|
||||
// std::bitset<N>::reference because the compiler can't deduce Allocator and N
|
||||
// in partial specialization.
|
||||
template <typename BitRef, typename Char>
|
||||
struct formatter<BitRef, Char,
|
||||
enable_if_t<detail::is_bit_reference_like<BitRef>::value>>
|
||||
: formatter<bool, Char> {
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const BitRef& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<bool, Char>::format(v, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::atomic<T>, Char,
|
||||
enable_if_t<is_formattable<T, Char>::value>>
|
||||
: formatter<T, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::atomic<T>& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<T, Char>::format(v.load(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef __cpp_lib_atomic_flag_test
|
||||
template <typename Char>
|
||||
struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(const std::atomic_flag& v, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<bool, Char>::format(v.test(), ctx);
|
||||
}
|
||||
};
|
||||
#endif // __cpp_lib_atomic_flag_test
|
||||
|
||||
template <typename T, typename Char> struct formatter<std::complex<T>, Char> {
|
||||
private:
|
||||
detail::dynamic_format_specs<Char> specs_;
|
||||
|
||||
template <typename FormatContext, typename OutputIt>
|
||||
FMT_CONSTEXPR auto do_format(const std::complex<T>& c,
|
||||
detail::dynamic_format_specs<Char>& specs,
|
||||
FormatContext& ctx, OutputIt out) const
|
||||
-> OutputIt {
|
||||
if (c.real() != 0) {
|
||||
*out++ = Char('(');
|
||||
out = detail::write<Char>(out, c.real(), specs, ctx.locale());
|
||||
specs.set_sign(sign::plus);
|
||||
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
|
||||
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
|
||||
*out++ = Char('i');
|
||||
*out++ = Char(')');
|
||||
return out;
|
||||
}
|
||||
out = detail::write<Char>(out, c.imag(), specs, ctx.locale());
|
||||
if (!detail::isfinite(c.imag())) *out++ = Char(' ');
|
||||
*out++ = Char('i');
|
||||
return out;
|
||||
}
|
||||
|
||||
public:
|
||||
FMT_CONSTEXPR auto parse(parse_context<Char>& ctx) -> const Char* {
|
||||
if (ctx.begin() == ctx.end() || *ctx.begin() == '}') return ctx.begin();
|
||||
return parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
|
||||
detail::type_constant<T, Char>::value);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const std::complex<T>& c, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto specs = specs_;
|
||||
if (specs.dynamic()) {
|
||||
detail::handle_dynamic_spec(specs.dynamic_width(), specs.width,
|
||||
specs.width_ref, ctx);
|
||||
detail::handle_dynamic_spec(specs.dynamic_precision(), specs.precision,
|
||||
specs.precision_ref, ctx);
|
||||
}
|
||||
|
||||
if (specs.width == 0) return do_format(c, specs, ctx, ctx.out());
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
|
||||
auto outer_specs = format_specs();
|
||||
outer_specs.width = specs.width;
|
||||
outer_specs.copy_fill_from(specs);
|
||||
outer_specs.set_align(specs.align());
|
||||
|
||||
specs.width = 0;
|
||||
specs.set_fill({});
|
||||
specs.set_align(align::none);
|
||||
|
||||
do_format(c, specs, ctx, basic_appender<Char>(buf));
|
||||
return detail::write<Char>(ctx.out(),
|
||||
basic_string_view<Char>(buf.data(), buf.size()),
|
||||
outer_specs);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<std::reference_wrapper<T>, Char,
|
||||
// Guard against format_as because reference_wrapper is
|
||||
// implicitly convertible to T&.
|
||||
enable_if_t<is_formattable<remove_cvref_t<T>, Char>::value &&
|
||||
!detail::has_format_as<T>::value &&
|
||||
!detail::has_format_as_member<T>::value>>
|
||||
: formatter<remove_cvref_t<T>, Char> {
|
||||
template <typename FormatContext>
|
||||
auto format(std::reference_wrapper<T> ref, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
return formatter<remove_cvref_t<T>, Char>::format(ref.get(), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_STD_H_
|
||||
+265
-120
@@ -5,53 +5,136 @@
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_WCHAR_H_
|
||||
#define FMT_WCHAR_H_
|
||||
|
||||
#include <cwchar>
|
||||
#ifndef FMT_XCHAR_H_
|
||||
#define FMT_XCHAR_H_
|
||||
|
||||
#include "color.h"
|
||||
#include "format.h"
|
||||
#include "ostream.h"
|
||||
#include "ranges.h"
|
||||
|
||||
#ifndef FMT_MODULE
|
||||
# include <cwchar>
|
||||
# if FMT_USE_LOCALE
|
||||
# include <locale>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
|
||||
|
||||
template <typename S, typename = void> struct format_string_char {};
|
||||
|
||||
template <typename S>
|
||||
struct format_string_char<
|
||||
S, void_t<decltype(sizeof(detail::to_string_view(std::declval<S>())))>> {
|
||||
using type = char_t<S>;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
struct format_string_char<
|
||||
S, enable_if_t<std::is_base_of<detail::compile_string, S>::value>> {
|
||||
using type = typename S::char_type;
|
||||
};
|
||||
|
||||
template <typename S>
|
||||
using format_string_char_t = typename format_string_char<S>::type;
|
||||
|
||||
inline auto write_loc(basic_appender<wchar_t> out, loc_value value,
|
||||
const format_specs& specs, locale_ref loc) -> bool {
|
||||
#if FMT_USE_LOCALE
|
||||
auto& numpunct =
|
||||
std::use_facet<std::numpunct<wchar_t>>(loc.get<std::locale>());
|
||||
auto separator = std::wstring();
|
||||
auto grouping = numpunct.grouping();
|
||||
if (!grouping.empty()) separator = std::wstring(1, numpunct.thousands_sep());
|
||||
return value.visit(loc_writer<wchar_t>{out, specs, separator, grouping, {}});
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
template <typename Char>
|
||||
void vformat_to(buffer<Char>& buf, basic_string_view<Char> fmt,
|
||||
basic_format_args<buffered_context<Char>> args,
|
||||
locale_ref loc = {}) {
|
||||
static_assert(!std::is_same<Char, char>::value, "");
|
||||
auto out = basic_appender<Char>(buf);
|
||||
parse_format_string(
|
||||
fmt, format_handler<Char>{parse_context<Char>(fmt), {out, args, loc}});
|
||||
}
|
||||
} // namespace detail
|
||||
|
||||
FMT_BEGIN_EXPORT
|
||||
|
||||
using wstring_view = basic_string_view<wchar_t>;
|
||||
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
||||
using wformat_context = buffer_context<wchar_t>;
|
||||
using wformat_parse_context = parse_context<wchar_t>;
|
||||
using wformat_context = buffered_context<wchar_t>;
|
||||
using wformat_args = basic_format_args<wformat_context>;
|
||||
using wmemory_buffer = basic_memory_buffer<wchar_t>;
|
||||
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
// Workaround broken conversion on older gcc.
|
||||
template <typename... Args> using wformat_string = wstring_view;
|
||||
#else
|
||||
template <typename... Args>
|
||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
||||
#endif
|
||||
template <typename Char, typename... T> struct basic_fstring {
|
||||
private:
|
||||
basic_string_view<Char> str_;
|
||||
|
||||
template <typename... Args>
|
||||
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
|
||||
const Args&... args) {
|
||||
return {args...};
|
||||
static constexpr int num_static_named_args =
|
||||
detail::count_static_named_args<T...>();
|
||||
|
||||
using checker = detail::format_string_checker<
|
||||
Char, static_cast<int>(sizeof...(T)), num_static_named_args,
|
||||
num_static_named_args != detail::count_named_args<T...>()>;
|
||||
|
||||
using arg_pack = detail::arg_pack<T...>;
|
||||
|
||||
public:
|
||||
using t = basic_fstring;
|
||||
|
||||
template <typename S,
|
||||
FMT_ENABLE_IF(
|
||||
std::is_convertible<const S&, basic_string_view<Char>>::value)>
|
||||
FMT_CONSTEVAL FMT_ALWAYS_INLINE basic_fstring(const S& s) : str_(s) {
|
||||
if (FMT_USE_CONSTEVAL)
|
||||
detail::parse_format_string<Char>(s, checker(s, arg_pack()));
|
||||
}
|
||||
template <typename S,
|
||||
FMT_ENABLE_IF(std::is_base_of<detail::compile_string, S>::value&&
|
||||
std::is_same<typename S::char_type, Char>::value)>
|
||||
FMT_ALWAYS_INLINE basic_fstring(const S&) : str_(S()) {
|
||||
FMT_CONSTEXPR auto sv = basic_string_view<Char>(S());
|
||||
FMT_CONSTEXPR int ignore =
|
||||
(parse_format_string(sv, checker(sv, arg_pack())), 0);
|
||||
detail::ignore_unused(ignore);
|
||||
}
|
||||
basic_fstring(runtime_format_string<Char> fmt) : str_(fmt.str) {}
|
||||
|
||||
operator basic_string_view<Char>() const { return str_; }
|
||||
auto get() const -> basic_string_view<Char> { return str_; }
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
using basic_format_string = basic_fstring<Char, T...>;
|
||||
|
||||
template <typename... T>
|
||||
using wformat_string = typename basic_format_string<wchar_t, T...>::t;
|
||||
inline auto runtime(wstring_view s) -> runtime_format_string<wchar_t> {
|
||||
return {{s}};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
constexpr auto make_wformat_args(T&... args)
|
||||
-> decltype(fmt::make_format_args<wformat_context>(args...)) {
|
||||
return fmt::make_format_args<wformat_context>(args...);
|
||||
}
|
||||
|
||||
#if !FMT_USE_NONTYPE_TEMPLATE_ARGS
|
||||
inline namespace literals {
|
||||
constexpr auto operator"" _format(const wchar_t* s, size_t n)
|
||||
-> detail::udl_formatter<wchar_t> {
|
||||
return {{s, n}};
|
||||
}
|
||||
|
||||
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
|
||||
inline auto operator""_a(const wchar_t* s, size_t) -> detail::udl_arg<wchar_t> {
|
||||
return {s};
|
||||
}
|
||||
#endif
|
||||
} // namespace literals
|
||||
#endif
|
||||
|
||||
template <typename It, typename Sentinel>
|
||||
auto join(It begin, Sentinel end, wstring_view sep)
|
||||
@@ -59,9 +142,9 @@ auto join(It begin, Sentinel end, wstring_view sep)
|
||||
return {begin, end, sep};
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
template <typename Range, FMT_ENABLE_IF(!is_tuple_like<Range>::value)>
|
||||
auto join(Range&& range, wstring_view sep)
|
||||
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
|
||||
-> join_view<decltype(std::begin(range)), decltype(std::end(range)),
|
||||
wchar_t> {
|
||||
return join(std::begin(range), std::end(range), sep);
|
||||
}
|
||||
@@ -72,118 +155,144 @@ auto join(std::initializer_list<T> list, wstring_view sep)
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const Locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
return detail::vformat(loc, to_string_view(format_str), args);
|
||||
template <typename Tuple, FMT_ENABLE_IF(is_tuple_like<Tuple>::value)>
|
||||
auto join(const Tuple& tuple, basic_string_view<wchar_t> sep)
|
||||
-> tuple_join_view<Tuple, wchar_t> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline std::basic_string<Char> format(const Locale& loc, const S& format_str,
|
||||
Args&&... args) {
|
||||
return detail::vformat(loc, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto vformat(basic_string_view<Char> fmt,
|
||||
basic_format_args<buffered_context<Char>> args)
|
||||
-> std::basic_string<Char> {
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vformat_to(buf, fmt, args);
|
||||
return {buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename Char = char_t<S>,
|
||||
template <typename... T>
|
||||
auto format(wformat_string<T...> fmt, T&&... args) -> std::wstring {
|
||||
return vformat(fmt::wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename... T>
|
||||
auto format_to(OutputIt out, wformat_string<T...> fmt, T&&... args)
|
||||
-> OutputIt {
|
||||
return vformat_to(out, fmt::wstring_view(fmt),
|
||||
fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
// Pass char_t as a default template parameter instead of using
|
||||
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||
template <typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value &&
|
||||
!std::is_same<Char, wchar_t>::value)>
|
||||
auto format(const S& fmt, T&&... args) -> std::basic_string<Char> {
|
||||
return vformat(detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat(locale_ref loc, const S& fmt,
|
||||
basic_format_args<buffered_context<Char>> args)
|
||||
-> std::basic_string<Char> {
|
||||
auto buf = basic_memory_buffer<Char>();
|
||||
detail::vformat_to(buf, detail::to_string_view(fmt), args, loc);
|
||||
return {buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
template <typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||
inline auto format(locale_ref loc, const S& fmt, T&&... args)
|
||||
-> std::basic_string<Char> {
|
||||
return vformat(loc, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
auto vformat_to(OutputIt out, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
auto vformat_to(OutputIt out, const S& fmt,
|
||||
basic_format_args<buffered_context<Char>> args) -> OutputIt {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, detail::to_string_view(fmt), args);
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value &&
|
||||
!std::is_same<Char, char>::value &&
|
||||
!std::is_same<Char, wchar_t>::value)>
|
||||
inline auto format_to(OutputIt out, const S& fmt, T&&... args) -> OutputIt {
|
||||
return vformat_to(out, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename OutputIt, typename... Args,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat_to(OutputIt out, locale_ref loc, const S& fmt,
|
||||
basic_format_args<buffered_context<Char>> args)
|
||||
-> OutputIt {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, to_string_view(format_str), args);
|
||||
return detail::get_iterator(buf);
|
||||
vformat_to(buf, detail::to_string_view(fmt), args, loc);
|
||||
return detail::get_iterator(buf, out);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||
return vformat_to(out, to_string_view(fmt), vargs);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char, size_t SIZE,
|
||||
typename Allocator, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
FMT_DEPRECATED auto format_to(basic_memory_buffer<Char, SIZE, Allocator>& buf,
|
||||
const S& format_str, Args&&... args) ->
|
||||
typename buffer_context<Char>::iterator {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
detail::vformat_to(buf, to_string_view(format_str), vargs);
|
||||
return detail::buffer_appender<Char>(buf);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename OutputIt, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline OutputIt vformat_to(
|
||||
OutputIt out, const Locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
|
||||
return detail::get_iterator(buf);
|
||||
}
|
||||
|
||||
template <
|
||||
typename OutputIt, typename Locale, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
|
||||
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
|
||||
Args&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
return vformat_to(out, loc, to_string_view(format_str), vargs);
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value &&
|
||||
detail::is_exotic_char<Char>::value>
|
||||
inline auto format_to(OutputIt out, locale_ref loc, const S& fmt, T&&... args)
|
||||
-> typename std::enable_if<enable, OutputIt>::type {
|
||||
return vformat_to(out, loc, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat_to_n(
|
||||
OutputIt out, size_t n, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
inline auto vformat_to_n(OutputIt out, size_t n, basic_string_view<Char> fmt,
|
||||
basic_format_args<buffered_context<Char>> args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
|
||||
n);
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
using traits = detail::fixed_buffer_traits;
|
||||
auto buf = detail::iterator_buffer<OutputIt, Char, traits>(out, n);
|
||||
detail::vformat_to(buf, fmt, args);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
template <typename OutputIt, typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
|
||||
const Args&... args) -> format_to_n_result<OutputIt> {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||
return vformat_to_n(out, n, to_string_view(fmt), vargs);
|
||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt, T&&... args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
return vformat_to_n(out, n, fmt::basic_string_view<Char>(fmt),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||
template <typename S, typename... T,
|
||||
typename Char = detail::format_string_char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
|
||||
detail::counting_buffer<Char> buf;
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||
detail::vformat_to(buf, to_string_view(fmt), vargs);
|
||||
inline auto formatted_size(const S& fmt, T&&... args) -> size_t {
|
||||
auto buf = detail::counting_buffer<Char>();
|
||||
detail::vformat_to(buf, detail::to_string_view(fmt),
|
||||
fmt::make_format_args<buffered_context<Char>>(args...));
|
||||
return buf.count();
|
||||
}
|
||||
|
||||
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
|
||||
wmemory_buffer buffer;
|
||||
detail::vformat_to(buffer, fmt, args);
|
||||
buffer.push_back(L'\0');
|
||||
if (std::fputws(buffer.data(), f) == -1)
|
||||
FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
auto buf = wmemory_buffer();
|
||||
detail::vformat_to(buf, fmt, args);
|
||||
buf.push_back(L'\0');
|
||||
if (std::fputws(buf.data(), f) == -1)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
}
|
||||
|
||||
inline void vprint(wstring_view fmt, wformat_args args) {
|
||||
@@ -192,20 +301,56 @@ inline void vprint(wstring_view fmt, wformat_args args) {
|
||||
|
||||
template <typename... T>
|
||||
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(f, wstring_view(fmt), make_wformat_args(args...));
|
||||
return vprint(f, wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(wstring_view(fmt), make_wformat_args(args...));
|
||||
return vprint(wstring_view(fmt), fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
/**
|
||||
Converts *value* to ``std::wstring`` using the default format for type *T*.
|
||||
*/
|
||||
template <typename T> inline std::wstring to_wstring(const T& value) {
|
||||
template <typename... T>
|
||||
void println(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||
return print(f, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
template <typename... T> void println(wformat_string<T...> fmt, T&&... args) {
|
||||
return print(L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
inline auto vformat(text_style ts, wstring_view fmt, wformat_args args)
|
||||
-> std::wstring {
|
||||
auto buf = wmemory_buffer();
|
||||
detail::vformat_to(buf, ts, fmt, args);
|
||||
return {buf.data(), buf.size()};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
inline auto format(text_style ts, wformat_string<T...> fmt, T&&... args)
|
||||
-> std::wstring {
|
||||
return fmt::vformat(ts, fmt, fmt::make_wformat_args(args...));
|
||||
}
|
||||
|
||||
inline void vprint(std::wostream& os, wstring_view fmt, wformat_args args) {
|
||||
auto buffer = basic_memory_buffer<wchar_t>();
|
||||
detail::vformat_to(buffer, fmt, args);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
void print(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
|
||||
vprint(os, fmt, fmt::make_format_args<buffered_context<wchar_t>>(args...));
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
void println(std::wostream& os, wformat_string<T...> fmt, T&&... args) {
|
||||
print(os, L"{}\n", fmt::format(fmt, std::forward<T>(args)...));
|
||||
}
|
||||
|
||||
/// Converts `value` to `std::wstring` using the default format for type `T`.
|
||||
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
||||
return format(FMT_STRING(L"{}"), value);
|
||||
}
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_EXPORT
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_WCHAR_H_
|
||||
#endif // FMT_XCHAR_H_
|
||||
|
||||
Vendored
+109
-54
@@ -1,45 +1,67 @@
|
||||
module;
|
||||
// put all implementation-provided headers into the global module fragment
|
||||
// to prevent attachment to this module
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
|
||||
#define FMT_MODULE
|
||||
|
||||
#ifdef _MSVC_LANG
|
||||
# define FMT_CPLUSPLUS _MSVC_LANG
|
||||
#else
|
||||
# define FMT_CPLUSPLUS __cplusplus
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
// Put all implementation-provided headers into the global module fragment
|
||||
// to prevent attachment to this module.
|
||||
#ifndef FMT_IMPORT_STD
|
||||
# include <algorithm>
|
||||
# include <bitset>
|
||||
# include <chrono>
|
||||
# include <cmath>
|
||||
# include <complex>
|
||||
# include <cstddef>
|
||||
# include <cstdint>
|
||||
# include <cstdio>
|
||||
# include <cstdlib>
|
||||
# include <cstring>
|
||||
# include <ctime>
|
||||
# include <exception>
|
||||
# if FMT_CPLUSPLUS > 202002L
|
||||
# include <expected>
|
||||
# endif
|
||||
# include <filesystem>
|
||||
# include <fstream>
|
||||
# include <functional>
|
||||
# include <iterator>
|
||||
# include <limits>
|
||||
# include <locale>
|
||||
# include <memory>
|
||||
# include <optional>
|
||||
# include <ostream>
|
||||
# include <source_location>
|
||||
# include <stdexcept>
|
||||
# include <string>
|
||||
# include <string_view>
|
||||
# include <system_error>
|
||||
# include <thread>
|
||||
# include <type_traits>
|
||||
# include <typeinfo>
|
||||
# include <utility>
|
||||
# include <variant>
|
||||
# include <vector>
|
||||
#else
|
||||
# include <limits.h>
|
||||
# include <stdint.h>
|
||||
# include <stdio.h>
|
||||
# include <stdlib.h>
|
||||
# include <string.h>
|
||||
# include <time.h>
|
||||
#endif
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <clocale>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <cwchar>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <version>
|
||||
|
||||
#if _MSC_VER
|
||||
#if __has_include(<cxxabi.h>)
|
||||
# include <cxxabi.h>
|
||||
#endif
|
||||
#if defined(_MSC_VER) || defined(__MINGW32__)
|
||||
# include <intrin.h>
|
||||
#endif
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
@@ -61,40 +83,73 @@ module;
|
||||
# endif
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
# if defined(__GLIBCXX__)
|
||||
# include <ext/stdio_filebuf.h>
|
||||
# include <ext/stdio_sync_filebuf.h>
|
||||
# endif
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
export module fmt;
|
||||
|
||||
#define FMT_MODULE_EXPORT export
|
||||
#define FMT_MODULE_EXPORT_BEGIN export {
|
||||
#define FMT_MODULE_EXPORT_END }
|
||||
#define FMT_BEGIN_DETAIL_NAMESPACE \
|
||||
} \
|
||||
namespace detail {
|
||||
#define FMT_END_DETAIL_NAMESPACE \
|
||||
} \
|
||||
export {
|
||||
|
||||
#if defined(_MSC_FULL_VER) && _MSC_FULL_VER > 192930036
|
||||
#define FMT_USE_NONTYPE_TEMPLATE_PARAMETERS 0
|
||||
#ifdef FMT_IMPORT_STD
|
||||
import std;
|
||||
#endif
|
||||
|
||||
// all library-provided declarations and definitions
|
||||
// must be in the module purview to be exported
|
||||
#define FMT_EXPORT export
|
||||
#define FMT_BEGIN_EXPORT export {
|
||||
#define FMT_END_EXPORT }
|
||||
|
||||
// If you define FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
// - all declarations are detached from module 'fmt'
|
||||
// - the module behaves like a traditional static library, too
|
||||
// - all library symbols are mangled traditionally
|
||||
// - you can mix TUs with either importing or #including the {fmt} API
|
||||
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
extern "C++" {
|
||||
#endif
|
||||
|
||||
#ifndef FMT_OS
|
||||
# define FMT_OS 1
|
||||
#endif
|
||||
|
||||
// All library-provided declarations and definitions must be in the module
|
||||
// purview to be exported.
|
||||
#include "fmt/args.h"
|
||||
#include "fmt/chrono.h"
|
||||
#include "fmt/color.h"
|
||||
#include "fmt/compile.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/os.h"
|
||||
#if FMT_OS
|
||||
# include "fmt/os.h"
|
||||
#endif
|
||||
#include "fmt/ostream.h"
|
||||
#include "fmt/printf.h"
|
||||
#include "fmt/ranges.h"
|
||||
#include "fmt/std.h"
|
||||
#include "fmt/xchar.h"
|
||||
|
||||
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
}
|
||||
#endif
|
||||
|
||||
// gcc doesn't yet implement private module fragments
|
||||
#if !FMT_GCC_VERSION
|
||||
module : private;
|
||||
module :private;
|
||||
#endif
|
||||
|
||||
#include "format.cc"
|
||||
#include "os.cc"
|
||||
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
extern "C++" {
|
||||
#endif
|
||||
|
||||
#if FMT_HAS_INCLUDE("format.cc")
|
||||
# include "format.cc"
|
||||
#endif
|
||||
#if FMT_OS && FMT_HAS_INCLUDE("os.cc")
|
||||
# include "os.cc"
|
||||
#endif
|
||||
|
||||
#ifdef FMT_ATTACH_TO_GLOBAL_MODULE
|
||||
}
|
||||
#endif
|
||||
|
||||
+21
-53
@@ -8,68 +8,36 @@
|
||||
#include "fmt/format-inl.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#if FMT_USE_LOCALE
|
||||
template FMT_API locale_ref::locale_ref(const std::locale& loc); // DEPRECATED!
|
||||
template FMT_API auto locale_ref::get<std::locale>() const -> std::locale;
|
||||
#endif
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
T value) {
|
||||
#ifdef FMT_FUZZ
|
||||
if (precision > 100000)
|
||||
throw std::runtime_error(
|
||||
"fuzz mode - avoid large allocation inside snprintf");
|
||||
#endif
|
||||
// Suppress the warning about nonliteral format string.
|
||||
int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
|
||||
return precision < 0 ? snprintf_ptr(buf, size, format, value)
|
||||
: snprintf_ptr(buf, size, format, precision, value);
|
||||
}
|
||||
|
||||
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
|
||||
FMT_NOEXCEPT;
|
||||
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
|
||||
FMT_NOEXCEPT;
|
||||
} // namespace detail
|
||||
|
||||
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
||||
int (*instantiate_format_float)(double, int, detail::float_specs,
|
||||
detail::buffer<char>&) = detail::format_float;
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
template FMT_API detail::locale_ref::locale_ref(const std::locale& loc);
|
||||
template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
|
||||
#endif
|
||||
template FMT_API auto dragonbox::to_decimal(float x) noexcept
|
||||
-> dragonbox::decimal_fp<float>;
|
||||
template FMT_API auto dragonbox::to_decimal(double x) noexcept
|
||||
-> dragonbox::decimal_fp<double>;
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template FMT_API std::string detail::grouping_impl<char>(locale_ref);
|
||||
template FMT_API char detail::thousands_sep_impl(locale_ref);
|
||||
template FMT_API char detail::decimal_point_impl(locale_ref);
|
||||
template FMT_API auto thousands_sep_impl(locale_ref)
|
||||
-> thousands_sep_result<char>;
|
||||
template FMT_API auto decimal_point_impl(locale_ref) -> char;
|
||||
|
||||
template FMT_API void detail::buffer<char>::append(const char*, const char*);
|
||||
|
||||
template FMT_API void detail::vformat_to(
|
||||
detail::buffer<char>&, string_view,
|
||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
|
||||
|
||||
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::snprintf_float(long double, int,
|
||||
detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::format_float(double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::format_float(long double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
// DEPRECATED!
|
||||
template FMT_API void buffer<char>::append(const char*, const char*);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template FMT_API std::string detail::grouping_impl<wchar_t>(locale_ref);
|
||||
template FMT_API wchar_t detail::thousands_sep_impl(locale_ref);
|
||||
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
|
||||
template FMT_API auto thousands_sep_impl(locale_ref)
|
||||
-> thousands_sep_result<wchar_t>;
|
||||
template FMT_API auto decimal_point_impl(locale_ref) -> wchar_t;
|
||||
|
||||
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
|
||||
const wchar_t*);
|
||||
|
||||
template struct detail::basic_data<void>;
|
||||
// DEPRECATED!
|
||||
template FMT_API void buffer<wchar_t>::append(const wchar_t*, const wchar_t*);
|
||||
|
||||
} // namespace detail
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
Vendored
+170
-132
@@ -12,43 +12,51 @@
|
||||
|
||||
#include "fmt/os.h"
|
||||
|
||||
#include <climits>
|
||||
#ifndef FMT_MODULE
|
||||
# include <climits>
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# if FMT_USE_FCNTL
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
|
||||
# ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <io.h>
|
||||
|
||||
# define O_CREAT _O_CREAT
|
||||
# define O_TRUNC _O_TRUNC
|
||||
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# ifdef _WRS_KERNEL // VxWorks7 kernel
|
||||
# include <ioLib.h> // getpagesize
|
||||
# endif
|
||||
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
# ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <io.h>
|
||||
# endif // _WIN32
|
||||
# endif // FMT_USE_FCNTL
|
||||
|
||||
# ifdef __MINGW32__
|
||||
# define _SH_DENYNO 0x40
|
||||
# endif
|
||||
# endif // _WIN32
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
# ifdef _WIN32
|
||||
# include <windows.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifdef fileno
|
||||
# undef fileno
|
||||
#ifdef _WIN32
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# endif
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
# ifndef S_IRGRP
|
||||
# define S_IRGRP 0
|
||||
# endif
|
||||
# ifndef S_IWGRP
|
||||
# define S_IWGRP 0
|
||||
# endif
|
||||
# ifndef S_IROTH
|
||||
# define S_IROTH 0
|
||||
# endif
|
||||
# ifndef S_IWOTH
|
||||
# define S_IWOTH 0
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
@@ -58,48 +66,20 @@ using rwresult = int;
|
||||
|
||||
// On Windows the count argument to read and write is unsigned, so convert
|
||||
// it from size_t preventing integer overflow.
|
||||
inline unsigned convert_rwcount(std::size_t count) {
|
||||
inline unsigned convert_rwcount(size_t count) {
|
||||
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
||||
}
|
||||
#elif FMT_USE_FCNTL
|
||||
// Return type of read and write functions.
|
||||
using rwresult = ssize_t;
|
||||
|
||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
inline auto convert_rwcount(size_t count) -> size_t { return count; }
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#ifdef _WIN32
|
||||
detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
|
||||
if (int error_code = convert(s)) {
|
||||
FMT_THROW(windows_error(error_code,
|
||||
"cannot convert string from UTF-16 to UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
|
||||
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
// WideCharToMultiByte does not support zero length, handle separately.
|
||||
buffer_.resize(1);
|
||||
buffer_[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
|
||||
nullptr, nullptr);
|
||||
if (length == 0) return GetLastError();
|
||||
buffer_.resize(length + 1);
|
||||
length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
|
||||
length, nullptr, nullptr);
|
||||
if (length == 0) return GetLastError();
|
||||
buffer_[length] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
class system_message {
|
||||
@@ -109,7 +89,7 @@ class system_message {
|
||||
unsigned long result_;
|
||||
wchar_t* message_;
|
||||
|
||||
static bool is_whitespace(wchar_t c) FMT_NOEXCEPT {
|
||||
static bool is_whitespace(wchar_t c) noexcept {
|
||||
return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0';
|
||||
}
|
||||
|
||||
@@ -128,20 +108,20 @@ class system_message {
|
||||
}
|
||||
}
|
||||
~system_message() { LocalFree(message_); }
|
||||
explicit operator bool() const FMT_NOEXCEPT { return result_ != 0; }
|
||||
operator basic_string_view<wchar_t>() const FMT_NOEXCEPT {
|
||||
explicit operator bool() const noexcept { return result_ != 0; }
|
||||
operator basic_string_view<wchar_t>() const noexcept {
|
||||
return basic_string_view<wchar_t>(message_, result_);
|
||||
}
|
||||
};
|
||||
|
||||
class utf8_system_category final : public std::error_category {
|
||||
public:
|
||||
const char* name() const FMT_NOEXCEPT override { return "system"; }
|
||||
const char* name() const noexcept override { return "system"; }
|
||||
std::string message(int error_code) const override {
|
||||
system_message msg(error_code);
|
||||
auto&& msg = system_message(error_code);
|
||||
if (msg) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
||||
auto utf8_message = to_utf8<wchar_t>();
|
||||
if (utf8_message.convert(msg)) {
|
||||
return utf8_message.str();
|
||||
}
|
||||
}
|
||||
@@ -151,7 +131,7 @@ class utf8_system_category final : public std::error_category {
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_API const std::error_category& system_category() FMT_NOEXCEPT {
|
||||
FMT_API const std::error_category& system_category() noexcept {
|
||||
static const detail::utf8_system_category category;
|
||||
return category;
|
||||
}
|
||||
@@ -163,13 +143,14 @@ std::system_error vwindows_error(int err_code, string_view format_str,
|
||||
}
|
||||
|
||||
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||
const char* message) FMT_NOEXCEPT {
|
||||
const char* message) noexcept {
|
||||
FMT_TRY {
|
||||
system_message msg(error_code);
|
||||
auto&& msg = system_message(error_code);
|
||||
if (msg) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
||||
format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
|
||||
auto utf8_message = to_utf8<wchar_t>();
|
||||
if (utf8_message.convert(msg)) {
|
||||
fmt::format_to(appender(out), FMT_STRING("{}: {}"), message,
|
||||
string_view(utf8_message));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -178,12 +159,12 @@ void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
void report_windows_error(int error_code, const char* message) FMT_NOEXCEPT {
|
||||
report_error(detail::format_windows_error, error_code, message);
|
||||
void report_windows_error(int error_code, const char* message) noexcept {
|
||||
do_report_error(detail::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
buffered_file::~buffered_file() FMT_NOEXCEPT {
|
||||
buffered_file::~buffered_file() noexcept {
|
||||
if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
|
||||
report_system_error(errno, "cannot close file");
|
||||
}
|
||||
@@ -192,39 +173,57 @@ buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
|
||||
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
|
||||
nullptr);
|
||||
if (!file_)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot open file {}"),
|
||||
filename.c_str()));
|
||||
}
|
||||
|
||||
void buffered_file::close() {
|
||||
if (!file_) return;
|
||||
int result = FMT_SYSTEM(fclose(file_));
|
||||
file_ = nullptr;
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
||||
}
|
||||
|
||||
// A macro used to prevent expansion of fileno on broken versions of MinGW.
|
||||
#define FMT_ARGS
|
||||
|
||||
int buffered_file::fileno() const {
|
||||
int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
|
||||
if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor"));
|
||||
auto buffered_file::descriptor() const -> int {
|
||||
#ifdef FMT_HAS_SYSTEM
|
||||
// fileno is a macro on OpenBSD.
|
||||
# ifdef fileno
|
||||
# undef fileno
|
||||
# endif
|
||||
int fd = FMT_POSIX_CALL(fileno(file_));
|
||||
#elif defined(_WIN32)
|
||||
int fd = _fileno(file_);
|
||||
#else
|
||||
int fd = fileno(file_);
|
||||
#endif
|
||||
if (fd == -1)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot get file descriptor")));
|
||||
return fd;
|
||||
}
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
# ifdef _WIN32
|
||||
using mode_t = int;
|
||||
# endif
|
||||
|
||||
constexpr mode_t default_open_mode =
|
||||
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
|
||||
|
||||
file::file(cstring_view path, int oflag) {
|
||||
int mode = S_IRUSR | S_IWUSR;
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
|
||||
auto converted = detail::utf8_to_utf16(string_view(path.c_str()));
|
||||
*this = file::open_windows_file(converted.c_str(), oflag);
|
||||
# else
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
|
||||
# endif
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, default_open_mode)));
|
||||
if (fd_ == -1)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
|
||||
FMT_THROW(
|
||||
system_error(errno, FMT_STRING("cannot open file {}"), path.c_str()));
|
||||
# endif
|
||||
}
|
||||
|
||||
file::~file() FMT_NOEXCEPT {
|
||||
file::~file() noexcept {
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
|
||||
@@ -237,10 +236,11 @@ void file::close() {
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
int result = FMT_POSIX_CALL(close(fd_));
|
||||
fd_ = -1;
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot close file")));
|
||||
}
|
||||
|
||||
long long file::size() const {
|
||||
auto file::size() const -> long long {
|
||||
# ifdef _WIN32
|
||||
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
|
||||
// is less than 0x0500 as is the case with some default MinGW builds.
|
||||
@@ -251,7 +251,7 @@ long long file::size() const {
|
||||
if (size_lower == INVALID_FILE_SIZE) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != NO_ERROR)
|
||||
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
|
||||
FMT_THROW(windows_error(error, "cannot get file size"));
|
||||
}
|
||||
unsigned long long long_size = size_upper;
|
||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||
@@ -259,33 +259,36 @@ long long file::size() const {
|
||||
using Stat = struct stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
FMT_THROW(system_error(errno, "cannot get file attributes"));
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot get file attributes")));
|
||||
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
|
||||
"return type of file::size is not large enough");
|
||||
return file_stat.st_size;
|
||||
# endif
|
||||
}
|
||||
|
||||
std::size_t file::read(void* buffer, std::size_t count) {
|
||||
auto file::read(void* buffer, size_t count) -> size_t {
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot read from file")));
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
std::size_t file::write(const void* buffer, std::size_t count) {
|
||||
auto file::write(const void* buffer, size_t count) -> size_t {
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
if (result < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
file file::dup(int fd) {
|
||||
auto file::dup(int fd) -> file {
|
||||
// Don't retry as dup doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
||||
int new_fd = FMT_POSIX_CALL(dup(fd));
|
||||
if (new_fd == -1)
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
|
||||
FMT_THROW(system_error(
|
||||
errno, FMT_STRING("cannot duplicate file descriptor {}"), fd));
|
||||
return file(new_fd);
|
||||
}
|
||||
|
||||
@@ -293,22 +296,47 @@ void file::dup2(int fd) {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) {
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
|
||||
fd_, fd));
|
||||
FMT_THROW(system_error(
|
||||
errno, FMT_STRING("cannot duplicate file descriptor {} to {}"), fd_,
|
||||
fd));
|
||||
}
|
||||
}
|
||||
|
||||
void file::dup2(int fd, std::error_code& ec) FMT_NOEXCEPT {
|
||||
void file::dup2(int fd, std::error_code& ec) noexcept {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) ec = std::error_code(errno, std::generic_category());
|
||||
}
|
||||
|
||||
void file::pipe(file& read_end, file& write_end) {
|
||||
// Close the descriptors first to make sure that assignments don't throw
|
||||
// and there are no leaks.
|
||||
read_end.close();
|
||||
write_end.close();
|
||||
auto file::fdopen(const char* mode) -> buffered_file {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
# if defined(__MINGW32__) && defined(_POSIX_)
|
||||
FILE* f = ::fdopen(fd_, mode);
|
||||
# else
|
||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
# endif
|
||||
if (!f) {
|
||||
FMT_THROW(system_error(
|
||||
errno, FMT_STRING("cannot associate stream with file descriptor")));
|
||||
}
|
||||
buffered_file bf(f);
|
||||
fd_ = -1;
|
||||
return bf;
|
||||
}
|
||||
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
file file::open_windows_file(wcstring_view path, int oflag) {
|
||||
int fd = -1;
|
||||
auto err = _wsopen_s(&fd, path.c_str(), oflag, _SH_DENYNO, default_open_mode);
|
||||
if (fd == -1) {
|
||||
FMT_THROW(system_error(err, FMT_STRING("cannot open file {}"),
|
||||
detail::to_utf8<wchar_t>(path.c_str()).c_str()));
|
||||
}
|
||||
return file(fd);
|
||||
}
|
||||
# endif
|
||||
|
||||
pipe::pipe() {
|
||||
int fds[2] = {};
|
||||
# ifdef _WIN32
|
||||
// Make the default pipe capacity same as on Linux 2.6.11+.
|
||||
@@ -319,42 +347,52 @@ void file::pipe(file& read_end, file& write_end) {
|
||||
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
# endif
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
|
||||
// The following assignments don't throw because read_fd and write_fd
|
||||
// are closed.
|
||||
if (result != 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot create pipe")));
|
||||
// The following assignments don't throw.
|
||||
read_end = file(fds[0]);
|
||||
write_end = file(fds[1]);
|
||||
}
|
||||
|
||||
buffered_file file::fdopen(const char* mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
# if defined(__MINGW32__) && defined(_POSIX_)
|
||||
FILE* f = ::fdopen(fd_, mode);
|
||||
# else
|
||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
# endif
|
||||
if (!f)
|
||||
FMT_THROW(
|
||||
system_error(errno, "cannot associate stream with file descriptor"));
|
||||
buffered_file bf(f);
|
||||
fd_ = -1;
|
||||
return bf;
|
||||
}
|
||||
|
||||
long getpagesize() {
|
||||
# ifdef _WIN32
|
||||
# if !defined(__MSDOS__)
|
||||
auto getpagesize() -> long {
|
||||
# ifdef _WIN32
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return si.dwPageSize;
|
||||
# else
|
||||
# else
|
||||
# ifdef _WRS_KERNEL
|
||||
long size = FMT_POSIX_CALL(getpagesize());
|
||||
# else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
|
||||
# endif
|
||||
|
||||
if (size < 0)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot get memory page size")));
|
||||
return size;
|
||||
# endif
|
||||
}
|
||||
# endif
|
||||
|
||||
void ostream::grow(buffer<char>& buf, size_t) {
|
||||
if (buf.size() == buf.capacity()) static_cast<ostream&>(buf).flush();
|
||||
}
|
||||
|
||||
FMT_API void ostream::grow(size_t) {
|
||||
if (this->size() == this->capacity()) flush();
|
||||
ostream::ostream(cstring_view path, const detail::ostream_params& params)
|
||||
: buffer<char>(grow), file_(path, params.oflag) {
|
||||
set(new char[params.buffer_size], params.buffer_size);
|
||||
}
|
||||
|
||||
ostream::ostream(ostream&& other) noexcept
|
||||
: buffer<char>(grow, other.data(), other.size(), other.capacity()),
|
||||
file_(std::move(other.file_)) {
|
||||
other.clear();
|
||||
other.set(nullptr, 0);
|
||||
}
|
||||
|
||||
ostream::~ostream() {
|
||||
flush();
|
||||
delete[] data();
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
<manifest package="net.fmtlib" />
|
||||
<manifest package="dev.fmt" />
|
||||
|
||||
+5
-6
@@ -3,18 +3,17 @@
|
||||
|
||||
# A vagrant config for testing against gcc-4.8.
|
||||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu/xenial64"
|
||||
config.disksize.size = '15GB'
|
||||
config.vm.box = "bento/ubuntu-22.04-arm64"
|
||||
|
||||
config.vm.provider "virtualbox" do |vb|
|
||||
config.vm.provider "vmware_desktop" do |vb|
|
||||
vb.memory = "4096"
|
||||
end
|
||||
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
apt-get update
|
||||
apt-get install -y g++ make wget git
|
||||
wget -q https://github.com/Kitware/CMake/releases/download/v3.14.4/cmake-3.14.4-Linux-x86_64.tar.gz
|
||||
tar xzf cmake-3.14.4-Linux-x86_64.tar.gz
|
||||
ln -s `pwd`/cmake-3.14.4-Linux-x86_64/bin/cmake /usr/local/bin
|
||||
wget -q https://github.com/Kitware/CMake/releases/download/v3.26.0/cmake-3.26.0-Linux-x86_64.tar.gz
|
||||
tar xzf cmake-3.26.0-Linux-x86_64.tar.gz
|
||||
ln -s `pwd`/cmake-3.26.0-Linux-x86_64/bin/cmake /usr/local/bin
|
||||
SHELL
|
||||
end
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Build the project on AppVeyor.
|
||||
|
||||
import os
|
||||
from subprocess import check_call
|
||||
|
||||
build = os.environ['BUILD']
|
||||
config = os.environ['CONFIGURATION']
|
||||
platform = os.environ['PLATFORM']
|
||||
path = os.environ['PATH']
|
||||
image = os.environ['APPVEYOR_BUILD_WORKER_IMAGE']
|
||||
jobid = os.environ['APPVEYOR_JOB_ID']
|
||||
cmake_command = ['cmake', '-DFMT_PEDANTIC=ON', '-DCMAKE_BUILD_TYPE=' + config, '..']
|
||||
if build == 'mingw':
|
||||
cmake_command.append('-GMinGW Makefiles')
|
||||
build_command = ['mingw32-make', '-j4']
|
||||
test_command = ['mingw32-make', 'test']
|
||||
# Remove the path to Git bin directory from $PATH because it breaks
|
||||
# MinGW config.
|
||||
path = path.replace(r'C:\Program Files (x86)\Git\bin', '')
|
||||
os.environ['PATH'] = r'C:\MinGW\bin;' + path
|
||||
else:
|
||||
# Add MSBuild 14.0 to PATH as described in
|
||||
# http://help.appveyor.com/discussions/problems/2229-v140-not-found-on-vs2105rc.
|
||||
os.environ['PATH'] = r'C:\Program Files (x86)\MSBuild\15.0\Bin;' + path
|
||||
if image == 'Visual Studio 2019':
|
||||
generator = 'Visual Studio 16 2019'
|
||||
if platform == 'x64':
|
||||
cmake_command.extend(['-A', 'x64'])
|
||||
else:
|
||||
if image == 'Visual Studio 2015':
|
||||
generator = 'Visual Studio 14 2015'
|
||||
elif image == 'Visual Studio 2017':
|
||||
generator = 'Visual Studio 15 2017'
|
||||
if platform == 'x64':
|
||||
generator += ' Win64'
|
||||
cmake_command.append('-G' + generator)
|
||||
build_command = ['cmake', '--build', '.', '--config', config, '--', '/m:4']
|
||||
test_command = ['ctest', '-C', config]
|
||||
|
||||
check_call(cmake_command)
|
||||
check_call(build_command)
|
||||
check_call(test_command)
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
configuration:
|
||||
- Debug
|
||||
- Release
|
||||
|
||||
clone_depth: 1
|
||||
|
||||
image:
|
||||
- Visual Studio 2015
|
||||
|
||||
platform:
|
||||
- x64
|
||||
|
||||
environment:
|
||||
CTEST_OUTPUT_ON_FAILURE: 1
|
||||
MSVC_DEFAULT_OPTIONS: ON
|
||||
BUILD: msvc
|
||||
|
||||
before_build:
|
||||
- mkdir build
|
||||
- cd build
|
||||
|
||||
build_script:
|
||||
- python ../support/appveyor-build.py
|
||||
|
||||
on_failure:
|
||||
- appveyor PushArtifact Testing/Temporary/LastTest.log
|
||||
- appveyor AddTest test
|
||||
|
||||
# Uncomment this to debug AppVeyor failures.
|
||||
#on_finish:
|
||||
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Build the documentation in CI.
|
||||
|
||||
from __future__ import print_function
|
||||
import errno, os, shutil, subprocess, sys, urllib
|
||||
from subprocess import call, check_call, Popen, PIPE, STDOUT
|
||||
|
||||
def rmtree_if_exists(dir):
|
||||
try:
|
||||
shutil.rmtree(dir)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
pass
|
||||
|
||||
# Build the docs.
|
||||
fmt_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
|
||||
sys.path.insert(0, os.path.join(fmt_dir, 'doc'))
|
||||
import build
|
||||
build.create_build_env()
|
||||
html_dir = build.build_docs()
|
||||
|
||||
repo = 'fmtlib.github.io'
|
||||
branch = os.environ['GITHUB_REF']
|
||||
is_ci = 'CI' in os.environ
|
||||
if is_ci and branch != 'refs/heads/master':
|
||||
print('Branch: ' + branch)
|
||||
exit(0) # Ignore non-master branches
|
||||
if is_ci and 'KEY' not in os.environ:
|
||||
# Don't update the repo if building in CI from an account that doesn't have
|
||||
# push access.
|
||||
print('Skipping update of ' + repo)
|
||||
exit(0)
|
||||
|
||||
# Clone the fmtlib.github.io repo.
|
||||
rmtree_if_exists(repo)
|
||||
git_url = 'https://github.com/' if is_ci else 'git@github.com:'
|
||||
check_call(['git', 'clone', git_url + 'fmtlib/{}.git'.format(repo)])
|
||||
|
||||
# Copy docs to the repo.
|
||||
target_dir = os.path.join(repo, 'dev')
|
||||
rmtree_if_exists(target_dir)
|
||||
shutil.copytree(html_dir, target_dir, ignore=shutil.ignore_patterns('.*'))
|
||||
if is_ci:
|
||||
check_call(['git', 'config', '--global', 'user.name', 'fmtbot'])
|
||||
check_call(['git', 'config', '--global', 'user.email', 'viz@fmt.dev'])
|
||||
|
||||
# Push docs to GitHub pages.
|
||||
check_call(['git', 'add', '--all'], cwd=repo)
|
||||
if call(['git', 'diff-index', '--quiet', 'HEAD'], cwd=repo):
|
||||
check_call(['git', 'commit', '-m', 'Update documentation'], cwd=repo)
|
||||
cmd = 'git push'
|
||||
if is_ci:
|
||||
cmd += ' https://$KEY@github.com/fmtlib/fmtlib.github.io.git master'
|
||||
p = Popen(cmd, shell=True, stdout=PIPE, stderr=STDOUT, cwd=repo)
|
||||
# Print the output without the key.
|
||||
print(p.communicate()[0].replace(os.environ['KEY'], '$KEY'))
|
||||
if p.returncode != 0:
|
||||
raise subprocess.CalledProcessError(p.returncode, cmd)
|
||||
+1
-1
@@ -11,7 +11,7 @@ buildscript {
|
||||
// https://developer.android.com/studio/releases/gradle-plugin#updating-gradle
|
||||
//
|
||||
// Notice that 4.0.0 here is the version of [Android Gradle Plugin]
|
||||
// Accroding to URL above you will need Gradle 6.1 or higher
|
||||
// According to URL above you will need Gradle 6.1 or higher
|
||||
//
|
||||
classpath "com.android.tools.build:gradle:4.1.1"
|
||||
}
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Compile source on a range of commits
|
||||
|
||||
Usage:
|
||||
check-commits <start> <source>
|
||||
"""
|
||||
|
||||
import docopt, os, sys, tempfile
|
||||
from subprocess import check_call, check_output, run
|
||||
|
||||
args = docopt.docopt(__doc__)
|
||||
start = args.get('<start>')
|
||||
source = args.get('<source>')
|
||||
|
||||
cwd = os.getcwd()
|
||||
|
||||
with tempfile.TemporaryDirectory() as work_dir:
|
||||
check_call(['git', 'clone', 'https://github.com/fmtlib/fmt.git'],
|
||||
cwd=work_dir)
|
||||
repo_dir = os.path.join(work_dir, 'fmt')
|
||||
commits = check_output(
|
||||
['git', 'rev-list', f'{start}..HEAD', '--abbrev-commit',
|
||||
'--', 'include', 'src'],
|
||||
text=True, cwd=repo_dir).rstrip().split('\n')
|
||||
commits.reverse()
|
||||
print('Time\tCommit')
|
||||
for commit in commits:
|
||||
check_call(['git', '-c', 'advice.detachedHead=false', 'checkout', commit],
|
||||
cwd=repo_dir)
|
||||
returncode = run(
|
||||
['c++', '-std=c++11', '-O3', '-DNDEBUG', '-I', 'include',
|
||||
'src/format.cc', os.path.join(cwd, source)], cwd=repo_dir).returncode
|
||||
if returncode != 0:
|
||||
continue
|
||||
times = []
|
||||
for i in range(5):
|
||||
output = check_output([os.path.join(repo_dir, 'a.out')], text=True)
|
||||
times.append(float(output))
|
||||
message = check_output(['git', 'log', '-1', '--pretty=format:%s', commit],
|
||||
cwd=repo_dir, text=True)
|
||||
print(f'{min(times)}\t{commit} {message[:40]}')
|
||||
sys.stdout.flush()
|
||||
@@ -1,70 +0,0 @@
|
||||
# C++14 feature support detection
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
if (NOT CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
endif()
|
||||
message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
|
||||
|
||||
if (CMAKE_CXX_STANDARD EQUAL 20)
|
||||
check_cxx_compiler_flag(-std=c++20 has_std_20_flag)
|
||||
check_cxx_compiler_flag(-std=c++2a has_std_2a_flag)
|
||||
|
||||
if (has_std_20_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++20)
|
||||
elseif (has_std_2a_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++2a)
|
||||
endif ()
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 17)
|
||||
check_cxx_compiler_flag(-std=c++17 has_std_17_flag)
|
||||
check_cxx_compiler_flag(-std=c++1z has_std_1z_flag)
|
||||
|
||||
if (has_std_17_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++17)
|
||||
elseif (has_std_1z_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++1z)
|
||||
endif ()
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 14)
|
||||
check_cxx_compiler_flag(-std=c++14 has_std_14_flag)
|
||||
check_cxx_compiler_flag(-std=c++1y has_std_1y_flag)
|
||||
|
||||
if (has_std_14_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++14)
|
||||
elseif (has_std_1y_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++1y)
|
||||
endif ()
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 11)
|
||||
check_cxx_compiler_flag(-std=c++11 has_std_11_flag)
|
||||
check_cxx_compiler_flag(-std=c++0x has_std_0x_flag)
|
||||
|
||||
if (has_std_11_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++11)
|
||||
elseif (has_std_0x_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++0x)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG})
|
||||
|
||||
# Check if user-defined literals are available
|
||||
check_cxx_source_compiles("
|
||||
void operator\"\" _udl(long double);
|
||||
int main() {}"
|
||||
SUPPORTS_USER_DEFINED_LITERALS)
|
||||
if (NOT SUPPORTS_USER_DEFINED_LITERALS)
|
||||
set (SUPPORTS_USER_DEFINED_LITERALS OFF)
|
||||
endif ()
|
||||
|
||||
# Check if <variant> is available
|
||||
set(CMAKE_REQUIRED_FLAGS -std=c++1z)
|
||||
check_cxx_source_compiles("
|
||||
#include <variant>
|
||||
int main() {}"
|
||||
FMT_HAS_VARIANT)
|
||||
if (NOT FMT_HAS_VARIANT)
|
||||
set (FMT_HAS_VARIANT OFF)
|
||||
endif ()
|
||||
|
||||
set(CMAKE_REQUIRED_FLAGS )
|
||||
@@ -1,4 +1,7 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake)
|
||||
if (NOT TARGET fmt::fmt)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake)
|
||||
endif ()
|
||||
|
||||
check_required_components(fmt)
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Compute 10 ** exp with exp in the range [min_exponent, max_exponent] and print
|
||||
# normalized (with most-significant bit equal to 1) significands in hexadecimal.
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
min_exponent = -348
|
||||
max_exponent = 340
|
||||
step = 8
|
||||
significand_size = 64
|
||||
exp_offset = 2000
|
||||
|
||||
class fp:
|
||||
pass
|
||||
|
||||
powers = []
|
||||
for i, exp in enumerate(range(min_exponent, max_exponent + 1, step)):
|
||||
result = fp()
|
||||
n = 10 ** exp if exp >= 0 else 2 ** exp_offset / 10 ** -exp
|
||||
k = significand_size + 1
|
||||
# Convert to binary and round.
|
||||
binary = '{:b}'.format(n)
|
||||
result.f = (int('{:0<{}}'.format(binary[:k], k), 2) + 1) / 2
|
||||
result.e = len(binary) - (exp_offset if exp < 0 else 0) - significand_size
|
||||
powers.append(result)
|
||||
# Sanity check.
|
||||
exp_offset10 = 400
|
||||
actual = result.f * 10 ** exp_offset10
|
||||
if result.e > 0:
|
||||
actual *= 2 ** result.e
|
||||
else:
|
||||
for j in range(-result.e):
|
||||
actual /= 2
|
||||
expected = 10 ** (exp_offset10 + exp)
|
||||
precision = len('{}'.format(expected)) - len('{}'.format(actual - expected))
|
||||
if precision < 19:
|
||||
print('low precision:', precision)
|
||||
exit(1)
|
||||
|
||||
print('Significands:', end='')
|
||||
for i, fp in enumerate(powers):
|
||||
if i % 3 == 0:
|
||||
print(end='\n ')
|
||||
print(' {:0<#16x}'.format(fp.f, ), end=',')
|
||||
|
||||
print('\n\nExponents:', end='')
|
||||
for i, fp in enumerate(powers):
|
||||
if i % 11 == 0:
|
||||
print(end='\n ')
|
||||
print(' {:5}'.format(fp.e), end=',')
|
||||
|
||||
print('\n\nMax exponent difference:',
|
||||
max([x.e - powers[i - 1].e for i, x in enumerate(powers)][1:]))
|
||||
-290
@@ -1,290 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""Manage site and releases.
|
||||
|
||||
Usage:
|
||||
manage.py release [<branch>]
|
||||
manage.py site
|
||||
|
||||
For the release command $FMT_TOKEN should contain a GitHub personal access token
|
||||
obtained from https://github.com/settings/tokens.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import datetime, docopt, errno, fileinput, json, os
|
||||
import re, requests, shutil, sys, tempfile
|
||||
from contextlib import contextmanager
|
||||
from distutils.version import LooseVersion
|
||||
from subprocess import check_call
|
||||
|
||||
|
||||
class Git:
|
||||
def __init__(self, dir):
|
||||
self.dir = dir
|
||||
|
||||
def call(self, method, args, **kwargs):
|
||||
return check_call(['git', method] + list(args), **kwargs)
|
||||
|
||||
def add(self, *args):
|
||||
return self.call('add', args, cwd=self.dir)
|
||||
|
||||
def checkout(self, *args):
|
||||
return self.call('checkout', args, cwd=self.dir)
|
||||
|
||||
def clean(self, *args):
|
||||
return self.call('clean', args, cwd=self.dir)
|
||||
|
||||
def clone(self, *args):
|
||||
return self.call('clone', list(args) + [self.dir])
|
||||
|
||||
def commit(self, *args):
|
||||
return self.call('commit', args, cwd=self.dir)
|
||||
|
||||
def pull(self, *args):
|
||||
return self.call('pull', args, cwd=self.dir)
|
||||
|
||||
def push(self, *args):
|
||||
return self.call('push', args, cwd=self.dir)
|
||||
|
||||
def reset(self, *args):
|
||||
return self.call('reset', args, cwd=self.dir)
|
||||
|
||||
def update(self, *args):
|
||||
clone = not os.path.exists(self.dir)
|
||||
if clone:
|
||||
self.clone(*args)
|
||||
return clone
|
||||
|
||||
|
||||
def clean_checkout(repo, branch):
|
||||
repo.clean('-f', '-d')
|
||||
repo.reset('--hard')
|
||||
repo.checkout(branch)
|
||||
|
||||
|
||||
class Runner:
|
||||
def __init__(self, cwd):
|
||||
self.cwd = cwd
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
kwargs['cwd'] = kwargs.get('cwd', self.cwd)
|
||||
check_call(args, **kwargs)
|
||||
|
||||
|
||||
def create_build_env():
|
||||
"""Create a build environment."""
|
||||
class Env:
|
||||
pass
|
||||
env = Env()
|
||||
|
||||
# Import the documentation build module.
|
||||
env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
sys.path.insert(0, os.path.join(env.fmt_dir, 'doc'))
|
||||
import build
|
||||
|
||||
env.build_dir = 'build'
|
||||
env.versions = build.versions
|
||||
|
||||
# Virtualenv and repos are cached to speed up builds.
|
||||
build.create_build_env(os.path.join(env.build_dir, 'virtualenv'))
|
||||
|
||||
env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt'))
|
||||
return env
|
||||
|
||||
|
||||
@contextmanager
|
||||
def rewrite(filename):
|
||||
class Buffer:
|
||||
pass
|
||||
buffer = Buffer()
|
||||
if not os.path.exists(filename):
|
||||
buffer.data = ''
|
||||
yield buffer
|
||||
return
|
||||
with open(filename) as f:
|
||||
buffer.data = f.read()
|
||||
yield buffer
|
||||
with open(filename, 'w') as f:
|
||||
f.write(buffer.data)
|
||||
|
||||
|
||||
fmt_repo_url = 'git@github.com:fmtlib/fmt'
|
||||
|
||||
|
||||
def update_site(env):
|
||||
env.fmt_repo.update(fmt_repo_url)
|
||||
|
||||
doc_repo = Git(os.path.join(env.build_dir, 'fmtlib.github.io'))
|
||||
doc_repo.update('git@github.com:fmtlib/fmtlib.github.io')
|
||||
|
||||
for version in env.versions:
|
||||
clean_checkout(env.fmt_repo, version)
|
||||
target_doc_dir = os.path.join(env.fmt_repo.dir, 'doc')
|
||||
# Remove the old theme.
|
||||
for entry in os.listdir(target_doc_dir):
|
||||
path = os.path.join(target_doc_dir, entry)
|
||||
if os.path.isdir(path):
|
||||
shutil.rmtree(path)
|
||||
# Copy the new theme.
|
||||
for entry in ['_static', '_templates', 'basic-bootstrap', 'bootstrap',
|
||||
'conf.py', 'fmt.less']:
|
||||
src = os.path.join(env.fmt_dir, 'doc', entry)
|
||||
dst = os.path.join(target_doc_dir, entry)
|
||||
copy = shutil.copytree if os.path.isdir(src) else shutil.copyfile
|
||||
copy(src, dst)
|
||||
# Rename index to contents.
|
||||
contents = os.path.join(target_doc_dir, 'contents.rst')
|
||||
if not os.path.exists(contents):
|
||||
os.rename(os.path.join(target_doc_dir, 'index.rst'), contents)
|
||||
# Fix issues in reference.rst/api.rst.
|
||||
for filename in ['reference.rst', 'api.rst', 'index.rst']:
|
||||
pattern = re.compile('doxygenfunction.. (bin|oct|hexu|hex)$', re.M)
|
||||
with rewrite(os.path.join(target_doc_dir, filename)) as b:
|
||||
b.data = b.data.replace('std::ostream &', 'std::ostream&')
|
||||
b.data = re.sub(pattern, r'doxygenfunction:: \1(int)', b.data)
|
||||
b.data = b.data.replace('std::FILE*', 'std::FILE *')
|
||||
b.data = b.data.replace('unsigned int', 'unsigned')
|
||||
#b.data = b.data.replace('operator""_', 'operator"" _')
|
||||
b.data = b.data.replace(
|
||||
'format_to_n(OutputIt, size_t, string_view, Args&&',
|
||||
'format_to_n(OutputIt, size_t, const S&, const Args&')
|
||||
b.data = b.data.replace(
|
||||
'format_to_n(OutputIt, std::size_t, string_view, Args&&',
|
||||
'format_to_n(OutputIt, std::size_t, const S&, const Args&')
|
||||
if version == ('3.0.2'):
|
||||
b.data = b.data.replace(
|
||||
'fprintf(std::ostream&', 'fprintf(std::ostream &')
|
||||
if version == ('5.3.0'):
|
||||
b.data = b.data.replace(
|
||||
'format_to(OutputIt, const S&, const Args&...)',
|
||||
'format_to(OutputIt, const S &, const Args &...)')
|
||||
if version.startswith('5.') or version.startswith('6.'):
|
||||
b.data = b.data.replace(', size_t', ', std::size_t')
|
||||
if version.startswith('7.'):
|
||||
b.data = b.data.replace(', std::size_t', ', size_t')
|
||||
b.data = b.data.replace('join(It, It', 'join(It, Sentinel')
|
||||
b.data = b.data.replace('aa long', 'a long')
|
||||
b.data = b.data.replace('serveral', 'several')
|
||||
if version.startswith('6.2.'):
|
||||
b.data = b.data.replace(
|
||||
'vformat(const S&, basic_format_args<' +
|
||||
'buffer_context<Char>>)',
|
||||
'vformat(const S&, basic_format_args<' +
|
||||
'buffer_context<type_identity_t<Char>>>)')
|
||||
# Fix a broken link in index.rst.
|
||||
index = os.path.join(target_doc_dir, 'index.rst')
|
||||
with rewrite(index) as b:
|
||||
b.data = b.data.replace(
|
||||
'doc/latest/index.html#format-string-syntax', 'syntax.html')
|
||||
# Build the docs.
|
||||
html_dir = os.path.join(env.build_dir, 'html')
|
||||
if os.path.exists(html_dir):
|
||||
shutil.rmtree(html_dir)
|
||||
include_dir = env.fmt_repo.dir
|
||||
if LooseVersion(version) >= LooseVersion('5.0.0'):
|
||||
include_dir = os.path.join(include_dir, 'include', 'fmt')
|
||||
elif LooseVersion(version) >= LooseVersion('3.0.0'):
|
||||
include_dir = os.path.join(include_dir, 'fmt')
|
||||
import build
|
||||
build.build_docs(version, doc_dir=target_doc_dir,
|
||||
include_dir=include_dir, work_dir=env.build_dir)
|
||||
shutil.rmtree(os.path.join(html_dir, '.doctrees'))
|
||||
# Create symlinks for older versions.
|
||||
for link, target in {'index': 'contents', 'api': 'reference'}.items():
|
||||
link = os.path.join(html_dir, link) + '.html'
|
||||
target += '.html'
|
||||
if os.path.exists(os.path.join(html_dir, target)) and \
|
||||
not os.path.exists(link):
|
||||
os.symlink(target, link)
|
||||
# Copy docs to the website.
|
||||
version_doc_dir = os.path.join(doc_repo.dir, version)
|
||||
try:
|
||||
shutil.rmtree(version_doc_dir)
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
raise
|
||||
shutil.move(html_dir, version_doc_dir)
|
||||
|
||||
|
||||
def release(args):
|
||||
env = create_build_env()
|
||||
fmt_repo = env.fmt_repo
|
||||
|
||||
branch = args.get('<branch>')
|
||||
if branch is None:
|
||||
branch = 'master'
|
||||
if not fmt_repo.update('-b', branch, fmt_repo_url):
|
||||
clean_checkout(fmt_repo, branch)
|
||||
|
||||
# Convert changelog from RST to GitHub-flavored Markdown and get the
|
||||
# version.
|
||||
changelog = 'ChangeLog.rst'
|
||||
changelog_path = os.path.join(fmt_repo.dir, changelog)
|
||||
import rst2md
|
||||
changes, version = rst2md.convert(changelog_path)
|
||||
cmakelists = 'CMakeLists.txt'
|
||||
for line in fileinput.input(os.path.join(fmt_repo.dir, cmakelists),
|
||||
inplace=True):
|
||||
prefix = 'set(FMT_VERSION '
|
||||
if line.startswith(prefix):
|
||||
line = prefix + version + ')\n'
|
||||
sys.stdout.write(line)
|
||||
|
||||
# Update the version in the changelog.
|
||||
title_len = 0
|
||||
for line in fileinput.input(changelog_path, inplace=True):
|
||||
if line.decode('utf-8').startswith(version + ' - TBD'):
|
||||
line = version + ' - ' + datetime.date.today().isoformat()
|
||||
title_len = len(line)
|
||||
line += '\n'
|
||||
elif title_len:
|
||||
line = '-' * title_len + '\n'
|
||||
title_len = 0
|
||||
sys.stdout.write(line)
|
||||
|
||||
# Add the version to the build script.
|
||||
script = os.path.join('doc', 'build.py')
|
||||
script_path = os.path.join(fmt_repo.dir, script)
|
||||
for line in fileinput.input(script_path, inplace=True):
|
||||
m = re.match(r'( *versions = )\[(.+)\]', line)
|
||||
if m:
|
||||
line = '{}[{}, \'{}\']\n'.format(m.group(1), m.group(2), version)
|
||||
sys.stdout.write(line)
|
||||
|
||||
fmt_repo.checkout('-B', 'release')
|
||||
fmt_repo.add(changelog, cmakelists, script)
|
||||
fmt_repo.commit('-m', 'Update version')
|
||||
|
||||
# Build the docs and package.
|
||||
run = Runner(fmt_repo.dir)
|
||||
run('cmake', '.')
|
||||
run('make', 'doc', 'package_source')
|
||||
update_site(env)
|
||||
|
||||
# Create a release on GitHub.
|
||||
fmt_repo.push('origin', 'release')
|
||||
params = {'access_token': os.getenv('FMT_TOKEN')}
|
||||
r = requests.post('https://api.github.com/repos/fmtlib/fmt/releases',
|
||||
params=params,
|
||||
data=json.dumps({'tag_name': version,
|
||||
'target_commitish': 'release',
|
||||
'body': changes, 'draft': True}))
|
||||
if r.status_code != 201:
|
||||
raise Exception('Failed to create a release ' + str(r))
|
||||
id = r.json()['id']
|
||||
uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
|
||||
package = 'fmt-{}.zip'.format(version)
|
||||
r = requests.post(
|
||||
'{}/{}/assets?name={}'.format(uploads_url, id, package),
|
||||
headers={'Content-Type': 'application/zip'},
|
||||
params=params, data=open('build/fmt/' + package, 'rb'))
|
||||
if r.status_code != 201:
|
||||
raise Exception('Failed to upload an asset ' + str(r))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = docopt.docopt(__doc__)
|
||||
if args.get('release'):
|
||||
release(args)
|
||||
elif args.get('site'):
|
||||
update_site(create_build_env())
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
# A script to invoke mkdocs with the correct environment.
|
||||
# Additionally supports deploying via mike:
|
||||
# ./mkdocs deploy [mike-deploy-options]
|
||||
# For example:
|
||||
# ./mkdocs deploy <version>
|
||||
# This will checkout the website to fmt/build/fmt.dev and deploy documentation
|
||||
# <version> there.
|
||||
|
||||
import errno, os, shutil, sys
|
||||
from subprocess import call
|
||||
|
||||
support_dir = os.path.dirname(os.path.normpath(__file__))
|
||||
build_dir = os.path.join(os.path.dirname(support_dir), 'build')
|
||||
|
||||
# Set PYTHONPATH for the mkdocstrings handler.
|
||||
env = os.environ.copy()
|
||||
path = env.get('PYTHONPATH')
|
||||
env['PYTHONPATH'] = \
|
||||
(path + ':' if path else '') + os.path.join(support_dir, 'python')
|
||||
|
||||
redirect_page = \
|
||||
'''<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Redirecting</title>
|
||||
<noscript>
|
||||
<meta http-equiv="refresh" content="1; url=11.0/" />
|
||||
</noscript>
|
||||
<script>
|
||||
window.location.replace(
|
||||
"api/" + window.location.search + window.location.hash
|
||||
);
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
Redirecting to <a href="api/">api</a>...
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
config_path = os.path.join(support_dir, 'mkdocs.yml')
|
||||
args = sys.argv[1:]
|
||||
if len(args) > 0:
|
||||
command = args[0]
|
||||
if command == 'deploy' or command == 'set-default':
|
||||
git_url = 'https://github.com/' if 'CI' in os.environ else 'git@github.com:'
|
||||
site_repo = git_url + 'fmtlib/fmt.dev.git'
|
||||
|
||||
site_dir = os.path.join(build_dir, 'fmt.dev')
|
||||
try:
|
||||
shutil.rmtree(site_dir)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOENT:
|
||||
pass
|
||||
ret = call(['git', 'clone', '--depth=1', site_repo, site_dir])
|
||||
if ret != 0:
|
||||
sys.exit(ret)
|
||||
|
||||
# Copy the config to the build dir because the site is built relative to it.
|
||||
config_build_path = os.path.join(build_dir, 'mkdocs.yml')
|
||||
shutil.copyfile(config_path, config_build_path)
|
||||
|
||||
version = args[1]
|
||||
ret = call(['mike'] + args + ['--config-file', config_build_path,
|
||||
'--branch', 'master'], cwd=site_dir, env=env)
|
||||
if ret != 0 or version == 'dev':
|
||||
sys.exit(ret)
|
||||
current_doc_path = os.path.join(site_dir, version)
|
||||
# mike stages files added by deploy for deletion for unclear reason,
|
||||
# undo it.
|
||||
ret = call(['git', 'reset', '--hard'], cwd=site_dir)
|
||||
if False:
|
||||
os.makedirs(current_doc_path, exist_ok=True)
|
||||
redirect_page_path = os.path.join(current_doc_path, 'api.html')
|
||||
with open(redirect_page_path, "w") as file:
|
||||
file.write(redirect_page)
|
||||
ret = call(['git', 'add', redirect_page_path], cwd=site_dir)
|
||||
if ret != 0:
|
||||
sys.exit(ret)
|
||||
ret = call(['git', 'commit', '--amend', '--no-edit'], cwd=site_dir)
|
||||
sys.exit(ret)
|
||||
elif not command.startswith('-'):
|
||||
args += ['-f', config_path]
|
||||
sys.exit(call(['mkdocs'] + args, env=env))
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
site_name: '{fmt}'
|
||||
|
||||
docs_dir: ../doc
|
||||
|
||||
repo_url: https://github.com/fmtlib/fmt
|
||||
|
||||
theme:
|
||||
name: material
|
||||
features:
|
||||
- navigation.tabs
|
||||
- navigation.top
|
||||
- toc.integrate
|
||||
|
||||
extra_javascript:
|
||||
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/highlight.min.js
|
||||
- fmt.js
|
||||
|
||||
extra_css:
|
||||
- https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.7.2/styles/default.min.css
|
||||
- fmt.css
|
||||
|
||||
markdown_extensions:
|
||||
- pymdownx.highlight:
|
||||
# Use JavaScript syntax highlighter instead of Pygments because it
|
||||
# automatically applies to code blocks extracted through Doxygen.
|
||||
use_pygments: false
|
||||
anchor_linenums: true
|
||||
line_spans: __span
|
||||
pygments_lang_class: true
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.snippets
|
||||
|
||||
plugins:
|
||||
- search
|
||||
- mkdocstrings:
|
||||
default_handler: cxx
|
||||
nav:
|
||||
- Home: index.md
|
||||
- Get Started: get-started.md
|
||||
- API: api.md
|
||||
- Syntax: syntax.md
|
||||
|
||||
exclude_docs: ChangeLog-old.md
|
||||
|
||||
extra:
|
||||
version:
|
||||
provider: mike
|
||||
generator: false
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# This script is based on
|
||||
# https://github.com/rust-lang/rust/blob/master/library/core/src/unicode/printable.py
|
||||
# distributed under https://github.com/rust-lang/rust/blob/master/LICENSE-MIT.
|
||||
|
||||
# This script uses the following Unicode tables:
|
||||
# - UnicodeData.txt
|
||||
|
||||
|
||||
from collections import namedtuple
|
||||
import csv
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
NUM_CODEPOINTS=0x110000
|
||||
|
||||
def to_ranges(iter):
|
||||
current = None
|
||||
for i in iter:
|
||||
if current is None or i != current[1] or i in (0x10000, 0x20000):
|
||||
if current is not None:
|
||||
yield tuple(current)
|
||||
current = [i, i + 1]
|
||||
else:
|
||||
current[1] += 1
|
||||
if current is not None:
|
||||
yield tuple(current)
|
||||
|
||||
def get_escaped(codepoints):
|
||||
for c in codepoints:
|
||||
if (c.class_ or "Cn") in "Cc Cf Cs Co Cn Zl Zp Zs".split() and c.value != ord(' '):
|
||||
yield c.value
|
||||
|
||||
def get_file(f):
|
||||
try:
|
||||
return open(os.path.basename(f))
|
||||
except FileNotFoundError:
|
||||
subprocess.run(["curl", "-O", f], check=True)
|
||||
return open(os.path.basename(f))
|
||||
|
||||
Codepoint = namedtuple('Codepoint', 'value class_')
|
||||
|
||||
def get_codepoints(f):
|
||||
r = csv.reader(f, delimiter=";")
|
||||
prev_codepoint = 0
|
||||
class_first = None
|
||||
for row in r:
|
||||
codepoint = int(row[0], 16)
|
||||
name = row[1]
|
||||
class_ = row[2]
|
||||
|
||||
if class_first is not None:
|
||||
if not name.endswith("Last>"):
|
||||
raise ValueError("Missing Last after First")
|
||||
|
||||
for c in range(prev_codepoint + 1, codepoint):
|
||||
yield Codepoint(c, class_first)
|
||||
|
||||
class_first = None
|
||||
if name.endswith("First>"):
|
||||
class_first = class_
|
||||
|
||||
yield Codepoint(codepoint, class_)
|
||||
prev_codepoint = codepoint
|
||||
|
||||
if class_first is not None:
|
||||
raise ValueError("Missing Last after First")
|
||||
|
||||
for c in range(prev_codepoint + 1, NUM_CODEPOINTS):
|
||||
yield Codepoint(c, None)
|
||||
|
||||
def compress_singletons(singletons):
|
||||
uppers = [] # (upper, # items in lowers)
|
||||
lowers = []
|
||||
|
||||
for i in singletons:
|
||||
upper = i >> 8
|
||||
lower = i & 0xff
|
||||
if len(uppers) == 0 or uppers[-1][0] != upper:
|
||||
uppers.append((upper, 1))
|
||||
else:
|
||||
upper, count = uppers[-1]
|
||||
uppers[-1] = upper, count + 1
|
||||
lowers.append(lower)
|
||||
|
||||
return uppers, lowers
|
||||
|
||||
def compress_normal(normal):
|
||||
# lengths 0x00..0x7f are encoded as 00, 01, ..., 7e, 7f
|
||||
# lengths 0x80..0x7fff are encoded as 80 80, 80 81, ..., ff fe, ff ff
|
||||
compressed = [] # [truelen, (truelenaux), falselen, (falselenaux)]
|
||||
|
||||
prev_start = 0
|
||||
for start, count in normal:
|
||||
truelen = start - prev_start
|
||||
falselen = count
|
||||
prev_start = start + count
|
||||
|
||||
assert truelen < 0x8000 and falselen < 0x8000
|
||||
entry = []
|
||||
if truelen > 0x7f:
|
||||
entry.append(0x80 | (truelen >> 8))
|
||||
entry.append(truelen & 0xff)
|
||||
else:
|
||||
entry.append(truelen & 0x7f)
|
||||
if falselen > 0x7f:
|
||||
entry.append(0x80 | (falselen >> 8))
|
||||
entry.append(falselen & 0xff)
|
||||
else:
|
||||
entry.append(falselen & 0x7f)
|
||||
|
||||
compressed.append(entry)
|
||||
|
||||
return compressed
|
||||
|
||||
def print_singletons(uppers, lowers, uppersname, lowersname):
|
||||
print(" static constexpr singleton {}[] = {{".format(uppersname))
|
||||
for u, c in uppers:
|
||||
print(" {{{:#04x}, {}}},".format(u, c))
|
||||
print(" };")
|
||||
print(" static constexpr unsigned char {}[] = {{".format(lowersname))
|
||||
for i in range(0, len(lowers), 8):
|
||||
print(" {}".format(" ".join("{:#04x},".format(l) for l in lowers[i:i+8])))
|
||||
print(" };")
|
||||
|
||||
def print_normal(normal, normalname):
|
||||
print(" static constexpr unsigned char {}[] = {{".format(normalname))
|
||||
for v in normal:
|
||||
print(" {}".format(" ".join("{:#04x},".format(i) for i in v)))
|
||||
print(" };")
|
||||
|
||||
def main():
|
||||
file = get_file("https://www.unicode.org/Public/UNIDATA/UnicodeData.txt")
|
||||
|
||||
codepoints = get_codepoints(file)
|
||||
|
||||
CUTOFF=0x10000
|
||||
singletons0 = []
|
||||
singletons1 = []
|
||||
normal0 = []
|
||||
normal1 = []
|
||||
extra = []
|
||||
|
||||
for a, b in to_ranges(get_escaped(codepoints)):
|
||||
if a > 2 * CUTOFF:
|
||||
extra.append((a, b - a))
|
||||
elif a == b - 1:
|
||||
if a & CUTOFF:
|
||||
singletons1.append(a & ~CUTOFF)
|
||||
else:
|
||||
singletons0.append(a)
|
||||
elif a == b - 2:
|
||||
if a & CUTOFF:
|
||||
singletons1.append(a & ~CUTOFF)
|
||||
singletons1.append((a + 1) & ~CUTOFF)
|
||||
else:
|
||||
singletons0.append(a)
|
||||
singletons0.append(a + 1)
|
||||
else:
|
||||
if a >= 2 * CUTOFF:
|
||||
extra.append((a, b - a))
|
||||
elif a & CUTOFF:
|
||||
normal1.append((a & ~CUTOFF, b - a))
|
||||
else:
|
||||
normal0.append((a, b - a))
|
||||
|
||||
singletons0u, singletons0l = compress_singletons(singletons0)
|
||||
singletons1u, singletons1l = compress_singletons(singletons1)
|
||||
normal0 = compress_normal(normal0)
|
||||
normal1 = compress_normal(normal1)
|
||||
|
||||
print("""\
|
||||
FMT_FUNC auto is_printable(uint32_t cp) -> bool {\
|
||||
""")
|
||||
print_singletons(singletons0u, singletons0l, 'singletons0', 'singletons0_lower')
|
||||
print_singletons(singletons1u, singletons1l, 'singletons1', 'singletons1_lower')
|
||||
print_normal(normal0, 'normal0')
|
||||
print_normal(normal1, 'normal1')
|
||||
print("""\
|
||||
auto lower = static_cast<uint16_t>(cp);
|
||||
if (cp < 0x10000) {
|
||||
return is_printable(lower, singletons0,
|
||||
sizeof(singletons0) / sizeof(*singletons0),
|
||||
singletons0_lower, normal0, sizeof(normal0));
|
||||
}
|
||||
if (cp < 0x20000) {
|
||||
return is_printable(lower, singletons1,
|
||||
sizeof(singletons1) / sizeof(*singletons1),
|
||||
singletons1_lower, normal1, sizeof(normal1));
|
||||
}\
|
||||
""")
|
||||
for a, b in extra:
|
||||
print(" if (0x{:x} <= cp && cp < 0x{:x}) return false;".format(a, a + b))
|
||||
print("""\
|
||||
return cp < 0x{:x};
|
||||
}}\
|
||||
""".format(NUM_CODEPOINTS))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
+338
@@ -0,0 +1,338 @@
|
||||
# A basic mkdocstrings handler for {fmt}.
|
||||
# Copyright (c) 2012 - present, Victor Zverovich
|
||||
# https://github.com/fmtlib/fmt/blob/master/LICENSE
|
||||
|
||||
import os
|
||||
import xml.etree.ElementTree as ElementTree
|
||||
from pathlib import Path
|
||||
from subprocess import PIPE, STDOUT, CalledProcessError, Popen
|
||||
from typing import Any, List, Mapping, Optional
|
||||
|
||||
from mkdocstrings.handlers.base import BaseHandler
|
||||
|
||||
|
||||
class Definition:
|
||||
"""A definition extracted by Doxygen."""
|
||||
|
||||
def __init__(self, name: str, kind: Optional[str] = None,
|
||||
node: Optional[ElementTree.Element] = None,
|
||||
is_member: bool = False):
|
||||
self.name = name
|
||||
self.kind = kind if kind is not None else node.get('kind')
|
||||
self.desc = None
|
||||
self.id = name if not is_member else None
|
||||
self.members = None
|
||||
self.params = None
|
||||
self.template_params = None
|
||||
self.trailing_return_type = None
|
||||
self.type = None
|
||||
|
||||
|
||||
# A map from Doxygen to HTML tags.
|
||||
tag_map = {
|
||||
'bold': 'b',
|
||||
'emphasis': 'em',
|
||||
'computeroutput': 'code',
|
||||
'para': 'p',
|
||||
'programlisting': 'pre',
|
||||
'verbatim': 'pre'
|
||||
}
|
||||
|
||||
# A map from Doxygen tags to text.
|
||||
tag_text_map = {
|
||||
'codeline': '',
|
||||
'highlight': '',
|
||||
'sp': ' '
|
||||
}
|
||||
|
||||
|
||||
def escape_html(s: str) -> str:
|
||||
return s.replace("<", "<")
|
||||
|
||||
|
||||
def doxyxml2html(nodes: List[ElementTree.Element]):
|
||||
out = ''
|
||||
for n in nodes:
|
||||
tag = tag_map.get(n.tag)
|
||||
if not tag:
|
||||
out += tag_text_map[n.tag]
|
||||
out += '<' + tag + '>' if tag else ''
|
||||
out += '<code class="language-cpp">' if tag == 'pre' else ''
|
||||
if n.text:
|
||||
out += escape_html(n.text)
|
||||
out += doxyxml2html(list(n))
|
||||
out += '</code>' if tag == 'pre' else ''
|
||||
out += '</' + tag + '>' if tag else ''
|
||||
if n.tail:
|
||||
out += n.tail
|
||||
return out
|
||||
|
||||
|
||||
def convert_template_params(node: ElementTree.Element) -> Optional[List[Definition]]:
|
||||
template_param_list = node.find('templateparamlist')
|
||||
if template_param_list is None:
|
||||
return None
|
||||
params = []
|
||||
for param_node in template_param_list.findall('param'):
|
||||
name = param_node.find('declname')
|
||||
param = Definition(name.text if name is not None else '', 'param')
|
||||
param.type = param_node.find('type').text
|
||||
params.append(param)
|
||||
return params
|
||||
|
||||
|
||||
def get_description(node: ElementTree.Element) -> List[ElementTree.Element]:
|
||||
return node.findall('briefdescription/para') + \
|
||||
node.findall('detaileddescription/para')
|
||||
|
||||
|
||||
def normalize_type(type_: str) -> str:
|
||||
type_ = type_.replace('< ', '<').replace(' >', '>')
|
||||
return type_.replace(' &', '&').replace(' *', '*')
|
||||
|
||||
|
||||
def convert_type(type_: ElementTree.Element) -> Optional[str]:
|
||||
if type_ is None:
|
||||
return None
|
||||
result = type_.text if type_.text else ''
|
||||
for ref in type_:
|
||||
result += ref.text
|
||||
if ref.tail:
|
||||
result += ref.tail
|
||||
result += type_.tail.strip()
|
||||
return normalize_type(result)
|
||||
|
||||
|
||||
def convert_params(func: ElementTree.Element) -> List[Definition]:
|
||||
params = []
|
||||
for p in func.findall('param'):
|
||||
d = Definition(p.find('declname').text, 'param')
|
||||
d.type = convert_type(p.find('type'))
|
||||
params.append(d)
|
||||
return params
|
||||
|
||||
|
||||
def convert_return_type(d: Definition, node: ElementTree.Element) -> None:
|
||||
d.trailing_return_type = None
|
||||
if d.type == 'auto' or d.type == 'constexpr auto':
|
||||
parts = node.find('argsstring').text.split(' -> ')
|
||||
if len(parts) > 1:
|
||||
d.trailing_return_type = normalize_type(parts[1])
|
||||
|
||||
|
||||
def render_param(param: Definition) -> str:
|
||||
return param.type + (f' {param.name}' if len(param.name) > 0 else '')
|
||||
|
||||
|
||||
def render_decl(d: Definition) -> str:
|
||||
text = ''
|
||||
if d.id is not None:
|
||||
text += f'<a id="{d.id}">\n'
|
||||
text += '<pre><code class="language-cpp decl">'
|
||||
|
||||
text += '<div>'
|
||||
if d.template_params is not None:
|
||||
text += 'template <'
|
||||
text += ', '.join([render_param(p) for p in d.template_params])
|
||||
text += '>\n'
|
||||
text += '</div>'
|
||||
|
||||
text += '<div>'
|
||||
end = ';'
|
||||
if d.kind == 'function' or d.kind == 'variable':
|
||||
text += d.type + ' ' if len(d.type) > 0 else ''
|
||||
elif d.kind == 'typedef':
|
||||
text += 'using '
|
||||
elif d.kind == 'define':
|
||||
end = ''
|
||||
else:
|
||||
text += d.kind + ' '
|
||||
text += d.name
|
||||
|
||||
if d.params is not None:
|
||||
params = ', '.join([
|
||||
(p.type + ' ' if p.type else '') + p.name for p in d.params])
|
||||
text += '(' + escape_html(params) + ')'
|
||||
if d.trailing_return_type:
|
||||
text += ' -⁠> ' + escape_html(d.trailing_return_type)
|
||||
elif d.kind == 'typedef':
|
||||
text += ' = ' + escape_html(d.type)
|
||||
|
||||
text += end
|
||||
text += '</div>'
|
||||
text += '</code></pre>\n'
|
||||
if d.id is not None:
|
||||
text += f'</a>\n'
|
||||
return text
|
||||
|
||||
|
||||
class CxxHandler(BaseHandler):
|
||||
def __init__(self, **kwargs: Any) -> None:
|
||||
super().__init__(handler='cxx', **kwargs)
|
||||
|
||||
headers = [
|
||||
'args.h', 'base.h', 'chrono.h', 'color.h', 'compile.h', 'format.h',
|
||||
'os.h', 'ostream.h', 'printf.h', 'ranges.h', 'std.h', 'xchar.h'
|
||||
]
|
||||
|
||||
# Run doxygen.
|
||||
cmd = ['doxygen', '-']
|
||||
support_dir = Path(__file__).parents[3]
|
||||
top_dir = os.path.dirname(support_dir)
|
||||
include_dir = os.path.join(top_dir, 'include', 'fmt')
|
||||
self._ns2doxyxml = {}
|
||||
build_dir = os.path.join(top_dir, 'build')
|
||||
os.makedirs(build_dir, exist_ok=True)
|
||||
self._doxyxml_dir = os.path.join(build_dir, 'doxyxml')
|
||||
p = Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT)
|
||||
_, _ = p.communicate(input=r'''
|
||||
PROJECT_NAME = fmt
|
||||
GENERATE_XML = YES
|
||||
GENERATE_LATEX = NO
|
||||
GENERATE_HTML = NO
|
||||
INPUT = {0}
|
||||
XML_OUTPUT = {1}
|
||||
QUIET = YES
|
||||
AUTOLINK_SUPPORT = NO
|
||||
MACRO_EXPANSION = YES
|
||||
PREDEFINED = _WIN32=1 \
|
||||
__linux__=1 \
|
||||
FMT_ENABLE_IF(...)= \
|
||||
FMT_USE_USER_LITERALS=1 \
|
||||
FMT_USE_ALIAS_TEMPLATES=1 \
|
||||
FMT_USE_NONTYPE_TEMPLATE_ARGS=1 \
|
||||
FMT_API= \
|
||||
"FMT_BEGIN_NAMESPACE=namespace fmt {{" \
|
||||
"FMT_END_NAMESPACE=}}" \
|
||||
"FMT_DOC=1"
|
||||
'''.format(
|
||||
' '.join([os.path.join(include_dir, h) for h in headers]),
|
||||
self._doxyxml_dir).encode('utf-8'))
|
||||
if p.returncode != 0:
|
||||
raise CalledProcessError(p.returncode, cmd)
|
||||
|
||||
# Merge all file-level XMLs into one to simplify search.
|
||||
self._file_doxyxml = None
|
||||
for h in headers:
|
||||
filename = h.replace(".h", "_8h.xml")
|
||||
with open(os.path.join(self._doxyxml_dir, filename)) as f:
|
||||
doxyxml = ElementTree.parse(f)
|
||||
if self._file_doxyxml is None:
|
||||
self._file_doxyxml = doxyxml
|
||||
continue
|
||||
root = self._file_doxyxml.getroot()
|
||||
for node in doxyxml.getroot():
|
||||
root.append(node)
|
||||
|
||||
def collect_compound(self, identifier: str,
|
||||
cls: List[ElementTree.Element]) -> Definition:
|
||||
"""Collect a compound definition such as a struct."""
|
||||
path = os.path.join(self._doxyxml_dir, cls[0].get('refid') + '.xml')
|
||||
with open(path) as f:
|
||||
xml = ElementTree.parse(f)
|
||||
node = xml.find('compounddef')
|
||||
d = Definition(identifier, node=node)
|
||||
d.template_params = convert_template_params(node)
|
||||
d.desc = get_description(node)
|
||||
d.members = []
|
||||
for m in \
|
||||
node.findall('sectiondef[@kind="public-attrib"]/memberdef') + \
|
||||
node.findall('sectiondef[@kind="public-func"]/memberdef'):
|
||||
name = m.find('name').text
|
||||
# Doxygen incorrectly classifies members of private unnamed unions as
|
||||
# public members of the containing class.
|
||||
if name.endswith('_'):
|
||||
continue
|
||||
desc = get_description(m)
|
||||
if len(desc) == 0:
|
||||
continue
|
||||
kind = m.get('kind')
|
||||
member = Definition(name if name else '', kind=kind, is_member=True)
|
||||
type_text = m.find('type').text
|
||||
member.type = type_text if type_text else ''
|
||||
if kind == 'function':
|
||||
member.params = convert_params(m)
|
||||
convert_return_type(member, m)
|
||||
member.template_params = None
|
||||
member.desc = desc
|
||||
d.members.append(member)
|
||||
return d
|
||||
|
||||
def collect(self, identifier: str, _config: Mapping[str, Any]) -> Definition:
|
||||
qual_name = 'fmt::' + identifier
|
||||
|
||||
param_str = None
|
||||
paren = qual_name.find('(')
|
||||
if paren > 0:
|
||||
qual_name, param_str = qual_name[:paren], qual_name[paren + 1:-1]
|
||||
|
||||
colons = qual_name.rfind('::')
|
||||
namespace, name = qual_name[:colons], qual_name[colons + 2:]
|
||||
|
||||
# Load XML.
|
||||
doxyxml = self._ns2doxyxml.get(namespace)
|
||||
if doxyxml is None:
|
||||
path = f'namespace{namespace.replace("::", "_1_1")}.xml'
|
||||
with open(os.path.join(self._doxyxml_dir, path)) as f:
|
||||
doxyxml = ElementTree.parse(f)
|
||||
self._ns2doxyxml[namespace] = doxyxml
|
||||
|
||||
nodes = doxyxml.findall(
|
||||
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
|
||||
if len(nodes) == 0:
|
||||
nodes = self._file_doxyxml.findall(
|
||||
f"compounddef/sectiondef/memberdef/name[.='{name}']/..")
|
||||
candidates = []
|
||||
for node in nodes:
|
||||
# Process a function or a typedef.
|
||||
params = None
|
||||
d = Definition(name, node=node)
|
||||
if d.kind == 'function':
|
||||
params = convert_params(node)
|
||||
node_param_str = ', '.join([p.type for p in params])
|
||||
if param_str and param_str != node_param_str:
|
||||
candidates.append(f'{name}({node_param_str})')
|
||||
continue
|
||||
elif d.kind == 'define':
|
||||
params = []
|
||||
for p in node.findall('param'):
|
||||
param = Definition(p.find('defname').text, kind='param')
|
||||
param.type = None
|
||||
params.append(param)
|
||||
d.type = convert_type(node.find('type'))
|
||||
d.template_params = convert_template_params(node)
|
||||
d.params = params
|
||||
convert_return_type(d, node)
|
||||
d.desc = get_description(node)
|
||||
return d
|
||||
|
||||
cls = doxyxml.findall(f"compounddef/innerclass[.='{qual_name}']")
|
||||
if not cls:
|
||||
raise Exception(f'Cannot find {identifier}. Candidates: {candidates}')
|
||||
return self.collect_compound(identifier, cls)
|
||||
|
||||
def render(self, d: Definition, config: dict) -> str:
|
||||
if d.id is not None:
|
||||
self.do_heading('', 0, id=d.id)
|
||||
text = '<div class="docblock">\n'
|
||||
text += render_decl(d)
|
||||
text += '<div class="docblock-desc">\n'
|
||||
text += doxyxml2html(d.desc)
|
||||
if d.members is not None:
|
||||
for m in d.members:
|
||||
text += self.render(m, config)
|
||||
text += '</div>\n'
|
||||
text += '</div>\n'
|
||||
return text
|
||||
|
||||
|
||||
def get_handler(theme: str, custom_templates: Optional[str] = None,
|
||||
**_config: Any) -> CxxHandler:
|
||||
"""Return an instance of `CxxHandler`.
|
||||
|
||||
Arguments:
|
||||
theme: The theme to use when rendering contents.
|
||||
custom_templates: Directory containing custom templates.
|
||||
**_config: Configuration passed to the handler.
|
||||
"""
|
||||
return CxxHandler(theme=theme, custom_templates=custom_templates)
|
||||
+1
@@ -0,0 +1 @@
|
||||
mkdocsstrings requires a handler to have a templates directory.
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""Make a release.
|
||||
|
||||
Usage:
|
||||
release.py [<branch>]
|
||||
|
||||
For the release command $FMT_TOKEN should contain a GitHub personal access token
|
||||
obtained from https://github.com/settings/tokens.
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
import datetime, docopt, errno, fileinput, json, os
|
||||
import re, shutil, sys
|
||||
from subprocess import check_call
|
||||
import urllib.request
|
||||
|
||||
|
||||
class Git:
|
||||
def __init__(self, dir):
|
||||
self.dir = dir
|
||||
|
||||
def call(self, method, args, **kwargs):
|
||||
return check_call(['git', method] + list(args), **kwargs)
|
||||
|
||||
def add(self, *args):
|
||||
return self.call('add', args, cwd=self.dir)
|
||||
|
||||
def checkout(self, *args):
|
||||
return self.call('checkout', args, cwd=self.dir)
|
||||
|
||||
def clean(self, *args):
|
||||
return self.call('clean', args, cwd=self.dir)
|
||||
|
||||
def clone(self, *args):
|
||||
return self.call('clone', list(args) + [self.dir])
|
||||
|
||||
def commit(self, *args):
|
||||
return self.call('commit', args, cwd=self.dir)
|
||||
|
||||
def pull(self, *args):
|
||||
return self.call('pull', args, cwd=self.dir)
|
||||
|
||||
def push(self, *args):
|
||||
return self.call('push', args, cwd=self.dir)
|
||||
|
||||
def reset(self, *args):
|
||||
return self.call('reset', args, cwd=self.dir)
|
||||
|
||||
def update(self, *args):
|
||||
clone = not os.path.exists(self.dir)
|
||||
if clone:
|
||||
self.clone(*args)
|
||||
return clone
|
||||
|
||||
|
||||
def clean_checkout(repo, branch):
|
||||
repo.clean('-f', '-d')
|
||||
repo.reset('--hard')
|
||||
repo.checkout(branch)
|
||||
|
||||
|
||||
class Runner:
|
||||
def __init__(self, cwd):
|
||||
self.cwd = cwd
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
kwargs['cwd'] = kwargs.get('cwd', self.cwd)
|
||||
check_call(args, **kwargs)
|
||||
|
||||
|
||||
def create_build_env():
|
||||
"""Create a build environment."""
|
||||
class Env:
|
||||
pass
|
||||
env = Env()
|
||||
env.fmt_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
env.build_dir = 'build'
|
||||
env.fmt_repo = Git(os.path.join(env.build_dir, 'fmt'))
|
||||
return env
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
args = docopt.docopt(__doc__)
|
||||
env = create_build_env()
|
||||
fmt_repo = env.fmt_repo
|
||||
|
||||
branch = args.get('<branch>')
|
||||
if branch is None:
|
||||
branch = 'master'
|
||||
if not fmt_repo.update('-b', branch, 'git@github.com:fmtlib/fmt'):
|
||||
clean_checkout(fmt_repo, branch)
|
||||
|
||||
# Update the date in the changelog and extract the version and the first
|
||||
# section content.
|
||||
changelog = 'ChangeLog.md'
|
||||
changelog_path = os.path.join(fmt_repo.dir, changelog)
|
||||
is_first_section = True
|
||||
first_section = []
|
||||
for i, line in enumerate(fileinput.input(changelog_path, inplace=True)):
|
||||
if i == 0:
|
||||
version = re.match(r'# (.*) - TBD', line).group(1)
|
||||
line = '# {} - {}\n'.format(
|
||||
version, datetime.date.today().isoformat())
|
||||
elif not is_first_section:
|
||||
pass
|
||||
elif line.startswith('#'):
|
||||
is_first_section = False
|
||||
else:
|
||||
first_section.append(line)
|
||||
sys.stdout.write(line)
|
||||
if first_section[0] == '\n':
|
||||
first_section.pop(0)
|
||||
|
||||
ns_version = None
|
||||
base_h_path = os.path.join(fmt_repo.dir, 'include', 'fmt', 'base.h')
|
||||
for line in fileinput.input(base_h_path):
|
||||
m = re.match(r'\s*inline namespace v(.*) .*', line)
|
||||
if m:
|
||||
ns_version = m.group(1)
|
||||
break
|
||||
major_version = version.split('.')[0]
|
||||
if not ns_version or ns_version != major_version:
|
||||
raise Exception(f'Version mismatch {ns_version} != {major_version}')
|
||||
|
||||
# Workaround GitHub-flavored Markdown treating newlines as <br>.
|
||||
changes = ''
|
||||
code_block = False
|
||||
stripped = False
|
||||
for line in first_section:
|
||||
if re.match(r'^\s*```', line):
|
||||
code_block = not code_block
|
||||
changes += line
|
||||
stripped = False
|
||||
continue
|
||||
if code_block:
|
||||
changes += line
|
||||
continue
|
||||
if line == '\n' or re.match(r'^\s*\|.*', line):
|
||||
if stripped:
|
||||
changes += '\n'
|
||||
stripped = False
|
||||
changes += line
|
||||
continue
|
||||
if stripped:
|
||||
line = ' ' + line.lstrip()
|
||||
changes += line.rstrip()
|
||||
stripped = True
|
||||
|
||||
fmt_repo.checkout('-B', 'release')
|
||||
fmt_repo.add(changelog)
|
||||
fmt_repo.commit('-m', 'Update version')
|
||||
|
||||
# Build the docs and package.
|
||||
run = Runner(fmt_repo.dir)
|
||||
run('cmake', '.')
|
||||
run('make', 'doc', 'package_source')
|
||||
|
||||
# Create a release on GitHub.
|
||||
fmt_repo.push('origin', 'release')
|
||||
auth_headers = {'Authorization': 'token ' + os.getenv('FMT_TOKEN')}
|
||||
req = urllib.request.Request(
|
||||
'https://api.github.com/repos/fmtlib/fmt/releases',
|
||||
data=json.dumps({'tag_name': version,
|
||||
'target_commitish': 'release',
|
||||
'body': changes, 'draft': True}).encode('utf-8'),
|
||||
headers=auth_headers, method='POST')
|
||||
with urllib.request.urlopen(req) as response:
|
||||
if response.status != 201:
|
||||
raise Exception(f'Failed to create a release ' +
|
||||
'{response.status} {response.reason}')
|
||||
response_data = json.loads(response.read().decode('utf-8'))
|
||||
id = response_data['id']
|
||||
|
||||
# Upload the package.
|
||||
uploads_url = 'https://uploads.github.com/repos/fmtlib/fmt/releases'
|
||||
package = 'fmt-{}.zip'.format(version)
|
||||
req = urllib.request.Request(
|
||||
f'{uploads_url}/{id}/assets?name={package}',
|
||||
headers={'Content-Type': 'application/zip'} | auth_headers,
|
||||
data=open('build/fmt/' + package, 'rb').read(), method='POST')
|
||||
with urllib.request.urlopen(req) as response:
|
||||
if response.status != 201:
|
||||
raise Exception(f'Failed to upload an asset '
|
||||
'{response.status} {response.reason}')
|
||||
|
||||
short_version = '.'.join(version.split('.')[:-1])
|
||||
check_call(['./mkdocs', 'deploy', short_version])
|
||||
-159
@@ -1,159 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# reStructuredText (RST) to GitHub-flavored Markdown converter
|
||||
|
||||
import re, sys
|
||||
from docutils import core, nodes, writers
|
||||
|
||||
|
||||
def is_github_ref(node):
|
||||
return re.match('https://github.com/.*/(issues|pull)/.*', node['refuri'])
|
||||
|
||||
|
||||
class Translator(nodes.NodeVisitor):
|
||||
def __init__(self, document):
|
||||
nodes.NodeVisitor.__init__(self, document)
|
||||
self.output = ''
|
||||
self.indent = 0
|
||||
self.preserve_newlines = False
|
||||
|
||||
def write(self, text):
|
||||
self.output += text.replace('\n', '\n' + ' ' * self.indent)
|
||||
|
||||
def visit_document(self, node):
|
||||
pass
|
||||
|
||||
def depart_document(self, node):
|
||||
pass
|
||||
|
||||
def visit_section(self, node):
|
||||
pass
|
||||
|
||||
def depart_section(self, node):
|
||||
# Skip all sections except the first one.
|
||||
raise nodes.StopTraversal
|
||||
|
||||
def visit_title(self, node):
|
||||
self.version = re.match(r'(\d+\.\d+\.\d+).*', node.children[0]).group(1)
|
||||
raise nodes.SkipChildren
|
||||
|
||||
def visit_title_reference(self, node):
|
||||
raise Exception(node)
|
||||
|
||||
def depart_title(self, node):
|
||||
pass
|
||||
|
||||
def visit_Text(self, node):
|
||||
if not self.preserve_newlines:
|
||||
node = node.replace('\n', ' ')
|
||||
self.write(node)
|
||||
|
||||
def depart_Text(self, node):
|
||||
pass
|
||||
|
||||
def visit_bullet_list(self, node):
|
||||
pass
|
||||
|
||||
def depart_bullet_list(self, node):
|
||||
pass
|
||||
|
||||
def visit_list_item(self, node):
|
||||
self.write('* ')
|
||||
self.indent += 2
|
||||
|
||||
def depart_list_item(self, node):
|
||||
self.indent -= 2
|
||||
self.write('\n\n')
|
||||
|
||||
def visit_paragraph(self, node):
|
||||
pass
|
||||
|
||||
def depart_paragraph(self, node):
|
||||
pass
|
||||
|
||||
def visit_reference(self, node):
|
||||
if not is_github_ref(node):
|
||||
self.write('[')
|
||||
|
||||
def depart_reference(self, node):
|
||||
if not is_github_ref(node):
|
||||
self.write('](' + node['refuri'] + ')')
|
||||
|
||||
def visit_target(self, node):
|
||||
pass
|
||||
|
||||
def depart_target(self, node):
|
||||
pass
|
||||
|
||||
def visit_literal(self, node):
|
||||
self.write('`')
|
||||
|
||||
def depart_literal(self, node):
|
||||
self.write('`')
|
||||
|
||||
def visit_literal_block(self, node):
|
||||
self.write('\n\n```')
|
||||
if 'c++' in node['classes']:
|
||||
self.write('c++')
|
||||
self.write('\n')
|
||||
self.preserve_newlines = True
|
||||
|
||||
def depart_literal_block(self, node):
|
||||
self.write('\n```\n')
|
||||
self.preserve_newlines = False
|
||||
|
||||
def visit_inline(self, node):
|
||||
pass
|
||||
|
||||
def depart_inline(self, node):
|
||||
pass
|
||||
|
||||
def visit_image(self, node):
|
||||
self.write('')
|
||||
|
||||
def depart_image(self, node):
|
||||
pass
|
||||
|
||||
def write_row(self, row, widths):
|
||||
for i, entry in enumerate(row):
|
||||
text = entry[0][0] if len(entry) > 0 else ''
|
||||
if i != 0:
|
||||
self.write('|')
|
||||
self.write('{:{}}'.format(text, widths[i]))
|
||||
self.write('\n')
|
||||
|
||||
def visit_table(self, node):
|
||||
table = node.children[0]
|
||||
colspecs = table[:-2]
|
||||
thead = table[-2]
|
||||
tbody = table[-1]
|
||||
widths = [int(cs['colwidth']) for cs in colspecs]
|
||||
sep = '|'.join(['-' * w for w in widths]) + '\n'
|
||||
self.write('\n\n')
|
||||
self.write_row(thead[0], widths)
|
||||
self.write(sep)
|
||||
for row in tbody:
|
||||
self.write_row(row, widths)
|
||||
raise nodes.SkipChildren
|
||||
|
||||
def depart_table(self, node):
|
||||
pass
|
||||
|
||||
class MDWriter(writers.Writer):
|
||||
"""GitHub-flavored markdown writer"""
|
||||
|
||||
supported = ('md',)
|
||||
"""Formats this writer supports."""
|
||||
|
||||
def translate(self):
|
||||
translator = Translator(self.document)
|
||||
self.document.walkabout(translator)
|
||||
self.output = (translator.output, translator.version)
|
||||
|
||||
|
||||
def convert(rst_path):
|
||||
"""Converts RST file to Markdown."""
|
||||
return core.publish_file(source_path=rst_path, writer=MDWriter())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
convert(sys.argv[1])
|
||||
@@ -1,7 +0,0 @@
|
||||
# Sphinx configuration for readthedocs.
|
||||
|
||||
import os, sys
|
||||
|
||||
master_doc = 'index'
|
||||
html_theme = 'theme'
|
||||
html_theme_path = ["."]
|
||||
@@ -1,2 +0,0 @@
|
||||
If you are not redirected automatically, follow the
|
||||
`link to the fmt documentation <https://fmt.dev/latest/>`_.
|
||||
@@ -1,17 +0,0 @@
|
||||
{% extends "basic/layout.html" %}
|
||||
|
||||
{% block extrahead %}
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="refresh" content="1;url=https://fmt.dev/latest/">
|
||||
<script type="text/javascript">
|
||||
window.location.href = "https://fmt.dev/latest/"
|
||||
</script>
|
||||
<title>Page Redirection</title>
|
||||
{% endblock %}
|
||||
|
||||
{% block document %}
|
||||
If you are not redirected automatically, follow the <a href='https://fmt.dev/latest/'>link to the fmt documentation</a>.
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
{% endblock %}
|
||||
@@ -1,2 +0,0 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
Vendored
-721
@@ -1,721 +0,0 @@
|
||||
// linalg.h - 2.2-beta - Single-header public domain linear algebra library
|
||||
//
|
||||
// The intent of this library is to provide the bulk of the functionality
|
||||
// you need to write programs that frequently use small, fixed-size vectors
|
||||
// and matrices, in domains such as computational geometry or computer
|
||||
// graphics. It strives for terse, readable source code.
|
||||
//
|
||||
// The original author of this software is Sterling Orsten, and its permanent
|
||||
// home is <http://github.com/sgorsten/linalg/>. If you find this software
|
||||
// useful, an acknowledgement in your source text and/or product documentation
|
||||
// is appreciated, but not required.
|
||||
//
|
||||
// The author acknowledges significant insights and contributions by:
|
||||
// Stan Melax <http://github.com/melax/>
|
||||
// Dimitri Diakopoulos <http://github.com/ddiakopoulos/>
|
||||
//
|
||||
// Some features are deprecated. Define LINALG_FORWARD_COMPATIBLE to remove them.
|
||||
|
||||
|
||||
|
||||
// This is free and unencumbered software released into the public domain.
|
||||
//
|
||||
// Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
// distribute this software, either in source code form or as a compiled
|
||||
// binary, for any purpose, commercial or non-commercial, and by any
|
||||
// means.
|
||||
//
|
||||
// In jurisdictions that recognize copyright laws, the author or authors
|
||||
// of this software dedicate any and all copyright interest in the
|
||||
// software to the public domain. We make this dedication for the benefit
|
||||
// of the public at large and to the detriment of our heirs and
|
||||
// successors. We intend this dedication to be an overt act of
|
||||
// relinquishment in perpetuity of all present and future rights to this
|
||||
// software under copyright law.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
// OTHER DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
// For more information, please refer to <http://unlicense.org/>
|
||||
|
||||
|
||||
|
||||
#pragma once
|
||||
#ifndef LINALG_H
|
||||
#define LINALG_H
|
||||
|
||||
#include <cmath> // For various unary math functions, such as std::sqrt
|
||||
#include <cstdlib> // To resolve std::abs ambiguity on clang
|
||||
#include <cstdint> // For implementing namespace linalg::aliases
|
||||
#include <array> // For std::array
|
||||
#include <iosfwd> // For forward definitions of std::ostream
|
||||
#include <type_traits> // For std::enable_if, std::is_same, std::declval
|
||||
#include <functional> // For std::hash declaration
|
||||
|
||||
// In Visual Studio 2015, `constexpr` applied to a member function implies `const`, which causes ambiguous overload resolution
|
||||
#if _MSC_VER <= 1900
|
||||
#define LINALG_CONSTEXPR14
|
||||
#else
|
||||
#define LINALG_CONSTEXPR14 constexpr
|
||||
#endif
|
||||
|
||||
namespace linalg
|
||||
{
|
||||
// Small, fixed-length vector type, consisting of exactly M elements of type T, and presumed to be a column-vector unless otherwise noted.
|
||||
template<class T, int M> struct vec;
|
||||
|
||||
// Small, fixed-size matrix type, consisting of exactly M rows and N columns of type T, stored in column-major order.
|
||||
template<class T, int M, int N> struct mat;
|
||||
|
||||
// Specialize converter<T,U> with a function application operator that converts type U to type T to enable implicit conversions
|
||||
template<class T, class U> struct converter {};
|
||||
namespace detail
|
||||
{
|
||||
template<class T, class U> using conv_t = typename std::enable_if<!std::is_same<T,U>::value, decltype(converter<T,U>{}(std::declval<U>()))>::type;
|
||||
|
||||
// Trait for retrieving scalar type of any linear algebra object
|
||||
template<class A> struct scalar_type {};
|
||||
template<class T, int M > struct scalar_type<vec<T,M >> { using type = T; };
|
||||
template<class T, int M, int N> struct scalar_type<mat<T,M,N>> { using type = T; };
|
||||
|
||||
// Type returned by the compare(...) function which supports all six comparison operators against 0
|
||||
template<class T> struct ord { T a,b; };
|
||||
template<class T> constexpr bool operator == (const ord<T> & o, std::nullptr_t) { return o.a == o.b; }
|
||||
template<class T> constexpr bool operator != (const ord<T> & o, std::nullptr_t) { return !(o.a == o.b); }
|
||||
template<class T> constexpr bool operator < (const ord<T> & o, std::nullptr_t) { return o.a < o.b; }
|
||||
template<class T> constexpr bool operator > (const ord<T> & o, std::nullptr_t) { return o.b < o.a; }
|
||||
template<class T> constexpr bool operator <= (const ord<T> & o, std::nullptr_t) { return !(o.b < o.a); }
|
||||
template<class T> constexpr bool operator >= (const ord<T> & o, std::nullptr_t) { return !(o.a < o.b); }
|
||||
|
||||
// Patterns which can be used with the compare(...) function
|
||||
template<class A, class B> struct any_compare {};
|
||||
template<class T> struct any_compare<vec<T,1>,vec<T,1>> { using type=ord<T>; constexpr ord<T> operator() (const vec<T,1> & a, const vec<T,1> & b) const { return ord<T>{a.x,b.x}; } };
|
||||
template<class T> struct any_compare<vec<T,2>,vec<T,2>> { using type=ord<T>; constexpr ord<T> operator() (const vec<T,2> & a, const vec<T,2> & b) const { return !(a.x==b.x) ? ord<T>{a.x,b.x} : ord<T>{a.y,b.y}; } };
|
||||
template<class T> struct any_compare<vec<T,3>,vec<T,3>> { using type=ord<T>; constexpr ord<T> operator() (const vec<T,3> & a, const vec<T,3> & b) const { return !(a.x==b.x) ? ord<T>{a.x,b.x} : !(a.y==b.y) ? ord<T>{a.y,b.y} : ord<T>{a.z,b.z}; } };
|
||||
template<class T> struct any_compare<vec<T,4>,vec<T,4>> { using type=ord<T>; constexpr ord<T> operator() (const vec<T,4> & a, const vec<T,4> & b) const { return !(a.x==b.x) ? ord<T>{a.x,b.x} : !(a.y==b.y) ? ord<T>{a.y,b.y} : !(a.z==b.z) ? ord<T>{a.z,b.z} : ord<T>{a.w,b.w}; } };
|
||||
template<class T, int M> struct any_compare<mat<T,M,1>,mat<T,M,1>> { using type=ord<T>; constexpr ord<T> operator() (const mat<T,M,1> & a, const mat<T,M,1> & b) const { return compare(a.x,b.x); } };
|
||||
template<class T, int M> struct any_compare<mat<T,M,2>,mat<T,M,2>> { using type=ord<T>; constexpr ord<T> operator() (const mat<T,M,2> & a, const mat<T,M,2> & b) const { return a.x!=b.x ? compare(a.x,b.x) : compare(a.y,b.y); } };
|
||||
template<class T, int M> struct any_compare<mat<T,M,3>,mat<T,M,3>> { using type=ord<T>; constexpr ord<T> operator() (const mat<T,M,3> & a, const mat<T,M,3> & b) const { return a.x!=b.x ? compare(a.x,b.x) : a.y!=b.y ? compare(a.y,b.y) : compare(a.z,b.z); } };
|
||||
template<class T, int M> struct any_compare<mat<T,M,4>,mat<T,M,4>> { using type=ord<T>; constexpr ord<T> operator() (const mat<T,M,4> & a, const mat<T,M,4> & b) const { return a.x!=b.x ? compare(a.x,b.x) : a.y!=b.y ? compare(a.y,b.y) : a.z!=b.z ? compare(a.z,b.z) : compare(a.w,b.w); } };
|
||||
|
||||
// Helper for compile-time index-based access to members of vector and matrix types
|
||||
template<int I> struct getter;
|
||||
template<> struct getter<0> { template<class A> constexpr auto operator() (A & a) const -> decltype(a.x) { return a.x; } };
|
||||
template<> struct getter<1> { template<class A> constexpr auto operator() (A & a) const -> decltype(a.y) { return a.y; } };
|
||||
template<> struct getter<2> { template<class A> constexpr auto operator() (A & a) const -> decltype(a.z) { return a.z; } };
|
||||
template<> struct getter<3> { template<class A> constexpr auto operator() (A & a) const -> decltype(a.w) { return a.w; } };
|
||||
|
||||
// Stand-in for std::integer_sequence/std::make_integer_sequence
|
||||
template<int... I> struct seq {};
|
||||
template<int A, int N> struct make_seq_impl;
|
||||
template<int A> struct make_seq_impl<A,0> { using type=seq<>; };
|
||||
template<int A> struct make_seq_impl<A,1> { using type=seq<A+0>; };
|
||||
template<int A> struct make_seq_impl<A,2> { using type=seq<A+0,A+1>; };
|
||||
template<int A> struct make_seq_impl<A,3> { using type=seq<A+0,A+1,A+2>; };
|
||||
template<int A> struct make_seq_impl<A,4> { using type=seq<A+0,A+1,A+2,A+3>; };
|
||||
template<int A, int B> using make_seq = typename make_seq_impl<A,B-A>::type;
|
||||
template<class T, int M, int... I> vec<T,sizeof...(I)> constexpr swizzle(const vec<T,M> & v, seq<I...> i) { return {getter<I>{}(v)...}; }
|
||||
template<class T, int M, int N, int... I, int... J> mat<T,sizeof...(I),sizeof...(J)> constexpr swizzle(const mat<T,M,N> & m, seq<I...> i, seq<J...> j) { return {swizzle(getter<J>{}(m),i)...}; }
|
||||
|
||||
// SFINAE helpers to determine result of function application
|
||||
template<class F, class... T> using ret_t = decltype(std::declval<F>()(std::declval<T>()...));
|
||||
|
||||
// SFINAE helper which is defined if all provided types are scalars
|
||||
struct empty {};
|
||||
template<class... T> struct scalars;
|
||||
template<> struct scalars<> { using type=void; };
|
||||
template<class T, class... U> struct scalars<T,U...> : std::conditional<std::is_arithmetic<T>::value, scalars<U...>, empty>::type {};
|
||||
template<class... T> using scalars_t = typename scalars<T...>::type;
|
||||
|
||||
// Helpers which indicate how apply(F, ...) should be called for various arguments
|
||||
template<class F, class Void, class... T> struct apply {}; // Patterns which contain only vectors or scalars
|
||||
template<class F, int M, class A > struct apply<F, scalars_t< >, vec<A,M> > { using type=vec<ret_t<F,A >,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, const vec<A,M> & a ) { return {f(getter<I>{}(a) )...}; } };
|
||||
template<class F, int M, class A, class B > struct apply<F, scalars_t< >, vec<A,M>, vec<B,M> > { using type=vec<ret_t<F,A,B >,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, const vec<A,M> & a, const vec<B,M> & b ) { return {f(getter<I>{}(a), getter<I>{}(b) )...}; } };
|
||||
template<class F, int M, class A, class B > struct apply<F, scalars_t<B >, vec<A,M>, B > { using type=vec<ret_t<F,A,B >,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, const vec<A,M> & a, B b ) { return {f(getter<I>{}(a), b )...}; } };
|
||||
template<class F, int M, class A, class B > struct apply<F, scalars_t<A >, A, vec<B,M> > { using type=vec<ret_t<F,A,B >,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, A a, const vec<B,M> & b ) { return {f(a, getter<I>{}(b) )...}; } };
|
||||
template<class F, int M, class A, class B, class C> struct apply<F, scalars_t< >, vec<A,M>, vec<B,M>, vec<C,M>> { using type=vec<ret_t<F,A,B,C>,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, const vec<A,M> & a, const vec<B,M> & b, const vec<C,M> & c) { return {f(getter<I>{}(a), getter<I>{}(b), getter<I>{}(c))...}; } };
|
||||
template<class F, int M, class A, class B, class C> struct apply<F, scalars_t<C >, vec<A,M>, vec<B,M>, C > { using type=vec<ret_t<F,A,B,C>,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, const vec<A,M> & a, const vec<B,M> & b, C c) { return {f(getter<I>{}(a), getter<I>{}(b), c )...}; } };
|
||||
template<class F, int M, class A, class B, class C> struct apply<F, scalars_t<B >, vec<A,M>, B, vec<C,M>> { using type=vec<ret_t<F,A,B,C>,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, const vec<A,M> & a, B b, const vec<C,M> & c) { return {f(getter<I>{}(a), b, getter<I>{}(c))...}; } };
|
||||
template<class F, int M, class A, class B, class C> struct apply<F, scalars_t<B,C>, vec<A,M>, B, C > { using type=vec<ret_t<F,A,B,C>,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, const vec<A,M> & a, B b, C c) { return {f(getter<I>{}(a), b, c )...}; } };
|
||||
template<class F, int M, class A, class B, class C> struct apply<F, scalars_t<A >, A, vec<B,M>, vec<C,M>> { using type=vec<ret_t<F,A,B,C>,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, A a, const vec<B,M> & b, const vec<C,M> & c) { return {f(a, getter<I>{}(b), getter<I>{}(c))...}; } };
|
||||
template<class F, int M, class A, class B, class C> struct apply<F, scalars_t<A,C>, A, vec<B,M>, C > { using type=vec<ret_t<F,A,B,C>,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, A a, const vec<B,M> & b, C c) { return {f(a, getter<I>{}(b), c )...}; } };
|
||||
template<class F, int M, class A, class B, class C> struct apply<F, scalars_t<A,B>, A, B, vec<C,M>> { using type=vec<ret_t<F,A,B,C>,M>; enum {size=M, mm=0}; template<int... I> static constexpr type impl(seq<I...>, F f, A a, B b, const vec<C,M> & c) { return {f(a, b, getter<I>{}(c))...}; } };
|
||||
template<class F, int M, int N, class A > struct apply<F, scalars_t< >, mat<A,M,N> > { using type=mat<ret_t<F,A >,M,N>; enum {size=N, mm=0}; template<int... J> static constexpr type impl(seq<J...>, F f, const mat<A,M,N> & a ) { return {apply<F, void, vec<A,M> >::impl(make_seq<0,M>{}, f, getter<J>{}(a) )...}; } };
|
||||
template<class F, int M, int N, class A, class B> struct apply<F, scalars_t< >, mat<A,M,N>, mat<B,M,N>> { using type=mat<ret_t<F,A,B>,M,N>; enum {size=N, mm=1}; template<int... J> static constexpr type impl(seq<J...>, F f, const mat<A,M,N> & a, const mat<B,M,N> & b) { return {apply<F, void, vec<A,M>, vec<B,M>>::impl(make_seq<0,M>{}, f, getter<J>{}(a), getter<J>{}(b))...}; } };
|
||||
template<class F, int M, int N, class A, class B> struct apply<F, scalars_t<B>, mat<A,M,N>, B > { using type=mat<ret_t<F,A,B>,M,N>; enum {size=N, mm=0}; template<int... J> static constexpr type impl(seq<J...>, F f, const mat<A,M,N> & a, B b) { return {apply<F, void, vec<A,M>, B >::impl(make_seq<0,M>{}, f, getter<J>{}(a), b )...}; } };
|
||||
template<class F, int M, int N, class A, class B> struct apply<F, scalars_t<A>, A, mat<B,M,N>> { using type=mat<ret_t<F,A,B>,M,N>; enum {size=N, mm=0}; template<int... J> static constexpr type impl(seq<J...>, F f, A a, const mat<B,M,N> & b) { return {apply<F, void, A, vec<B,M>>::impl(make_seq<0,M>{}, f, a, getter<J>{}(b))...}; } };
|
||||
template<class F, class... A> struct apply<F, scalars_t<A...>, A...> { using type = ret_t<F,A...>; enum {size=0, mm=0}; static constexpr type impl(seq<>, F f, A... a) { return f(a...); } };
|
||||
|
||||
// Function objects for selecting between alternatives
|
||||
struct min { template<class A, class B> constexpr auto operator() (A a, B b) const -> typename std::remove_reference<decltype(a<b ? a : b)>::type { return a<b ? a : b; } };
|
||||
struct max { template<class A, class B> constexpr auto operator() (A a, B b) const -> typename std::remove_reference<decltype(a<b ? b : a)>::type { return a<b ? b : a; } };
|
||||
struct clamp { template<class A, class B, class C> constexpr auto operator() (A a, B b, C c) const -> typename std::remove_reference<decltype(a<b ? b : a<c ? a : c)>::type { return a<b ? b : a<c ? a : c; } };
|
||||
struct select { template<class A, class B, class C> constexpr auto operator() (A a, B b, C c) const -> typename std::remove_reference<decltype(a ? b : c)>::type { return a ? b : c; } };
|
||||
struct lerp { template<class A, class B, class C> constexpr auto operator() (A a, B b, C c) const -> decltype(a*(1-c) + b*c) { return a*(1-c) + b*c; } };
|
||||
|
||||
// Function objects for applying operators
|
||||
struct op_pos { template<class A> constexpr auto operator() (A a) const -> decltype(+a) { return +a; } };
|
||||
struct op_neg { template<class A> constexpr auto operator() (A a) const -> decltype(-a) { return -a; } };
|
||||
struct op_not { template<class A> constexpr auto operator() (A a) const -> decltype(!a) { return !a; } };
|
||||
struct op_cmp { template<class A> constexpr auto operator() (A a) const -> decltype(~(a)) { return ~a; } };
|
||||
struct op_mul { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a * b) { return a * b; } };
|
||||
struct op_div { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a / b) { return a / b; } };
|
||||
struct op_mod { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a % b) { return a % b; } };
|
||||
struct op_add { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a + b) { return a + b; } };
|
||||
struct op_sub { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a - b) { return a - b; } };
|
||||
struct op_lsh { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a << b) { return a << b; } };
|
||||
struct op_rsh { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a >> b) { return a >> b; } };
|
||||
struct op_lt { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a < b) { return a < b; } };
|
||||
struct op_gt { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a > b) { return a > b; } };
|
||||
struct op_le { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a <= b) { return a <= b; } };
|
||||
struct op_ge { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a >= b) { return a >= b; } };
|
||||
struct op_eq { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a == b) { return a == b; } };
|
||||
struct op_ne { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a != b) { return a != b; } };
|
||||
struct op_int { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a & b) { return a & b; } };
|
||||
struct op_xor { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a ^ b) { return a ^ b; } };
|
||||
struct op_un { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a | b) { return a | b; } };
|
||||
struct op_and { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a && b) { return a && b; } };
|
||||
struct op_or { template<class A, class B> constexpr auto operator() (A a, B b) const -> decltype(a || b) { return a || b; } };
|
||||
|
||||
// Function objects for applying standard library math functions
|
||||
struct std_abs { template<class A> auto operator() (A a) const -> decltype(std::abs (a)) { return std::abs (a); } };
|
||||
struct std_floor { template<class A> auto operator() (A a) const -> decltype(std::floor(a)) { return std::floor(a); } };
|
||||
struct std_ceil { template<class A> auto operator() (A a) const -> decltype(std::ceil (a)) { return std::ceil (a); } };
|
||||
struct std_exp { template<class A> auto operator() (A a) const -> decltype(std::exp (a)) { return std::exp (a); } };
|
||||
struct std_log { template<class A> auto operator() (A a) const -> decltype(std::log (a)) { return std::log (a); } };
|
||||
struct std_log10 { template<class A> auto operator() (A a) const -> decltype(std::log10(a)) { return std::log10(a); } };
|
||||
struct std_sqrt { template<class A> auto operator() (A a) const -> decltype(std::sqrt (a)) { return std::sqrt (a); } };
|
||||
struct std_sin { template<class A> auto operator() (A a) const -> decltype(std::sin (a)) { return std::sin (a); } };
|
||||
struct std_cos { template<class A> auto operator() (A a) const -> decltype(std::cos (a)) { return std::cos (a); } };
|
||||
struct std_tan { template<class A> auto operator() (A a) const -> decltype(std::tan (a)) { return std::tan (a); } };
|
||||
struct std_asin { template<class A> auto operator() (A a) const -> decltype(std::asin (a)) { return std::asin (a); } };
|
||||
struct std_acos { template<class A> auto operator() (A a) const -> decltype(std::acos (a)) { return std::acos (a); } };
|
||||
struct std_atan { template<class A> auto operator() (A a) const -> decltype(std::atan (a)) { return std::atan (a); } };
|
||||
struct std_sinh { template<class A> auto operator() (A a) const -> decltype(std::sinh (a)) { return std::sinh (a); } };
|
||||
struct std_cosh { template<class A> auto operator() (A a) const -> decltype(std::cosh (a)) { return std::cosh (a); } };
|
||||
struct std_tanh { template<class A> auto operator() (A a) const -> decltype(std::tanh (a)) { return std::tanh (a); } };
|
||||
struct std_round { template<class A> auto operator() (A a) const -> decltype(std::round(a)) { return std::round(a); } };
|
||||
struct std_fmod { template<class A, class B> auto operator() (A a, B b) const -> decltype(std::fmod (a, b)) { return std::fmod (a, b); } };
|
||||
struct std_pow { template<class A, class B> auto operator() (A a, B b) const -> decltype(std::pow (a, b)) { return std::pow (a, b); } };
|
||||
struct std_atan2 { template<class A, class B> auto operator() (A a, B b) const -> decltype(std::atan2 (a, b)) { return std::atan2 (a, b); } };
|
||||
struct std_copysign { template<class A, class B> auto operator() (A a, B b) const -> decltype(std::copysign(a, b)) { return std::copysign(a, b); } };
|
||||
}
|
||||
|
||||
// Small, fixed-length vector type, consisting of exactly M elements of type T, and presumed to be a column-vector unless otherwise noted
|
||||
template<class T> struct vec<T,1>
|
||||
{
|
||||
T x;
|
||||
constexpr vec() : x() {}
|
||||
constexpr vec(const T & x_) : x(x_) {}
|
||||
// NOTE: vec<T,1> does NOT have a constructor from pointer, this can conflict with initializing its single element from zero
|
||||
template<class U>
|
||||
constexpr explicit vec(const vec<U,1> & v) : vec(static_cast<T>(v.x)) {}
|
||||
constexpr const T & operator[] (int i) const { return x; }
|
||||
LINALG_CONSTEXPR14 T & operator[] (int i) { return x; }
|
||||
|
||||
template<class U, class=detail::conv_t<vec,U>> constexpr vec(const U & u) : vec(converter<vec,U>{}(u)) {}
|
||||
template<class U, class=detail::conv_t<U,vec>> constexpr operator U () const { return converter<U,vec>{}(*this); }
|
||||
};
|
||||
template<class T> struct vec<T,2>
|
||||
{
|
||||
T x,y;
|
||||
constexpr vec() : x(), y() {}
|
||||
constexpr vec(const T & x_, const T & y_) : x(x_), y(y_) {}
|
||||
constexpr explicit vec(const T & s) : vec(s, s) {}
|
||||
constexpr explicit vec(const T * p) : vec(p[0], p[1]) {}
|
||||
template<class U>
|
||||
constexpr explicit vec(const vec<U,2> & v) : vec(static_cast<T>(v.x), static_cast<T>(v.y)) {}
|
||||
constexpr const T & operator[] (int i) const { return i==0?x:y; }
|
||||
LINALG_CONSTEXPR14 T & operator[] (int i) { return i==0?x:y; }
|
||||
|
||||
template<class U, class=detail::conv_t<vec,U>> constexpr vec(const U & u) : vec(converter<vec,U>{}(u)) {}
|
||||
template<class U, class=detail::conv_t<U,vec>> constexpr operator U () const { return converter<U,vec>{}(*this); }
|
||||
};
|
||||
template<class T> struct vec<T,3>
|
||||
{
|
||||
T x,y,z;
|
||||
constexpr vec() : x(), y(), z() {}
|
||||
constexpr vec(const T & x_, const T & y_,
|
||||
const T & z_) : x(x_), y(y_), z(z_) {}
|
||||
constexpr vec(const vec<T,2> & xy,
|
||||
const T & z_) : vec(xy.x, xy.y, z_) {}
|
||||
constexpr explicit vec(const T & s) : vec(s, s, s) {}
|
||||
constexpr explicit vec(const T * p) : vec(p[0], p[1], p[2]) {}
|
||||
template<class U>
|
||||
constexpr explicit vec(const vec<U,3> & v) : vec(static_cast<T>(v.x), static_cast<T>(v.y), static_cast<T>(v.z)) {}
|
||||
constexpr const T & operator[] (int i) const { return i==0?x:i==1?y:z; }
|
||||
LINALG_CONSTEXPR14 T & operator[] (int i) { return i==0?x:i==1?y:z; }
|
||||
constexpr const vec<T,2> & xy() const { return *reinterpret_cast<const vec<T,2> *>(this); }
|
||||
vec<T,2> & xy() { return *reinterpret_cast<vec<T,2> *>(this); }
|
||||
|
||||
template<class U, class=detail::conv_t<vec,U>> constexpr vec(const U & u) : vec(converter<vec,U>{}(u)) {}
|
||||
template<class U, class=detail::conv_t<U,vec>> constexpr operator U () const { return converter<U,vec>{}(*this); }
|
||||
};
|
||||
template<class T> struct vec<T,4>
|
||||
{
|
||||
T x,y,z,w;
|
||||
constexpr vec() : x(), y(), z(), w() {}
|
||||
constexpr vec(const T & x_, const T & y_,
|
||||
const T & z_, const T & w_) : x(x_), y(y_), z(z_), w(w_) {}
|
||||
constexpr vec(const vec<T,2> & xy,
|
||||
const T & z_, const T & w_) : vec(xy.x, xy.y, z_, w_) {}
|
||||
constexpr vec(const vec<T,3> & xyz,
|
||||
const T & w_) : vec(xyz.x, xyz.y, xyz.z, w_) {}
|
||||
constexpr explicit vec(const T & s) : vec(s, s, s, s) {}
|
||||
constexpr explicit vec(const T * p) : vec(p[0], p[1], p[2], p[3]) {}
|
||||
template<class U>
|
||||
constexpr explicit vec(const vec<U,4> & v) : vec(static_cast<T>(v.x), static_cast<T>(v.y), static_cast<T>(v.z), static_cast<T>(v.w)) {}
|
||||
constexpr const T & operator[] (int i) const { return i==0?x:i==1?y:i==2?z:w; }
|
||||
LINALG_CONSTEXPR14 T & operator[] (int i) { return i==0?x:i==1?y:i==2?z:w; }
|
||||
constexpr const vec<T,2> & xy() const { return *reinterpret_cast<const vec<T,2> *>(this); }
|
||||
constexpr const vec<T,3> & xyz() const { return *reinterpret_cast<const vec<T,3> *>(this); }
|
||||
vec<T,2> & xy() { return *reinterpret_cast<vec<T,2> *>(this); }
|
||||
vec<T,3> & xyz() { return *reinterpret_cast<vec<T,3> *>(this); }
|
||||
|
||||
template<class U, class=detail::conv_t<vec,U>> constexpr vec(const U & u) : vec(converter<vec,U>{}(u)) {}
|
||||
template<class U, class=detail::conv_t<U,vec>> constexpr operator U () const { return converter<U,vec>{}(*this); }
|
||||
};
|
||||
|
||||
// Small, fixed-size matrix type, consisting of exactly M rows and N columns of type T, stored in column-major order.
|
||||
template<class T, int M> struct mat<T,M,1>
|
||||
{
|
||||
typedef vec<T,M> V;
|
||||
V x;
|
||||
constexpr mat() : x() {}
|
||||
constexpr mat(const V & x_) : x(x_) {}
|
||||
constexpr explicit mat(const T & s) : x(s) {}
|
||||
constexpr explicit mat(const T * p) : x(p+M*0) {}
|
||||
template<class U>
|
||||
constexpr explicit mat(const mat<U,M,1> & m) : mat(V(m.x)) {}
|
||||
constexpr vec<T,1> row(int i) const { return {x[i]}; }
|
||||
constexpr const V & operator[] (int j) const { return x; }
|
||||
LINALG_CONSTEXPR14 V & operator[] (int j) { return x; }
|
||||
|
||||
template<class U, class=detail::conv_t<mat,U>> constexpr mat(const U & u) : mat(converter<mat,U>{}(u)) {}
|
||||
template<class U, class=detail::conv_t<U,mat>> constexpr operator U () const { return converter<U,mat>{}(*this); }
|
||||
};
|
||||
template<class T, int M> struct mat<T,M,2>
|
||||
{
|
||||
typedef vec<T,M> V;
|
||||
V x,y;
|
||||
constexpr mat() : x(), y() {}
|
||||
constexpr mat(const V & x_, const V & y_) : x(x_), y(y_) {}
|
||||
constexpr explicit mat(const T & s) : x(s), y(s) {}
|
||||
constexpr explicit mat(const T * p) : x(p+M*0), y(p+M*1) {}
|
||||
template<class U>
|
||||
constexpr explicit mat(const mat<U,M,2> & m) : mat(V(m.x), V(m.y)) {}
|
||||
constexpr vec<T,2> row(int i) const { return {x[i], y[i]}; }
|
||||
constexpr const V & operator[] (int j) const { return j==0?x:y; }
|
||||
LINALG_CONSTEXPR14 V & operator[] (int j) { return j==0?x:y; }
|
||||
|
||||
template<class U, class=detail::conv_t<mat,U>> constexpr mat(const U & u) : mat(converter<mat,U>{}(u)) {}
|
||||
template<class U, class=detail::conv_t<U,mat>> constexpr operator U () const { return converter<U,mat>{}(*this); }
|
||||
};
|
||||
template<class T, int M> struct mat<T,M,3>
|
||||
{
|
||||
typedef vec<T,M> V;
|
||||
V x,y,z;
|
||||
constexpr mat() : x(), y(), z() {}
|
||||
constexpr mat(const V & x_, const V & y_,
|
||||
const V & z_) : x(x_), y(y_), z(z_) {}
|
||||
constexpr explicit mat(const T & s) : x(s), y(s), z(s) {}
|
||||
constexpr explicit mat(const T * p) : x(p+M*0), y(p+M*1), z(p+M*2) {}
|
||||
template<class U>
|
||||
constexpr explicit mat(const mat<U,M,3> & m) : mat(V(m.x), V(m.y), V(m.z)) {}
|
||||
constexpr vec<T,3> row(int i) const { return {x[i], y[i], z[i]}; }
|
||||
constexpr const V & operator[] (int j) const { return j==0?x:j==1?y:z; }
|
||||
LINALG_CONSTEXPR14 V & operator[] (int j) { return j==0?x:j==1?y:z; }
|
||||
|
||||
template<class U, class=detail::conv_t<mat,U>> constexpr mat(const U & u) : mat(converter<mat,U>{}(u)) {}
|
||||
template<class U, class=detail::conv_t<U,mat>> constexpr operator U () const { return converter<U,mat>{}(*this); }
|
||||
};
|
||||
template<class T, int M> struct mat<T,M,4>
|
||||
{
|
||||
typedef vec<T,M> V;
|
||||
V x,y,z,w;
|
||||
constexpr mat() : x(), y(), z(), w() {}
|
||||
constexpr mat(const V & x_, const V & y_,
|
||||
const V & z_, const V & w_) : x(x_), y(y_), z(z_), w(w_) {}
|
||||
constexpr explicit mat(const T & s) : x(s), y(s), z(s), w(s) {}
|
||||
constexpr explicit mat(const T * p) : x(p+M*0), y(p+M*1), z(p+M*2), w(p+M*3) {}
|
||||
template<class U>
|
||||
constexpr explicit mat(const mat<U,M,4> & m) : mat(V(m.x), V(m.y), V(m.z), V(m.w)) {}
|
||||
constexpr vec<T,4> row(int i) const { return {x[i], y[i], z[i], w[i]}; }
|
||||
constexpr const V & operator[] (int j) const { return j==0?x:j==1?y:j==2?z:w; }
|
||||
LINALG_CONSTEXPR14 V & operator[] (int j) { return j==0?x:j==1?y:j==2?z:w; }
|
||||
|
||||
template<class U, class=detail::conv_t<mat,U>> constexpr mat(const U & u) : mat(converter<mat,U>{}(u)) {}
|
||||
template<class U, class=detail::conv_t<U,mat>> constexpr operator U () const { return converter<U,mat>{}(*this); }
|
||||
};
|
||||
|
||||
// Define a type which will convert to the multiplicative identity of any square matrix
|
||||
struct identity_t { constexpr explicit identity_t(int) {} };
|
||||
template<class T> struct converter<mat<T,1,1>, identity_t> { constexpr mat<T,1,1> operator() (identity_t) const { return {vec<T,1>{1}}; } };
|
||||
template<class T> struct converter<mat<T,2,2>, identity_t> { constexpr mat<T,2,2> operator() (identity_t) const { return {{1,0},{0,1}}; } };
|
||||
template<class T> struct converter<mat<T,3,3>, identity_t> { constexpr mat<T,3,3> operator() (identity_t) const { return {{1,0,0},{0,1,0},{0,0,1}}; } };
|
||||
template<class T> struct converter<mat<T,4,4>, identity_t> { constexpr mat<T,4,4> operator() (identity_t) const { return {{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}; } };
|
||||
constexpr identity_t identity {1};
|
||||
|
||||
// Produce a scalar by applying f(A,B) -> A to adjacent pairs of elements from a vec/mat in left-to-right/column-major order (matching the associativity of arithmetic and logical operators)
|
||||
template<class F, class A, class B> constexpr A fold(F f, A a, const vec<B,1> & b) { return f(a, b.x); }
|
||||
template<class F, class A, class B> constexpr A fold(F f, A a, const vec<B,2> & b) { return f(f(a, b.x), b.y); }
|
||||
template<class F, class A, class B> constexpr A fold(F f, A a, const vec<B,3> & b) { return f(f(f(a, b.x), b.y), b.z); }
|
||||
template<class F, class A, class B> constexpr A fold(F f, A a, const vec<B,4> & b) { return f(f(f(f(a, b.x), b.y), b.z), b.w); }
|
||||
template<class F, class A, class B, int M> constexpr A fold(F f, A a, const mat<B,M,1> & b) { return fold(f, a, b.x); }
|
||||
template<class F, class A, class B, int M> constexpr A fold(F f, A a, const mat<B,M,2> & b) { return fold(f, fold(f, a, b.x), b.y); }
|
||||
template<class F, class A, class B, int M> constexpr A fold(F f, A a, const mat<B,M,3> & b) { return fold(f, fold(f, fold(f, a, b.x), b.y), b.z); }
|
||||
template<class F, class A, class B, int M> constexpr A fold(F f, A a, const mat<B,M,4> & b) { return fold(f, fold(f, fold(f, fold(f, a, b.x), b.y), b.z), b.w); }
|
||||
|
||||
// Type aliases for the result of calling apply(...) with various arguments, can be used with return type SFINAE to constrian overload sets
|
||||
template<class F, class... A> using apply_t = typename detail::apply<F,void,A...>::type;
|
||||
template<class F, class... A> using mm_apply_t = typename std::enable_if<detail::apply<F,void,A...>::mm, apply_t<F,A...>>::type;
|
||||
template<class F, class... A> using no_mm_apply_t = typename std::enable_if<!detail::apply<F,void,A...>::mm, apply_t<F,A...>>::type;
|
||||
template<class A> using scalar_t = typename detail::scalar_type<A>::type; // Underlying scalar type when performing elementwise operations
|
||||
|
||||
// apply(f,...) applies the provided function in an elementwise fashion to its arguments, producing an object of the same dimensions
|
||||
template<class F, class... A> constexpr apply_t<F,A...> apply(F func, const A & ... args) { return detail::apply<F,void,A...>::impl(detail::make_seq<0,detail::apply<F,void,A...>::size>{}, func, args...); }
|
||||
|
||||
// map(a,f) is equivalent to apply(f,a)
|
||||
template<class A, class F> constexpr apply_t<F,A> map(const A & a, F func) { return apply(func, a); }
|
||||
|
||||
// zip(a,b,f) is equivalent to apply(f,a,b)
|
||||
template<class A, class B, class F> constexpr apply_t<F,A,B> zip(const A & a, const B & b, F func) { return apply(func, a, b); }
|
||||
|
||||
// Relational operators are defined to compare the elements of two vectors or matrices lexicographically, in column-major order
|
||||
template<class A, class B> constexpr typename detail::any_compare<A,B>::type compare(const A & a, const B & b) { return detail::any_compare<A,B>()(a,b); }
|
||||
template<class A, class B> constexpr auto operator == (const A & a, const B & b) -> decltype(compare(a,b) == 0) { return compare(a,b) == 0; }
|
||||
template<class A, class B> constexpr auto operator != (const A & a, const B & b) -> decltype(compare(a,b) != 0) { return compare(a,b) != 0; }
|
||||
template<class A, class B> constexpr auto operator < (const A & a, const B & b) -> decltype(compare(a,b) < 0) { return compare(a,b) < 0; }
|
||||
template<class A, class B> constexpr auto operator > (const A & a, const B & b) -> decltype(compare(a,b) > 0) { return compare(a,b) > 0; }
|
||||
template<class A, class B> constexpr auto operator <= (const A & a, const B & b) -> decltype(compare(a,b) <= 0) { return compare(a,b) <= 0; }
|
||||
template<class A, class B> constexpr auto operator >= (const A & a, const B & b) -> decltype(compare(a,b) >= 0) { return compare(a,b) >= 0; }
|
||||
|
||||
// Functions for coalescing scalar values
|
||||
template<class A> constexpr bool any (const A & a) { return fold(detail::op_or{}, false, a); }
|
||||
template<class A> constexpr bool all (const A & a) { return fold(detail::op_and{}, true, a); }
|
||||
template<class A> constexpr scalar_t<A> sum (const A & a) { return fold(detail::op_add{}, scalar_t<A>(0), a); }
|
||||
template<class A> constexpr scalar_t<A> product(const A & a) { return fold(detail::op_mul{}, scalar_t<A>(1), a); }
|
||||
template<class A> constexpr scalar_t<A> minelem(const A & a) { return fold(detail::min{}, a.x, a); }
|
||||
template<class A> constexpr scalar_t<A> maxelem(const A & a) { return fold(detail::max{}, a.x, a); }
|
||||
template<class T, int M> int argmin(const vec<T,M> & a) { int j=0; for(int i=1; i<M; ++i) if(a[i] < a[j]) j = i; return j; }
|
||||
template<class T, int M> int argmax(const vec<T,M> & a) { int j=0; for(int i=1; i<M; ++i) if(a[i] > a[j]) j = i; return j; }
|
||||
|
||||
// Unary operators are defined component-wise for linalg types
|
||||
template<class A> constexpr apply_t<detail::op_pos, A> operator + (const A & a) { return apply(detail::op_pos{}, a); }
|
||||
template<class A> constexpr apply_t<detail::op_neg, A> operator - (const A & a) { return apply(detail::op_neg{}, a); }
|
||||
template<class A> constexpr apply_t<detail::op_cmp, A> operator ~ (const A & a) { return apply(detail::op_cmp{}, a); }
|
||||
template<class A> constexpr apply_t<detail::op_not, A> operator ! (const A & a) { return apply(detail::op_not{}, a); }
|
||||
|
||||
// Binary operators are defined component-wise for linalg types, EXCEPT for `operator *`
|
||||
template<class A, class B> constexpr apply_t<detail::op_add, A, B> operator + (const A & a, const B & b) { return apply(detail::op_add{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_sub, A, B> operator - (const A & a, const B & b) { return apply(detail::op_sub{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_mul, A, B> cmul (const A & a, const B & b) { return apply(detail::op_mul{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_div, A, B> operator / (const A & a, const B & b) { return apply(detail::op_div{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_mod, A, B> operator % (const A & a, const B & b) { return apply(detail::op_mod{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_un, A, B> operator | (const A & a, const B & b) { return apply(detail::op_un{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_xor, A, B> operator ^ (const A & a, const B & b) { return apply(detail::op_xor{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_int, A, B> operator & (const A & a, const B & b) { return apply(detail::op_int{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_lsh, A, B> operator << (const A & a, const B & b) { return apply(detail::op_lsh{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_rsh, A, B> operator >> (const A & a, const B & b) { return apply(detail::op_rsh{}, a, b); }
|
||||
|
||||
// Binary `operator *` was originally defined component-wise for all patterns, in a fashion consistent with the other operators. However,
|
||||
// this was one of the most frequent sources of confusion among new users of this library, with the binary `operator *` being used accidentally
|
||||
// by users who INTENDED the semantics of the algebraic matrix product, but RECEIVED the semantics of the Hadamard product. While there is
|
||||
// precedent within the HLSL, Fortran, R, APL, J, and Mathematica programming languages for `operator *` having the semantics of the Hadamard
|
||||
// product between matrices, it is counterintuitive to users of GLSL, Eigen, and many other languages and libraries that chose matrix product
|
||||
// semantics for `operator *`.
|
||||
//
|
||||
// For these reasons, binary `operator *` is now DEPRECATED between pairs of matrices. Users may call `cmul(...)` for component-wise multiplication,
|
||||
// or `mul(...)` for matrix multiplication. Binary `operator *` continues to be available for vector * vector, vector * scalar, matrix * scalar, etc.
|
||||
template<class A, class B> constexpr no_mm_apply_t<detail::op_mul, A, B> operator * (const A & a, const B & b) { return cmul(a,b); }
|
||||
#ifndef LINALG_FORWARD_COMPATIBLE
|
||||
template<class A, class B> [[deprecated("`operator *` between pairs of matrices is deprecated. See the source text for details.")]] constexpr mm_apply_t<detail::op_mul, A, B> operator * (const A & a, const B & b) { return cmul(a,b); }
|
||||
#endif
|
||||
|
||||
// Binary assignment operators a $= b is always defined as though it were explicitly written a = a $ b
|
||||
template<class A, class B> constexpr auto operator += (A & a, const B & b) -> decltype(a = a + b) { return a = a + b; }
|
||||
template<class A, class B> constexpr auto operator -= (A & a, const B & b) -> decltype(a = a - b) { return a = a - b; }
|
||||
template<class A, class B> constexpr auto operator *= (A & a, const B & b) -> decltype(a = a * b) { return a = a * b; }
|
||||
template<class A, class B> constexpr auto operator /= (A & a, const B & b) -> decltype(a = a / b) { return a = a / b; }
|
||||
template<class A, class B> constexpr auto operator %= (A & a, const B & b) -> decltype(a = a % b) { return a = a % b; }
|
||||
template<class A, class B> constexpr auto operator |= (A & a, const B & b) -> decltype(a = a | b) { return a = a | b; }
|
||||
template<class A, class B> constexpr auto operator ^= (A & a, const B & b) -> decltype(a = a ^ b) { return a = a ^ b; }
|
||||
template<class A, class B> constexpr auto operator &= (A & a, const B & b) -> decltype(a = a & b) { return a = a & b; }
|
||||
template<class A, class B> constexpr auto operator <<= (A & a, const B & b) -> decltype(a = a << b) { return a = a << b; }
|
||||
template<class A, class B> constexpr auto operator >>= (A & a, const B & b) -> decltype(a = a >> b) { return a = a >> b; }
|
||||
|
||||
// Swizzles and subobjects
|
||||
template<int... I, class T, int M> constexpr vec<T,sizeof...(I)> swizzle(const vec<T,M> & a) { return {detail::getter<I>{}(a)...}; }
|
||||
template<int I0, int I1, class T, int M> constexpr vec<T,I1-I0> subvec (const vec<T,M> & a) { return detail::swizzle(a, detail::make_seq<I0,I1>{}); }
|
||||
template<int I0, int J0, int I1, int J1, class T, int M, int N> constexpr mat<T,I1-I0,J1-J0> submat (const mat<T,M,N> & a) { return detail::swizzle(a, detail::make_seq<I0,I1>{}, detail::make_seq<J0,J1>{}); }
|
||||
|
||||
// Component-wise standard library math functions
|
||||
template<class A> apply_t<detail::std_abs, A> abs (const A & a) { return apply(detail::std_abs{}, a); }
|
||||
template<class A> apply_t<detail::std_floor, A> floor(const A & a) { return apply(detail::std_floor{}, a); }
|
||||
template<class A> apply_t<detail::std_ceil, A> ceil (const A & a) { return apply(detail::std_ceil{}, a); }
|
||||
template<class A> apply_t<detail::std_exp, A> exp (const A & a) { return apply(detail::std_exp{}, a); }
|
||||
template<class A> apply_t<detail::std_log, A> log (const A & a) { return apply(detail::std_log{}, a); }
|
||||
template<class A> apply_t<detail::std_log10, A> log10(const A & a) { return apply(detail::std_log10{}, a); }
|
||||
template<class A> apply_t<detail::std_sqrt, A> sqrt (const A & a) { return apply(detail::std_sqrt{}, a); }
|
||||
template<class A> apply_t<detail::std_sin, A> sin (const A & a) { return apply(detail::std_sin{}, a); }
|
||||
template<class A> apply_t<detail::std_cos, A> cos (const A & a) { return apply(detail::std_cos{}, a); }
|
||||
template<class A> apply_t<detail::std_tan, A> tan (const A & a) { return apply(detail::std_tan{}, a); }
|
||||
template<class A> apply_t<detail::std_asin, A> asin (const A & a) { return apply(detail::std_asin{}, a); }
|
||||
template<class A> apply_t<detail::std_acos, A> acos (const A & a) { return apply(detail::std_acos{}, a); }
|
||||
template<class A> apply_t<detail::std_atan, A> atan (const A & a) { return apply(detail::std_atan{}, a); }
|
||||
template<class A> apply_t<detail::std_sinh, A> sinh (const A & a) { return apply(detail::std_sinh{}, a); }
|
||||
template<class A> apply_t<detail::std_cosh, A> cosh (const A & a) { return apply(detail::std_cosh{}, a); }
|
||||
template<class A> apply_t<detail::std_tanh, A> tanh (const A & a) { return apply(detail::std_tanh{}, a); }
|
||||
template<class A> apply_t<detail::std_round, A> round(const A & a) { return apply(detail::std_round{}, a); }
|
||||
|
||||
template<class A, class B> apply_t<detail::std_fmod, A, B> fmod (const A & a, const B & b) { return apply(detail::std_fmod{}, a, b); }
|
||||
template<class A, class B> apply_t<detail::std_pow, A, B> pow (const A & a, const B & b) { return apply(detail::std_pow{}, a, b); }
|
||||
template<class A, class B> apply_t<detail::std_atan2, A, B> atan2 (const A & a, const B & b) { return apply(detail::std_atan2{}, a, b); }
|
||||
template<class A, class B> apply_t<detail::std_copysign, A, B> copysign(const A & a, const B & b) { return apply(detail::std_copysign{}, a, b); }
|
||||
|
||||
// Component-wise relational functions on vectors
|
||||
template<class A, class B> constexpr apply_t<detail::op_eq, A, B> equal (const A & a, const B & b) { return apply(detail::op_eq{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_ne, A, B> nequal (const A & a, const B & b) { return apply(detail::op_ne{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_lt, A, B> less (const A & a, const B & b) { return apply(detail::op_lt{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_gt, A, B> greater(const A & a, const B & b) { return apply(detail::op_gt{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_le, A, B> lequal (const A & a, const B & b) { return apply(detail::op_le{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::op_ge, A, B> gequal (const A & a, const B & b) { return apply(detail::op_ge{}, a, b); }
|
||||
|
||||
// Component-wise selection functions on vectors
|
||||
template<class A, class B> constexpr apply_t<detail::min, A, B> min(const A & a, const B & b) { return apply(detail::min{}, a, b); }
|
||||
template<class A, class B> constexpr apply_t<detail::max, A, B> max(const A & a, const B & b) { return apply(detail::max{}, a, b); }
|
||||
template<class X, class L, class H> constexpr apply_t<detail::clamp, X, L, H> clamp (const X & x, const L & l, const H & h) { return apply(detail::clamp{}, x, l, h); }
|
||||
template<class P, class A, class B> constexpr apply_t<detail::select, P, A, B> select(const P & p, const A & a, const B & b) { return apply(detail::select{}, p, a, b); }
|
||||
template<class A, class B, class T> constexpr apply_t<detail::lerp, A, B, T> lerp (const A & a, const B & b, const T & t) { return apply(detail::lerp{}, a, b, t); }
|
||||
|
||||
// Support for vector algebra
|
||||
template<class T> constexpr T cross (const vec<T,2> & a, const vec<T,2> & b) { return a.x*b.y-a.y*b.x; }
|
||||
template<class T> constexpr vec<T,2> cross (T a, const vec<T,2> & b) { return {-a*b.y, a*b.x}; }
|
||||
template<class T> constexpr vec<T,2> cross (const vec<T,2> & a, T b) { return {a.y*b, -a.x*b}; }
|
||||
template<class T> constexpr vec<T,3> cross (const vec<T,3> & a, const vec<T,3> & b) { return {a.y*b.z-a.z*b.y, a.z*b.x-a.x*b.z, a.x*b.y-a.y*b.x}; }
|
||||
template<class T, int M> constexpr T dot (const vec<T,M> & a, const vec<T,M> & b) { return sum(a*b); }
|
||||
template<class T, int M> constexpr T length2 (const vec<T,M> & a) { return dot(a,a); }
|
||||
template<class T, int M> T length (const vec<T,M> & a) { return std::sqrt(length2(a)); }
|
||||
template<class T, int M> vec<T,M> normalize(const vec<T,M> & a) { return a / length(a); }
|
||||
template<class T, int M> constexpr T distance2(const vec<T,M> & a, const vec<T,M> & b) { return length2(b-a); }
|
||||
template<class T, int M> T distance (const vec<T,M> & a, const vec<T,M> & b) { return length(b-a); }
|
||||
template<class T, int M> T uangle (const vec<T,M> & a, const vec<T,M> & b) { T d=dot(a,b); return d > 1 ? 0 : std::acos(d < -1 ? -1 : d); }
|
||||
template<class T, int M> T angle (const vec<T,M> & a, const vec<T,M> & b) { return uangle(normalize(a), normalize(b)); }
|
||||
template<class T> vec<T,2> rot (T a, const vec<T,2> & v) { const T s = std::sin(a), c = std::cos(a); return {v.x*c - v.y*s, v.x*s + v.y*c}; }
|
||||
template<class T, int M> vec<T,M> nlerp (const vec<T,M> & a, const vec<T,M> & b, T t) { return normalize(lerp(a,b,t)); }
|
||||
template<class T, int M> vec<T,M> slerp (const vec<T,M> & a, const vec<T,M> & b, T t) { T th=uangle(a,b); return th == 0 ? a : a*(std::sin(th*(1-t))/std::sin(th)) + b*(std::sin(th*t)/std::sin(th)); }
|
||||
|
||||
// Support for quaternion algebra using 4D vectors, representing xi + yj + zk + w
|
||||
template<class T> constexpr vec<T,4> qconj(const vec<T,4> & q) { return {-q.x,-q.y,-q.z,q.w}; }
|
||||
template<class T> vec<T,4> qinv (const vec<T,4> & q) { return qconj(q)/length2(q); }
|
||||
template<class T> vec<T,4> qexp (const vec<T,4> & q) { const auto v = q.xyz(); const auto vv = length(v); return std::exp(q.w) * vec<T,4>{v * (vv > 0 ? std::sin(vv)/vv : 0), std::cos(vv)}; }
|
||||
template<class T> vec<T,4> qlog (const vec<T,4> & q) { const auto v = q.xyz(); const auto vv = length(v), qq = length(q); return {v * (vv > 0 ? std::acos(q.w/qq)/vv : 0), std::log(qq)}; }
|
||||
template<class T> vec<T,4> qpow (const vec<T,4> & q, const T & p) { const auto v = q.xyz(); const auto vv = length(v), qq = length(q), th = std::acos(q.w/qq); return std::pow(qq,p)*vec<T,4>{v * (vv > 0 ? std::sin(p*th)/vv : 0), std::cos(p*th)}; }
|
||||
template<class T> constexpr vec<T,4> qmul (const vec<T,4> & a, const vec<T,4> & b) { return {a.x*b.w+a.w*b.x+a.y*b.z-a.z*b.y, a.y*b.w+a.w*b.y+a.z*b.x-a.x*b.z, a.z*b.w+a.w*b.z+a.x*b.y-a.y*b.x, a.w*b.w-a.x*b.x-a.y*b.y-a.z*b.z}; }
|
||||
template<class T, class... R> constexpr vec<T,4> qmul(const vec<T,4> & a, R... r) { return qmul(a, qmul(r...)); }
|
||||
|
||||
// Support for 3D spatial rotations using quaternions, via qmul(qmul(q, v), qconj(q))
|
||||
template<class T> constexpr vec<T,3> qxdir (const vec<T,4> & q) { return {q.w*q.w+q.x*q.x-q.y*q.y-q.z*q.z, (q.x*q.y+q.z*q.w)*2, (q.z*q.x-q.y*q.w)*2}; }
|
||||
template<class T> constexpr vec<T,3> qydir (const vec<T,4> & q) { return {(q.x*q.y-q.z*q.w)*2, q.w*q.w-q.x*q.x+q.y*q.y-q.z*q.z, (q.y*q.z+q.x*q.w)*2}; }
|
||||
template<class T> constexpr vec<T,3> qzdir (const vec<T,4> & q) { return {(q.z*q.x+q.y*q.w)*2, (q.y*q.z-q.x*q.w)*2, q.w*q.w-q.x*q.x-q.y*q.y+q.z*q.z}; }
|
||||
template<class T> constexpr mat<T,3,3> qmat (const vec<T,4> & q) { return {qxdir(q), qydir(q), qzdir(q)}; }
|
||||
template<class T> constexpr vec<T,3> qrot (const vec<T,4> & q, const vec<T,3> & v) { return qxdir(q)*v.x + qydir(q)*v.y + qzdir(q)*v.z; }
|
||||
template<class T> T qangle(const vec<T,4> & q) { return std::atan2(length(q.xyz()), q.w)*2; }
|
||||
template<class T> vec<T,3> qaxis (const vec<T,4> & q) { return normalize(q.xyz()); }
|
||||
template<class T> vec<T,4> qnlerp(const vec<T,4> & a, const vec<T,4> & b, T t) { return nlerp(a, dot(a,b) < 0 ? -b : b, t); }
|
||||
template<class T> vec<T,4> qslerp(const vec<T,4> & a, const vec<T,4> & b, T t) { return slerp(a, dot(a,b) < 0 ? -b : b, t); }
|
||||
|
||||
// Support for matrix algebra
|
||||
template<class T, int M> constexpr vec<T,M> mul(const mat<T,M,1> & a, const vec<T,1> & b) { return a.x*b.x; }
|
||||
template<class T, int M> constexpr vec<T,M> mul(const mat<T,M,2> & a, const vec<T,2> & b) { return a.x*b.x + a.y*b.y; }
|
||||
template<class T, int M> constexpr vec<T,M> mul(const mat<T,M,3> & a, const vec<T,3> & b) { return a.x*b.x + a.y*b.y + a.z*b.z; }
|
||||
template<class T, int M> constexpr vec<T,M> mul(const mat<T,M,4> & a, const vec<T,4> & b) { return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; }
|
||||
template<class T, int M, int N> constexpr mat<T,M,1> mul(const mat<T,M,N> & a, const mat<T,N,1> & b) { return {mul(a,b.x)}; }
|
||||
template<class T, int M, int N> constexpr mat<T,M,2> mul(const mat<T,M,N> & a, const mat<T,N,2> & b) { return {mul(a,b.x), mul(a,b.y)}; }
|
||||
template<class T, int M, int N> constexpr mat<T,M,3> mul(const mat<T,M,N> & a, const mat<T,N,3> & b) { return {mul(a,b.x), mul(a,b.y), mul(a,b.z)}; }
|
||||
template<class T, int M, int N> constexpr mat<T,M,4> mul(const mat<T,M,N> & a, const mat<T,N,4> & b) { return {mul(a,b.x), mul(a,b.y), mul(a,b.z), mul(a,b.w)}; }
|
||||
template<class T, int M, int N, int P> constexpr vec<T,M> mul(const mat<T,M,N> & a, const mat<T,N,P> & b, const vec<T,P> & c) { return mul(mul(a,b),c); }
|
||||
template<class T, int M, int N, int P, int Q> constexpr mat<T,M,Q> mul(const mat<T,M,N> & a, const mat<T,N,P> & b, const mat<T,P,Q> & c) { return mul(mul(a,b),c); }
|
||||
template<class T, int M, int N, int P, int Q> constexpr vec<T,M> mul(const mat<T,M,N> & a, const mat<T,N,P> & b, const mat<T,P,Q> & c, const vec<T,Q> & d) { return mul(mul(a,b,c),d); }
|
||||
template<class T, int M, int N, int P, int Q, int R> constexpr mat<T,M,R> mul(const mat<T,M,N> & a, const mat<T,N,P> & b, const mat<T,P,Q> & c, const mat<T,Q,R> & d) { return mul(mul(a,b,c),d); }
|
||||
template<class T, int M> constexpr mat<T,M,1> outerprod(const vec<T,M> & a, const vec<T,1> & b) { return {a*b.x}; }
|
||||
template<class T, int M> constexpr mat<T,M,2> outerprod(const vec<T,M> & a, const vec<T,2> & b) { return {a*b.x, a*b.y}; }
|
||||
template<class T, int M> constexpr mat<T,M,3> outerprod(const vec<T,M> & a, const vec<T,3> & b) { return {a*b.x, a*b.y, a*b.z}; }
|
||||
template<class T, int M> constexpr mat<T,M,4> outerprod(const vec<T,M> & a, const vec<T,4> & b) { return {a*b.x, a*b.y, a*b.z, a*b.w}; }
|
||||
template<class T> constexpr vec<T,1> diagonal(const mat<T,1,1> & a) { return {a.x.x}; }
|
||||
template<class T> constexpr vec<T,2> diagonal(const mat<T,2,2> & a) { return {a.x.x, a.y.y}; }
|
||||
template<class T> constexpr vec<T,3> diagonal(const mat<T,3,3> & a) { return {a.x.x, a.y.y, a.z.z}; }
|
||||
template<class T> constexpr vec<T,4> diagonal(const mat<T,4,4> & a) { return {a.x.x, a.y.y, a.z.z, a.w.w}; }
|
||||
template<class T, int N> constexpr T trace(const mat<T,N,N> & a) { return sum(diagonal(a)); }
|
||||
template<class T, int M> constexpr mat<T,M,1> transpose(const mat<T,1,M> & m) { return {m.row(0)}; }
|
||||
template<class T, int M> constexpr mat<T,M,2> transpose(const mat<T,2,M> & m) { return {m.row(0), m.row(1)}; }
|
||||
template<class T, int M> constexpr mat<T,M,3> transpose(const mat<T,3,M> & m) { return {m.row(0), m.row(1), m.row(2)}; }
|
||||
template<class T, int M> constexpr mat<T,M,4> transpose(const mat<T,4,M> & m) { return {m.row(0), m.row(1), m.row(2), m.row(3)}; }
|
||||
template<class T, int M> constexpr mat<T,1,M> transpose(const vec<T,M> & m) { return transpose(mat<T,M,1>(m)); }
|
||||
template<class T> constexpr mat<T,1,1> adjugate(const mat<T,1,1> & a) { return {vec<T,1>{1}}; }
|
||||
template<class T> constexpr mat<T,2,2> adjugate(const mat<T,2,2> & a) { return {{a.y.y, -a.x.y}, {-a.y.x, a.x.x}}; }
|
||||
template<class T> constexpr mat<T,3,3> adjugate(const mat<T,3,3> & a);
|
||||
template<class T> constexpr mat<T,4,4> adjugate(const mat<T,4,4> & a);
|
||||
template<class T, int N> constexpr mat<T,N,N> comatrix(const mat<T,N,N> & a) { return transpose(adjugate(a)); }
|
||||
template<class T> constexpr T determinant(const mat<T,1,1> & a) { return a.x.x; }
|
||||
template<class T> constexpr T determinant(const mat<T,2,2> & a) { return a.x.x*a.y.y - a.x.y*a.y.x; }
|
||||
template<class T> constexpr T determinant(const mat<T,3,3> & a) { return a.x.x*(a.y.y*a.z.z - a.z.y*a.y.z) + a.x.y*(a.y.z*a.z.x - a.z.z*a.y.x) + a.x.z*(a.y.x*a.z.y - a.z.x*a.y.y); }
|
||||
template<class T> constexpr T determinant(const mat<T,4,4> & a);
|
||||
template<class T, int N> constexpr mat<T,N,N> inverse(const mat<T,N,N> & a) { return adjugate(a)/determinant(a); }
|
||||
|
||||
// Vectors and matrices can be used as ranges
|
||||
template<class T, int M> T * begin( vec<T,M> & a) { return &a.x; }
|
||||
template<class T, int M> const T * begin(const vec<T,M> & a) { return &a.x; }
|
||||
template<class T, int M> T * end ( vec<T,M> & a) { return begin(a) + M; }
|
||||
template<class T, int M> const T * end (const vec<T,M> & a) { return begin(a) + M; }
|
||||
template<class T, int M, int N> vec<T,M> * begin( mat<T,M,N> & a) { return &a.x; }
|
||||
template<class T, int M, int N> const vec<T,M> * begin(const mat<T,M,N> & a) { return &a.x; }
|
||||
template<class T, int M, int N> vec<T,M> * end ( mat<T,M,N> & a) { return begin(a) + N; }
|
||||
template<class T, int M, int N> const vec<T,M> * end (const mat<T,M,N> & a) { return begin(a) + N; }
|
||||
|
||||
// Factory functions for 3D spatial transformations (will possibly be removed or changed in a future version)
|
||||
enum fwd_axis { neg_z, pos_z }; // Should projection matrices be generated assuming forward is {0,0,-1} or {0,0,1}
|
||||
enum z_range { neg_one_to_one, zero_to_one }; // Should projection matrices map z into the range of [-1,1] or [0,1]?
|
||||
template<class T> vec<T,4> rotation_quat (const vec<T,3> & axis, T angle) { return {axis*std::sin(angle/2), std::cos(angle/2)}; }
|
||||
template<class T> vec<T,4> rotation_quat (const mat<T,3,3> & m);
|
||||
template<class T> mat<T,4,4> translation_matrix(const vec<T,3> & translation) { return {{1,0,0,0},{0,1,0,0},{0,0,1,0},{translation,1}}; }
|
||||
template<class T> mat<T,4,4> rotation_matrix (const vec<T,4> & rotation) { return {{qxdir(rotation),0}, {qydir(rotation),0}, {qzdir(rotation),0}, {0,0,0,1}}; }
|
||||
template<class T> mat<T,4,4> scaling_matrix (const vec<T,3> & scaling) { return {{scaling.x,0,0,0}, {0,scaling.y,0,0}, {0,0,scaling.z,0}, {0,0,0,1}}; }
|
||||
template<class T> mat<T,4,4> pose_matrix (const vec<T,4> & q, const vec<T,3> & p) { return {{qxdir(q),0}, {qydir(q),0}, {qzdir(q),0}, {p,1}}; }
|
||||
template<class T> mat<T,4,4> lookat_matrix (const vec<T,3> & eye, const vec<T,3> & center, const vec<T,3> & view_y_dir, fwd_axis fwd = neg_z);
|
||||
template<class T> mat<T,4,4> frustum_matrix (T x0, T x1, T y0, T y1, T n, T f, fwd_axis a = neg_z, z_range z = neg_one_to_one);
|
||||
template<class T> mat<T,4,4> perspective_matrix(T fovy, T aspect, T n, T f, fwd_axis a = neg_z, z_range z = neg_one_to_one) { T y = n*std::tan(fovy / 2), x = y*aspect; return frustum_matrix(-x, x, -y, y, n, f, a, z); }
|
||||
|
||||
// Provide implicit conversion between linalg::vec<T,M> and std::array<T,M>
|
||||
template<class T> struct converter<vec<T,1>, std::array<T,1>> { vec<T,1> operator() (const std::array<T,1> & a) const { return {a[0]}; } };
|
||||
template<class T> struct converter<vec<T,2>, std::array<T,2>> { vec<T,2> operator() (const std::array<T,2> & a) const { return {a[0], a[1]}; } };
|
||||
template<class T> struct converter<vec<T,3>, std::array<T,3>> { vec<T,3> operator() (const std::array<T,3> & a) const { return {a[0], a[1], a[2]}; } };
|
||||
template<class T> struct converter<vec<T,4>, std::array<T,4>> { vec<T,4> operator() (const std::array<T,4> & a) const { return {a[0], a[1], a[2], a[3]}; } };
|
||||
|
||||
template<class T> struct converter<std::array<T,1>, vec<T,1>> { std::array<T,1> operator() (const vec<T,1> & a) const { return {a[0]}; } };
|
||||
template<class T> struct converter<std::array<T,2>, vec<T,2>> { std::array<T,2> operator() (const vec<T,2> & a) const { return {a[0], a[1]}; } };
|
||||
template<class T> struct converter<std::array<T,3>, vec<T,3>> { std::array<T,3> operator() (const vec<T,3> & a) const { return {a[0], a[1], a[2]}; } };
|
||||
template<class T> struct converter<std::array<T,4>, vec<T,4>> { std::array<T,4> operator() (const vec<T,4> & a) const { return {a[0], a[1], a[2], a[3]}; } };
|
||||
|
||||
// Provide typedefs for common element types and vector/matrix sizes
|
||||
namespace aliases
|
||||
{
|
||||
typedef vec<bool,1> bool1; typedef vec<uint8_t,1> byte1; typedef vec<int16_t,1> short1; typedef vec<uint16_t,1> ushort1;
|
||||
typedef vec<bool,2> bool2; typedef vec<uint8_t,2> byte2; typedef vec<int16_t,2> short2; typedef vec<uint16_t,2> ushort2;
|
||||
typedef vec<bool,3> bool3; typedef vec<uint8_t,3> byte3; typedef vec<int16_t,3> short3; typedef vec<uint16_t,3> ushort3;
|
||||
typedef vec<bool,4> bool4; typedef vec<uint8_t,4> byte4; typedef vec<int16_t,4> short4; typedef vec<uint16_t,4> ushort4;
|
||||
typedef vec<int,1> int1; typedef vec<unsigned,1> uint1; typedef vec<float,1> float1; typedef vec<double,1> double1;
|
||||
typedef vec<int,2> int2; typedef vec<unsigned,2> uint2; typedef vec<float,2> float2; typedef vec<double,2> double2;
|
||||
typedef vec<int,3> int3; typedef vec<unsigned,3> uint3; typedef vec<float,3> float3; typedef vec<double,3> double3;
|
||||
typedef vec<int,4> int4; typedef vec<unsigned,4> uint4; typedef vec<float,4> float4; typedef vec<double,4> double4;
|
||||
typedef mat<bool,1,1> bool1x1; typedef mat<int,1,1> int1x1; typedef mat<float,1,1> float1x1; typedef mat<double,1,1> double1x1;
|
||||
typedef mat<bool,1,2> bool1x2; typedef mat<int,1,2> int1x2; typedef mat<float,1,2> float1x2; typedef mat<double,1,2> double1x2;
|
||||
typedef mat<bool,1,3> bool1x3; typedef mat<int,1,3> int1x3; typedef mat<float,1,3> float1x3; typedef mat<double,1,3> double1x3;
|
||||
typedef mat<bool,1,4> bool1x4; typedef mat<int,1,4> int1x4; typedef mat<float,1,4> float1x4; typedef mat<double,1,4> double1x4;
|
||||
typedef mat<bool,2,1> bool2x1; typedef mat<int,2,1> int2x1; typedef mat<float,2,1> float2x1; typedef mat<double,2,1> double2x1;
|
||||
typedef mat<bool,2,2> bool2x2; typedef mat<int,2,2> int2x2; typedef mat<float,2,2> float2x2; typedef mat<double,2,2> double2x2;
|
||||
typedef mat<bool,2,3> bool2x3; typedef mat<int,2,3> int2x3; typedef mat<float,2,3> float2x3; typedef mat<double,2,3> double2x3;
|
||||
typedef mat<bool,2,4> bool2x4; typedef mat<int,2,4> int2x4; typedef mat<float,2,4> float2x4; typedef mat<double,2,4> double2x4;
|
||||
typedef mat<bool,3,1> bool3x1; typedef mat<int,3,1> int3x1; typedef mat<float,3,1> float3x1; typedef mat<double,3,1> double3x1;
|
||||
typedef mat<bool,3,2> bool3x2; typedef mat<int,3,2> int3x2; typedef mat<float,3,2> float3x2; typedef mat<double,3,2> double3x2;
|
||||
typedef mat<bool,3,3> bool3x3; typedef mat<int,3,3> int3x3; typedef mat<float,3,3> float3x3; typedef mat<double,3,3> double3x3;
|
||||
typedef mat<bool,3,4> bool3x4; typedef mat<int,3,4> int3x4; typedef mat<float,3,4> float3x4; typedef mat<double,3,4> double3x4;
|
||||
typedef mat<bool,4,1> bool4x1; typedef mat<int,4,1> int4x1; typedef mat<float,4,1> float4x1; typedef mat<double,4,1> double4x1;
|
||||
typedef mat<bool,4,2> bool4x2; typedef mat<int,4,2> int4x2; typedef mat<float,4,2> float4x2; typedef mat<double,4,2> double4x2;
|
||||
typedef mat<bool,4,3> bool4x3; typedef mat<int,4,3> int4x3; typedef mat<float,4,3> float4x3; typedef mat<double,4,3> double4x3;
|
||||
typedef mat<bool,4,4> bool4x4; typedef mat<int,4,4> int4x4; typedef mat<float,4,4> float4x4; typedef mat<double,4,4> double4x4;
|
||||
}
|
||||
|
||||
// Provide output streaming operators, writing something that resembles an aggregate literal that could be used to construct the specified value
|
||||
namespace ostream_overloads
|
||||
{
|
||||
template<class C, class T> std::basic_ostream<C> & operator << (std::basic_ostream<C> & out, const vec<T,1> & v) { return out << '{' << v[0] << '}'; }
|
||||
template<class C, class T> std::basic_ostream<C> & operator << (std::basic_ostream<C> & out, const vec<T,2> & v) { return out << '{' << v[0] << ',' << v[1] << '}'; }
|
||||
template<class C, class T> std::basic_ostream<C> & operator << (std::basic_ostream<C> & out, const vec<T,3> & v) { return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << '}'; }
|
||||
template<class C, class T> std::basic_ostream<C> & operator << (std::basic_ostream<C> & out, const vec<T,4> & v) { return out << '{' << v[0] << ',' << v[1] << ',' << v[2] << ',' << v[3] << '}'; }
|
||||
|
||||
template<class C, class T, int M> std::basic_ostream<C> & operator << (std::basic_ostream<C> & out, const mat<T,M,1> & m) { return out << '{' << m[0] << '}'; }
|
||||
template<class C, class T, int M> std::basic_ostream<C> & operator << (std::basic_ostream<C> & out, const mat<T,M,2> & m) { return out << '{' << m[0] << ',' << m[1] << '}'; }
|
||||
template<class C, class T, int M> std::basic_ostream<C> & operator << (std::basic_ostream<C> & out, const mat<T,M,3> & m) { return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << '}'; }
|
||||
template<class C, class T, int M> std::basic_ostream<C> & operator << (std::basic_ostream<C> & out, const mat<T,M,4> & m) { return out << '{' << m[0] << ',' << m[1] << ',' << m[2] << ',' << m[3] << '}'; }
|
||||
}
|
||||
}
|
||||
|
||||
namespace std
|
||||
{
|
||||
// Provide specializations for std::hash<...> with linalg types
|
||||
template<class T> struct hash<linalg::vec<T,1>> { std::size_t operator()(const linalg::vec<T,1> & v) const { std::hash<T> h; return h(v.x); } };
|
||||
template<class T> struct hash<linalg::vec<T,2>> { std::size_t operator()(const linalg::vec<T,2> & v) const { std::hash<T> h; return h(v.x) ^ (h(v.y) << 1); } };
|
||||
template<class T> struct hash<linalg::vec<T,3>> { std::size_t operator()(const linalg::vec<T,3> & v) const { std::hash<T> h; return h(v.x) ^ (h(v.y) << 1) ^ (h(v.z) << 2); } };
|
||||
template<class T> struct hash<linalg::vec<T,4>> { std::size_t operator()(const linalg::vec<T,4> & v) const { std::hash<T> h; return h(v.x) ^ (h(v.y) << 1) ^ (h(v.z) << 2) ^ (h(v.w) << 3); } };
|
||||
|
||||
template<class T, int M> struct hash<linalg::mat<T,M,1>> { std::size_t operator()(const linalg::mat<T,M,1> & v) const { std::hash<linalg::vec<T,M>> h; return h(v.x); } };
|
||||
template<class T, int M> struct hash<linalg::mat<T,M,2>> { std::size_t operator()(const linalg::mat<T,M,2> & v) const { std::hash<linalg::vec<T,M>> h; return h(v.x) ^ (h(v.y) << M); } };
|
||||
template<class T, int M> struct hash<linalg::mat<T,M,3>> { std::size_t operator()(const linalg::mat<T,M,3> & v) const { std::hash<linalg::vec<T,M>> h; return h(v.x) ^ (h(v.y) << M) ^ (h(v.z) << (M*2)); } };
|
||||
template<class T, int M> struct hash<linalg::mat<T,M,4>> { std::size_t operator()(const linalg::mat<T,M,4> & v) const { std::hash<linalg::vec<T,M>> h; return h(v.x) ^ (h(v.y) << M) ^ (h(v.z) << (M*2)) ^ (h(v.w) << (M*3)); } };
|
||||
}
|
||||
|
||||
// Definitions of functions too long to be defined inline
|
||||
template<class T> constexpr linalg::mat<T,3,3> linalg::adjugate(const mat<T,3,3> & a)
|
||||
{
|
||||
return {{a.y.y*a.z.z - a.z.y*a.y.z, a.z.y*a.x.z - a.x.y*a.z.z, a.x.y*a.y.z - a.y.y*a.x.z},
|
||||
{a.y.z*a.z.x - a.z.z*a.y.x, a.z.z*a.x.x - a.x.z*a.z.x, a.x.z*a.y.x - a.y.z*a.x.x},
|
||||
{a.y.x*a.z.y - a.z.x*a.y.y, a.z.x*a.x.y - a.x.x*a.z.y, a.x.x*a.y.y - a.y.x*a.x.y}};
|
||||
}
|
||||
|
||||
template<class T> constexpr linalg::mat<T,4,4> linalg::adjugate(const mat<T,4,4> & a)
|
||||
{
|
||||
return {{a.y.y*a.z.z*a.w.w + a.w.y*a.y.z*a.z.w + a.z.y*a.w.z*a.y.w - a.y.y*a.w.z*a.z.w - a.z.y*a.y.z*a.w.w - a.w.y*a.z.z*a.y.w,
|
||||
a.x.y*a.w.z*a.z.w + a.z.y*a.x.z*a.w.w + a.w.y*a.z.z*a.x.w - a.w.y*a.x.z*a.z.w - a.z.y*a.w.z*a.x.w - a.x.y*a.z.z*a.w.w,
|
||||
a.x.y*a.y.z*a.w.w + a.w.y*a.x.z*a.y.w + a.y.y*a.w.z*a.x.w - a.x.y*a.w.z*a.y.w - a.y.y*a.x.z*a.w.w - a.w.y*a.y.z*a.x.w,
|
||||
a.x.y*a.z.z*a.y.w + a.y.y*a.x.z*a.z.w + a.z.y*a.y.z*a.x.w - a.x.y*a.y.z*a.z.w - a.z.y*a.x.z*a.y.w - a.y.y*a.z.z*a.x.w},
|
||||
{a.y.z*a.w.w*a.z.x + a.z.z*a.y.w*a.w.x + a.w.z*a.z.w*a.y.x - a.y.z*a.z.w*a.w.x - a.w.z*a.y.w*a.z.x - a.z.z*a.w.w*a.y.x,
|
||||
a.x.z*a.z.w*a.w.x + a.w.z*a.x.w*a.z.x + a.z.z*a.w.w*a.x.x - a.x.z*a.w.w*a.z.x - a.z.z*a.x.w*a.w.x - a.w.z*a.z.w*a.x.x,
|
||||
a.x.z*a.w.w*a.y.x + a.y.z*a.x.w*a.w.x + a.w.z*a.y.w*a.x.x - a.x.z*a.y.w*a.w.x - a.w.z*a.x.w*a.y.x - a.y.z*a.w.w*a.x.x,
|
||||
a.x.z*a.y.w*a.z.x + a.z.z*a.x.w*a.y.x + a.y.z*a.z.w*a.x.x - a.x.z*a.z.w*a.y.x - a.y.z*a.x.w*a.z.x - a.z.z*a.y.w*a.x.x},
|
||||
{a.y.w*a.z.x*a.w.y + a.w.w*a.y.x*a.z.y + a.z.w*a.w.x*a.y.y - a.y.w*a.w.x*a.z.y - a.z.w*a.y.x*a.w.y - a.w.w*a.z.x*a.y.y,
|
||||
a.x.w*a.w.x*a.z.y + a.z.w*a.x.x*a.w.y + a.w.w*a.z.x*a.x.y - a.x.w*a.z.x*a.w.y - a.w.w*a.x.x*a.z.y - a.z.w*a.w.x*a.x.y,
|
||||
a.x.w*a.y.x*a.w.y + a.w.w*a.x.x*a.y.y + a.y.w*a.w.x*a.x.y - a.x.w*a.w.x*a.y.y - a.y.w*a.x.x*a.w.y - a.w.w*a.y.x*a.x.y,
|
||||
a.x.w*a.z.x*a.y.y + a.y.w*a.x.x*a.z.y + a.z.w*a.y.x*a.x.y - a.x.w*a.y.x*a.z.y - a.z.w*a.x.x*a.y.y - a.y.w*a.z.x*a.x.y},
|
||||
{a.y.x*a.w.y*a.z.z + a.z.x*a.y.y*a.w.z + a.w.x*a.z.y*a.y.z - a.y.x*a.z.y*a.w.z - a.w.x*a.y.y*a.z.z - a.z.x*a.w.y*a.y.z,
|
||||
a.x.x*a.z.y*a.w.z + a.w.x*a.x.y*a.z.z + a.z.x*a.w.y*a.x.z - a.x.x*a.w.y*a.z.z - a.z.x*a.x.y*a.w.z - a.w.x*a.z.y*a.x.z,
|
||||
a.x.x*a.w.y*a.y.z + a.y.x*a.x.y*a.w.z + a.w.x*a.y.y*a.x.z - a.x.x*a.y.y*a.w.z - a.w.x*a.x.y*a.y.z - a.y.x*a.w.y*a.x.z,
|
||||
a.x.x*a.y.y*a.z.z + a.z.x*a.x.y*a.y.z + a.y.x*a.z.y*a.x.z - a.x.x*a.z.y*a.y.z - a.y.x*a.x.y*a.z.z - a.z.x*a.y.y*a.x.z}};
|
||||
}
|
||||
|
||||
template<class T> constexpr T linalg::determinant(const mat<T,4,4> & a)
|
||||
{
|
||||
return a.x.x*(a.y.y*a.z.z*a.w.w + a.w.y*a.y.z*a.z.w + a.z.y*a.w.z*a.y.w - a.y.y*a.w.z*a.z.w - a.z.y*a.y.z*a.w.w - a.w.y*a.z.z*a.y.w)
|
||||
+ a.x.y*(a.y.z*a.w.w*a.z.x + a.z.z*a.y.w*a.w.x + a.w.z*a.z.w*a.y.x - a.y.z*a.z.w*a.w.x - a.w.z*a.y.w*a.z.x - a.z.z*a.w.w*a.y.x)
|
||||
+ a.x.z*(a.y.w*a.z.x*a.w.y + a.w.w*a.y.x*a.z.y + a.z.w*a.w.x*a.y.y - a.y.w*a.w.x*a.z.y - a.z.w*a.y.x*a.w.y - a.w.w*a.z.x*a.y.y)
|
||||
+ a.x.w*(a.y.x*a.w.y*a.z.z + a.z.x*a.y.y*a.w.z + a.w.x*a.z.y*a.y.z - a.y.x*a.z.y*a.w.z - a.w.x*a.y.y*a.z.z - a.z.x*a.w.y*a.y.z);
|
||||
}
|
||||
|
||||
template<class T> linalg::vec<T,4> linalg::rotation_quat(const mat<T,3,3> & m)
|
||||
{
|
||||
const vec<T,4> q {m.x.x-m.y.y-m.z.z, m.y.y-m.x.x-m.z.z, m.z.z-m.x.x-m.y.y, m.x.x+m.y.y+m.z.z}, s[] {
|
||||
{1, m.x.y + m.y.x, m.z.x + m.x.z, m.y.z - m.z.y},
|
||||
{m.x.y + m.y.x, 1, m.y.z + m.z.y, m.z.x - m.x.z},
|
||||
{m.x.z + m.z.x, m.y.z + m.z.y, 1, m.x.y - m.y.x},
|
||||
{m.y.z - m.z.y, m.z.x - m.x.z, m.x.y - m.y.x, 1}};
|
||||
return copysign(normalize(sqrt(max(T(0), T(1)+q))), s[argmax(q)]);
|
||||
}
|
||||
|
||||
template<class T> linalg::mat<T,4,4> linalg::lookat_matrix(const vec<T,3> & eye, const vec<T,3> & center, const vec<T,3> & view_y_dir, fwd_axis a)
|
||||
{
|
||||
const vec<T,3> f = normalize(center - eye), z = a == pos_z ? f : -f, x = normalize(cross(view_y_dir, z)), y = cross(z, x);
|
||||
return inverse(mat<T,4,4>{{x,0},{y,0},{z,0},{eye,1}});
|
||||
}
|
||||
|
||||
template<class T> linalg::mat<T,4,4> linalg::frustum_matrix(T x0, T x1, T y0, T y1, T n, T f, fwd_axis a, z_range z)
|
||||
{
|
||||
const T s = a == pos_z ? T(1) : T(-1), o = z == neg_one_to_one ? n : 0;
|
||||
return {{2*n/(x1-x0),0,0,0}, {0,2*n/(y1-y0),0,0}, {-s*(x0+x1)/(x1-x0),-s*(y0+y1)/(y1-y0),s*(f+o)/(f-n),s}, {0,0,-(n+o)*f/(f-n),0}};
|
||||
}
|
||||
|
||||
#endif
|
||||
Vendored
+16
@@ -270,6 +270,21 @@ void scard_loop(SCARDCONTEXT hContext, LPCTSTR slot0_reader_name, LPCTSTR slot1_
|
||||
continue;
|
||||
} else if (lRet != SCARD_S_SUCCESS) {
|
||||
log_warning("scard", "failed SCardGetStatusChange: 0x{:08x}", static_cast<unsigned>(lRet));
|
||||
|
||||
// scard service disappeared, probably because the last reader was unplugged
|
||||
// we currently can't recover from this; we would need to rediscover reader devices
|
||||
// using SCardListReaders
|
||||
if (lRet == SCARD_E_SERVICE_STOPPED) {
|
||||
log_warning(
|
||||
"scard",
|
||||
"scard service stopped, polling will stop; "
|
||||
"the smart card reader was likely unplugged");
|
||||
break;
|
||||
}
|
||||
|
||||
// SCardGetStatusChange can return immediately on some error states;
|
||||
// wait a bit to prevent excessive polling
|
||||
Sleep(SCARD_POLL_INTERVAL_MS * 10);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -399,6 +414,7 @@ int scard_threadmain() {
|
||||
if (reader_name_slots[1]) {
|
||||
HeapFree(GetProcessHeap(), 0, reader_name_slots[1]);
|
||||
}
|
||||
log_misc("scard", "exiting main thread");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "acioemu/handle.h"
|
||||
#include "avs/game.h"
|
||||
#include "hooks/avshook.h"
|
||||
#include "hooks/cfgmgr32hook.h"
|
||||
#include "hooks/devicehook.h"
|
||||
#include "hooks/setupapihook.h"
|
||||
#include "hooks/sleephook.h"
|
||||
@@ -183,10 +185,37 @@ namespace games::ddr {
|
||||
// init device hook
|
||||
devicehook_init();
|
||||
|
||||
// init SETUP API
|
||||
setupapihook_init(avs::game::DLL_INSTANCE);
|
||||
|
||||
// DDR ACE actually uses another DLL for things
|
||||
if (game_mdx != nullptr) {
|
||||
setupapihook_init(game_mdx);
|
||||
}
|
||||
|
||||
// add fake devices
|
||||
if (avs::game::DLL_NAME == "arkmdxbio2.dll") {
|
||||
devicehook_add(new acioemu::ACIOHandle(L"COM1"));
|
||||
} else if(avs::game::DLL_NAME == "arkmdxp4.dll") {
|
||||
|
||||
if (avs::game::SPEC[0] == 'I') {
|
||||
// settings (bio2)
|
||||
SETUPAPI_SETTINGS settingsbio2 {};
|
||||
const char property2[] = "BIO2(VIDEO)";
|
||||
settingsbio2.class_guid[0] = 0x4D36E978;
|
||||
settingsbio2.class_guid[1] = 0x11CEE325;
|
||||
settingsbio2.class_guid[2] = 0x8C1BF;
|
||||
settingsbio2.class_guid[3] = 0x1803E12B;
|
||||
memcpy(settingsbio2.property_devicedesc, property2, sizeof(property2));
|
||||
setupapihook_add(settingsbio2);
|
||||
|
||||
// cfgmgr (bio2)
|
||||
CFGMGR32_HOOK_SETTING settingbio2 {};
|
||||
settingbio2.device_id = "USB\\VID_1CCF&PID_804C";
|
||||
settingbio2.device_node_id = "USB\\VID_1CCF&PID_804C&MI_00\\?&????????&?&????";
|
||||
cfgmgr32hook_init(avs::game::DLL_INSTANCE);
|
||||
cfgmgr32hook_add(settingbio2);
|
||||
}
|
||||
} else if (avs::game::DLL_NAME == "arkmdxp4.dll") {
|
||||
devicehook_add(new DDRP4IOHandle());
|
||||
} else {
|
||||
devicehook_add(new DDRFOOTHandle());
|
||||
@@ -228,14 +257,6 @@ namespace games::ddr {
|
||||
memcpy(settingsp4io.property_devicedesc, settings_property, strlen(settings_property) + 1);
|
||||
memcpy(settingsp4io.interface_detail, settings_detail_p4io, sizeof(settings_detail_p4io));
|
||||
|
||||
// init SETUP API
|
||||
setupapihook_init(avs::game::DLL_INSTANCE);
|
||||
|
||||
// DDR ACE actually uses another DLL for things
|
||||
if (game_mdx != nullptr) {
|
||||
setupapihook_init(game_mdx);
|
||||
}
|
||||
|
||||
// add settings
|
||||
setupapihook_add(settings1);
|
||||
setupapihook_add(settings2);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "games/game.h"
|
||||
#include "external/linalg.h"
|
||||
#include "touch/touch.h"
|
||||
|
||||
namespace games::drs {
|
||||
|
||||
@@ -22,35 +22,31 @@
|
||||
static std::filesystem::path dll_path;
|
||||
static HMODULE iidx_module;
|
||||
static uintptr_t addr_hook_a = 0;
|
||||
static uintptr_t addr_hook_b = 0;
|
||||
static uintptr_t addr_textures = 0;
|
||||
static uintptr_t addr_camera_manager = 0;
|
||||
static uintptr_t addr_device_offset = 0;
|
||||
static uintptr_t addr_afp_texture_offset = 0;
|
||||
|
||||
typedef void **(__fastcall *camera_hook_a_t)(PBYTE);
|
||||
typedef LPDIRECT3DTEXTURE9 (*camera_hook_b_t)(void*, int);
|
||||
static camera_hook_a_t camera_hook_a_orig = nullptr;
|
||||
static camera_hook_b_t camera_hook_b_orig = nullptr;
|
||||
auto static hook_a_init = std::once_flag {};
|
||||
|
||||
static LPDIRECT3DDEVICE9EX device;
|
||||
static LPDIRECT3DTEXTURE9 *texture_a = nullptr;
|
||||
static LPDIRECT3DTEXTURE9 *texture_b = nullptr;
|
||||
static LPDIRECT3DTEXTURE9 *camera_texture_a = nullptr;
|
||||
static LPDIRECT3DTEXTURE9 *camera_texture_b = nullptr;
|
||||
static LPDIRECT3DTEXTURE9 *preview_texture_a = nullptr;
|
||||
static LPDIRECT3DTEXTURE9 *preview_texture_b = nullptr;
|
||||
|
||||
struct PredefinedHook {
|
||||
std::string pe_identifier;
|
||||
uintptr_t hook_a;
|
||||
uintptr_t hook_b;
|
||||
uintptr_t hook_textures;
|
||||
uintptr_t hook_camera_manager;
|
||||
uintptr_t hook_device_offset;
|
||||
uintptr_t hook_afp_texture_offset;
|
||||
};
|
||||
|
||||
PredefinedHook g_predefinedHooks[] =
|
||||
{
|
||||
// 27 003
|
||||
{ "5f713b52_6d4090", 0x005971b0, 0x005fb2c0, 0x04e49898, 0x000000d0 },
|
||||
// 27 010
|
||||
{ "5f713946_7f52b0", 0x006b89e0, 0x0071c950, 0x04fd08b8, 0x000000d0 },
|
||||
};
|
||||
PredefinedHook g_predefinedHooks[] = {};
|
||||
|
||||
const DWORD g_predefinedHooksLength = ARRAYSIZE(g_predefinedHooks);
|
||||
|
||||
@@ -75,8 +71,8 @@ namespace games::iidx {
|
||||
|
||||
if (sscanf(
|
||||
s.c_str(),
|
||||
"%i,%i,%i,%i",
|
||||
(int*)&addr_hook_a, (int*)&addr_hook_b, (int*)&addr_textures, (int*)&addr_device_offset) == 4) {
|
||||
"%i,%i,%i,%i,%i",
|
||||
(int*)&addr_hook_a, (int*)&addr_textures, (int*)&addr_camera_manager, (int*)&addr_device_offset, (int*)&addr_afp_texture_offset) == 5) {
|
||||
|
||||
return true;
|
||||
|
||||
@@ -101,8 +97,8 @@ namespace games::iidx {
|
||||
|
||||
// there are three different ways we try to hook the camera entry points
|
||||
// 1) user-provided override via cmd line
|
||||
// 2) predefined offsets using PE header matching (needed for iidx27 and few others)
|
||||
// 3) signature match (should work for most iidx28-31)
|
||||
// 2) predefined offsets using PE header matching
|
||||
// 3) signature match (should work for most iidx27-33)
|
||||
|
||||
// method 1: user provided
|
||||
if (TDJ_CAMERA_OVERRIDE.has_value() && parse_cmd_params()) {
|
||||
@@ -117,10 +113,11 @@ namespace games::iidx {
|
||||
for (DWORD i = 0; i < g_predefinedHooksLength; i++) {
|
||||
if (pe.compare(g_predefinedHooks[i].pe_identifier) == 0) {
|
||||
log_misc("iidx:camhook", "Found predefined addresses");
|
||||
addr_hook_a = g_predefinedHooks[i].hook_a;
|
||||
addr_hook_b = g_predefinedHooks[i].hook_b;
|
||||
addr_textures = g_predefinedHooks[i].hook_textures;
|
||||
addr_device_offset = g_predefinedHooks[i].hook_device_offset;
|
||||
addr_hook_a = g_predefinedHooks[i].hook_a;
|
||||
addr_textures = g_predefinedHooks[i].hook_textures;
|
||||
addr_camera_manager = g_predefinedHooks[i].hook_camera_manager;
|
||||
addr_device_offset = g_predefinedHooks[i].hook_device_offset;
|
||||
addr_afp_texture_offset = g_predefinedHooks[i].hook_afp_texture_offset;
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
@@ -131,8 +128,8 @@ namespace games::iidx {
|
||||
// --- addr_hook_a ---
|
||||
uint8_t *addr_hook_a_ptr = reinterpret_cast<uint8_t *>(find_pattern(
|
||||
iidx_module,
|
||||
"488BC4565741564883EC5048C740D8FEFFFFFF",
|
||||
"XXXXXXXXXXXXXXXXXXX",
|
||||
"E800000000488B8F0000000048894D",
|
||||
"X????XXX????XXX",
|
||||
0, 0));
|
||||
|
||||
if (addr_hook_a_ptr == nullptr) {
|
||||
@@ -141,24 +138,10 @@ namespace games::iidx {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
addr_hook_a = (uint64_t) (addr_hook_a_ptr - (uint8_t*) iidx_module);
|
||||
|
||||
// addr_hook_b
|
||||
uint8_t* addr_hook_b_ptr = reinterpret_cast<uint8_t *>(find_pattern(
|
||||
iidx_module,
|
||||
"E8000000004885C07439E8",
|
||||
"X????XXXXXX",
|
||||
0, 0));
|
||||
|
||||
if (addr_hook_b_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_hook_b");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// displace with the content of wildcard bytes
|
||||
int32_t disp_b = *((int32_t *) (addr_hook_b_ptr + 1));
|
||||
int32_t disp_a = *((int32_t *) (addr_hook_a_ptr + 1));
|
||||
|
||||
addr_hook_b = (addr_hook_b_ptr - (uint8_t*) iidx_module) + disp_b + 5;
|
||||
addr_hook_a = (addr_hook_a_ptr - (uint8_t*) iidx_module) + disp_a + 5;
|
||||
|
||||
// --- addr_textures ---
|
||||
uint8_t* addr_textures_ptr = reinterpret_cast<uint8_t *>(find_pattern(
|
||||
@@ -194,6 +177,28 @@ namespace games::iidx {
|
||||
|
||||
addr_textures = (addr_textures_ptr - (uint8_t*) iidx_module) + disp_textures + 7;
|
||||
|
||||
// --- addr_camera_manager ---
|
||||
uint8_t *addr_camera_manager_ptr = reinterpret_cast<uint8_t *>(find_pattern(
|
||||
iidx_module,
|
||||
"E80000000033FF488B58",
|
||||
"X????XXXXX",
|
||||
0, 0));
|
||||
|
||||
if (addr_camera_manager_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_get_camera_manager");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// displace with the content of wildcard bytes
|
||||
int32_t disp_camera_manager = *((int32_t *) (addr_camera_manager_ptr + 1));
|
||||
|
||||
addr_camera_manager_ptr = addr_camera_manager_ptr + disp_camera_manager + 5;
|
||||
|
||||
// once more to get the final address
|
||||
disp_camera_manager = *((int32_t *) (addr_camera_manager_ptr + 3));
|
||||
|
||||
addr_camera_manager = (addr_camera_manager_ptr - (uint8_t*) iidx_module) + disp_camera_manager + 7;
|
||||
|
||||
// addr_device_offset
|
||||
search_from = (addr_hook_a_ptr - (uint8_t*) iidx_module);
|
||||
uint8_t *addr_device_ptr = reinterpret_cast<uint8_t *>(find_pattern_from(
|
||||
@@ -201,39 +206,47 @@ namespace games::iidx {
|
||||
"488B89",
|
||||
"XXX",
|
||||
3, 0, search_from));
|
||||
addr_device_offset = *addr_device_ptr;
|
||||
|
||||
if (addr_textures_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_textures (part 3)");
|
||||
if (addr_device_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_device_ptr");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
addr_device_offset = *addr_device_ptr;
|
||||
|
||||
// --- addr_afp_texture_offset ---
|
||||
uint8_t *addr_afp_texture_ptr = reinterpret_cast<uint8_t *>(find_pattern(
|
||||
iidx_module,
|
||||
"488B41004885C07400488D5424",
|
||||
"XXX?XXXX?XXXX",
|
||||
0, 0));
|
||||
|
||||
if (addr_afp_texture_ptr == nullptr) {
|
||||
log_warning("iidx:camhook", "failed to find hook: addr_afp_texture_offset");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
addr_afp_texture_offset = *(addr_afp_texture_ptr + 3);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void **__fastcall camera_hook_a(PBYTE a1) {
|
||||
std::call_once(hook_a_init, [&]{
|
||||
device = *reinterpret_cast<LPDIRECT3DDEVICE9EX*>(a1 + addr_device_offset);
|
||||
auto const textures = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
|
||||
auto const preview = *reinterpret_cast<LPDIRECT3DTEXTURE9**>((uint8_t*)iidx_module + addr_textures);
|
||||
auto const manager = reinterpret_cast<Camera::CCameraManager2*>((uint8_t*)iidx_module + addr_camera_manager);
|
||||
|
||||
texture_a = textures;
|
||||
texture_b = textures + 2;
|
||||
camera_texture_a = manager->cameras->a->d3d9_texture(addr_afp_texture_offset);
|
||||
camera_texture_b = manager->cameras->b->d3d9_texture(addr_afp_texture_offset);
|
||||
preview_texture_a = preview;
|
||||
preview_texture_b = preview + 2;
|
||||
|
||||
init_local_camera();
|
||||
});
|
||||
return camera_hook_a_orig(a1);
|
||||
}
|
||||
|
||||
static LPDIRECT3DTEXTURE9 camera_hook_b(void* a1, int idx) {
|
||||
if (idx == 0 && top_camera) {
|
||||
return top_camera->GetTexture();
|
||||
}
|
||||
if (idx == 1 && front_camera) {
|
||||
return front_camera->GetTexture();
|
||||
}
|
||||
return camera_hook_b_orig(a1, idx);
|
||||
}
|
||||
|
||||
bool create_hooks() {
|
||||
auto *hook_a_ptr = reinterpret_cast<uint16_t *>(
|
||||
reinterpret_cast<intptr_t>(iidx_module) + addr_hook_a);
|
||||
@@ -246,17 +259,6 @@ namespace games::iidx {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
auto *hook_b_ptr = reinterpret_cast<uint16_t *>(
|
||||
reinterpret_cast<intptr_t>(iidx_module) + addr_hook_b);
|
||||
|
||||
if (!detour::trampoline(
|
||||
reinterpret_cast<camera_hook_b_t>(hook_b_ptr),
|
||||
camera_hook_b,
|
||||
&camera_hook_b_orig)) {
|
||||
log_warning("iidx:camhook", "failed to trampoline hook_b");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -443,7 +445,7 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
IIDXLocalCamera* add_top_camera(IMFActivate* pActivate) {
|
||||
auto top_camera = new IIDXLocalCamera("top", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, texture_a);
|
||||
auto top_camera = new IIDXLocalCamera("top", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, camera_texture_a, preview_texture_a);
|
||||
if (top_camera->m_initialized) {
|
||||
LOCAL_CAMERA_LIST.push_back(top_camera);
|
||||
} else {
|
||||
@@ -454,7 +456,7 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
IIDXLocalCamera* add_front_camera(IMFActivate* pActivate) {
|
||||
auto front_camera = new IIDXLocalCamera("front", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, texture_b);
|
||||
auto front_camera = new IIDXLocalCamera("front", TDJ_CAMERA_PREFER_16_9, pActivate, s_pD3DManager, device, camera_texture_b, preview_texture_b);
|
||||
if (front_camera->m_initialized) {
|
||||
LOCAL_CAMERA_LIST.push_back(front_camera);
|
||||
} else {
|
||||
@@ -469,8 +471,8 @@ namespace games::iidx {
|
||||
if (result) {
|
||||
log_misc(
|
||||
"iidx:camhook",
|
||||
"found hooks:\n hook_a 0x{:x}\n hook_b 0x{:x}\n textures 0x{:x}\n device_offset 0x{:x}",
|
||||
addr_hook_a, addr_hook_b, addr_textures, addr_device_offset);
|
||||
"found hooks:\n hook_a 0x{:x}\n textures 0x{:x}\n camera_manager 0x{:x}\n device_offset 0x{:x}\n afp_texture_offset 0x{:x}",
|
||||
addr_hook_a, addr_textures, addr_camera_manager, addr_device_offset, addr_afp_texture_offset);
|
||||
result = create_hooks();
|
||||
if (result) {
|
||||
init_mf_library();
|
||||
@@ -507,7 +509,7 @@ namespace games::iidx {
|
||||
// check parse error
|
||||
auto error = doc.GetParseError();
|
||||
if (error) {
|
||||
log_warning("iidx:camhook", "config parse error: {}", error);
|
||||
log_warning("iidx:camhook", "config parse error: {}", static_cast<uint32_t>(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ namespace games::iidx {
|
||||
|
||||
// settings
|
||||
bool FLIP_CAMS = false;
|
||||
bool DISABLE_CAMS = false;
|
||||
std::optional<bool> DISABLE_CAMS;
|
||||
bool TDJ_CAMERA = false;
|
||||
bool TDJ_CAMERA_PREFER_16_9 = true;
|
||||
bool TDJ_MODE = false;
|
||||
@@ -235,6 +235,9 @@ namespace games::iidx {
|
||||
} else if (data.find(" W:touch: missing trigger:") != std::string::npos) {
|
||||
out.clear();
|
||||
return true;
|
||||
} else if (data.find(" W:CTexture: no such texture: id 0") != std::string::npos) {
|
||||
out.clear();
|
||||
return true;
|
||||
} else if (data.find("SuperstepSound: Audio device is not available") != std::string::npos) {
|
||||
deferredlogs::defer_error_messages(deferredlogs::SUPERSTEP_SOUND_ERROR_MESSAGE);
|
||||
return false;
|
||||
@@ -334,11 +337,6 @@ namespace games::iidx {
|
||||
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
|
||||
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
|
||||
}
|
||||
|
||||
// prevent crash on TDJ mode without correct DLL
|
||||
if (!GetModuleHandle("nvcuda.dll")) {
|
||||
DISABLE_CAMS = true;
|
||||
}
|
||||
}
|
||||
|
||||
// insert BI2X hooks
|
||||
@@ -399,7 +397,10 @@ namespace games::iidx {
|
||||
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
|
||||
|
||||
// check if cam hook should be enabled
|
||||
if (!DISABLE_CAMS) {
|
||||
if (!DISABLE_CAMS.has_value()) {
|
||||
log_fatal("iidx", "assertion failure - DISABLE_CAMS not set during attach");
|
||||
}
|
||||
if (!DISABLE_CAMS.value()) {
|
||||
init_legacy_camera_hook(FLIP_CAMS);
|
||||
}
|
||||
|
||||
@@ -409,15 +410,31 @@ namespace games::iidx {
|
||||
|
||||
void IIDXGame::pre_attach() {
|
||||
Game::pre_attach();
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
|
||||
// environment variables must be set before the DLL is loaded as the VC++ runtime copies all
|
||||
// environment variables at startup
|
||||
if (SCREEN_MODE.has_value()) {
|
||||
log_misc("iidx", "SCREEN_MODE env var set to {}", SCREEN_MODE.value().c_str());
|
||||
SetEnvironmentVariable("SCREEN_MODE", SCREEN_MODE.value().c_str());
|
||||
}
|
||||
|
||||
// check for cab camera access for the second time (first time was in launcher.cpp)
|
||||
// this time, we are inside -iidx module hook, which means the user is likely NOT on a cab
|
||||
// therefore, start with cams OFF by default, and allow user to forcibly override to ON
|
||||
if (!games::iidx::DISABLE_CAMS.has_value()) {
|
||||
games::iidx::DISABLE_CAMS = true;
|
||||
if (options->at(launcher::Options::IIDXCabCamAccess).is_active() &&
|
||||
options->at(launcher::Options::IIDXCabCamAccess).value_text() == "on") {
|
||||
games::iidx::DISABLE_CAMS = false;
|
||||
}
|
||||
if (games::iidx::DISABLE_CAMS.value()) {
|
||||
log_misc("iidx", "CONNECT_CAMERA env var set to 0");
|
||||
SetEnvironmentVariable("CONNECT_CAMERA", "0");
|
||||
}
|
||||
}
|
||||
|
||||
// windowed subscreen, enabled by default, unless turned off by user
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
if (GRAPHICS_WINDOWED && !options->at(launcher::Options::spice2x_IIDXNoSub).value_bool()) {
|
||||
GRAPHICS_IIDX_WSUB = true;
|
||||
}
|
||||
@@ -437,7 +454,7 @@ namespace games::iidx {
|
||||
"!!! please do the following instead: !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! Revert your changes to XML file so it says !!!\n"
|
||||
"!!! <model __type=\"str\">LDJ</model> !!!\n"
|
||||
"!!! <model __type=\"str\">LDJ</model> !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! In SpiceCfg, enable 'IIDX TDJ Mode' or provide -iidxtdj flag !!!\n"
|
||||
"!!! in command line !!!\n"
|
||||
@@ -449,6 +466,32 @@ namespace games::iidx {
|
||||
|
||||
log_fatal("iidx", "BAD MODEL NAME ERROR - TDJ specified, must be LDJ instead");
|
||||
}
|
||||
|
||||
// check -monitor + TDJ mode
|
||||
if (!GRAPHICS_WINDOWED &&
|
||||
options->at(launcher::Options::DisplayAdapter).is_active() &&
|
||||
TDJ_MODE) {
|
||||
log_warning(
|
||||
"iidx",
|
||||
"\n\n!!! using -monitor option with TDJ is NOT recommended due to known !!!\n"
|
||||
"!!! compatibility issues with the game !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
|
||||
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
|
||||
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
|
||||
);
|
||||
|
||||
deferredlogs::defer_error_messages({
|
||||
"-monitor option is NOT recommended when running with TDJ mode",
|
||||
" due to known compatibility issues with the game:",
|
||||
" * game may launch in wrong resolution or refresh rate",
|
||||
" * touch / mouse input may stop working in subscreen / overlay",
|
||||
" recommended fix is to NOT use -monitor and instead set the primary",
|
||||
" monitor in Windows settings before launching the game"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void IIDXGame::detach() {
|
||||
@@ -773,7 +816,15 @@ namespace games::iidx {
|
||||
// <=24 : 32-bit only
|
||||
// 25-26: has neither (no patch needed - WASAPI Exclusive by default)
|
||||
// 27-30: has both (envvar will be respected, ASIO or WASAPI)
|
||||
// 31+: only has XONAR (ASIO by default, signature patch can be used to force WASAPI - for now)
|
||||
// 31-32: only has XONAR (ASIO by default, signature patch can be used to force WASAPI - for now)
|
||||
// 33 : early versions only have XONAR, but later datecodes have both; SOUND_OUTPUT_DEVICE
|
||||
// returned and it works again when set as env var
|
||||
|
||||
log_info(
|
||||
"iidx",
|
||||
"has_SOUND_OUTPUT_DEVICE: {}, has_XONAR_SOUND_CARD: {}",
|
||||
(has_SOUND_OUTPUT_DEVICE != 0),
|
||||
(has_XONAR_SOUND_CARD != 0));
|
||||
|
||||
if (!has_SOUND_OUTPUT_DEVICE && !has_XONAR_SOUND_CARD) {
|
||||
// iidx 25-26
|
||||
@@ -781,16 +832,10 @@ namespace games::iidx {
|
||||
return;
|
||||
}
|
||||
|
||||
if (has_SOUND_OUTPUT_DEVICE && has_XONAR_SOUND_CARD) {
|
||||
// iidx 27-30
|
||||
log_info("iidx", "This game accepts SOUND_OUTPUT_DEVICE environment variable");
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("iidx", "This game supports ASIO but does not accept SOUND_OUTPUT_DEVICE environment variable");
|
||||
|
||||
// patch game to force wasapi
|
||||
if (SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() && SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi") {
|
||||
// if the game doesn't accept SOUND_OUTPUT_DEVICE, patch game to force wasapi
|
||||
if (!has_SOUND_OUTPUT_DEVICE &&
|
||||
SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() &&
|
||||
SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi") {
|
||||
intptr_t result = replace_pattern(
|
||||
avs::game::DLL_INSTANCE,
|
||||
"FF5008E8??????FF83780803740D",
|
||||
@@ -808,8 +853,6 @@ namespace games::iidx {
|
||||
"Successfully forced WASAPI as audio engine using signature matching @ 0x{:x}.",
|
||||
result);
|
||||
}
|
||||
} else {
|
||||
log_info("iidx", "Not applying force wasapi patch; game will use ASIO");
|
||||
}
|
||||
|
||||
// patch iidx32+ for asio compatibility
|
||||
@@ -817,7 +860,8 @@ namespace games::iidx {
|
||||
// the patch is only really needed for (some) non-XONAR devices but since people sometimes disguise
|
||||
// other devices as a XONAR, don't check for the exact string (common ASIO workaround for INF)
|
||||
if (avs::game::is_ext(2024090100, MAXINT) &&
|
||||
!(SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() && SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi")) {
|
||||
!(SOUND_OUTPUT_DEVICE_IN_EFFECT.has_value() &&
|
||||
SOUND_OUTPUT_DEVICE_IN_EFFECT.value() == "wasapi")) {
|
||||
|
||||
// in iidx32 final:
|
||||
// ff 50 08 call QWORD PTR [rax+0x8] ; ASIO instance AddRef
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace games::iidx {
|
||||
// settings
|
||||
|
||||
extern bool FLIP_CAMS;
|
||||
extern bool DISABLE_CAMS;
|
||||
extern std::optional<bool> DISABLE_CAMS;
|
||||
extern bool TDJ_CAMERA;
|
||||
extern bool TDJ_CAMERA_PREFER_16_9;
|
||||
extern std::optional<std::string> TDJ_CAMERA_OVERRIDE;
|
||||
|
||||
@@ -213,6 +213,8 @@ namespace games::iidx {
|
||||
}
|
||||
|
||||
void init_legacy_camera_hook(bool flip_cams) {
|
||||
log_info("iidx::cam", "initializing legacy camera hook");
|
||||
|
||||
should_flip_cams = flip_cams;
|
||||
|
||||
// camera media framework hook
|
||||
|
||||
@@ -46,14 +46,17 @@ namespace games::iidx {
|
||||
IMFActivate *pActivate,
|
||||
IDirect3DDeviceManager9 *pD3DManager,
|
||||
LPDIRECT3DDEVICE9EX device,
|
||||
LPDIRECT3DTEXTURE9 *texture_target
|
||||
LPDIRECT3DTEXTURE9 *camera_texture_target,
|
||||
LPDIRECT3DTEXTURE9 *preview_texture_target
|
||||
):
|
||||
m_nRefCount(1),
|
||||
m_name(name),
|
||||
m_prefer_16_by_9(prefer_16_by_9),
|
||||
m_device(device),
|
||||
m_texture_target(texture_target),
|
||||
m_texture_original(*texture_target)
|
||||
m_camera_texture_target(camera_texture_target),
|
||||
m_preview_texture_target(preview_texture_target),
|
||||
m_camera_texture_original(*camera_texture_target),
|
||||
m_preview_texture_original(*preview_texture_target)
|
||||
{
|
||||
InitializeCriticalSection(&m_critsec);
|
||||
|
||||
@@ -691,7 +694,8 @@ namespace games::iidx {
|
||||
if (FAILED(hr)) { goto done; }
|
||||
|
||||
// Make the game use this new texture as camera stream source
|
||||
*m_texture_target = m_texture;
|
||||
*m_camera_texture_target = m_texture;
|
||||
*m_preview_texture_target = m_texture;
|
||||
m_active = TRUE;
|
||||
|
||||
// Create texture for colour conversion
|
||||
@@ -860,7 +864,8 @@ namespace games::iidx {
|
||||
|
||||
ULONG uCount = InterlockedDecrement(&m_nRefCount);
|
||||
|
||||
*m_texture_target = m_texture_original;
|
||||
*m_camera_texture_target = m_camera_texture_original;
|
||||
*m_preview_texture_target = m_preview_texture_original;
|
||||
|
||||
for (size_t i = 0; i < m_mediaTypeInfos.size(); i++) {
|
||||
SafeRelease(&(m_mediaTypeInfos.at(i).p_mediaType));
|
||||
|
||||
@@ -66,6 +66,25 @@ extern std::string CAMERA_CONTROL_LABELS[];
|
||||
extern std::string DRAW_MODE_LABELS[];
|
||||
|
||||
namespace games::iidx {
|
||||
namespace Camera {
|
||||
struct PlayVideoCamera {
|
||||
IDirect3DTexture9** d3d9_texture(const uintptr_t offset) {
|
||||
auto const afp_texture = *reinterpret_cast<uint8_t**>
|
||||
(reinterpret_cast<uint8_t*>(this) + offset);
|
||||
return reinterpret_cast<IDirect3DTexture9**>(afp_texture + 0x8);
|
||||
}
|
||||
};
|
||||
|
||||
struct CCameraManager2 {
|
||||
using camera_pointers = struct {
|
||||
PlayVideoCamera* a;
|
||||
PlayVideoCamera* b;
|
||||
};
|
||||
void* vftbl;
|
||||
camera_pointers* cameras;
|
||||
};
|
||||
}
|
||||
|
||||
class IIDXLocalCamera {
|
||||
protected:
|
||||
virtual ~IIDXLocalCamera() {};
|
||||
@@ -98,8 +117,9 @@ namespace games::iidx {
|
||||
// DirectX9 DeviceEx
|
||||
LPDIRECT3DDEVICE9EX m_device;
|
||||
|
||||
// Address to hook camera texture onto the game
|
||||
LPDIRECT3DTEXTURE9 *m_texture_target = nullptr;
|
||||
// Address to hook camera textures onto the game
|
||||
LPDIRECT3DTEXTURE9 *m_camera_texture_target = nullptr;
|
||||
LPDIRECT3DTEXTURE9 *m_preview_texture_target = nullptr;
|
||||
|
||||
// Target texture (to be shown in the game)
|
||||
LPDIRECT3DTEXTURE9 m_texture = nullptr;
|
||||
@@ -117,8 +137,9 @@ namespace games::iidx {
|
||||
LPDIRECT3DTEXTURE9 m_transformResultTexture = nullptr;
|
||||
IDirect3DSurface9 *m_pTransformResultSurf = nullptr;
|
||||
|
||||
// Storing original texture for clean up
|
||||
LPDIRECT3DTEXTURE9 m_texture_original = nullptr;
|
||||
// Storing original textures for clean up
|
||||
LPDIRECT3DTEXTURE9 m_camera_texture_original = nullptr;
|
||||
LPDIRECT3DTEXTURE9 m_preview_texture_original = nullptr;
|
||||
|
||||
IAMCameraControl *m_pCameraControl = nullptr;
|
||||
|
||||
@@ -154,7 +175,8 @@ namespace games::iidx {
|
||||
IMFActivate *pActivate,
|
||||
IDirect3DDeviceManager9 *pD3DManager,
|
||||
LPDIRECT3DDEVICE9EX device,
|
||||
LPDIRECT3DTEXTURE9 *texture_target
|
||||
LPDIRECT3DTEXTURE9 *camera_texture_target,
|
||||
LPDIRECT3DTEXTURE9 *preview_texture_target
|
||||
);
|
||||
LPDIRECT3DTEXTURE9 GetTexture();
|
||||
ULONG Release();
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "avs/game.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "misc/eamuse.h"
|
||||
#include "misc/wintouchemu.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
@@ -32,6 +33,8 @@ namespace games::mfc {
|
||||
static uint8_t CARD_TYPE = 0;
|
||||
static uint8_t CARD_UID[8];
|
||||
|
||||
bool HG_MODE = false;
|
||||
|
||||
typedef int (__cdecl *inifile_param_num_t)(const char *, int *);
|
||||
static inifile_param_num_t inifile_param_num_real;
|
||||
|
||||
@@ -50,8 +53,10 @@ namespace games::mfc {
|
||||
static void __cdecl touch_init(int width, int height) {
|
||||
log_info("mfc", "call touch_init(width: {}, height: {})", width, height);
|
||||
|
||||
// attach touch module
|
||||
if (!TOUCH_ATTACHED) {
|
||||
if (HG_MODE) {
|
||||
wintouchemu::ADD_TOUCH_FLAG_PRIMARY = true;
|
||||
wintouchemu::hook("MFC9", avs::game::DLL_INSTANCE);
|
||||
} else if (!TOUCH_ATTACHED) { // attach touch module
|
||||
|
||||
// store touch size specification
|
||||
TOUCH_MAX_X = width;
|
||||
@@ -328,6 +333,10 @@ namespace games::mfc {
|
||||
if (allinone_module == nullptr) {
|
||||
log_misc("mfc", "using system.dll instead of allinone.dll for i/o hooks");
|
||||
allinone_module = system_module;
|
||||
|
||||
if (avs::game::SPEC[0] == 'F') {
|
||||
HG_MODE = true;
|
||||
}
|
||||
}
|
||||
|
||||
// network fix
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
namespace games::mfc {
|
||||
|
||||
extern bool HG_MODE;
|
||||
|
||||
struct joystick_state {
|
||||
bool up = false;
|
||||
bool down = false;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/shared/lcdhandle.h"
|
||||
#include "games/io.h"
|
||||
#include "hooks/audio/audio.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "hooks/devicehook.h"
|
||||
@@ -12,6 +13,7 @@
|
||||
#include "hooks/powrprof.h"
|
||||
#include "hooks/sleephook.h"
|
||||
#include "hooks/winuser.h"
|
||||
#include "launcher/options.h"
|
||||
#include "touch/touch.h"
|
||||
#include "util/deferlog.h"
|
||||
#include "util/detour.h"
|
||||
@@ -106,8 +108,8 @@ namespace games::sdvx {
|
||||
if (ENABLE_COM_PORT_SCAN_HOOK &&
|
||||
lpSubKey != nullptr && phkResult != nullptr &&
|
||||
_stricmp(lpSubKey, "HARDWARE\\DEVICEMAP\\SERIALCOMM") == 0) {
|
||||
log_info("sdvx::io", "failing HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
|
||||
return 2; //ERROR_FILE_NOT_FOUND
|
||||
log_info("sdvx::io", "hook access to HKLM\\HARDWARE\\DEVICEMAP\\SERIALCOMM to force COM1 ICCA");
|
||||
return 2; // ERROR_FILE_NOT_FOUND
|
||||
}
|
||||
|
||||
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
|
||||
@@ -291,6 +293,37 @@ namespace games::sdvx {
|
||||
"sdvx",
|
||||
"BAD MODEL NAME ERROR - model name set to UFC, must be KFC instead");
|
||||
}
|
||||
|
||||
#ifdef SPICE64 // SDVX5+ specific code
|
||||
bool isValkyrieCabinetMode = avs::game::SPEC[0] == 'G' || avs::game::SPEC[0] == 'H';
|
||||
auto options = games::get_options(eamuse_get_game());
|
||||
// check -monitor + UFC mode
|
||||
if (!GRAPHICS_WINDOWED &&
|
||||
options->at(launcher::Options::DisplayAdapter).is_active() &&
|
||||
isValkyrieCabinetMode) {
|
||||
log_warning(
|
||||
"sdvx",
|
||||
"\n\n!!! using -monitor option with VM mode is NOT recommended due to !!!\n"
|
||||
"!!! known compatibility issues with the game !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! * game may launch in wrong resolution or refresh rate !!!\n"
|
||||
"!!! * touch / mouse input may stop working in subscreen / overlay !!!\n"
|
||||
"!!! !!!\n"
|
||||
"!!! recommendation is to NOT use -monitor and instead set the !!!\n"
|
||||
"!!! primary monitor in Windows settings before launching the game !!!\n\n"
|
||||
);
|
||||
|
||||
deferredlogs::defer_error_messages({
|
||||
"-monitor option is NOT recommended when running with Valkyrie mode",
|
||||
" due to known compatibility issues with the game:",
|
||||
" * game may launch in wrong resolution or refresh rate",
|
||||
" * touch / mouse input may stop working in subscreen / overlay",
|
||||
" recommended fix is to NOT use -monitor and instead set the primary",
|
||||
" monitor in Windows settings before launching the game"
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void SDVXGame::attach() {
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
#include "audio.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <external/robin_hood.h>
|
||||
|
||||
#include <windows.h>
|
||||
#include <mmsystem.h>
|
||||
#include <msacm.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/memutils.h"
|
||||
#include "util/time.h"
|
||||
#include "util/unique_plain_ptr.h"
|
||||
|
||||
#define ACM_CACHE_ENABLED 1
|
||||
#define ACM_DEBUG_PERF 0
|
||||
#define ACM_DEBUG_VERBOSE 0
|
||||
|
||||
#if ACM_DEBUG_VERBOSE
|
||||
#define log_debug(module, format_str, ...) logger::push( \
|
||||
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
|
||||
#else
|
||||
#define log_debug(module, format_str, ...)
|
||||
#endif
|
||||
|
||||
namespace hooks::audio::acm {
|
||||
struct WAVE_FORMAT {
|
||||
uint32_t suggest;
|
||||
// for WAVEFORMATEX
|
||||
std::vector<uint8_t> wave_format_ex;
|
||||
|
||||
bool operator==(const WAVE_FORMAT& other) const {
|
||||
return (this->suggest == other.suggest &&
|
||||
this->wave_format_ex == other.wave_format_ex);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace robin_hood {
|
||||
template <>
|
||||
struct hash<hooks::audio::acm::WAVE_FORMAT> {
|
||||
std::size_t operator()(const hooks::audio::acm::WAVE_FORMAT& k) const noexcept {
|
||||
std::size_t h = robin_hood::hash_bytes(
|
||||
k.wave_format_ex.data(), k.wave_format_ex.size());
|
||||
|
||||
h = h ^ robin_hood::hash<uint32_t>{}(k.suggest) << 1;
|
||||
return h;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
namespace hooks::audio::acm {
|
||||
|
||||
static decltype(acmFormatSuggest) *acmFormatSuggest_orig = nullptr;
|
||||
static std::mutex acm_formats_mutex;
|
||||
static robin_hood::unordered_map<WAVE_FORMAT, std::vector<uint8_t>> acm_formats;
|
||||
|
||||
// hooks calls to acmFormatSuggest and returns cached results from previous calls
|
||||
//
|
||||
// iidx calls this many times during song load and reload (function of # of keysounds)
|
||||
// - with near-identical arguments over and over again
|
||||
// (when hovering over song in song select, selecting a song, and exiting result screen)
|
||||
//
|
||||
// on Linux this results in ACM module load/unload which is very expensive,
|
||||
// resulting in significant perf improvement (seconds per song load);
|
||||
// on Windows this saves milliseconds per song, at best
|
||||
//
|
||||
// https://codeberg.org/nixac/spicetools/issues/2
|
||||
// https://codeberg.org/nixac/spicetools/issues/4
|
||||
MMRESULT
|
||||
acmFormatSuggest_cached (
|
||||
HACMDRIVER had,
|
||||
LPWAVEFORMATEX pwfxSrc,
|
||||
LPWAVEFORMATEX pwfxDst,
|
||||
DWORD cbwfxDst,
|
||||
DWORD fdwSuggest) {
|
||||
|
||||
if (had != 0 || cbwfxDst == 0) {
|
||||
return acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
}
|
||||
|
||||
WAVE_FORMAT key;
|
||||
key.suggest = fdwSuggest;
|
||||
const size_t src_size = sizeof(*pwfxSrc) + pwfxSrc->cbSize;
|
||||
key.wave_format_ex.insert(
|
||||
key.wave_format_ex.end(),
|
||||
reinterpret_cast<uint8_t *>(pwfxSrc),
|
||||
reinterpret_cast<uint8_t *>(pwfxSrc) + src_size);
|
||||
|
||||
std::lock_guard<std::mutex> lock(acm_formats_mutex);
|
||||
|
||||
MMRESULT mmresult = 0;
|
||||
|
||||
if (acm_formats.contains(key)) {
|
||||
// found in cache
|
||||
const auto &result = acm_formats.at(key);
|
||||
|
||||
if (cbwfxDst >= result.size()) {
|
||||
// dest buffer big enough, return cached result
|
||||
log_debug("audio::acm", "acmFormatSuggest cache hit, copying {} bytes", result.size());
|
||||
std::memcpy(reinterpret_cast<uint8_t *>(pwfxDst), result.data(), result.size());
|
||||
mmresult = 0;
|
||||
} else {
|
||||
// dest buffer not big enough, call original
|
||||
log_debug("audio::acm", "acmFormatSuggest cache fail; cbwfxDst too small ({})", cbwfxDst);
|
||||
mmresult = acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
}
|
||||
|
||||
} else {
|
||||
log_debug("audio::acm", "acmFormatSuggest cache miss; calling original");
|
||||
mmresult = acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
|
||||
// cache the result, but don't allow unconstrained growth
|
||||
if (mmresult == 0 && acm_formats.size() < 128) {
|
||||
const size_t dest_size = sizeof(*pwfxDst) + pwfxDst->cbSize;
|
||||
log_debug(
|
||||
"audio::acm",
|
||||
"acmFormatSuggest cache add; current cache size {}, new data {} bytes",
|
||||
acm_formats.size(),
|
||||
dest_size);
|
||||
|
||||
acm_formats[key] = std::vector<uint8_t>();
|
||||
acm_formats[key].reserve(dest_size);
|
||||
acm_formats[key].insert(
|
||||
acm_formats[key].begin(),
|
||||
reinterpret_cast<uint8_t *>(pwfxDst),
|
||||
reinterpret_cast<uint8_t *>(pwfxDst) + dest_size);
|
||||
}
|
||||
}
|
||||
|
||||
return mmresult;
|
||||
}
|
||||
|
||||
MMRESULT
|
||||
ACMAPI
|
||||
acmFormatSuggest_hook (
|
||||
HACMDRIVER had,
|
||||
LPWAVEFORMATEX pwfxSrc,
|
||||
LPWAVEFORMATEX pwfxDst,
|
||||
DWORD cbwfxDst,
|
||||
DWORD fdwSuggest) {
|
||||
|
||||
log_debug(
|
||||
"audio::acm",
|
||||
"acmFormatSuggest called: had={}, "
|
||||
"formattag={}, ch={}, samplespersec={}, bytespersec={}, blockalign={}, bits={}, extrasize={}",
|
||||
fmt::ptr(had),
|
||||
pwfxSrc->wFormatTag,
|
||||
pwfxSrc->nChannels,
|
||||
pwfxSrc->nSamplesPerSec,
|
||||
pwfxSrc->nAvgBytesPerSec,
|
||||
pwfxSrc->nBlockAlign,
|
||||
pwfxSrc->wBitsPerSample,
|
||||
pwfxSrc->cbSize);
|
||||
|
||||
#if ACM_DEBUG_PERF
|
||||
const auto start = get_performance_milliseconds();
|
||||
#endif
|
||||
|
||||
// make a call to acmFormatSuggest
|
||||
|
||||
#if ACM_CACHE_ENABLED
|
||||
const auto result = acmFormatSuggest_cached(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
#else
|
||||
const auto result = acmFormatSuggest_orig(had, pwfxSrc, pwfxDst, cbwfxDst, fdwSuggest);
|
||||
#endif
|
||||
|
||||
// log result
|
||||
|
||||
#if ACM_DEBUG_PERF
|
||||
const auto delta = get_performance_milliseconds() - start;
|
||||
static double delta_total = 0;
|
||||
delta_total += delta;
|
||||
|
||||
log_info(
|
||||
"audio::acm",
|
||||
"acmFormatSuggest_hook returned {}, took {} us (running total {} ms)",
|
||||
result,
|
||||
delta * 1000,
|
||||
delta_total);
|
||||
#else
|
||||
log_debug("audio::acm", "acmFormatSuggest_hook returned {}", result);
|
||||
#endif
|
||||
|
||||
if (result == 0 && cbwfxDst > sizeof(WAVEFORMATEX)) {
|
||||
log_debug(
|
||||
"audio::acm",
|
||||
"....formattag={}, ch={}, samplespersec={}, bytespersec={}, blockalign={}, bits={}, extrasize={}",
|
||||
pwfxDst->wFormatTag,
|
||||
pwfxDst->nChannels,
|
||||
pwfxDst->nSamplesPerSec,
|
||||
pwfxDst->nAvgBytesPerSec,
|
||||
pwfxDst->nBlockAlign,
|
||||
pwfxDst->wBitsPerSample,
|
||||
pwfxDst->cbSize);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void init() {
|
||||
|
||||
// only enabled on Linux as the performance gains are negligible on Windows
|
||||
#if SPICE_LINUX
|
||||
|
||||
HMODULE msacm = libutils::try_library("msacm32.dll");
|
||||
if (msacm == nullptr) {
|
||||
log_info("audio::acm", "msacm32.dll failed to hook");
|
||||
return;
|
||||
}
|
||||
|
||||
acmFormatSuggest_orig = detour::iat_try(
|
||||
"acmFormatSuggest", acmFormatSuggest_hook, avs::game::DLL_INSTANCE);
|
||||
if (acmFormatSuggest_orig != nullptr) {
|
||||
log_misc("audio::acm", "acmFormatSuggest hooked");
|
||||
|
||||
#if !ACM_CACHE_ENABLED
|
||||
log_warning("audio::acm", "acmFormatSuggest cache DISABLED");
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
namespace hooks::audio::acm {
|
||||
void init();
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "util/memutils.h"
|
||||
|
||||
#include "audio_private.h"
|
||||
#include "acm.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
DEFINE_GUID(CLSID_MMDeviceEnumerator,
|
||||
@@ -101,6 +102,7 @@ namespace hooks::audio {
|
||||
|
||||
log_info("audio", "initializing");
|
||||
init_low_latency();
|
||||
hooks::audio::acm::init();
|
||||
|
||||
// general hooks
|
||||
CoCreateInstance_orig = detour::iat_try("CoCreateInstance", CoCreateInstance_hook);
|
||||
|
||||
@@ -35,7 +35,7 @@ std::string share_mode_str(AUDCLNT_SHAREMODE share_mode) {
|
||||
ENUM_VARIANT(AUDCLNT_SHAREMODE_SHARED);
|
||||
ENUM_VARIANT(AUDCLNT_SHAREMODE_EXCLUSIVE);
|
||||
default:
|
||||
return fmt::format("ShareMode(0x{:08x})", share_mode);
|
||||
return fmt::format("ShareMode(0x{:08x})", static_cast<uint32_t>(share_mode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "cfg/screen_resize.h"
|
||||
#include "games/iidx/iidx.h"
|
||||
#include "games/sdvx/sdvx.h"
|
||||
#include "games/mfc/mfc.h"
|
||||
#include "games/io.h"
|
||||
#include "hooks/graphics/graphics.h"
|
||||
#include "launcher/launcher.h"
|
||||
@@ -188,7 +189,7 @@ static std::string format2s(D3DFORMAT format) {
|
||||
ENUM_VARIANT(D3DFMT_A2B10G10R10_XR_BIAS);
|
||||
ENUM_VARIANT(D3DFMT_BINARYBUFFER);
|
||||
default:
|
||||
return fmt::to_string(format);
|
||||
return fmt::to_string(static_cast<uint32_t>(format));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -695,7 +696,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
{
|
||||
log_misc("graphics::d3d9", "IDirect3D9::CreateDevice hook hit ({}, {}, {}, {}, {}, {})",
|
||||
Adapter,
|
||||
DeviceType,
|
||||
static_cast<uint32_t>(DeviceType),
|
||||
fmt::ptr(hFocusWindow),
|
||||
behavior2s(BehaviorFlags),
|
||||
fmt::ptr(pPresentationParameters),
|
||||
@@ -770,9 +771,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
|
||||
params->BackBufferHeight,
|
||||
format2s(params->BackBufferFormat),
|
||||
params->BackBufferCount,
|
||||
params->MultiSampleType,
|
||||
static_cast<uint32_t>(params->MultiSampleType),
|
||||
params->MultiSampleQuality,
|
||||
params->SwapEffect,
|
||||
static_cast<uint32_t>(params->SwapEffect),
|
||||
params->Windowed,
|
||||
params->EnableAutoDepthStencil,
|
||||
format2s(params->AutoDepthStencilFormat),
|
||||
@@ -884,7 +885,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
|
||||
log_misc("graphics::d3d9", "IDirect3D9Ex::CreateDeviceEx hook hit ({}, {}, {}, {}, {}, {})",
|
||||
Adapter,
|
||||
DeviceType,
|
||||
static_cast<uint32_t>(DeviceType),
|
||||
fmt::ptr(hFocusWindow),
|
||||
behavior2s(BehaviorFlags),
|
||||
fmt::ptr(pPresentationParameters),
|
||||
@@ -959,9 +960,9 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
params->BackBufferHeight,
|
||||
format2s(params->BackBufferFormat),
|
||||
params->BackBufferCount,
|
||||
params->MultiSampleType,
|
||||
static_cast<uint32_t>(params->MultiSampleType),
|
||||
params->MultiSampleQuality,
|
||||
params->SwapEffect,
|
||||
static_cast<uint32_t>(params->SwapEffect),
|
||||
params->Windowed,
|
||||
params->EnableAutoDepthStencil,
|
||||
format2s(params->AutoDepthStencilFormat),
|
||||
@@ -990,7 +991,7 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDeviceEx(
|
||||
fullscreen_display_mode->Height,
|
||||
fullscreen_display_mode->RefreshRate,
|
||||
format2s(fullscreen_display_mode->Format),
|
||||
fullscreen_display_mode->ScanLineOrdering);
|
||||
static_cast<uint32_t>(fullscreen_display_mode->ScanLineOrdering));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1360,6 +1361,11 @@ void graphics_d3d9_on_present(
|
||||
graphics_d3d9_ldj_on_present(wrapped_device);
|
||||
}
|
||||
|
||||
const bool is_mfc = avs::game::is_model("KK9") && games::mfc::HG_MODE;
|
||||
if (is_mfc) {
|
||||
wintouchemu::update();
|
||||
}
|
||||
|
||||
// check screenshot key
|
||||
static bool trigger_last = false;
|
||||
auto buttons = games::get_buttons_overlay(eamuse_get_game());
|
||||
|
||||
@@ -36,6 +36,14 @@ constexpr bool CUSTOM_RESET = false;
|
||||
|
||||
constexpr D3DFORMAT D3DFMT_DF24 = static_cast<D3DFORMAT>(MAKEFOURCC('D', 'F', '2', '4'));
|
||||
|
||||
auto format_as(D3DPOOL f) {
|
||||
return fmt::underlying(f);
|
||||
}
|
||||
|
||||
auto format_as(D3DFORMAT f) {
|
||||
return fmt::underlying(f);
|
||||
}
|
||||
|
||||
std::string usage2s(DWORD dwUsage) {
|
||||
FLAGS_START(dwUsage);
|
||||
FLAG(dwUsage, D3DUSAGE_RENDERTARGET);
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/logging.h"
|
||||
#include "util/utils.h"
|
||||
|
||||
#include "nvenc_hook.h"
|
||||
|
||||
@@ -16,17 +17,54 @@
|
||||
|
||||
typedef NVENCSTATUS(NVENCAPI *NvEncodeAPICreateInstance_Type)(NV_ENCODE_API_FUNCTION_LIST*);
|
||||
static NvEncodeAPICreateInstance_Type NvEncodeAPICreateInstance_orig = nullptr;
|
||||
|
||||
static PNVENCOPENENCODESESSIONEX nvEncOpenEncodeSessionEx_orig = nullptr;
|
||||
static PNVENCINITIALIZEENCODER nvEncInitializeEncoder_orig = nullptr;
|
||||
static PNVENCGETENCODEPRESETCONFIG nvEncGetEncodePresetConfig_orig = nullptr;
|
||||
static PNVENCGETENCODEPRESETCONFIGEX nvEncGetEncodePresetConfigEx_orig = nullptr;
|
||||
static BOOL initialized = false;
|
||||
static BOOL new_preset_guids = false;
|
||||
|
||||
namespace nvenc_hook {
|
||||
|
||||
bool FORCE_DISABLE;
|
||||
|
||||
std::optional<std::string> VIDEO_CQP_STRING_OVERRIDE;
|
||||
std::optional<NV_ENC_QP> VIDEO_CQP_OVERRIDE;
|
||||
|
||||
void parse_qcp_params();
|
||||
|
||||
void dump_init_params(NV_ENC_INITIALIZE_PARAMS* encode) {
|
||||
log_misc("nvenc_hook", "NV_ENC_INITIALIZE_PARAMS:");
|
||||
log_misc("nvenc_hook", " version: {:x}", encode->version);
|
||||
log_misc("nvenc_hook", " encodeGUID: {}", guid2s(encode->encodeGUID));
|
||||
log_misc("nvenc_hook", " presetGUID: {}", guid2s(encode->presetGUID));
|
||||
log_misc("nvenc_hook", " encodeWidth: {}", encode->encodeWidth);
|
||||
log_misc("nvenc_hook", " encodeHeight: {}", encode->encodeHeight);
|
||||
log_misc("nvenc_hook", " darWidth: {}", encode->darWidth);
|
||||
log_misc("nvenc_hook", " darHeight: {}", encode->darHeight);
|
||||
log_misc("nvenc_hook", " frameRateNum: {}", encode->frameRateNum);
|
||||
log_misc("nvenc_hook", " frameRateDen: {}", encode->frameRateDen);
|
||||
log_misc("nvenc_hook", " enableEncodeAsync: {}", encode->enableEncodeAsync);
|
||||
log_misc("nvenc_hook", " enablePTD: {}", encode->enablePTD);
|
||||
log_misc("nvenc_hook", " maxEncodeWidth: {}", encode->maxEncodeWidth);
|
||||
log_misc("nvenc_hook", " maxEncodeHeight: {}", encode->maxEncodeHeight);
|
||||
log_misc("nvenc_hook", " tuningInfo: {}", static_cast<uint32_t>(encode->tuningInfo));
|
||||
log_misc("nvenc_hook", " bufferFormat: {}", static_cast<uint32_t>(encode->bufferFormat));
|
||||
log_misc("nvenc_hook", " encodeConfig.version: {:x}", encode->encodeConfig->version);
|
||||
log_misc("nvenc_hook", " encodeConfig.profileGUID: {}", guid2s(encode->encodeConfig->profileGUID));
|
||||
log_misc("nvenc_hook", " encodeConfig.gopLength: {}", encode->encodeConfig->gopLength);
|
||||
log_misc("nvenc_hook", " encodeConfig.frameIntervalP: {}", encode->encodeConfig->frameIntervalP);
|
||||
log_misc("nvenc_hook", " encodeConfig.monoChromeEncoding: {}", encode->encodeConfig->monoChromeEncoding);
|
||||
|
||||
const auto rc = &encode->encodeConfig->rcParams;
|
||||
const auto h264 = &encode->encodeConfig->encodeCodecConfig.h264Config;
|
||||
log_misc("nvenc_hook", " encodeConfig.encodeCodecConfig.h264Config.sliceMode: {}", h264->sliceMode);
|
||||
log_misc("nvenc_hook", " encodeConfig.encodeCodecConfig.h264Config.sliceModeData: {}", h264->sliceModeData);
|
||||
log_misc("nvenc_hook", " encodeConfig.rcParams.version: {:x}", rc->version);
|
||||
log_misc("nvenc_hook", " encodeConfig.rcParams.rateControlMode: {}", static_cast<uint32_t>(rc->rateControlMode));
|
||||
}
|
||||
|
||||
NVENCSTATUS NVENCAPI nvEncOpenEncodeSessionEx_hook(
|
||||
NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS *openSessionExParams,
|
||||
void **encoder
|
||||
@@ -51,41 +89,113 @@ namespace nvenc_hook {
|
||||
try {
|
||||
// check input params
|
||||
if (createEncodeParams == nullptr || createEncodeParams->encodeConfig == nullptr) {
|
||||
log_warning("nvenc_hook", "nvEncInitializeEncoder called with invalid params");
|
||||
goto done;
|
||||
}
|
||||
|
||||
const auto rc = &createEncodeParams->encodeConfig->rcParams;
|
||||
if (rc->rateControlMode != NV_ENC_PARAMS_RC_CONSTQP) {
|
||||
|
||||
log_misc("nvenc_hook", "nvEncInitializeEncoder hook hit with expected params");
|
||||
dump_init_params(createEncodeParams);
|
||||
if (new_preset_guids) {
|
||||
log_misc("nvenc_hook", "swapping out encoder preset for newer NVENC SDK");
|
||||
memcpy(&createEncodeParams->presetGUID, &NV_ENC_PRESET_P4_GUID, sizeof(GUID));
|
||||
createEncodeParams->tuningInfo = NV_ENC_TUNING_INFO_HIGH_QUALITY;
|
||||
}
|
||||
|
||||
// cqp p/b/i override
|
||||
if (rc->rateControlMode == NV_ENC_PARAMS_RC_CONSTQP) {
|
||||
// print out most relevant video quality settings
|
||||
// note: NvEncoder.cpp sample uses {28, 31, 25} (and that's what some hex edits modify)
|
||||
// but bm2dx later adds 8 to each value, before calling this routine
|
||||
log_misc(
|
||||
"nvenc_hook", "nvEncInitializeEncoder: original constQP p={}, b={}, i={}",
|
||||
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
|
||||
|
||||
// video quality override
|
||||
if (VIDEO_CQP_OVERRIDE.has_value()) {
|
||||
rc->constQP = VIDEO_CQP_OVERRIDE.value();
|
||||
log_misc(
|
||||
"nvenc_hook", "nvEncInitializeEncoder: user overriden constQP p={}, b={}, i={}",
|
||||
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
|
||||
}
|
||||
} else {
|
||||
log_warning(
|
||||
"nvenc_hook",
|
||||
"nvEncInitializeEncoder: unexpected rateControlMode, expected: NV_ENC_PARAMS_RC_CONSTQP, actual: {}",
|
||||
rc->rateControlMode);
|
||||
goto done;
|
||||
}
|
||||
|
||||
log_misc("nvenc_hook", "nvEncInitializeEncoder hook hit with expected params");
|
||||
|
||||
// print out most relevant video quality settings
|
||||
// note: NvEncoder.cpp sample uses {28, 31, 25} (and that's what some hex edits modify)
|
||||
// but bm2dx later adds 8 to each value, before calling this routine
|
||||
log_misc(
|
||||
"nvenc_hook", "nvEncInitializeEncoder: original constQP p={}, b={}, i={}",
|
||||
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
|
||||
|
||||
// video quality override
|
||||
if (VIDEO_CQP_OVERRIDE.has_value()) {
|
||||
rc->constQP = VIDEO_CQP_OVERRIDE.value();
|
||||
log_misc(
|
||||
"nvenc_hook", "nvEncInitializeEncoder: user overriden constQP p={}, b={}, i={}",
|
||||
rc->constQP.qpInterP, rc->constQP.qpInterB, rc->constQP.qpIntra);
|
||||
static_cast<uint32_t>(rc->rateControlMode));
|
||||
}
|
||||
|
||||
} catch (const std::exception &ex) {}
|
||||
|
||||
done:
|
||||
return nvEncInitializeEncoder_orig(encoder, createEncodeParams);
|
||||
const auto status = nvEncInitializeEncoder_orig(encoder, createEncodeParams);
|
||||
log_misc(
|
||||
"nvenc_hook",
|
||||
"nvEncInitializeEncoder returned 0x{:x}",
|
||||
static_cast<uint32_t>(status));
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
NVENCSTATUS NVENCAPI nvEncGetEncodePresetConfig_hook (
|
||||
void* encoder, GUID encodeGUID, GUID presetGUID, NV_ENC_PRESET_CONFIG* presetConfig) {
|
||||
|
||||
// IIDX32 calls this with
|
||||
// presetGUID = {34DBA71D-A77B-4B8F-9C3E-B6D5DA24C012} (NV_ENC_PRESET_HQ_GUID) (for h264)
|
||||
// this preset is deprecated according to NVIDIA
|
||||
|
||||
const auto status = nvEncGetEncodePresetConfig_orig(
|
||||
encoder, encodeGUID, presetGUID, presetConfig);
|
||||
|
||||
log_misc(
|
||||
"nvenc_hook",
|
||||
"NvEncGetEncodePresetConfig called with encodeGUID = {}, presetGUID = {}. and returned 0x{:x}",
|
||||
guid2s(encodeGUID),
|
||||
guid2s(presetGUID),
|
||||
static_cast<uint32_t>(status));
|
||||
|
||||
if (status == NV_ENC_SUCCESS) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// in NVIDIA driver 591.44 released in December 2025,
|
||||
// NvEncGetEncodePresetConfig started to fail with NV_ENC_ERR_UNSUPPORTED_PARAM and
|
||||
// eventually cause a crash
|
||||
//
|
||||
// IIDX32 calls this with
|
||||
// presetGUID = {34DBA71D-A77B-4B8F-9C3E-B6D5DA24C012} (NV_ENC_PRESET_HQ_GUID) (for h264)
|
||||
// this preset is deprecated according to NVIDIA
|
||||
// https://docs.nvidia.com/video-technologies/video-codec-sdk/13.0/deprecation-notices/index.html
|
||||
//
|
||||
// references:
|
||||
// https://github.com/NVIDIA/video-sdk-samples/tree/aa3544dcea2fe63122e4feb83bf805ea40e58dbe/Samples/NvCodec/NvEncoder
|
||||
// https://forums.developer.nvidia.com/t/drivers-591-44-broke-nvenc-getencodepresetconfig-no-longer-works/353613
|
||||
// https://docs.nvidia.com/video-technologies/video-codec-sdk/11.1/nvenc-preset-migration-guide/index.html
|
||||
|
||||
new_preset_guids = true;
|
||||
const auto status_ex =
|
||||
nvEncGetEncodePresetConfigEx_orig(
|
||||
encoder,
|
||||
encodeGUID,
|
||||
NV_ENC_PRESET_P4_GUID,
|
||||
NV_ENC_TUNING_INFO_HIGH_QUALITY,
|
||||
presetConfig);
|
||||
|
||||
log_misc(
|
||||
"nvenc_hook",
|
||||
"called NvEncGetEncodePresetConfigEx instead; returned 0x{:x}",
|
||||
static_cast<uint32_t>(status_ex));
|
||||
|
||||
return status_ex;
|
||||
}
|
||||
|
||||
NVENCSTATUS NVENCAPI NvEncodeAPICreateInstance_hook(NV_ENCODE_API_FUNCTION_LIST *pFunctionList) {
|
||||
|
||||
if (FORCE_DISABLE) {
|
||||
return NV_ENC_ERR_NO_ENCODE_DEVICE;
|
||||
}
|
||||
|
||||
// log_misc("nvenc_hook", "NvEncodeAPICreateInstance hook hit");
|
||||
auto status = NvEncodeAPICreateInstance_orig(pFunctionList);
|
||||
|
||||
@@ -101,6 +211,11 @@ namespace nvenc_hook {
|
||||
pFunctionList->nvEncInitializeEncoder,
|
||||
nvEncInitializeEncoder_hook,
|
||||
&nvEncInitializeEncoder_orig);
|
||||
detour::trampoline_try(
|
||||
pFunctionList->nvEncGetEncodePresetConfig,
|
||||
nvEncGetEncodePresetConfig_hook,
|
||||
&nvEncGetEncodePresetConfig_orig);
|
||||
nvEncGetEncodePresetConfigEx_orig = pFunctionList->nvEncGetEncodePresetConfigEx;
|
||||
log_misc("nvenc_hook", "NvEncodeAPICreateInstance_hook called and functions hooked");
|
||||
initialized = true;
|
||||
}
|
||||
@@ -123,6 +238,7 @@ namespace nvenc_hook {
|
||||
} else {
|
||||
log_warning("nvenc_hook", "failed to hook NvEncodeAPICreateInstance");
|
||||
}
|
||||
|
||||
parse_qcp_params();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef SPICE64
|
||||
|
||||
namespace nvenc_hook {
|
||||
|
||||
extern bool FORCE_DISABLE;
|
||||
extern std::optional<std::string> VIDEO_CQP_STRING_OVERRIDE;
|
||||
void initialize();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <iphlpapi.h>
|
||||
#include <stdlib.h>
|
||||
#include <string>
|
||||
#include <mutex>
|
||||
|
||||
#include "avs/core.h"
|
||||
#include "avs/ea3.h"
|
||||
@@ -12,6 +13,7 @@
|
||||
#include "util/detour.h"
|
||||
#include "util/fileutils.h"
|
||||
#include "util/libutils.h"
|
||||
#include "util/deferlog.h"
|
||||
|
||||
// hooking related stuff
|
||||
static decltype(GetAdaptersInfo) *GetAdaptersInfo_orig = nullptr;
|
||||
@@ -27,6 +29,19 @@ static struct in_addr network;
|
||||
static struct in_addr prefix;
|
||||
static struct in_addr subnet;
|
||||
|
||||
static void defer_network_adapter_error() {
|
||||
static std::once_flag printed;
|
||||
std::call_once(printed, []() {
|
||||
deferredlogs::defer_error_messages({
|
||||
"network adapter issue detected!",
|
||||
" ensure you have at least one network adapter with a valid IPv4 address",
|
||||
" the IPv4 address can be external or internal, it just needs to be valid",
|
||||
" the network adapter can be a wired or wireless connection",
|
||||
" you still need to do this even if you are connecting to a local server!",
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG pOutBufLen) {
|
||||
|
||||
// call orig
|
||||
@@ -53,6 +68,15 @@ static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG p
|
||||
free(pAdapterInfo2);
|
||||
}
|
||||
|
||||
if (ret != ERROR_SUCCESS && ret != ERROR_BUFFER_OVERFLOW) {
|
||||
defer_network_adapter_error();
|
||||
log_warning(
|
||||
"network",
|
||||
"GetAdaptersInfo failed with {}; "
|
||||
"check if you have at least one network adapter with a valid IPv4 address!",
|
||||
ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -101,8 +125,13 @@ static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG p
|
||||
free(pIpForwardTable);
|
||||
pIpForwardTable = (MIB_IPFORWARDTABLE *) malloc(dwSize);
|
||||
}
|
||||
if (GetIpForwardTable(pIpForwardTable, &dwSize, 1) != NO_ERROR || pIpForwardTable->dwNumEntries == 0)
|
||||
if (GetIpForwardTable(pIpForwardTable, &dwSize, 1) != NO_ERROR || pIpForwardTable->dwNumEntries == 0) {
|
||||
defer_network_adapter_error();
|
||||
if (GetAdaptersInfo_log) {
|
||||
log_warning("network", "GetIpForwardTable failed");
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// determine best interface
|
||||
DWORD best = pIpForwardTable->table[0].dwForwardIfIndex;
|
||||
@@ -134,6 +163,20 @@ static ULONG WINAPI GetAdaptersInfo_hook(PIP_ADAPTER_INFO pAdapterInfo, PULONG p
|
||||
info = info->Next;
|
||||
}
|
||||
|
||||
if (pAdapterInfo->IpAddressList.IpAddress.String[0] == 0 ||
|
||||
pAdapterInfo->IpAddressList.IpAddress.String[0] == '0') {
|
||||
defer_network_adapter_error();
|
||||
if (GetAdaptersInfo_log) {
|
||||
log_warning(
|
||||
"network",
|
||||
"invalid IPv4 address for adapter {}, {} = {}; "
|
||||
"ensure you have at least one network adapter with valid IPv4 address!",
|
||||
pAdapterInfo->AdapterName,
|
||||
pAdapterInfo->Description,
|
||||
pAdapterInfo->IpAddressList.IpAddress.String);
|
||||
}
|
||||
}
|
||||
|
||||
// return original value
|
||||
GetAdaptersInfo_log = false;
|
||||
return ret;
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <assert.h>
|
||||
#include <shlwapi.h>
|
||||
#include <cfg/configurator.h>
|
||||
|
||||
@@ -299,10 +300,14 @@ int main_implementation(int argc, char *argv[]) {
|
||||
cfg::CONFIGURATOR_TYPE = cfg::ConfigType::Config;
|
||||
cfg_run = true;
|
||||
}
|
||||
if (options[launcher::Options::EAmusementEmulation].value_bool()) {
|
||||
|
||||
// -ea, but ignored if -url is set
|
||||
if (options[launcher::Options::EAmusementEmulation].value_bool() &&
|
||||
!options[launcher::Options::ServiceURL].is_active()) {
|
||||
avs::ea3::URL_SLASH = 1;
|
||||
easrv_port = 8080;
|
||||
}
|
||||
|
||||
if (options[launcher::Options::SmartEAmusement].value_bool()) {
|
||||
easrv_smart = true;
|
||||
}
|
||||
@@ -458,17 +463,26 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::IIDXCameraOrderFlip].value_bool()) {
|
||||
games::iidx::FLIP_CAMS = true;
|
||||
}
|
||||
|
||||
// IIDX CONNECT_CAMERA logic here for cases where user is running without -iidx module
|
||||
// for now, we assume that user may be running on a cab
|
||||
// (games::iidx::DISABLE_CAMS starts out as false unless user overrides)
|
||||
// we will check again in IIDX module with a different default
|
||||
assert(!games::iidx::DISABLE_CAMS.has_value());
|
||||
if (options[launcher::Options::IIDXDisableCameras].value_bool()) {
|
||||
games::iidx::DISABLE_CAMS = true;
|
||||
}
|
||||
if (options[launcher::Options::IIDXCabCamAccess].is_active() &&
|
||||
options[launcher::Options::IIDXCabCamAccess].value_text() == "off") {
|
||||
games::iidx::DISABLE_CAMS = true;
|
||||
}
|
||||
if (options[launcher::Options::IIDXCamHook].value_bool()) {
|
||||
games::iidx::TDJ_CAMERA = true;
|
||||
// Disable legacy behaviour to avoid conflict
|
||||
games::iidx::DISABLE_CAMS = true;
|
||||
}
|
||||
if (games::iidx::DISABLE_CAMS) {
|
||||
SetEnvironmentVariable("CONNECT_CAMERA", "0");
|
||||
}
|
||||
// CONNECT_CAMERA env var will be set once logging is enabled
|
||||
|
||||
if (options[launcher::Options::IIDXCamHookOverride].is_active()) {
|
||||
games::iidx::TDJ_CAMERA_OVERRIDE = options[launcher::Options::IIDXCamHookOverride].value_text();
|
||||
}
|
||||
@@ -525,6 +539,9 @@ int main_implementation(int argc, char *argv[]) {
|
||||
if (options[launcher::Options::IIDXRecQuality].is_active()) {
|
||||
nvenc_hook::VIDEO_CQP_STRING_OVERRIDE = options[launcher::Options::IIDXRecQuality].value_text();
|
||||
}
|
||||
if (options[launcher::Options::IIDXRecDisable].value_bool()) {
|
||||
nvenc_hook::FORCE_DISABLE = true;
|
||||
}
|
||||
#endif
|
||||
if (options[launcher::Options::LoadJubeatModule].value_bool()) {
|
||||
attach_jb = true;
|
||||
@@ -1303,25 +1320,6 @@ int main_implementation(int argc, char *argv[]) {
|
||||
);
|
||||
}
|
||||
|
||||
if (options[launcher::Options::EAmusementEmulation].value_bool() &&
|
||||
options[launcher::Options::ServiceURL].is_active() &&
|
||||
!cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_warning(
|
||||
"launcher",
|
||||
"BAD EAMUSE SETTINGS ERROR\n\n\n"
|
||||
"-------------------------------------------------------------------\n"
|
||||
"WARNING - WARNING - WARNING - WARNING - WARNING - WARNING - WARNING\n"
|
||||
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
|
||||
"A service URL is set **AND** E-Amusement emulation is enabled.\n"
|
||||
"Either remove the service URL, or disable E-Amusement emulation.\n"
|
||||
"Otherwise you may experience problems logging in.\n"
|
||||
"-------------------------------------------------------------------\n\n\n"
|
||||
);
|
||||
log_fatal(
|
||||
"launcher",
|
||||
"BAD EAMUSE SETTINGS ERROR: both -ea and -url are set");
|
||||
}
|
||||
|
||||
if (options[launcher::Options::FullscreenResolution].is_active()) {
|
||||
std::pair<uint32_t, uint32_t> result;
|
||||
if (parse_width_height(options[launcher::Options::FullscreenResolution].value_text(), result)) {
|
||||
@@ -1333,7 +1331,7 @@ int main_implementation(int argc, char *argv[]) {
|
||||
|
||||
// SDVXFullscreenLandscape disabled due to it messing with in-game camera angle
|
||||
// FullscreenOrientationFlip continues to live on, however.
|
||||
if (options[launcher::Options::SDVXFullscreenLandscape].value_bool()) {
|
||||
if (options[launcher::Options::SDVXFullscreenLandscape].value_bool() && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "-sdvxlandscape removed due to a camera bug in SDVX!");
|
||||
}
|
||||
// if (options[launcher::Options::SDVXFullscreenLandscape].value_bool() && !GRAPHICS_WINDOWED) {
|
||||
@@ -1348,8 +1346,15 @@ int main_implementation(int argc, char *argv[]) {
|
||||
GRAPHICS_FS_ORIENTATION_SWAP = true;
|
||||
}
|
||||
|
||||
if (games::iidx::DISABLE_CAMS.has_value() &&
|
||||
games::iidx::DISABLE_CAMS.value() &&
|
||||
!cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_misc("launcher", "CONNECT_CAMERA env var set to 0");
|
||||
SetEnvironmentVariable("CONNECT_CAMERA", "0");
|
||||
}
|
||||
|
||||
// deleted options
|
||||
if (options[launcher::Options::OpenKFControl].value_bool()) {
|
||||
if (options[launcher::Options::OpenKFControl].value_bool() && !cfg::CONFIGURATOR_STANDALONE) {
|
||||
log_fatal("launcher", "KFControl has been removed from spice2x; please use an older version.");
|
||||
}
|
||||
if (options[launcher::Options::VREnable].value_bool()) {
|
||||
@@ -2468,7 +2473,7 @@ void dump_button_bindings(std::vector<Button> *buttons) {
|
||||
button->getName(),
|
||||
button->getDeviceIdentifier(),
|
||||
button->getVKey(),
|
||||
button->getAnalogType()
|
||||
static_cast<uint32_t>(button->getAnalogType())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2481,7 +2486,7 @@ void dump_button_bindings(std::vector<Button> *buttons) {
|
||||
button->getName(),
|
||||
alt.isNaive() ? "Naive" : alt.getDeviceIdentifier(),
|
||||
alt.getVKey(),
|
||||
alt.getAnalogType()
|
||||
static_cast<uint32_t>(alt.getAnalogType())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,10 @@ static const std::vector<std::string> CATEGORY_ORDER_NONE = {
|
||||
""
|
||||
};
|
||||
|
||||
static auto format_as(OptionType f) {
|
||||
return fmt::underlying(f);
|
||||
}
|
||||
|
||||
bool launcher::USE_CMD_OVERRIDE = false;
|
||||
|
||||
/*
|
||||
@@ -456,13 +460,30 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
.title = "IIDX Disable Cameras",
|
||||
// IIDXDisableCameras
|
||||
.title = "IIDX Disable Cameras (DEPRECATED - no longer needed)",
|
||||
.name = "iidxdisablecams",
|
||||
.desc = "Disables cameras",
|
||||
.type = OptionType::Bool,
|
||||
.hidden = true,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options",
|
||||
},
|
||||
{
|
||||
// IIDXCabCamAccess
|
||||
.title = "IIDX Use Official AC Cams",
|
||||
.name = "iidxcabcams",
|
||||
.desc = "For IIDX25+, allow direct access to cameras from real arcade cabinets. "
|
||||
"Only turn this on if you have OFFICIAL arcade cameras connected to the correct USB ports. Default: auto",
|
||||
.type = OptionType::Enum,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
.elements = {
|
||||
{"auto", ""},
|
||||
{"off", ""},
|
||||
{"on", ""}
|
||||
},
|
||||
},
|
||||
{
|
||||
// IIDXCamHook
|
||||
.title = "IIDX Cam Hook",
|
||||
@@ -497,10 +518,10 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.display_name = "iidxcamhookoffset",
|
||||
.aliases = "iidxcamhookoffset",
|
||||
.desc = "Override DLL offsets for camera hook; "
|
||||
"format: 4 hex values separated by commas "
|
||||
"(offset_hook_a, offset_hook_b, offset_textures, offset_d3d_device)",
|
||||
"format: 5 hex values separated by commas "
|
||||
"(offset_hook_a, offset_textures, offset_camera_manager, offset_d3d_device, offset_afp_texture)",
|
||||
.type = OptionType::Text,
|
||||
.setting_name = "0x5817a0,0x5ea3b0,0x6fffbd8,0xe0",
|
||||
.setting_name = "0x5817a0,0x6fffbd8,0xbbae40,0xe0,0x30",
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
@@ -2218,7 +2239,7 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
},
|
||||
{
|
||||
// IIDXRecQuality
|
||||
.title = "IIDX NVENC Quality",
|
||||
.title = "IIDX Recording Quality",
|
||||
.name = "iidxreccqp",
|
||||
.desc = "WARNING: double check if your network allows this & your hardware supports it.\n\n"
|
||||
"For TDJ Play Record feature, specify ConstQP quality settings for NVENC. Lower is higher quality but bigger file.\n\n"
|
||||
@@ -2227,6 +2248,15 @@ static const std::vector<OptionDefinition> OPTION_DEFINITIONS = {
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
// IIDXRecDisable
|
||||
.title = "IIDX Recording Force Disable",
|
||||
.name = "iidxnorec",
|
||||
.desc = "When enabled, this prevents the play record feature from being used",
|
||||
.type = OptionType::Bool,
|
||||
.game_name = "Beatmania IIDX",
|
||||
.category = "Game Options (Advanced)",
|
||||
},
|
||||
{
|
||||
// MidiAlgoVer
|
||||
.title = "MIDI Note Input Algorithm",
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace launcher {
|
||||
LoadIIDXModule,
|
||||
IIDXCameraOrderFlip,
|
||||
IIDXDisableCameras,
|
||||
IIDXCabCamAccess,
|
||||
IIDXCamHook,
|
||||
IIDXCamHookRatio,
|
||||
IIDXCamHookOverride,
|
||||
@@ -237,6 +238,7 @@ namespace launcher {
|
||||
spice2x_EnableSMXStage,
|
||||
spice2x_EnableSMXDedicab,
|
||||
IIDXRecQuality,
|
||||
IIDXRecDisable,
|
||||
MidiAlgoVer,
|
||||
MidiNoteSustain,
|
||||
InputRequiresFocus,
|
||||
|
||||
@@ -318,10 +318,6 @@ stb_image_write (Public Domain)
|
||||
-------------------------------------------
|
||||
http://nothings.org/stb/stb_image_write.h
|
||||
|
||||
linalg.h (Public Domain, Unlicense)
|
||||
-------------------------------------------
|
||||
https://github.com/sgorsten/linalg
|
||||
|
||||
fmt (2-clause BSD)
|
||||
-------------------------------------------
|
||||
Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace clipboard {
|
||||
Gdiplus::GpBitmap *bitmap = nullptr;
|
||||
auto status = imports::GdipCreateBitmapFromFile(path.c_str(), &bitmap);
|
||||
if (status != Gdiplus::Ok) {
|
||||
log_warning("clipboard", "failed to create GDI+ bitmap: {}", status);
|
||||
log_warning("clipboard", "failed to create GDI+ bitmap: {}", static_cast<uint32_t>(status));
|
||||
|
||||
imports::GdiplusShutdown(token);
|
||||
CloseClipboard();
|
||||
@@ -123,7 +123,7 @@ namespace clipboard {
|
||||
log_warning("clipboard", "failed to save image to clipboard");
|
||||
}
|
||||
} else {
|
||||
log_warning("clipboard", "failed to retrieve HBITMAP from image bitmap: {}", status);
|
||||
log_warning("clipboard", "failed to retrieve HBITMAP from image bitmap: {}", static_cast<uint32_t>(status));
|
||||
}
|
||||
|
||||
// Clean up after ourselves. hmem can't be deleted because it is owned by the clipboard now.
|
||||
|
||||
@@ -34,6 +34,7 @@ namespace wintouchemu {
|
||||
bool FORCE = false;
|
||||
bool INJECT_MOUSE_AS_WM_TOUCH = false;
|
||||
bool LOG_FPS = false;
|
||||
bool ADD_TOUCH_FLAG_PRIMARY = false;
|
||||
|
||||
static inline bool is_emu_enabled() {
|
||||
return FORCE || !is_touch_available("wintouchemu::is_emu_enabled") || GRAPHICS_SHOW_CURSOR;
|
||||
@@ -187,16 +188,28 @@ namespace wintouchemu {
|
||||
switch (touch_event->type) {
|
||||
case TOUCH_DOWN:
|
||||
if (valid) {
|
||||
if (ADD_TOUCH_FLAG_PRIMARY) {
|
||||
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
|
||||
}
|
||||
|
||||
touch_input->dwFlags |= TOUCHEVENTF_DOWN;
|
||||
}
|
||||
break;
|
||||
case TOUCH_MOVE:
|
||||
if (valid) {
|
||||
if (ADD_TOUCH_FLAG_PRIMARY) {
|
||||
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
|
||||
}
|
||||
|
||||
touch_input->dwFlags |= TOUCHEVENTF_MOVE;
|
||||
}
|
||||
break;
|
||||
case TOUCH_UP:
|
||||
// don't check valid so that this touch ID can be released
|
||||
if (ADD_TOUCH_FLAG_PRIMARY) {
|
||||
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
|
||||
}
|
||||
|
||||
touch_input->dwFlags |= TOUCHEVENTF_UP;
|
||||
break;
|
||||
}
|
||||
@@ -238,16 +251,28 @@ namespace wintouchemu {
|
||||
switch (mouse_state.touch_event) {
|
||||
case TOUCHEVENTF_DOWN:
|
||||
if (valid) {
|
||||
if (ADD_TOUCH_FLAG_PRIMARY) {
|
||||
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
|
||||
}
|
||||
|
||||
touch_input->dwFlags |= TOUCHEVENTF_DOWN;
|
||||
}
|
||||
break;
|
||||
case TOUCHEVENTF_MOVE:
|
||||
if (valid) {
|
||||
if (ADD_TOUCH_FLAG_PRIMARY) {
|
||||
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
|
||||
}
|
||||
|
||||
touch_input->dwFlags |= TOUCHEVENTF_MOVE;
|
||||
}
|
||||
break;
|
||||
case TOUCHEVENTF_UP:
|
||||
// don't check valid so that this touch ID can be released
|
||||
if (ADD_TOUCH_FLAG_PRIMARY) {
|
||||
touch_input->dwFlags |= TOUCHEVENTF_PRIMARY;
|
||||
}
|
||||
|
||||
touch_input->dwFlags |= TOUCHEVENTF_UP;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace wintouchemu {
|
||||
extern bool FORCE;
|
||||
extern bool INJECT_MOUSE_AS_WM_TOUCH;
|
||||
extern bool LOG_FPS;
|
||||
extern bool ADD_TOUCH_FLAG_PRIMARY;
|
||||
|
||||
void hook(const char *window_title, HMODULE module = nullptr, int delay_in_s=0);
|
||||
void hook_title_ends(
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
CardManager::CardManager(SpiceOverlay *overlay) : Window(overlay) {
|
||||
@@ -451,7 +450,7 @@ namespace overlay::windows {
|
||||
// check parse error
|
||||
auto error = doc.GetParseError();
|
||||
if (error) {
|
||||
log_warning("cardmanager", "config parse error: {}", error);
|
||||
log_warning("cardmanager", "config parse error: {}", static_cast<uint32_t>(error));
|
||||
}
|
||||
|
||||
// verify root is a dict
|
||||
@@ -542,7 +541,7 @@ namespace overlay::windows {
|
||||
// check parse error
|
||||
auto error = doc.GetParseError();
|
||||
if (error) {
|
||||
log_warning("cardmanager", "template parse error: {}", error);
|
||||
log_warning("cardmanager", "template parse error: {}", static_cast<uint32_t>(error));
|
||||
}
|
||||
|
||||
// add cards
|
||||
|
||||
@@ -1509,7 +1509,15 @@ namespace overlay::windows {
|
||||
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 0.7f, 0.f, 1.f));
|
||||
}
|
||||
ImGui::Text("%s\n\n", button_display.c_str());
|
||||
// this doesn't account for utf-8 but whatever
|
||||
if (button_display.size() >= 40) {
|
||||
ImGui::Text("%.37s...", button_display.c_str());
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(button_display.c_str());
|
||||
} else {
|
||||
ImGui::Text("%s", button_display.c_str());
|
||||
}
|
||||
ImGui::TextUnformatted("\n");
|
||||
if (button_state == GameAPI::Buttons::State::BUTTON_PRESSED) {
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
@@ -1727,6 +1735,13 @@ namespace overlay::windows {
|
||||
std::vector<int> analogs_midi_indices;
|
||||
if (this->analogs_devices_selected >= 0) {
|
||||
auto device = this->analogs_devices.at(this->analogs_devices_selected);
|
||||
|
||||
// add a tooltip to devices selector
|
||||
if (!device->name.empty()) {
|
||||
ImGui::SameLine();
|
||||
ImGui::HelpMarker(device->name.c_str());
|
||||
}
|
||||
|
||||
switch (device->type) {
|
||||
case rawinput::MOUSE: {
|
||||
|
||||
@@ -2036,6 +2051,11 @@ namespace overlay::windows {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Reset")) {
|
||||
analog.resetValues();
|
||||
}
|
||||
|
||||
// clean up
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
@@ -2828,7 +2848,10 @@ namespace overlay::windows {
|
||||
std::string current_item = option.value_text();
|
||||
for (auto &element : definition.elements) {
|
||||
if (element.first == current_item) {
|
||||
current_item += fmt::format(" ({})", element.second);
|
||||
if (!element.second.empty()) {
|
||||
current_item += fmt::format(" ({})", element.second);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (current_item.empty()) {
|
||||
@@ -2836,11 +2859,12 @@ namespace overlay::windows {
|
||||
}
|
||||
if (ImGui::BeginCombo("##combo", current_item.c_str(), 0)) {
|
||||
for (auto &element : definition.elements) {
|
||||
bool selected = current_item == element.first;
|
||||
std::string label = element.first;
|
||||
if (!element.second.empty()) {
|
||||
label += fmt::format(" ({})", element.second);
|
||||
}
|
||||
|
||||
bool selected = current_item == label;
|
||||
if (ImGui::Selectable(label.c_str(), selected)) {
|
||||
this->options_dirty = true;
|
||||
option.value = element.first;
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
|
||||
#include "generic_sub.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "avs/game.h"
|
||||
#include "games/io.h"
|
||||
#include "cfg/screen_resize.h"
|
||||
@@ -92,14 +90,20 @@ namespace overlay::windows {
|
||||
this->flags |= ImGuiWindowFlags_NoBackground;
|
||||
|
||||
if (this->disabled_message.has_value()) {
|
||||
this->draws_window = true;
|
||||
this->flags &= ~ImGuiWindowFlags_NoBackground;
|
||||
ImGui::TextColored(YELLOW, "%s", this->disabled_message.value().c_str());
|
||||
ImGui::TextUnformatted("");
|
||||
if (ImGui::Button("Close")) {
|
||||
this->set_active(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this->draw_texture();
|
||||
|
||||
if (!this->texture) {
|
||||
this->draws_window = true;
|
||||
this->flags &= ~ImGuiWindowFlags_NoBackground;
|
||||
ImGui::TextColored(YELLOW, "Failed to acquire surface texture for subscreen.");
|
||||
if (avs::game::is_model("LDJ")) {
|
||||
@@ -113,6 +117,10 @@ namespace overlay::windows {
|
||||
"Ensure that you did not accidentally enable a patch \n"
|
||||
"that turns off subscreen rendering.");
|
||||
}
|
||||
ImGui::TextUnformatted("");
|
||||
if (ImGui::Button("Close")) {
|
||||
this->set_active(false);
|
||||
}
|
||||
}
|
||||
|
||||
#if OVERLAYDBG
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace overlay::windows {
|
||||
this->disabled_message =
|
||||
"Close this overlay and use the second window.\n"
|
||||
"If you don't see the window, double check your DLL type and apply TDJ I/O patches as needed.";
|
||||
this->draws_window = false;
|
||||
}
|
||||
|
||||
float size = 0.5f;
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
|
||||
using namespace rapidjson;
|
||||
|
||||
|
||||
namespace overlay::windows {
|
||||
|
||||
|
||||
@@ -288,6 +287,12 @@ namespace overlay::windows {
|
||||
this->reload_local_patches();
|
||||
}
|
||||
|
||||
if (avs::game::DLL_NAME == "kamunity.dll") {
|
||||
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "Patches are not supported for Unity-based games.");
|
||||
ImGui::TextColored(ImVec4(1.f, 0.f, 0.f, 1.f), "Instead, look for downloads of pre-patched DLLs.");
|
||||
return;
|
||||
}
|
||||
|
||||
// game code info
|
||||
std::string identifiers;
|
||||
identifiers += avs::game::get_identifier() + "\n\n";
|
||||
@@ -937,7 +942,7 @@ namespace overlay::windows {
|
||||
// check parse error
|
||||
auto error = doc.GetParseError();
|
||||
if (error) {
|
||||
log_warning("patchmanager", "config file parse error: {}", error);
|
||||
log_warning("patchmanager", "config file parse error: {}", static_cast<uint32_t>(error));
|
||||
}
|
||||
|
||||
// verify root is a dict
|
||||
@@ -1045,7 +1050,7 @@ namespace overlay::windows {
|
||||
// check parse error
|
||||
auto error = doc.GetParseError();
|
||||
if (error) {
|
||||
log_warning("patchmanager", "template parse error: {}", error);
|
||||
log_warning("patchmanager", "template parse error: {}", static_cast<uint32_t>(error));
|
||||
}
|
||||
|
||||
// auto apply setting
|
||||
@@ -1177,7 +1182,7 @@ namespace overlay::windows {
|
||||
// check parse error
|
||||
auto error = doc.GetParseError();
|
||||
if (error) {
|
||||
log_warning("patchmanager", "embedded patches json file parse error: {}", error);
|
||||
log_warning("patchmanager", "embedded patches json file parse error: {}", static_cast<uint32_t>(error));
|
||||
}
|
||||
|
||||
// iterate patches
|
||||
@@ -1509,7 +1514,7 @@ namespace overlay::windows {
|
||||
}
|
||||
case PatchType::Unknown:
|
||||
default:
|
||||
log_warning("patchmanager", "unknown patch type: {}", patch_data.type);
|
||||
log_warning("patchmanager", "unknown patch type: {}", static_cast<uint32_t>(patch_data.type));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1731,7 +1736,7 @@ namespace overlay::windows {
|
||||
"patchmanager",
|
||||
"patches file parse error at offset {}: {} ({})",
|
||||
error_offset,
|
||||
error,
|
||||
static_cast<uint32_t>(error),
|
||||
rapidjson::GetParseError_En(error));
|
||||
}
|
||||
|
||||
@@ -2239,7 +2244,7 @@ namespace overlay::windows {
|
||||
}
|
||||
case PatchType::Unknown:
|
||||
default:
|
||||
log_warning("patchmanager", "unknown patch type: {}", patch_data.type);
|
||||
log_warning("patchmanager", "unknown patch type: {}", static_cast<uint32_t>(patch_data.type));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
#include "piuio.h"
|
||||
#include "touch.h"
|
||||
#include "acio/mdxf/mdxf_poll.h"
|
||||
|
||||
extern "C" {
|
||||
#include "external/usbhidusage/usb-hid-usage.h"
|
||||
@@ -347,40 +348,38 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
||||
if (hid_handle != INVALID_HANDLE_VALUE) {
|
||||
|
||||
// check manufacturer string
|
||||
std::wstring man_ws;
|
||||
std::string man_ws;
|
||||
wchar_t man_str_buffer[256] {};
|
||||
if (HidD_GetManufacturerString(hid_handle, man_str_buffer, sizeof(man_str_buffer))) {
|
||||
man_ws = std::wstring(man_str_buffer);
|
||||
man_ws = wchar_to_u8(man_str_buffer);
|
||||
}
|
||||
|
||||
// get product string
|
||||
std::wstring prod_ws;
|
||||
std::string prod_ws;
|
||||
wchar_t prod_str_buffer[256] {};
|
||||
if (HidD_GetProductString(hid_handle, prod_str_buffer, sizeof(prod_str_buffer))) {
|
||||
prod_ws = std::wstring(prod_str_buffer);
|
||||
prod_ws = wchar_to_u8(prod_str_buffer);
|
||||
}
|
||||
|
||||
// For Bluetooth LE HID devices (e.g., newer Xbox controllers) HidD_GetProductString
|
||||
// and HidD_GetManufacturerString will return blank strings.
|
||||
// https://docs.microsoft.com/en-us/answers/questions/401236/hidd-getproductstring-with-ble-hid-device.html
|
||||
if (man_ws.empty() && prod_ws.empty()) {
|
||||
prod_ws = s2ws(device_description);
|
||||
prod_ws = device_description;
|
||||
}
|
||||
|
||||
// build desc string
|
||||
std::wstring desc_ws;
|
||||
std::string desc_ws;
|
||||
if (string_begins_with(prod_ws, man_ws)) {
|
||||
desc_ws = prod_ws;
|
||||
} else {
|
||||
desc_ws = man_ws;
|
||||
if (!man_ws.empty() && !prod_ws.empty()) {
|
||||
desc_ws += L" ";
|
||||
desc_ws += " ";
|
||||
}
|
||||
desc_ws += prod_ws;
|
||||
}
|
||||
|
||||
// convert to normal string
|
||||
new_device.desc = ws2s(desc_ws);
|
||||
new_device.desc = desc_ws;
|
||||
|
||||
// get attributes
|
||||
if (!HidD_GetAttributes(hid_handle, &hid_attributes)) {
|
||||
@@ -1148,7 +1147,7 @@ std::string rawinput::RawInputManager::rawinput_get_device_description(const raw
|
||||
|
||||
// get property
|
||||
DWORD desc_size = 0;
|
||||
if (SetupDiGetDeviceRegistryProperty(
|
||||
if (SetupDiGetDeviceRegistryPropertyW(
|
||||
devinfo, &devinfo_data, SPDRP_DEVICEDESC, nullptr, nullptr, 0, &desc_size))
|
||||
{
|
||||
continue;
|
||||
@@ -1160,14 +1159,32 @@ std::string rawinput::RawInputManager::rawinput_get_device_description(const raw
|
||||
// allocate buffer
|
||||
auto desc_data = std::make_unique<BYTE[]>(desc_size);
|
||||
|
||||
if (!SetupDiGetDeviceRegistryProperty(
|
||||
if (!SetupDiGetDeviceRegistryPropertyW(
|
||||
devinfo, &devinfo_data, SPDRP_DEVICEDESC, nullptr, desc_data.get(), desc_size, nullptr))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// kthxbye
|
||||
device_description = std::string(reinterpret_cast<char *>(desc_data.get()));
|
||||
// base description
|
||||
device_description = wchar_to_u8(reinterpret_cast<PWCHAR>(desc_data.get()));
|
||||
|
||||
// append HID product string if available
|
||||
HANDLE hid_handle = CreateFile(
|
||||
device_path.c_str(), 0,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
0, nullptr);
|
||||
if (hid_handle != INVALID_HANDLE_VALUE) {
|
||||
wchar_t product_buffer[126] {};
|
||||
if (HidD_GetProductString(hid_handle, product_buffer, sizeof(product_buffer))) {
|
||||
auto const product_str = wchar_to_u8(product_buffer);
|
||||
if (!product_str.empty() && device_description != product_str) {
|
||||
device_description += " - " + product_str;
|
||||
}
|
||||
}
|
||||
CloseHandle(hid_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1499,7 +1516,7 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
}
|
||||
|
||||
// get input time
|
||||
double input_time = get_performance_seconds();
|
||||
const auto input_time = get_performance_seconds();
|
||||
|
||||
// lock device
|
||||
device.mutex->lock();
|
||||
@@ -1827,8 +1844,14 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
||||
|
||||
// free device
|
||||
device.mutex->unlock();
|
||||
|
||||
// don't iterate through the other devices
|
||||
break;
|
||||
}
|
||||
|
||||
// update controller state ring buffers (DDR/MDXF)
|
||||
mdxf_poll();
|
||||
|
||||
// call the default window handler for cleanup
|
||||
DefWindowProc(hWnd, msg, wparam, lParam);
|
||||
|
||||
@@ -1922,7 +1945,7 @@ void CALLBACK rawinput::RawInputManager::input_midi_proc(HMIDIIN hMidiIn, UINT w
|
||||
}
|
||||
|
||||
// get input time
|
||||
auto input_time = get_performance_seconds();
|
||||
const auto input_time = get_performance_seconds();
|
||||
|
||||
// lock device
|
||||
std::lock_guard<std::mutex> lock(*device.mutex);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "smx.h"
|
||||
#include "util/logging.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace {
|
||||
static const std::string DEVICE_NAMES[] = {
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "smx.h"
|
||||
#include "util/logging.h"
|
||||
#include <fmt/format.h>
|
||||
|
||||
namespace {
|
||||
static const std::string PANEL_NAMES[] = {
|
||||
|
||||
@@ -18,6 +18,14 @@ namespace deferredlogs {
|
||||
std::mutex deferred_errors_mutex;
|
||||
std::vector<std::vector<std::string>> deferred_errors;
|
||||
|
||||
std::mutex softid_mutex;
|
||||
std::string softid;
|
||||
|
||||
void set_softid(const std::string& softid_new) {
|
||||
std::lock_guard<std::mutex> lock(softid_mutex);
|
||||
softid = softid_new;
|
||||
}
|
||||
|
||||
void defer_error_messages(std::initializer_list<std::string> messages) {
|
||||
std::lock_guard<std::mutex> lock(deferred_errors_mutex);
|
||||
deferred_errors.emplace_back(messages);
|
||||
@@ -45,12 +53,25 @@ namespace deferredlogs {
|
||||
msg += "/-------------------------- spice2x auto-troubleshooter -----------------------\\\n";
|
||||
msg += "\n";
|
||||
msg += " spice2x version: " + to_string(VERSION_STRING_CFG) + "\n";
|
||||
|
||||
// soft ID
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(softid_mutex);
|
||||
if (!softid.empty()) {
|
||||
msg += " game version: " + softid + "\n";
|
||||
} else {
|
||||
msg += " game version: unknown\n";
|
||||
}
|
||||
}
|
||||
|
||||
msg += "\n";
|
||||
|
||||
if (is_crash) {
|
||||
msg += " the game has crashed\n";
|
||||
msg += " share this entire log file with someone for troubleshooting (log.txt)\n";
|
||||
msg += " spice will also attempt to create a minidump (minidump.dmp)\n";
|
||||
msg += " * share this entire log file with someone for troubleshooting (log.txt)\n";
|
||||
msg += " * spice will also attempt to create a minidump (minidump.dmp)\n";
|
||||
msg += " minidump should only be shared with people you trust as it may contain\n";
|
||||
msg += " sensitive data (PCBID, card ID, etc)\n";
|
||||
msg += "\n";
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ namespace deferredlogs {
|
||||
// some shared error messages
|
||||
extern const std::initializer_list<std::string> SUPERSTEP_SOUND_ERROR_MESSAGE;
|
||||
|
||||
void set_softid(const std::string& softid);
|
||||
void defer_error_messages(std::initializer_list<std::string> messages);
|
||||
void dump_to_logger(bool is_crash=false);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace {
|
||||
{
|
||||
"msvcr100.dll",
|
||||
"Visual Studio 2010 (VC++ 10.0) SP1 Redistributable",
|
||||
"Download and install VisualCppRedist_AIO_x86_x64.exe from https://github.com/abbodi1406/vcredist/releases/latest",
|
||||
"Download and install from https://github.com/spice2x/spice2x.github.io/wiki/Visual-Cpp-Redistributable-Runtimes",
|
||||
},
|
||||
{
|
||||
"d3dx9_43.dll",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "fileutils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
|
||||
#include <sys/stat.h>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user