From 9f817b370e55c24db5dee3f6948d2d95df7c3207 Mon Sep 17 00:00:00 2001 From: pommicket Date: Tue, 16 Sep 2025 14:03:51 -0400 Subject: Start C++ library --- .gitignore | 6 +- cpp/CMakeLists.txt | 15 +++++ cpp/pom.cpp | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++ cpp/pom.hpp | 86 +++++++++++++++++++++++++++ pom.h | 6 +- 5 files changed, 278 insertions(+), 6 deletions(-) create mode 100644 cpp/CMakeLists.txt create mode 100644 cpp/pom.cpp create mode 100644 cpp/pom.hpp diff --git a/.gitignore b/.gitignore index 32272a7..c325e0d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ /doc -/Debug -/Release -/.cache +Debug +Release +.cache build* compile_commands.json diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt new file mode 100644 index 0000000..f26258d --- /dev/null +++ b/cpp/CMakeLists.txt @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 3.0...3.31) +project(pom) + +if (MSVC) + add_compile_options(/W4) +else() + add_compile_options(-Wall -Wextra -Wpedantic -Wshadow) +endif() + + +add_library(pom++ STATIC pom.cpp) +add_library(pom++-shared SHARED pom.cpp) +set_target_properties(pom++-shared PROPERTIES OUTPUT_NAME pom++) +target_include_directories(pom++ PRIVATE ..) +target_include_directories(pom++-shared PRIVATE ..) diff --git a/cpp/pom.cpp b/cpp/pom.cpp new file mode 100644 index 0000000..a883d34 --- /dev/null +++ b/cpp/pom.cpp @@ -0,0 +1,171 @@ +#include "pom.hpp" + +#include +#include +#include +#include +#include + +namespace pom { + +Configuration::~Configuration() { + pom_conf_free(static_cast(C)); +} + +Configuration::Configuration(): C(nullptr) {} + +Configuration::Configuration(void *c): C(c) {} + +Error::~Error() { if (m_is_original) free(C); } + +Error::Error(void *C_error): C(C_error), m_is_original(true) { + const pom_error *next = pom_error_next(static_cast(C)); + m_next = std::unique_ptr(new Error(static_cast(next))); +} + +// const_cast here to avoid creating a separate ConstError type +Error::Error(const void *C_error): C(const_cast(C_error)), m_is_original(false) { + const pom_error *next = pom_error_next(static_cast(C)); + m_next = std::unique_ptr(new Error(static_cast(next))); +} + +std::string_view Error::file() const noexcept { + return pom_error_file(static_cast(C)); +} + +uint64_t Error::line() const noexcept { + return pom_error_line(static_cast(C)); +} + +std::string_view Error::message() const noexcept { + return pom_error_message(static_cast(C)); +} + +const Error *Error::next() const noexcept { + return m_next.get(); +} + +const char *Error::what() const noexcept { + return m_is_original ? pom_error_to_string(static_cast(C)) + : pom_error_message(static_cast(C)); +} + +std::string_view Error::to_string() noexcept { + return what(); +} + +Configuration Configuration::section(std::string_view name) const { + std::string name_str(name); + const pom_conf *C_section = pom_conf_section( + static_cast(C), name_str.c_str()); + pom_conf *C_section_copy = pom_conf_copy(C_section); + return Configuration(static_cast(C_section_copy)); +} + +static void *allocator_calloc(void *udata, size_t n, size_t sz) { + return static_cast(udata)->calloc(n, sz); +} + +static void *allocator_realloc(void *udata, void *ptr, size_t sz) { + return static_cast(udata)->realloc(ptr, sz); +} + +static void allocator_free(void *udata, void *ptr) { + return static_cast(udata)->free(ptr); +} + +void Settings::to_C(void *C) const { + pom_settings &C_settings = *static_cast(C); + strcpy(C_settings.error_lang, m_error_lang); + if (m_allocator) { + C_settings.allocator_udata = m_allocator.get(); + C_settings.calloc = allocator_calloc; + C_settings.realloc = allocator_realloc; + C_settings.free = allocator_free; + } +} + +static size_t readable_read(void *udata, char *buf, size_t count) { + return static_cast(udata)->read(buf, count); +} + +Configuration Configuration::load(std::string_view filename, Reader &source, const Settings *settings) { + pom_settings C_settings = {}; + if (settings) { + settings->to_C(static_cast(&C_settings)); + } + pom_error *error; + std::string filename_str(filename); + void *C_conf = pom_load(&C_settings, filename_str.c_str(), readable_read, &source, &error); + if (error) { + throw Error(static_cast(error)); + } + return Configuration(C_conf); +} + +Configuration Configuration::load(std::string_view path, const Settings *settings) { + pom_settings C_settings = {}; + if (settings) { + settings->to_C(static_cast(&C_settings)); + } + pom_error *error; + std::string path_str(path); + void *C_conf = pom_load_path(&C_settings, path_str.c_str(), &error); + if (error) { + throw Error(static_cast(error)); + } + return Configuration(C_conf); +} + +class StringViewReader: public Reader { +public: + explicit StringViewReader(std::string_view v): string(v) {} + virtual size_t read(char *buf, size_t count) { + count = std::min(count, string.size()); + memcpy(buf, string.data(), count); + return count; + } +private: + std::string_view string; +}; + +Configuration Configuration::load(std::string_view filename, std::string_view string, const Settings *settings) { + StringViewReader reader(string); + return load(filename, reader, settings); +} + +class IStreamReader: public Reader { +public: + explicit IStreamReader(std::istream &stream): ifstream(stream) {} + virtual size_t read(char *buf, size_t count) { + ifstream.read(buf, count); + return ifstream.gcount(); + } +private: + std::istream &ifstream; +}; + + +Configuration Configuration::load(std::string_view filename, std::istream &stream, const Settings *settings) { + IStreamReader reader(stream); + return load(filename, reader, settings); +} + +void Configuration::merge(const Configuration &other) { + pom_conf_merge(static_cast(C), static_cast(other.C)); +} + +std::optional Configuration::get(std::string_view key) const { + std::string key_str(key); + const char *value = pom_conf_get(static_cast(C), key_str.c_str()); + if (!value) + return {}; + else + return value; +} + +std::string_view Configuration::get_or_default(std::string_view key, std::string_view dflt) const { + return get(key).value_or(dflt); +} + +} // namespace pom diff --git a/cpp/pom.hpp b/cpp/pom.hpp new file mode 100644 index 0000000..e3c66d4 --- /dev/null +++ b/cpp/pom.hpp @@ -0,0 +1,86 @@ +#ifndef POM_HPP_ +#define POM_HPP_ + +#include +#include +#include +#include +#include + +namespace pom { + +class Error: public std::exception { +public: + ~Error(); + std::string_view file() const noexcept; + uint64_t line() const noexcept; + std::string_view message() const noexcept; + const Error *next() const noexcept; + std::string_view to_string() noexcept; + /// You should only call this on the first error in an error list. + /// (This can't be enforced with constness because it needs to + /// override `std::exception::what`.) + virtual const char *what() const noexcept override; +private: + friend class Configuration; + friend class Parser; + Error() = delete; + Error(void *C_error); + Error(const void *C_error); + void *C; + bool m_is_original; + std::unique_ptr m_next; +}; + +class Allocator { +public: + virtual ~Allocator() = 0; + virtual void *calloc(size_t, size_t) = 0; + virtual void *realloc(void *, size_t) = 0; + virtual void free(void *) = 0; +}; + +class Settings { +public: + Settings(); + /// Set allocator. + void set_allocator(std::shared_ptr allocator) { + m_allocator = allocator; + } + void set_error_language(std::string_view lang); +private: + void to_C(void *C) const; + friend class Configuration; + char m_error_lang[16] = {}; + std::shared_ptr m_allocator; + uint32_t version = 1; // future-proofing +}; + +class Reader { +public: + virtual size_t read(char *buf, size_t count) = 0; +}; + +class Configuration { +public: + Configuration(); + ~Configuration(); + std::optional get(std::string_view key) const; + std::string_view get_or_default(std::string_view key, std::string_view dflt) const; + Configuration section(std::string_view name) const; + void merge(const Configuration &other); + static Configuration load(std::string_view filename, Reader &source, const Settings *settings = nullptr); + static Configuration load(std::string_view filename, std::istream &stream, const Settings *settings = nullptr); + static Configuration load(std::string_view path, const Settings *settings = nullptr); + static Configuration load(std::string_view filename, std::string_view string, const Settings *settings = nullptr); +private: + explicit Configuration(void *c); + void *C; +}; + + + + +} // namespace pom + +#endif // POM_HPP_ diff --git a/pom.h b/pom.h index f2f7e52..8ae9ff1 100644 --- a/pom.h +++ b/pom.h @@ -31,8 +31,8 @@ /// \mainpage libpom doxygen documentation /// /// See \ref pom.h for all types/functions. -#ifndef POM_H_ -#define POM_H_ +#ifndef POM__H_ +#define POM__H_ #ifndef POM_NO_STDIO #include @@ -533,4 +533,4 @@ pom_conf_free(pom_conf *conf); } // extern "C" #endif -#endif // POM_H_ +#endif // POM__H_ -- cgit v1.2.3