Compare commits

..
28 Commits
Author SHA1 Message Date
Riyyi 243442a3b6 Format: Fill float printing with '0's until the precision 2024-09-07 16:57:18 +02:00
Riyyi d5bf50715e System: Add missing include 2024-08-25 13:21:03 +02:00
Riyyi 8520952235 Meta: Add type compare is() function 2024-04-29 13:31:38 +02:00
Riyyi 526948874a Macro: Add string concat macro function 2023-12-03 20:13:58 +01:00
Riyyi c8e4ae884e Format: Improve the double formatting implementation 2023-11-26 10:33:56 +01:00
Riyyi 07c9f9959d Util: Fix singleton resource cleanup 2022-10-05 12:39:29 +02:00
Riyyi 9000d1d968 CMake: Populate list of required include directories for library targets
PUBLIC items will populate the INTERFACE_INCLUDE_DIRECTORIES property of
<target>. This property is a list of public include directories
requirements for a library.

Targets may populate this property to publish the include directories
required to compile against the headers for the target.
2022-09-28 14:32:23 +02:00
Riyyi 50fe09ee56 Util: Fix reading binary files 2022-09-26 02:40:32 +02:00
Riyyi 6cd2fe4e9a Util: Add ArgParser::parse overload for C legacy 2022-09-25 22:41:35 +02:00
Riyyi 703be90792 Util: Revert allow setting instance pointer manually
This is unnecessary as the allocation + placement new works.
2022-09-25 21:40:21 +02:00
Riyyi da9fb1ed0f Util: Allow setting instance pointer manually, ex: in constructor 2022-09-25 20:51:33 +02:00
Riyyi d6f378b6bc Json: Add support for float, deduplicate code 2022-09-25 01:45:23 +02:00
Riyyi e592ee7fd4 Util: Add length() and raw() functions to File class 2022-09-23 01:00:01 +02:00
Riyyi 8e2e1a13df Format: Add support for unsigned char 2022-09-22 14:26:13 +02:00
Riyyi afdd0f8b45 Format: Fix left shift operator log messages 2022-09-22 11:50:53 +02:00
Riyyi 93af096d68 Util: Set singleton instance pointer early 2022-09-21 23:20:19 +02:00
Riyyi 228e290b94 Json: Add support for more integral types 2022-09-21 17:24:38 +02:00
Riyyi cacd3ca8fd Doc: Add link to format specification and previous development locations 2022-09-20 15:11:01 +02:00
Riyyi 953df138c0 CMake: Automatically build tests during standalone compilation 2022-09-16 20:45:59 +02:00
Riyyi f3e49d8f74 CMake: Link instead of recompile 2022-09-16 20:45:39 +02:00
Riyyi 3b7d8d1e4a CMake: Allow for shared library compilation 2022-09-16 20:44:53 +02:00
Riyyi d3de1fb635 Macro: Add define for bit shifts 2022-08-31 21:30:30 +02:00
Riyyi f31f7feb5e Test: Silence compiler warning about signed int literal being too big 2022-08-24 22:07:49 +02:00
Riyyi 7093b5ad3d Json: Remove redundant width specifier to silence compiler warning 2022-08-24 21:46:55 +02:00
Riyyi 145c7e44a2 Format: Silence compiler warning about switch statement fall through 2022-08-24 21:46:49 +02:00
Riyyi 29406fac1f Meta: Dont try to lint files that are being deleted 2022-08-24 21:22:49 +02:00
Riyyi 27489c6cb4 Format: Re-add using statement to Formatter 2022-08-23 16:47:36 +02:00
Riyyi a83615083d Format: Allow user-defined types in Parser 2022-08-23 16:41:27 +02:00
23 changed files with 229 additions and 53 deletions
+13 -11
View File
@@ -4,8 +4,13 @@
# Set project name # Set project name
set(PROJECT "ruc") set(PROJECT "ruc")
# Unit tests if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
option(RUC_BUILD_TESTS "Build the RUC test programs" ON) set(RUC_STANDALONE TRUE)
endif()
# Options
option(BUILD_SHARED_LIBS "Build shared libraries" OFF)
option(RUC_BUILD_TESTS "Build the RUC test programs" ${RUC_STANDALONE})
# ------------------------------------------ # ------------------------------------------
@@ -55,8 +60,8 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Define library source files # Define library source files
file(GLOB_RECURSE LIBRARY_SOURCES "src/*.cpp") file(GLOB_RECURSE LIBRARY_SOURCES "src/*.cpp")
add_library(${PROJECT} STATIC ${LIBRARY_SOURCES}) add_library(${PROJECT} ${LIBRARY_SOURCES})
target_include_directories(${PROJECT} PRIVATE target_include_directories(${PROJECT} PUBLIC
"src") "src")
# ------------------------------------------ # ------------------------------------------
@@ -65,8 +70,8 @@ target_include_directories(${PROJECT} PRIVATE
# Define source files # Define source files
file(GLOB TEST_LIBRARY_SOURCES "test/*.cpp" "src/ruc/timer.cpp") file(GLOB TEST_LIBRARY_SOURCES "test/*.cpp" "src/ruc/timer.cpp")
add_library(${PROJECT}-test STATIC ${TEST_LIBRARY_SOURCES}) add_library(${PROJECT}-test ${TEST_LIBRARY_SOURCES})
target_include_directories(${PROJECT}-test PRIVATE target_include_directories(${PROJECT}-test PUBLIC
"src" "src"
"test") "test")
@@ -75,11 +80,8 @@ target_include_directories(${PROJECT}-test PRIVATE
if (RUC_BUILD_TESTS) if (RUC_BUILD_TESTS)
# Define test source files # Define test source files
file(GLOB_RECURSE TEST_SOURCES "test/*.cpp") file(GLOB_RECURSE TEST_SOURCES "test/unit/*.cpp")
set(TEST_SOURCES ${TEST_SOURCES} ${LIBRARY_SOURCES})
add_executable(${PROJECT}-unit-test ${TEST_SOURCES}) add_executable(${PROJECT}-unit-test ${TEST_SOURCES})
target_include_directories(${PROJECT}-unit-test PRIVATE target_link_libraries(${PROJECT}-unit-test ruc ruc-test)
"src"
"test")
endif() endif()
+6
View File
@@ -9,3 +9,9 @@ Utility library for C++.
- Formatting library - Formatting library
- JSON parsing - JSON parsing
- Unit test macros - Unit test macros
** Formatting library
The format specification has been heavily inspired by the ~{fmt}~ library, most of the [[https://fmt.dev/latest/syntax.html#format-specification-mini-language][mini-language]] is supported.
*Note*: Development was previously done at: [[https://github.com/riyyi/manafiles/tree/6f0e3d6063ab75ad81899135689569e440ddb813/src/util][manafiles/src/util]] and [[https://github.com/riyyi/manafiles/tree/6f0e3d6063ab75ad81899135689569e440ddb813/test][manafiles/test]].
+1 -1
View File
@@ -37,7 +37,7 @@ fi
subDir="$(dirname -- "$0")" subDir="$(dirname -- "$0")"
# Get all files staged for commit # Get all files staged for commit
files="$(git --no-pager diff --cached --name-only)" files="$(git --no-pager diff --cached --name-only --diff-filter=d)"
failures=0 failures=0
+1 -1
View File
@@ -31,7 +31,7 @@ else
Either skip this script, or install clang-format-11." Either skip this script, or install clang-format-11."
fi fi
files="${1:-$(git --no-pager diff --cached --name-only)}" files="${1:-$(git --no-pager diff --cached --name-only --diff-filter=d)}"
files="$(echo "$files" | grep -E '\.(cpp|h)$')" files="$(echo "$files" | grep -E '\.(cpp|h)$')"
if [ -z "$files" ]; then if [ -z "$files" ]; then
+1 -1
View File
@@ -23,7 +23,7 @@ if ! command -v shellcheck > /dev/null 2>&1; then
Either skip this script, or install shellcheck." Either skip this script, or install shellcheck."
fi fi
files="${1:-$(git --no-pager diff --cached --name-only)}" files="${1:-$(git --no-pager diff --cached --name-only --diff-filter=d)}"
files="$(echo "$files" | grep -E '\.sh$')" files="$(echo "$files" | grep -E '\.sh$')"
if [ -z "$files" ]; then if [ -z "$files" ]; then
+5
View File
@@ -259,6 +259,11 @@ bool ArgParser::parseArgument(std::string_view argument)
} }
bool ArgParser::parse(int argc, const char* argv[]) bool ArgParser::parse(int argc, const char* argv[])
{
return parse(argc, const_cast<char**>(argv));
}
bool ArgParser::parse(int argc, char* argv[])
{ {
bool result = true; bool result = true;
+1
View File
@@ -63,6 +63,7 @@ public:
}; };
bool parse(int argc, const char* argv[]); bool parse(int argc, const char* argv[]);
bool parse(int argc, char* argv[]);
void addOption(Option&& option); void addOption(Option&& option);
void addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString); void addOption(bool& value, char shortName, const char* longName, const char* usageString, const char* manString);
+37 -7
View File
@@ -6,8 +6,8 @@
#include <cstdint> // int32_t #include <cstdint> // int32_t
#include <filesystem> #include <filesystem>
#include <fstream> // ifstream, ios, ofstream #include <fstream> // std::ifstream, std::ofstream
#include <memory> // make_unique #include <memory> // std::make_shared, std::make_unique, std::shared_ptr
#include <string> #include <string>
#include <string_view> #include <string_view>
@@ -20,14 +20,11 @@ File::File(std::string_view path)
: m_path(path) : m_path(path)
{ {
// Create input stream object and open file // Create input stream object and open file
std::ifstream file(path.data(), std::ios::in); std::ifstream file(path.data());
VERIFY(file.is_open(), "failed to open file: '{}'", path); VERIFY(file.is_open(), "failed to open file: '{}'", path);
// Get length of the file // Get length of the file
file.seekg(0, std::ios::end); int32_t size = File::length(path, file);
int32_t size = file.tellg();
file.seekg(0, std::ios::beg);
VERIFY(size != -1, "failed to read file length: '{}'", path);
// Allocate memory filled with zeros // Allocate memory filled with zeros
auto buffer = std::make_unique<char[]>(size); auto buffer = std::make_unique<char[]>(size);
@@ -54,6 +51,39 @@ File File::create(std::string_view path)
return File(path); return File(path);
} }
int32_t File::length(std::string_view path, std::ifstream& file)
{
file.seekg(0, std::ios::end);
int32_t length = file.tellg();
file.seekg(0, std::ios::beg);
VERIFY(length != -1, "failed to read file length: '{}'", path);
return length;
}
// FIXME: Deduplicate code with constructor, this broke binary files only
std::shared_ptr<char[]> File::raw(std::string_view path)
{
// Create input stream object and open file
std::ifstream file(path.data());
VERIFY(file.is_open(), "failed to open file: '{}'", path);
// Get length of the file
int32_t size = File::length(path, file);
// Allocate memory filled with zeros
auto buffer = std::make_shared<char[]>(size + 1);
// Fill buffer with file contents
file.read(buffer.get(), size);
file.close();
// Null termination
buffer[size] = '\0';
return buffer;
}
// ----------------------------------------- // -----------------------------------------
void File::clear() void File::clear()
+4
View File
@@ -6,6 +6,8 @@
#pragma once #pragma once
#include <cstdint> // int32_t
#include <memory> // std::shared_ptr
#include <string> #include <string>
#include <string_view> #include <string_view>
@@ -17,6 +19,8 @@ public:
virtual ~File(); virtual ~File();
static File create(std::string_view path); static File create(std::string_view path);
static int32_t length(std::string_view path, std::ifstream& file);
static std::shared_ptr<char[]> raw(std::string_view path);
void clear(); void clear();
File& append(std::string_view data); File& append(std::string_view data);
+47 -8
View File
@@ -5,6 +5,7 @@
*/ */
#include <algorithm> // min #include <algorithm> // min
#include <charconv> // std::to_chars
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t #include <cstdint> // uint8_t, uint16_t, uint32_t, uint64_t
#include <iomanip> // setprecision #include <iomanip> // setprecision
@@ -178,15 +179,53 @@ void Builder::putI64(int64_t value,
void Builder::putF64(double number, uint8_t precision) const void Builder::putF64(double number, uint8_t precision) const
{ {
precision = std::min(precision, static_cast<uint8_t>(std::numeric_limits<double>::digits10)); char buffer[512];
auto conversion = std::to_chars(buffer, buffer + sizeof(buffer), number);
auto converted = std::string(buffer, conversion.ptr - buffer);
std::stringstream stream; size_t dot = converted.find('.');
stream size_t length = dot + precision + 1;
<< std::fixed << std::setprecision(precision)
<< number // There is no number behind the decimal point
<< std::defaultfloat << std::setprecision(6); if (dot == std::string::npos) {
std::string string = stream.str(); if (precision > 0) {
m_builder << string; converted += '.' + std::string(precision, '0');
}
m_builder << converted;
return;
}
// If there are less numbers behind the decimal point than the precision
if (converted.length() < length) {
converted += std::string(length - converted.length(), '0');
m_builder << converted;
return;
}
// Only round if there are more numbers behind the decimal point than the precision,
// or the number that comes after the maximum precision is higher than 4
if (converted.length() > length && converted[length] > '4') {
for (size_t i = length - 1; i >= 0 && i < converted.length(); --i) {
if (converted[i] == '.') {
continue;
}
if (converted[i] < '9') { // Overflow stops here
converted[i]++;
break;
}
converted[i] = '0';
}
}
// Cut off the characters after the requested precision
if (converted.length() > length) {
size_t last_included_number = converted.find_last_not_of("0", length - 1);
// If precision is zero, also cut off the '.', otherwise include the '0' after the '.'
size_t last_character_is_dot_offset = (converted[last_included_number] == '.') ? (precision > 0 ? 1 : -1) : 0;
converted = converted.substr(0, last_included_number + last_character_is_dot_offset + 1);
}
m_builder << converted;
} }
void Builder::putString(std::string_view string, char fill, Align align, size_t width) const void Builder::putString(std::string_view string, char fill, Align align, size_t width) const
+5
View File
@@ -81,6 +81,11 @@ void Formatter<const char*>::format(Builder& builder, const char* value) const
value != nullptr ? std::string_view { value, strlen(value) } : "nullptr"); value != nullptr ? std::string_view { value, strlen(value) } : "nullptr");
} }
void Formatter<const unsigned char*>::format(Builder& builder, const unsigned char* value) const
{
return Formatter<const char*>::format(builder, reinterpret_cast<const char*>(value));
}
// Pointer // Pointer
void Formatter<std::nullptr_t>::format(Builder& builder, std::nullptr_t) const void Formatter<std::nullptr_t>::format(Builder& builder, std::nullptr_t) const
+19 -2
View File
@@ -108,9 +108,11 @@ struct Formatter<T> {
uint8_t base = 0; uint8_t base = 0;
bool uppercase = false; bool uppercase = false;
switch (specifier.type) { switch (specifier.type) {
case PresentationType::Binary:
base = 2;
break;
case PresentationType::BinaryUppercase: case PresentationType::BinaryUppercase:
uppercase = true; uppercase = true;
case PresentationType::Binary:
base = 2; base = 2;
break; break;
case PresentationType::Octal: case PresentationType::Octal:
@@ -120,9 +122,11 @@ struct Formatter<T> {
case PresentationType::Decimal: case PresentationType::Decimal:
base = 10; base = 10;
break; break;
case PresentationType::Hex:
base = 16;
break;
case PresentationType::HexUppercase: case PresentationType::HexUppercase:
uppercase = true; uppercase = true;
case PresentationType::Hex:
base = 16; base = 16;
break; break;
default: default:
@@ -195,6 +199,19 @@ template<size_t N>
struct Formatter<char[N]> : Formatter<const char*> { struct Formatter<char[N]> : Formatter<const char*> {
}; };
template<>
struct Formatter<const unsigned char*> : Formatter<const char*> {
void format(Builder& builder, const unsigned char* value) const;
};
template<>
struct Formatter<unsigned char*> : Formatter<const unsigned char*> {
};
template<size_t N>
struct Formatter<unsigned char[N]> : Formatter<const unsigned char*> {
};
// Pointer // Pointer
template<typename T> template<typename T>
+2 -1
View File
@@ -70,7 +70,8 @@ private:
template<typename T> template<typename T>
const LogOperatorStyle& operator<<(const LogOperatorStyle& logOperatorStyle, const T& value) const LogOperatorStyle& operator<<(const LogOperatorStyle& logOperatorStyle, const T& value)
{ {
_format(const_cast<LogOperatorStyle&>(logOperatorStyle).builder(), value); Formatter<T> formatter;
formatter.format(const_cast<LogOperatorStyle&>(logOperatorStyle).builder(), value);
return logOperatorStyle; return logOperatorStyle;
} }
+2
View File
@@ -411,6 +411,8 @@ constexpr void Parser::checkSpecifierType(const Specifier& specifier, ParameterT
case ParameterType::Container: case ParameterType::Container:
checkSpecifierContainerType(specifier); checkSpecifierContainerType(specifier);
break; break;
case ParameterType::UserDefined:
break;
default: default:
VERIFY_NOT_REACHED(); VERIFY_NOT_REACHED();
} }
+1
View File
@@ -32,6 +32,7 @@ public:
String, String,
Pointer, Pointer,
Container, Container,
UserDefined,
}; };
Parser(std::string_view format, size_t parameterCount); Parser(std::string_view format, size_t parameterCount);
+8 -6
View File
@@ -7,7 +7,8 @@
#pragma once #pragma once
#include <algorithm> // transform #include <algorithm> // transform
#include <cstddef> // nullptr_t #include <cstddef> // nullptr_t, size_t
#include <cstdint> // int32_t, int64_t, uint32_t
#include <map> #include <map>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -17,6 +18,7 @@
#include "ruc/json/array.h" #include "ruc/json/array.h"
#include "ruc/json/object.h" #include "ruc/json/object.h"
#include "ruc/meta/assert.h" #include "ruc/meta/assert.h"
#include "ruc/meta/concepts.h"
#include "ruc/meta/odr.h" #include "ruc/meta/odr.h"
namespace ruc::json { namespace ruc::json {
@@ -44,15 +46,15 @@ void fromJson(const Json& json, bool& boolean)
boolean = json.asBool(); boolean = json.asBool();
} }
template<typename Json> template<typename Json, Integral T>
void fromJson(const Json& json, int& number) void fromJson(const Json& json, T& number)
{ {
VERIFY(json.type() == Json::Type::Number); VERIFY(json.type() == Json::Type::Number);
number = (int)json.asDouble(); number = static_cast<T>(json.asDouble());
} }
template<typename Json> template<typename Json, FloatingPoint T>
void fromJson(const Json& json, double& number) void fromJson(const Json& json, T& number)
{ {
VERIFY(json.type() == Json::Type::Number); VERIFY(json.type() == Json::Type::Number);
number = json.asDouble(); number = json.asDouble();
+1 -1
View File
@@ -266,7 +266,7 @@ Value Parser::consumeString()
break; break;
default: default:
char buffer[7]; char buffer[7];
sprintf(buffer, "\\u%0.4X", character); sprintf(buffer, "\\u%.4X", character);
return std::string(buffer); return std::string(buffer);
break; break;
} }
+9 -8
View File
@@ -6,8 +6,8 @@
#pragma once #pragma once
#include <cassert> // assert #include <cstddef> // nullptr_t, size_t
#include <cstddef> // nullptr_t #include <cstdint> // int32_t, int64_t, uint32_t
#include <map> #include <map>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
@@ -15,6 +15,7 @@
#include "ruc/json/array.h" #include "ruc/json/array.h"
#include "ruc/json/object.h" #include "ruc/json/object.h"
#include "ruc/meta/concepts.h"
#include "ruc/meta/odr.h" #include "ruc/meta/odr.h"
namespace ruc::json { namespace ruc::json {
@@ -30,20 +31,20 @@ struct jsonConstructor {
json.m_value.boolean = boolean; json.m_value.boolean = boolean;
} }
template<typename Json> template<typename Json, Integral T>
static void construct(Json& json, int number) static void construct(Json& json, T number)
{ {
json.destroy(); json.destroy();
json.m_type = Json::Type::Number; json.m_type = Json::Type::Number;
json.m_value.number = (double)number; json.m_value.number = static_cast<double>(number);
} }
template<typename Json> template<typename Json, FloatingPoint T>
static void construct(Json& json, double number) static void construct(Json& json, T number)
{ {
json.destroy(); json.destroy();
json.m_type = Json::Type::Number; json.m_type = Json::Type::Number;
json.m_value.number = number; json.m_value.number = static_cast<double>(number);
} }
template<typename Json> template<typename Json>
+12
View File
@@ -0,0 +1,12 @@
/*
* Copyright (C) 2022 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#define BIT(x) (1 << x)
#define CONCAT_IMPL(a, b) a##b
#define CONCAT(a, b) CONCAT_IMPL(a, b)
+29
View File
@@ -0,0 +1,29 @@
/*
* Copyright (C) 2023 Riyyi
*
* SPDX-License-Identifier: MIT
*/
#pragma once
#include <typeinfo>
template<typename T, typename U>
inline bool is(U& input)
{
if constexpr (requires { input.template fastIs<T>(); }) {
return input.template fastIs<T>();
}
return typeid(input) == typeid(T);
}
template<typename T, typename U>
inline bool is(U* input)
{
return input && is<T>(*input);
}
// References:
// - serenity/AK/TypeCasts.h
// - serenity/Userland/Libraries/LibJS/AST.h
+14 -4
View File
@@ -6,7 +6,9 @@
#pragma once #pragma once
#include <cassert> #include <cstdlib> // free, malloc
#include "meta/assert.h"
namespace ruc { namespace ruc {
@@ -16,7 +18,10 @@ public:
static inline T& the() static inline T& the()
{ {
if (s_instance == nullptr) { if (s_instance == nullptr) {
s_instance = new T { s {} }; // Allocate memory for this instance
s_instance = static_cast<T*>(malloc(sizeof(T)));
// Call the constructor using "placement new"
new (s_instance) T { s {} };
} }
return *s_instance; return *s_instance;
@@ -24,10 +29,15 @@ public:
static inline void destroy() static inline void destroy()
{ {
if (s_instance) { if (!s_instance) {
delete s_instance; return;
} }
// Call the destructor
s_instance->~T();
// Deallocate memory
free(static_cast<void*>(s_instance));
s_instance = nullptr; s_instance = nullptr;
} }
+1
View File
@@ -1,3 +1,4 @@
#include <algorithm> // std::sort, std::unique
#include <cerrno> // errno, EAGAIN, EINTR #include <cerrno> // errno, EAGAIN, EINTR
#include <cstddef> // size_t #include <cstddef> // size_t
#include <cstdio> // perror, ssize_t #include <cstdio> // perror, ssize_t
+10 -2
View File
@@ -55,7 +55,7 @@ TEST_CASE(FormatIntegral)
result = format("{}", u32); result = format("{}", u32);
EXPECT_EQ(result, "4294967295"); EXPECT_EQ(result, "4294967295");
size_t u64 = 18446744073709551615; // long unsigned int size_t u64 = 18446744073709551615u; // long unsigned int
result = format("{}", u64); result = format("{}", u64);
EXPECT_EQ(result, "18446744073709551615"); EXPECT_EQ(result, "18446744073709551615");
} }
@@ -109,6 +109,14 @@ TEST_CASE(FormatString)
result = format("{}", cString); result = format("{}", cString);
EXPECT_EQ(result, "C string"); EXPECT_EQ(result, "C string");
const unsigned char unsignedCharArray[] = "µnsïgned çhãr";
result = format("{}", unsignedCharArray);
EXPECT_EQ(result, "µnsïgned çhãr");
const unsigned char* unsignedCharPointer = &unsignedCharArray[0];
result = format("{}", unsignedCharPointer);
EXPECT_EQ(result, "µnsïgned çhãr");
std::string string = "string"; std::string string = "string";
result = format("{}", string); result = format("{}", string);
EXPECT_EQ(result, "string"); EXPECT_EQ(result, "string");
@@ -513,5 +521,5 @@ TEST_CASE(FormatContainers)
} }
// Local Variables: // Local Variables:
// lsp-in-cpp-project-cache: nil // lsp-in-cpp-project-cache: (nil)
// End: // End: