Skip to content

Commit 029d817

Browse files
committed
printing in char/wchar_t/char8_t/char16_t/char32_t
1 parent 77a46b6 commit 029d817

File tree

11 files changed

+307
-4
lines changed

11 files changed

+307
-4
lines changed

CMakeLists.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,23 @@ if (POLICY CMP0141) # MSVC hot-reload schenanigans
1010
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
1111
endif()
1212

13+
if(POLICY CMP0092)
14+
cmake_policy(SET CMP0092 NEW)
15+
endif()
16+
1317
if(MSVC)
18+
string(REPLACE "/EHsc" "" OLD_FLAGS "${CMAKE_CXX_FLAGS}")
19+
set(CMAKE_CXX_FLAGS "${OLD_FLAGS}")
1420
string(REPLACE "/RTC1" "" OLD_FLAGS "${CMAKE_CXX_FLAGS_DEBUG}")
1521
set(CMAKE_CXX_FLAGS_DEBUG "${OLD_FLAGS}")
16-
add_compile_options(/permissive- /Zc:preprocessor /JMC /std:c++latest /GR- /GS- /EHsc-)
22+
add_compile_options(/permissive- /Zc:preprocessor /JMC /std:c++latest /GR- /GS- /Zc:throwingNew- /EHs-)
1723
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
1824
add_compile_options(-ffreestanding -nostdinc++ -fno-exceptions -fno-rtti)
1925
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
2026
add_compile_options(-ffreestanding -nostdinc++ -fno-exceptions -fno-rtti)
2127
endif()
28+
29+
message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
2230

2331
add_subdirectory(starlib)
2432
add_subdirectory(testing)

starlib/CMakeLists.txt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@ add_library(starlib STATIC)
33
# Add module sources
44
target_sources(starlib
55
PUBLIC
6-
FILE_SET cxx_modules TYPE CXX_MODULES FILES
6+
FILE_SET CXX_MODULES TYPE CXX_MODULES FILES
77
starlib.ixx
88
process.ixx
9+
print.ixx
10+
native.ixx
11+
printInternals.ixx
912
PRIVATE
1013
starlib.cpp
1114
process.cpp
12-
"process.ixx")
15+
print.cpp
16+
printInternals.cpp
17+
native.cpp)
1318

1419
target_include_directories(starlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
1520

starlib/native.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
module;
2+
#ifdef _WIN32
3+
#include <Windows.h>
4+
#else
5+
6+
#endif
7+
8+
#include <cstddef>
9+
10+
module nativeAPI;
11+
12+
void* __cdecl operator new(std::size_t size) noexcept
13+
{
14+
return HeapAlloc(GetProcessHeap(), 0, size);
15+
}
16+
void __cdecl operator delete(void* lpMemory) noexcept
17+
{
18+
HeapFree(GetProcessHeap(), 0, lpMemory);
19+
}
20+
void* __cdecl operator new[](std::size_t size) noexcept
21+
{
22+
return HeapAlloc(GetProcessHeap(), 0, size);
23+
}
24+
void __cdecl operator delete[](void* lpMemory) noexcept
25+
{
26+
HeapFree(GetProcessHeap(), 0, lpMemory);
27+
}

starlib/native.ixx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// This module provides interface for native system APIs (at least tries to make it universal)
2+
3+
module;
4+
5+
#include <cstdint>
6+
#include <bit>
7+
8+
export module nativeAPI;
9+
10+
static_assert(std::bit_cast<std::uintptr_t>(nullptr) == 0, "Null pointer is not all-zero bits. While this is conforming, this library requires the bit pattern of nullptr to be the same as the bit pattern of 0");
11+
12+
namespace starlib::detail
13+
{
14+
export enum struct NativeHandle : std::uintptr_t { Null = 0 };
15+
export template <typename T>
16+
NativeHandle PointerToHandle(T* pointer) noexcept
17+
{
18+
return static_cast<NativeHandle>(reinterpret_cast<std::underlying_type_t<NativeHandle>>(pointer));
19+
}
20+
}

starlib/print.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
module;
2+
3+
#include <cstddef>
4+
5+
module print;
6+
7+
import :internal;
8+
9+
void starlib::Print(const char* szString, std::size_t dwSize)
10+
{
11+
detail::PrintString(szString, dwSize);
12+
}
13+
14+
void starlib::Print(const wchar_t* szString, std::size_t dwSize)
15+
{
16+
detail::PrintString(szString, dwSize);
17+
}
18+
19+
void starlib::Print(const char8_t* szString, std::size_t dwSize)
20+
{
21+
const auto converted = detail::Utf8ToCP(szString, dwSize);
22+
Print(converted.get(), dwSize);
23+
}
24+
25+
void starlib::Print(const char16_t* szString, std::size_t dwSize)
26+
{
27+
const auto converted = detail::Utf16ToCP(szString, dwSize);
28+
Print(converted.get(), dwSize);
29+
}
30+
void starlib::Print(const char32_t* szString, std::size_t dwSize)
31+
{
32+
const auto converted = detail::Utf32ToCP(szString, dwSize);
33+
Print(converted.get(), dwSize);
34+
}

starlib/print.ixx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
module;
2+
3+
#include <cstddef>
4+
#include <type_traits>
5+
#include <concepts>
6+
7+
export module print;
8+
9+
namespace starlib
10+
{
11+
export template<typename T>
12+
concept IsCharacter = std::disjunction_v<
13+
std::is_same<T, char>,
14+
std::is_same<T, wchar_t>,
15+
std::is_same<T, char8_t>,
16+
std::is_same<T, char16_t>,
17+
std::is_same<T, char32_t>
18+
>;
19+
20+
export void Print(const char* szString, std::size_t dwSize);
21+
export void Print(const wchar_t* szString, std::size_t dwSize);
22+
export void Print(const char8_t* szString, std::size_t dwSize);
23+
export void Print(const char16_t* szString, std::size_t dwSize);
24+
export void Print(const char32_t* szString, std::size_t dwSize);
25+
26+
// templates
27+
export template <std::size_t N, IsCharacter TChar>
28+
void Print(const TChar(&string)[N])
29+
{
30+
Print(string, N - 1);
31+
}
32+
33+
export void Print(std::integral auto integral)
34+
{
35+
36+
}
37+
}

starlib/printInternals.cpp

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
module;
2+
#ifdef _WIN32
3+
#include <Windows.h>
4+
#else
5+
// no includes here
6+
#endif
7+
8+
#include <cstddef>
9+
#include <utility>
10+
#include <memory>
11+
12+
module print;
13+
14+
import :internal;
15+
import nativeAPI;
16+
17+
using starlib::detail::NativeHandle;
18+
19+
static NativeHandle g_hStandardOutput{};
20+
21+
void starlib::detail::InitialisePrintBuffers(void)
22+
{
23+
if (g_hStandardOutput != NativeHandle::Null)
24+
return;
25+
26+
#ifdef _WIN32
27+
g_hStandardOutput = starlib::detail::PointerToHandle(GetStdHandle(STD_OUTPUT_HANDLE));
28+
SetConsoleCP(CP_UTF8); // Ensure UTF-8
29+
SetConsoleOutputCP(CP_UTF8);
30+
const auto hConsole = reinterpret_cast<HANDLE>(std::to_underlying(g_hStandardOutput));
31+
32+
DWORD mode{};
33+
if (GetConsoleMode(hConsole, &mode))
34+
{
35+
mode |= ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
36+
SetConsoleMode(hConsole, mode);
37+
}
38+
#else
39+
#endif
40+
}
41+
void starlib::detail::PrintString(const char* szBuffer, std::size_t dwLength)
42+
{
43+
InitialisePrintBuffers();
44+
45+
#ifdef _WIN32
46+
DWORD dwWritten{};
47+
const auto hConsole = reinterpret_cast<HANDLE>(std::to_underlying(g_hStandardOutput));
48+
WriteConsoleA(hConsole, szBuffer, dwLength, &dwWritten, nullptr);
49+
#else
50+
#endif
51+
}
52+
void starlib::detail::PrintString(const wchar_t* szBuffer, std::size_t dwLength)
53+
{
54+
InitialisePrintBuffers();
55+
56+
#ifdef _WIN32
57+
DWORD dwWritten{};
58+
WriteConsoleW(reinterpret_cast<HANDLE>(std::to_underlying(g_hStandardOutput)), szBuffer, dwLength, &dwWritten, nullptr);
59+
#else
60+
#endif
61+
}
62+
63+
std::unique_ptr<char[]> starlib::detail::Utf8ToCP(const char8_t* szInput, std::size_t& dwLength)
64+
{
65+
#ifdef _WIN32
66+
if (szInput == nullptr || dwLength == 0)
67+
return nullptr;
68+
69+
const auto wideSize = MultiByteToWideChar(CP_UTF8, 0, reinterpret_cast<const char*>(szInput), dwLength, nullptr, 0);
70+
71+
std::unique_ptr<wchar_t[]> wideBuffer = std::make_unique_for_overwrite<wchar_t[]>(wideSize);
72+
73+
wideBuffer[MultiByteToWideChar(CP_UTF8, 0, reinterpret_cast<const char*>(szInput), dwLength, wideBuffer.get(), wideSize)] = 0;
74+
75+
const auto convertedSize = WideCharToMultiByte(CP_ACP, 0, wideBuffer.get(), wideSize, nullptr, 0, nullptr, nullptr);
76+
77+
std::unique_ptr<char[]> converted = std::make_unique_for_overwrite<char[]>(convertedSize + 1);
78+
79+
WideCharToMultiByte(CP_ACP, 0, wideBuffer.get(), wideSize, converted.get(), convertedSize, nullptr, nullptr);
80+
converted[convertedSize] = 0;
81+
dwLength = convertedSize;
82+
return converted;
83+
#else
84+
#error Unsupported platform
85+
#endif
86+
}
87+
88+
std::unique_ptr<char[]> starlib::detail::Utf16ToCP(const char16_t* szInput, std::size_t& dwLength)
89+
{
90+
#ifdef _WIN32
91+
if (szInput == nullptr || dwLength == 0)
92+
return nullptr;
93+
94+
static_assert(sizeof(wchar_t) == 2);
95+
96+
const wchar_t* wideInput = reinterpret_cast<const wchar_t*>(szInput);
97+
std::unique_ptr<wchar_t[]> temporaryBuffer{};
98+
99+
const int requiredSize = WideCharToMultiByte(CP_ACP, 0, wideInput, dwLength, nullptr, 0, nullptr, nullptr);
100+
101+
if (requiredSize <= 0) return nullptr;
102+
103+
std::unique_ptr<char[]> output = std::make_unique_for_overwrite<char[]>(requiredSize + 1);
104+
WideCharToMultiByte(CP_ACP, 0, wideInput, dwLength, output.get(), requiredSize, nullptr, nullptr);
105+
output[requiredSize] = 0;
106+
dwLength = static_cast<std::size_t>(requiredSize);
107+
return output;
108+
#else
109+
#error Unsupported platform
110+
#endif
111+
}
112+
113+
std::unique_ptr<char[]> starlib::detail::Utf32ToCP(const char32_t* szInput, std::size_t& dwLength)
114+
{
115+
#ifdef _WIN32
116+
std::unique_ptr<char16_t[]> utf16String = std::make_unique_for_overwrite<char16_t[]>(dwLength * 2);
117+
std::size_t utf16Length{};
118+
119+
for (std::size_t i = 0; i < dwLength; ++i)
120+
{
121+
char32_t codepoint = szInput[i];
122+
if (codepoint <= 0xFFFF)
123+
{
124+
utf16String[utf16Length++] = static_cast<char16_t>(codepoint);
125+
}
126+
else if (codepoint <= 0x10FFFF)
127+
{
128+
codepoint -= 0x10000;
129+
utf16String[utf16Length++] = static_cast<char16_t>((codepoint >> 10) + 0xD800);
130+
utf16String[utf16Length++] = static_cast<char16_t>((codepoint & 0x3FF) + 0xDC00);
131+
}
132+
else return nullptr;
133+
}
134+
dwLength = utf16Length;
135+
return starlib::detail::Utf16ToCP(utf16String.get(), dwLength);
136+
#else
137+
#error Unsupported platform
138+
#endif
139+
}

starlib/printInternals.ixx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module;
2+
3+
#include <cstddef>
4+
#include <memory>
5+
6+
export module print:internal;
7+
8+
namespace starlib::detail
9+
{
10+
void InitialisePrintBuffers(void);
11+
export void PrintString(const char* szBuffer, std::size_t dwLength);
12+
export void PrintString(const wchar_t* szBuffer, std::size_t dwLength);
13+
14+
export std::unique_ptr<char[]> Utf8ToCP(const char8_t* szInput, std::size_t& dwLength);
15+
export std::unique_ptr<char[]> Utf16ToCP(const char16_t* szInput, std::size_t& dwLength);
16+
export std::unique_ptr<char[]> Utf32ToCP(const char32_t* szInput, std::size_t& dwLength);
17+
}

starlib/starlib.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ module starlib;
22

33
extern "C" void DefaultMain(void)
44
{
5-
starlib::process::Exit(1);
5+
starlib::process::Exit(Program::Main());
66
}
77

starlib/starlib.ixx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
export module starlib;
22

33
export import process;
4+
export import print;
5+
6+
export class Program
7+
{
8+
public:
9+
static int Main(void);
10+
};

0 commit comments

Comments
 (0)