Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ Checks: '*,
-boost-use-ranges,
-google-objc-function-naming,
-google-objc-global-variable-declaration,
-performance-enum-size'
-performance-enum-size,
-llvm-header-guard,
-portability-avoid-pragma-once'
WarningsAsErrors: ''
HeaderFilterRegex: '^(?!magic_enum).*$'
FormatStyle: Microsoft
Expand Down
11 changes: 11 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# clangd configuration for cpp-core (C++23)
# CMake generates compile_commands.json; the project copies it to the repo root
# via the copy-compile-commands target. Run: cmake -B build && cmake --build build
# clangd automatically uses compile_commands.json in the project root when present.

CompileFlags:
Add:
- -std=c++23
- -Wno-c++98-compat-pedantic
- -Wno-pre-c++20-compat-pedantic
- -Wno-switch-default
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ include(cmake/CPM.cmake)
CPMAddPackage(
NAME cmake-git-versioning
GITHUB_REPOSITORY Katze719/cmake-git-versioning
GIT_TAG v1.0.1
GIT_TAG v1.1.0
)

# Include the cmake-git-versioning module
Expand Down
2 changes: 1 addition & 1 deletion Doxyfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#--------------------------------------------------------------------------
# Doxyfile generated for cpp-core
# Doxyfile - generated for cpp-core
#--------------------------------------------------------------------------

# Project related --------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Header-only API definition library for cross-platform serial communication. Defi

## Requirements

* CMake 3.30
* CMake >= 3.30
* A C++23 compatible compiler (GCC 13+, Clang 16+, MSVC 2022+)
* Git (for automatic version detection)

Expand Down
2 changes: 1 addition & 1 deletion include/cpp_core.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

/*
* cpp_core.h Umbrella header for the cpp_core library
* cpp_core.h - Umbrella header for the cpp_core library
*
* Include this single file to gain access to the complete public C/C++ API
* of the cpp_core runtime. Internally it forwards to the individual header
Expand Down
98 changes: 98 additions & 0 deletions include/cpp_core/error_handling.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#pragma once

#include "status_codes.h"

#include <concepts>
#include <string>
#include <string_view>
#include <type_traits>

namespace cpp_core
{

// Concepts

// Matches any callable that can receive (int, const char*).
// clang-format off
template <typename F>
concept ErrorCallback = std::is_null_pointer_v<std::remove_cvref_t<F>>
|| std::is_same_v<std::remove_cvref_t<F>, std::nullptr_t>
|| requires(F &&func, int code, const char *msg) {
{ func(code, msg) } -> std::same_as<void>;
};
// clang-format on

// Matches anything implicitly convertible to a C-style function pointer (ErrorCallbackT).
template <typename F>
concept LegacyErrorCallback = std::is_convertible_v<F, void (*)(int, const char *)>;

// Matches any type whose value can be returned as a status/result code.
template <typename T>
concept StatusConvertible = std::is_arithmetic_v<T> && requires(StatusCodes code) { static_cast<T>(code); };

// Error invocation

// Safely invoke an error callback, does nothing if callback is nullptr.
template <ErrorCallback Callback>
constexpr auto invokeError(Callback &&callback, StatusCodes code, std::string_view message) noexcept -> void
{
if constexpr (std::is_null_pointer_v<std::remove_cvref_t<Callback>>)
{
(void)callback;
(void)code;
(void)message;
return;
}
else
{
if (callback != nullptr)
{
const std::string term(message);
callback(static_cast<int>(code), term.c_str());
}
}
}

// Fail helpers (concept-constrained)

// Report failure through callback and return the status code cast to Ret.
template <StatusConvertible Ret, ErrorCallback Callback>
constexpr auto failMsg(Callback &&callback, StatusCodes code, std::string_view message) -> Ret
{
invokeError(std::forward<Callback>(callback), code, message);
return static_cast<Ret>(code);
}

// Overload that builds the message by concatenating a prefix and a detail string.
template <StatusConvertible Ret, ErrorCallback Callback>
auto failMsg(Callback &&callback, StatusCodes code, std::string_view prefix, std::string_view detail) -> Ret
{
std::string full;
full.reserve(prefix.size() + 2 + detail.size());
full.append(prefix);
full.append(": ");
full.append(detail);
invokeError(std::forward<Callback>(callback), code, full);
return static_cast<Ret>(code);
}

// Pipe-style error chaining

// Chain operations that return a status-code integer.
// Stops at the first non-success result and returns it.
//
// auto result = chainStatus(
// [&] { return configureBaudrate(h, 9600); },
// [&] { return configureParity(h, 0); },
// [&] { return configureStopBits(h, 1); }
// );
template <std::invocable... Fns>
requires(std::is_convertible_v<std::invoke_result_t<Fns>, int> && ...)
constexpr auto chainStatus(Fns &&...fns) -> int
{
int result = 0;
((static_cast<void>(result = static_cast<int>(std::forward<Fns>(fns)())), result < 0) || ...);
return result;
}

} // namespace cpp_core
2 changes: 1 addition & 1 deletion include/cpp_core/interface/serial_close.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extern "C"
* @brief Close a previously opened serial port.
*
* The handle becomes invalid after the call. Passing an already invalid
* ( 0) handle is a no-op.
* (<= 0) handle is a no-op.
*
* @param handle Handle obtained from serialOpen().
* @param error_callback [optional] Callback to invoke on error. Defined in error_callback.h. Default is `nullptr`.
Expand Down
2 changes: 1 addition & 1 deletion include/cpp_core/interface/serial_drain.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ extern "C"
* @brief Wait until the operating-system driver has physically sent every queued byte.
*
* The function blocks the *calling thread* until the device driver reports
* that the transmit FIFO is **empty** i.e. all bytes handed to previous
* that the transmit FIFO is **empty** - i.e. all bytes handed to previous
* `serialWrite*` calls have been shifted out on the wire. It does *not*
* flush higher-level protocol buffers you may have implemented yourself.
*
Expand Down
4 changes: 2 additions & 2 deletions include/cpp_core/interface/serial_open.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ extern "C"
*
* @param port Null-terminated device identifier (e.g. "COM3", "/dev/ttyUSB0"). Passing `nullptr` results in
* a failure.
* @param baudrate Desired baud rate in bit/s ( 300).
* @param data_bits Number of data bits (58).
* @param baudrate Desired baud rate in bit/s (>= 300).
* @param data_bits Number of data bits (5-8).
* @param parity 0 = none, 1 = even, 2 = odd.
* @param stop_bits 0 = 1 stop bit, 2 = 2 stop bits.
* @param error_callback [optional] Callback to invoke on error. Defined in error_callback.h. Default is `nullptr`.
Expand Down
144 changes: 144 additions & 0 deletions include/cpp_core/result.hpp
Comment thread
Katze719 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#pragma once

#include "status_codes.h"

#include <concepts>
#include <expected>
#include <format>
#include <string>
#include <type_traits>
#include <utility>

namespace cpp_core
{

// Enriched error type carrying a StatusCode and an optional message.
struct Error
{
StatusCodes code;
std::string message;

constexpr explicit Error(StatusCodes code_in) noexcept : code(code_in)
{
}

Error(StatusCodes code_in, std::string msg) : code(code_in), message(std::move(msg))
{
}

[[nodiscard]] constexpr auto status() const noexcept -> int
{
return static_cast<int>(code);
}

[[nodiscard]] constexpr auto operator==(const Error &other) const noexcept -> bool
{
return code == other.code;
}

[[nodiscard]] constexpr auto operator==(StatusCodes code_in) const noexcept -> bool
{
return code == code_in;
}
};

// Monadic result type: either a value T or a cpp_core::Error.
// Wraps std::expected with ergonomic helpers tuned for this library.
//
// auto result = openPort()
// .transform([](auto h) { return h.fd(); })
// .or_else([](Error e) -> Result<int> { log(e); return std::unexpected(e); });
template <typename T> using Result = std::expected<T, Error>;

// Convenience alias for void results (operations that only fail or succeed).
using Status = Result<void>;

// Factory helpers

template <typename T> [[nodiscard]] constexpr auto ok(T &&value) -> Result<std::remove_cvref_t<T>>
{
return Result<std::remove_cvref_t<T>>{std::forward<T>(value)};
}

[[nodiscard]] constexpr auto ok() -> Status
{
return {};
}

template <typename T = void> [[nodiscard]] constexpr auto fail(StatusCodes code) -> Result<T>
{
return std::unexpected(Error{code});
}

template <typename T = void> [[nodiscard]] auto fail(StatusCodes code, std::string message) -> Result<T>
{
return std::unexpected(Error{code, std::move(message)});
}

// Concept: anything that is a Result<U> for some U

// clang-format off
template <typename R>
concept IsResult = requires {
typename R::value_type;
typename R::error_type;
} && std::same_as<typename R::error_type, Error>;
// clang-format on

// TRY macro
// Usage: auto value = CPP_CORE_TRY(someResultReturningCall());
// Propagates the error automatically if the result holds an error.

// NOLINTBEGIN(cppcoreguidelines-macro-usage)
#define CPP_CORE_TRY(expr) \
({ \
auto _cpp_core_result_ = (expr); \
if (!_cpp_core_result_.has_value()) \
return std::unexpected(std::move(_cpp_core_result_).error()); \
std::move(_cpp_core_result_).value(); \
})

#define CPP_CORE_TRY_VOID(expr) \
do \
{ \
auto _cpp_core_result_ = (expr); \
if (!_cpp_core_result_.has_value()) \
return std::unexpected(std::move(_cpp_core_result_).error()); \
} while (false)
Comment thread
Mqxx marked this conversation as resolved.
// NOLINTEND(cppcoreguidelines-macro-usage)

// Result -> C return code bridge
// Converts a Result<int>/Status back into the C API convention (negative = error)
// and optionally invokes the legacy ErrorCallbackT.

template <typename T, typename Callback>
requires std::is_arithmetic_v<T>
constexpr auto toCResult(const Result<T> &result, Callback error_callback) -> T
{
if (result.has_value())
{
return result.value();
}
const auto &err = result.error();
if (error_callback != nullptr)
{
error_callback(err.status(), err.message.c_str());
}
return static_cast<T>(err.code);
}

inline auto toCStatus(const Status &result, auto error_callback) -> int
{
if (result.has_value())
{
return 0;
}
const auto &err = result.error();
if (error_callback != nullptr)
{
error_callback(err.status(), err.message.c_str());
}
return err.status();
}

} // namespace cpp_core
Loading