diff options
39 files changed, 1668 insertions, 1089 deletions
diff --git a/nihil.config/CMakeLists.txt b/nihil.config/CMakeLists.txt index ed2bba3..6b1073d 100644 --- a/nihil.config/CMakeLists.txt +++ b/nihil.config/CMakeLists.txt @@ -2,9 +2,8 @@ add_library(nihil.config STATIC) target_link_libraries(nihil.config PUBLIC nihil nihil.ucl) -target_sources(nihil.config PUBLIC - FILE_SET modules TYPE CXX_MODULES FILES - +target_sources(nihil.config + PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.config.ccm error.ccm read.ccm @@ -13,6 +12,11 @@ target_sources(nihil.config PUBLIC option.ccm string.ccm + + PRIVATE + option.cc + store.cc + string.cc ) if(NIHIL_TESTS) diff --git a/nihil.config/option.cc b/nihil.config/option.cc new file mode 100644 index 0000000..9bf77c9 --- /dev/null +++ b/nihil.config/option.cc @@ -0,0 +1,67 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <iostream> +#include <string> + +module nihil.config; + +namespace nihil::config { + +auto option::name(this option const &self) noexcept +-> std::string_view +{ + return self._name; +} + +// Human-readable description of this option. +auto option::description(this option const &self) noexcept +-> std::string_view +{ + return self._description; +} + +// If true, this option is set to its default value. +auto option::is_default(this option const &self) noexcept +-> bool +{ + return self._is_default; +} + +// Get or set this option as a string. +auto option::string(this option const &self) +-> std::string +{ + return self.get_string(); +} + +auto option::string(this option &self, std::string_view value) +-> void +{ + self.set_string(value); + self._is_default = false; +} + +option::option(std::string_view name, + std::string_view description) + : _name(name) + , _description(description) +{ +} + +auto option::is_default(bool b) +-> void +{ + _is_default = b; +} + +auto operator<<(std::ostream &strm, option const &opt) +-> std::ostream & +{ + return strm << "<" << opt.name() << "=" << opt.string() << ">"; +} + +} // namespace nihil diff --git a/nihil.config/option.ccm b/nihil.config/option.ccm index 1be542e..c6a8329 100644 --- a/nihil.config/option.ccm +++ b/nihil.config/option.ccm @@ -4,11 +4,9 @@ module; -#include <iostream> +#include <iosfwd> #include <string> -#include <ucl++.h> - export module nihil.config:option; import nihil.ucl; @@ -23,34 +21,17 @@ namespace nihil::config { export struct option { // Short name of this option. - auto name(this option const &self) noexcept -> std::string_view - { - return self._name; - } + auto name(this option const &self) noexcept -> std::string_view; // Human-readable description of this option. - auto description(this option const &self) noexcept -> std::string_view - { - return self._description; - } + auto description(this option const &self) noexcept -> std::string_view; // If true, this option is set to its default value. - auto is_default(this option const &self) noexcept -> bool - { - return self._is_default; - } + auto is_default(this option const &self) noexcept -> bool; // Get or set this option as a string. - auto string(this option const &self) -> std::string - { - return self.get_string(); - } - - void string(this option &self, std::string_view value) - { - self.set_string(value); - self._is_default = false; - } + auto string(this option const &self) -> std::string; + auto string(this option &self, std::string_view value) -> void; /* * Return this object as a UCL object. This is used when writing the @@ -69,17 +50,9 @@ export struct option auto operator=(option const &) -> option& = delete; protected: - option(std::string_view name, - std::string_view description) - : _name(name) - , _description(description) - { - } - - auto is_default(bool b) -> void - { - _is_default = b; - } + option(std::string_view name, std::string_view description); + + auto is_default(bool b) -> void; /* * Get or set this option as a string. @@ -96,9 +69,6 @@ private: /* * Make options printable. This is mostly useful for testing. */ -export auto operator<<(std::ostream &strm, option const &opt) -> std::ostream & -{ - return strm << "<" << opt.name() << "=" << opt.string() << ">"; -} +export auto operator<<(std::ostream &strm, option const &opt) -> std::ostream &; } // namespace nihil diff --git a/nihil.config/store.cc b/nihil.config/store.cc new file mode 100644 index 0000000..2ec8ade --- /dev/null +++ b/nihil.config/store.cc @@ -0,0 +1,89 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <coroutine> +#include <filesystem> +#include <format> +#include <map> + +module nihil.config; + +import nihil; + +namespace nihil::config { + +unknown_option::unknown_option(std::string_view option_name) + : error(std::format("unknown configuration variable '{}'", + option_name)) + , _option_name(option_name) +{ +} + +auto unknown_option::option_name(this unknown_option const &self) +-> std::string_view +{ + return self._option_name; +} + +auto store::get() +-> store& +{ + if (instance == nullptr) + instance = new store; + + return *instance; +} + + +auto store::register_option(this store &self, option *object) +-> void +{ + auto [it, okay] = self.options.insert( + std::pair{object->name(), object}); + + if (!okay) + throw error(std::format( + "INTERNAL ERROR: attempt to register " + "duplicate config value '{0}'", + object->name())); +} + +auto store::unregister_option(this store &self, option *object) +-> void +{ + auto it = self.options.find(object->name()); + if (it == self.options.end()) + throw error(std::format( + "INTERNAL ERROR: attempt to unregister " + "non-existent config value '{}'", + object->name())); + + self.options.erase(it); +} + +auto store::fetch(this store const &self, std::string_view name) +-> option & +{ + if (auto it = self.options.find(name); it != self.options.end()) + return *it->second; + + throw unknown_option(name); +} + +auto store::all(this store const &self) +-> nihil::generator<option const &> +{ + for (auto &&it : self.options) + co_yield *it.second; +} + +auto get_option(std::string_view option_name) +-> option & +{ + return store::get().fetch(option_name); +} + +} // namespace nihil::config diff --git a/nihil.config/store.ccm b/nihil.config/store.ccm index e0eebc0..77b44b5 100644 --- a/nihil.config/store.ccm +++ b/nihil.config/store.ccm @@ -9,12 +9,12 @@ module; */ #include <coroutine> -#include <filesystem> -#include <format> +#include <string> #include <map> export module nihil.config:store; +import nihil; import :error; import :option; @@ -22,16 +22,8 @@ namespace nihil::config { // Exception thrown on an attempt to fetch an undefined option. export struct unknown_option final : error { - unknown_option(std::string_view option_name) - : error(std::format("unknown configuration variable '{}'", - option_name)) - , _option_name(option_name) - {} - - auto option_name(this unknown_option const &self) -> std::string_view - { - return self._option_name; - } + unknown_option(std::string_view option_name); + auto option_name(this unknown_option const &self) -> std::string_view; private: std::string _option_name; @@ -41,64 +33,27 @@ struct store final { /* * Get the global config store. */ - static auto get() -> store& { - if (instance == nullptr) - instance = new store; - - return *instance; - } - + static auto get() -> store &; /* * Register a new value with the config store. */ - auto register_option(this store &self, option *object) -> void - { - auto [it, okay] = self.options.insert( - std::pair{object->name(), object}); - - if (!okay) - throw error(std::format( - "INTERNAL ERROR: attempt to register " - "duplicate config value '{0}'", - object->name())); - } + auto register_option(this store &self, option *object) -> void; /* * Remove a value from the config store. */ - auto unregister_option(this store &self, option *object) -> void - { - auto it = self.options.find(object->name()); - if (it == self.options.end()) - throw error(std::format( - "INTERNAL ERROR: attempt to unregister " - "non-existent config value '{}'", - object->name())); - - self.options.erase(it); - } + auto unregister_option(this store &self, option *object) -> void; /* * Fetch an existing value in the config store. */ - auto fetch(this store const &self, std::string_view name) - -> option & - { - if (auto it = self.options.find(name); it != self.options.end()) - return *it->second; - - throw unknown_option(name); - } + auto fetch(this store const &self, std::string_view name) -> option &; /* * Fetch all values in the configuration store. */ - auto all(this auto &&self) -> nihil::generator<option const &> - { - for (auto &&it : self.options) - co_yield *it.second; - } + auto all(this store const &self) -> nihil::generator<option const &>; // Not movable or copyable. store(store const &) = delete; @@ -120,9 +75,6 @@ private: /* * The public API. */ -export auto get_option(std::string_view option_name) -> option & -{ - return store::get().fetch(option_name); -} +export auto get_option(std::string_view option_name) -> option &; } // namespace nihil::config diff --git a/nihil.config/string.cc b/nihil.config/string.cc new file mode 100644 index 0000000..6b201ae --- /dev/null +++ b/nihil.config/string.cc @@ -0,0 +1,57 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <format> +#include <string> + +module nihil.config; + +import nihil.ucl; + +namespace nihil::config { + +string::string( + std::string &storage, + std::string_view name, + std::string_view description) noexcept + : option(name, description) + , _storage(storage) +{ + store::get().register_option(this); +} + +string::~string() +{ + store::get().unregister_option(this); +} + +auto string::get_string() const -> std::string +{ + return _storage; +} + +auto string::set_string(std::string_view new_value) -> void +{ + _storage = new_value; +} + +auto string::to_ucl() const -> ucl::object +{ + return ucl::string(_storage); +} + +auto string::from_ucl(ucl::object const &uclobj) -> void +{ + try { + _storage = object_cast<ucl::string>(uclobj).value(); + is_default(false); + } catch (ucl::type_mismatch const &exc) { + throw error(std::format("'{}': expected string, not {}", + name(), str(exc.actual_type()))); + } +} + +} // namespace nihil::config diff --git a/nihil.config/string.ccm b/nihil.config/string.ccm index 57770ae..ae5efb9 100644 --- a/nihil.config/string.ccm +++ b/nihil.config/string.ccm @@ -9,10 +9,8 @@ module; export module nihil.config:string; -import nihil; import nihil.ucl; import :option; -import :store; namespace nihil::config { @@ -20,43 +18,15 @@ export struct string final : option { string(std::string &storage, std::string_view name, - std::string_view description) noexcept - : option(name, description) - , _storage(storage) - { - store::get().register_option(this); - } + std::string_view description) noexcept; - ~string() - { - store::get().unregister_option(this); - } + ~string(); - auto get_string() const -> std::string override - { - return _storage; - }; + auto get_string() const -> std::string override; + auto set_string(std::string_view new_value) -> void override; - auto set_string(std::string_view new_value) -> void override - { - _storage = new_value; - } - - auto to_ucl() const -> ucl::object override - { - return ucl::string(_storage); - } - - auto from_ucl(ucl::object const &uclobj) -> void override - { - try { - _storage = object_cast<ucl::string>(uclobj).value(); - is_default(false); - } catch (ucl::type_mismatch const &exc) { - throw error(std::format("'{}': expected string, not {}", - name(), str(exc.actual_type()))); - } - } + auto to_ucl() const -> ucl::object override; + auto from_ucl(ucl::object const &uclobj) -> void override; private: std::string &_storage; diff --git a/nihil.ucl/CMakeLists.txt b/nihil.ucl/CMakeLists.txt index cb051a3..0ee024c 100644 --- a/nihil.ucl/CMakeLists.txt +++ b/nihil.ucl/CMakeLists.txt @@ -4,8 +4,9 @@ pkg_check_modules(LIBUCL REQUIRED libucl) add_library(nihil.ucl STATIC) target_link_libraries(nihil.ucl PUBLIC nihil) -target_sources(nihil.ucl PUBLIC - FILE_SET modules TYPE CXX_MODULES FILES + +target_sources(nihil.ucl + PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.ucl.ccm emit.ccm error.ccm @@ -20,6 +21,18 @@ target_sources(nihil.ucl PUBLIC map.ccm real.ccm string.ccm + + PRIVATE + emit.cc + error.cc + parser.cc + type.cc + + object.cc + boolean.cc + integer.cc + real.cc + string.cc ) target_compile_options(nihil.ucl PUBLIC ${LIBUCL_CFLAGS_OTHER}) diff --git a/nihil.ucl/boolean.cc b/nihil.ucl/boolean.cc new file mode 100644 index 0000000..95b4e2f --- /dev/null +++ b/nihil.ucl/boolean.cc @@ -0,0 +1,77 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <compare> +#include <cstdlib> + +#include <ucl.h> + +module nihil.ucl; + +namespace nihil::ucl { + +boolean::boolean() : boolean(false) +{ +} + +boolean::boolean(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, uobj) +{ + if (type() != ucl_type) + throw type_mismatch(ucl_type, type()); +} + +boolean::boolean(noref_t, ::ucl_object_t *uobj) + : object(noref, uobj) +{ + if (type() != ucl_type) + throw type_mismatch(ucl_type, type()); +} + +boolean::boolean(contained_type value) + : object(noref, ::ucl_object_frombool(value)) +{ + if (_object == nullptr) + throw error("failed to create UCL object"); +} + +auto boolean::value(this boolean const &self) +-> contained_type +{ + auto v = contained_type{}; + auto const *uobj = self.get_ucl_object(); + + if (::ucl_object_toboolean_safe(uobj, &v)) + return v; + + std::abort(); +} + +auto operator== (boolean const &a, boolean const &b) +-> bool +{ + return a.value() == b.value(); +} + +auto operator<=> (boolean const &a, boolean const &b) +-> std::strong_ordering +{ + return a.value() <=> b.value(); +} + +auto operator== (boolean const &a, boolean::contained_type b) +-> bool +{ + return a.value() == b; +} + +auto operator<=> (boolean const &a, boolean::contained_type b) +-> std::strong_ordering +{ + return a.value() <=> b; +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/boolean.ccm b/nihil.ucl/boolean.ccm index 6a93867..78ede17 100644 --- a/nihil.ucl/boolean.ccm +++ b/nihil.ucl/boolean.ccm @@ -23,70 +23,28 @@ export struct boolean final : object { inline static constexpr object_type ucl_type = object_type::boolean; // Create a new boolean from a UCL object. - boolean(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, uobj) - { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); - } - - boolean(noref_t, ::ucl_object_t *uobj) - : object(noref, uobj) - { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); - } + boolean(ref_t, ::ucl_object_t const *uobj); + boolean(noref_t, ::ucl_object_t *uobj); // Create a new default-initialised boolean. - boolean() : boolean(false) {} + boolean(); // Create a new boolean from a value. - explicit boolean(contained_type value) - : object(noref, ::ucl_object_frombool(value)) - { - if (_object == nullptr) - throw error("failed to create UCL object"); - } + explicit boolean(contained_type value); // Return this object's value. - auto value(this boolean const &self) -> contained_type - { - auto v = contained_type{}; - auto const *uobj = self.get_ucl_object(); - - if (::ucl_object_toboolean_safe(uobj, &v)) - return v; - - std::abort(); - } + auto value(this boolean const &self) -> contained_type; }; /* * Comparison operators. */ -export auto operator== (boolean const &a, boolean const &b) - -> bool -{ - return a.value() == b.value(); -} - +export auto operator== (boolean const &a, boolean const &b) -> bool; +export auto operator== (boolean const &a, boolean::contained_type b) -> bool; export auto operator<=> (boolean const &a, boolean const &b) - -> std::strong_ordering -{ - return a.value() <=> b.value(); -} - -export auto operator== (boolean const &a, boolean::contained_type b) - -> bool -{ - return a.value() == b; -} - + -> std::strong_ordering; export auto operator<=> (boolean const &a, boolean::contained_type b) - -> std::strong_ordering -{ - return a.value() <=> b; -} + -> std::strong_ordering; } // namespace nihil::ucl diff --git a/nihil.ucl/emit.cc b/nihil.ucl/emit.cc new file mode 100644 index 0000000..480ddd8 --- /dev/null +++ b/nihil.ucl/emit.cc @@ -0,0 +1,21 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <iostream> +#include <iterator> + +module nihil.ucl; + +namespace nihil::ucl { + +auto operator<<(std::ostream &stream, object const &o) +-> std::ostream & +{ + emit(o, emitter::json, std::ostream_iterator<char>(stream)); + return stream; +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/emit.ccm b/nihil.ucl/emit.ccm index c4aa79b..849c5a7 100644 --- a/nihil.ucl/emit.ccm +++ b/nihil.ucl/emit.ccm @@ -9,7 +9,7 @@ module; #include <cstdlib> #include <format> #include <iterator> -#include <iostream> +#include <iosfwd> #include <span> #include <string> #include <utility> @@ -34,8 +34,8 @@ export enum struct emitter { * * We can't throw exceptions here since we're called from C code. The emit * functions return an integer value, but it's not really clear what this is - * for (for example, returning errors?) and the C API seems to mostly ignore - * it. So, we just eat errors and keep going. + * for and the C API seems to mostly ignore it. So, we just eat errors and + * keep going. */ template<std::output_iterator<char> Iterator> struct emit_wrapper { @@ -130,7 +130,7 @@ private: export auto emit(object const &object, emitter format, std::output_iterator<char> auto &&it) - -> void +-> void { auto ucl_format = static_cast<ucl_emitter>(format); auto wrapper = emit_wrapper(it); @@ -144,12 +144,7 @@ export auto emit(object const &object, emitter format, * Basic ostream printer for UCL; default to JSON since it's probably what * most people expect. */ -export auto operator<<(std::ostream &stream, object const &o) - -> std::ostream & -{ - emit(o, emitter::json, std::ostream_iterator<char>(stream)); - return stream; -} +export auto operator<<(std::ostream &stream, object const &o) -> std::ostream &; } // namespace nihil::ucl diff --git a/nihil.ucl/error.cc b/nihil.ucl/error.cc new file mode 100644 index 0000000..2f19cb7 --- /dev/null +++ b/nihil.ucl/error.cc @@ -0,0 +1,19 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <string> +#include <utility> + +module nihil.ucl; + +namespace nihil::ucl { + +error::error(std::string what) + : generic_error(std::move(what)) +{ +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/error.ccm b/nihil.ucl/error.ccm index c6a0f2d..cdb5c2b 100644 --- a/nihil.ucl/error.ccm +++ b/nihil.ucl/error.ccm @@ -4,8 +4,7 @@ module; -#include <format> -#include <utility> +#include <string> export module nihil.ucl:error; @@ -17,7 +16,7 @@ namespace nihil::ucl { * Exception thrown when an issue occurs with UCL. */ export struct error : generic_error { - error(std::string what) : generic_error(std::move(what)) {} + error(std::string what); }; } // namespace nihil::ucl diff --git a/nihil.ucl/integer.cc b/nihil.ucl/integer.cc new file mode 100644 index 0000000..16328d4 --- /dev/null +++ b/nihil.ucl/integer.cc @@ -0,0 +1,76 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <compare> +#include <cstdlib> + +#include <ucl.h> + +module nihil.ucl; + +namespace nihil::ucl { + +integer::integer(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, uobj) +{ + if (type() != ucl_type) + throw type_mismatch(ucl_type, type()); +} + +integer::integer(noref_t, ::ucl_object_t *uobj) + : object(noref, uobj) +{ + if (type() != ucl_type) + throw type_mismatch(ucl_type, type()); +} + +integer::integer() + : integer(0) +{} + +integer::integer(contained_type value) + : object(noref, ::ucl_object_fromint(value)) +{ + if (_object == nullptr) + throw error("failed to create UCL object"); +} + +auto integer::value(this integer const &self) -> contained_type +{ + auto v = contained_type{}; + auto const *uobj = self.get_ucl_object(); + + if (::ucl_object_toint_safe(uobj, &v)) + return v; + + std::abort(); +} + +auto operator== (integer const &a, integer const &b) +-> bool +{ + return a.value() == b.value(); +} + +auto operator<=> (integer const &a, integer const &b) +-> std::strong_ordering +{ + return a.value() <=> b.value(); +} + +auto operator== (integer const &a, integer::contained_type b) +-> bool +{ + return a.value() == b; +} + +auto operator<=> (integer const &a, integer::contained_type b) +-> std::strong_ordering +{ + return a.value() <=> b; +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/integer.ccm b/nihil.ucl/integer.ccm index 482a716..e43ae8b 100644 --- a/nihil.ucl/integer.ccm +++ b/nihil.ucl/integer.ccm @@ -4,10 +4,9 @@ module; -#include <cassert> +#include <compare> #include <cstdint> #include <cstdlib> -#include <string> #include <ucl.h> @@ -22,72 +21,28 @@ export struct integer final : object { inline static constexpr object_type ucl_type = object_type::integer; // Create a new integer from a UCL object. - integer(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, uobj) - { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); - } - - integer(noref_t, ::ucl_object_t *uobj) - : object(noref, uobj) - { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); - } + integer(ref_t, ::ucl_object_t const *uobj); + integer(noref_t, ::ucl_object_t *uobj); // Create a new default-initialised integer. - integer() - : integer(0) - {} + integer(); // Create a new integer from a value. - explicit integer(contained_type value) - : object(noref, ::ucl_object_fromint(value)) - { - if (_object == nullptr) - throw error("failed to create UCL object"); - } + explicit integer(contained_type value); // Return the value of this object. - auto value(this integer const &self) -> contained_type - { - auto v = contained_type{}; - auto const *uobj = self.get_ucl_object(); - - if (::ucl_object_toint_safe(uobj, &v)) - return v; - - std::abort(); - } + auto value(this integer const &self) -> contained_type; }; /* * Comparison operators. */ -export auto operator== (integer const &a, integer const &b) - -> bool -{ - return a.value() == b.value(); -} - +export auto operator== (integer const &a, integer const &b) -> bool; +export auto operator== (integer const &a, integer::contained_type b) -> bool; export auto operator<=> (integer const &a, integer const &b) - -> std::strong_ordering -{ - return a.value() <=> b.value(); -} - -export auto operator== (integer const &a, integer::contained_type b) - -> bool -{ - return a.value() == b; -} - + -> std::strong_ordering; export auto operator<=> (integer const &a, integer::contained_type b) - -> std::strong_ordering -{ - return a.value() <=> b; -} + -> std::strong_ordering; } // namespace nihil::ucl diff --git a/nihil.ucl/object.cc b/nihil.ucl/object.cc new file mode 100644 index 0000000..f435b90 --- /dev/null +++ b/nihil.ucl/object.cc @@ -0,0 +1,123 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <cstdlib> +#include <string> +#include <utility> + +#include <ucl.h> + +module nihil.ucl; + +namespace nihil::ucl { + +object::object(ref_t, ::ucl_object_t const *object) + : _object(::ucl_object_ref(object)) +{ +} + +object::object(noref_t, ::ucl_object_t *object) + : _object(object) +{ +} + +object::~object() { + if (_object != nullptr) + ::ucl_object_unref(_object); +} + +object::object(object &&other) noexcept + : _object(std::exchange(other._object, nullptr)) +{} + +object::object(object const &other) noexcept + : _object(nullptr) +{ + *this = other; +} + +auto object::operator=(this object &self, object &&other) noexcept + -> object & +{ + if (&self != &other) + self._object = std::exchange(other._object, nullptr); + return self; +} + +auto object::operator=(this object &self, object const &other) + -> object & +{ + if (&self != &other) { + auto *new_uobj = ::ucl_object_copy(other.get_ucl_object()); + if (new_uobj == nullptr) + throw error("failed to copy UCL object"); + + if (self._object != nullptr) + ::ucl_object_unref(self._object); + self._object = new_uobj; + } + + return self; +} + +auto object::ref(this object const &self) -> object +{ + return object(nihil::ucl::ref, self.get_ucl_object()); +} + +auto object::type(this object const &self) -> object_type +{ + auto utype = ::ucl_object_type(self.get_ucl_object()); + return static_cast<object_type>(utype); +} + +auto object::get_ucl_object(this object &self) -> ::ucl_object_t * +{ + if (self._object == nullptr) + throw error("attempt to access empty UCL object"); + return self._object; +} + +auto object::get_ucl_object(this object const &self) -> ::ucl_object_t const * +{ + if (self._object == nullptr) + throw error("attempt to access empty UCL object"); + return self._object; +} + +// Return the key of this object. +auto object::key(this object const &self) -> std::string_view +{ + auto dlen = std::size_t{}; + auto const *dptr = ::ucl_object_keyl(self.get_ucl_object(), + &dlen); + return {dptr, dlen}; +} + +auto swap(object &a, object &b) -> void +{ + std::swap(a._object, b._object); +} + +auto operator<=>(object const &lhs, object const &rhs) -> std::strong_ordering +{ + auto cmp = ::ucl_object_compare(lhs.get_ucl_object(), + rhs.get_ucl_object()); + + if (cmp < 0) + return std::strong_ordering::less; + else if (cmp > 0) + return std::strong_ordering::greater; + else + return std::strong_ordering::equal; +} + +auto operator==(object const &lhs, object const &rhs) -> bool +{ + return (lhs <=> rhs) == std::strong_ordering::equal; +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/object.ccm b/nihil.ucl/object.ccm index b220335..5becfa8 100644 --- a/nihil.ucl/object.ccm +++ b/nihil.ucl/object.ccm @@ -10,12 +10,9 @@ module; * */ -#include <algorithm> -#include <cassert> +#include <compare> #include <cstddef> -#include <format> #include <string> -#include <utility> #include <ucl.h> @@ -44,103 +41,40 @@ export struct object { // Create an object from an existing ucl_object_t. The first argument // determines whether we ref the object or not. - object(ref_t, ::ucl_object_t const *object) - : _object(::ucl_object_ref(object)) - { - } - - object(noref_t, ::ucl_object_t *object) - : _object(object) - { - } + object(ref_t, ::ucl_object_t const *object); + object(noref_t, ::ucl_object_t *object); // Free our object on destruction. - virtual ~object() { - if (_object != nullptr) - ::ucl_object_unref(_object); - } + virtual ~object(); // Movable. - object(object &&other) noexcept - : _object(std::exchange(other._object, nullptr)) - {} - - auto operator=(this object &self, object &&other) noexcept - -> object & - { - if (&self != &other) - self._object = std::exchange(other._object, nullptr); - return self; - } + object(object &&other) noexcept; + auto operator=(this object &self, object &&other) noexcept -> object&; // Copyable. - object(object const &other) noexcept - : _object(nullptr) - { - *this = other; - } - - auto operator=(this object &self, object const &other) - -> object & - { - if (&self != &other) { - auto *new_uobj = ::ucl_object_copy(other.get_ucl_object()); - if (new_uobj == nullptr) - throw error("failed to copy UCL object"); - - if (self._object != nullptr) - ::ucl_object_unref(self._object); - self._object = new_uobj; - } - - return self; - } + // Note that this copies the entire UCL object. + object(object const &other) noexcept; + auto operator=(this object &self, object const &other) -> object &; // Increase the refcount of this object. - auto ref(this object const &self) -> object - { - return object(nihil::ucl::ref, self.get_ucl_object()); - } + auto ref(this object const &self) -> object; // Return the type of this object. - auto type(this object const &self) -> object_type - { - auto utype = ::ucl_object_type(self.get_ucl_object()); - return static_cast<object_type>(utype); - } + auto type(this object const &self) -> object_type; // Return the underlying object. - auto get_ucl_object(this object &self) -> ::ucl_object_t * - { - if (self._object == nullptr) - throw error("attempt to access empty UCL object"); - return self._object; - } - - auto get_ucl_object(this object const &self) -> ::ucl_object_t const * - { - if (self._object == nullptr) - throw error("attempt to access empty UCL object"); - return self._object; - } + auto get_ucl_object(this object &self) -> ::ucl_object_t *; + + auto get_ucl_object(this object const &self) -> ::ucl_object_t const *; // Return the key of this object. - auto key(this object const &self) -> std::string_view - { - auto dlen = std::size_t{}; - auto const *dptr = ::ucl_object_keyl(self.get_ucl_object(), - &dlen); - return {dptr, dlen}; - } + auto key(this object const &self) -> std::string_view; protected: // The object we're wrapping. ::ucl_object_t *_object = nullptr; - friend auto swap(object &a, object &b) -> void - { - std::swap(a._object, b._object); - } + friend auto swap(object &a, object &b) -> void; private: @@ -152,104 +86,8 @@ private: * Object comparison. */ +export auto operator==(object const &lhs, object const &rhs) -> bool; export auto operator<=>(object const &lhs, object const &rhs) - -> std::strong_ordering -{ - auto cmp = ::ucl_object_compare(lhs.get_ucl_object(), - rhs.get_ucl_object()); - - if (cmp < 0) - return std::strong_ordering::less; - else if (cmp > 0) - return std::strong_ordering::greater; - else - return std::strong_ordering::equal; -} - -export auto operator==(object const &lhs, object const &rhs) -> bool -{ - return (lhs <=> rhs) == std::strong_ordering::equal; -} - -/*********************************************************************** - * Object iteration. - */ - -export struct iterator { - using difference_type = std::ptrdiff_t; - using value_type = object; - using reference = value_type &; - using const_reference = value_type const &; - using pointer = value_type *; - using const_pointer = value_type const *; - - struct sentinel{}; - - explicit iterator(object const &obj) - { - _state = std::make_shared<state>(obj); - ++(*this); - } - - auto operator==(this iterator const &self, sentinel) -> bool - { - return (self._state->cur == nullptr); - } - - auto operator++(this iterator &self) -> iterator & - { - self._state->next(); - return self; - } - - auto operator++(this iterator &self, int) -> iterator & - { - self._state->next(); - return self; - } - - auto operator*(this iterator const &self) -> object { - return object(ref, self._state->cur); - } - -private: - struct state { - state(object const &obj) - { - auto const *uobj = obj.get_ucl_object(); - if ((iter = ::ucl_object_iterate_new(uobj)) == nullptr) - throw error("failed to create UCL iterator"); - } - - state(state const &) = delete; - auto operator=(this state &, state const &) -> state& = delete; - - ~state() { - if (iter != nullptr) - ::ucl_object_iterate_free(iter); - } - - auto next() -> void { - cur = ::ucl_object_iterate_safe(iter, true); - } - - ucl_object_iter_t iter = nullptr; - ucl_object_t const *cur = nullptr; - }; - - std::shared_ptr<state> _state; -}; - -static_assert(std::input_iterator<iterator>); - -export auto begin(object const &o) -> iterator -{ - return iterator(o); -} - -export auto end(object const &) -> iterator::sentinel -{ - return {}; -} + -> std::strong_ordering; } // namespace nihil::ucl diff --git a/nihil.ucl/parser.cc b/nihil.ucl/parser.cc new file mode 100644 index 0000000..816116d --- /dev/null +++ b/nihil.ucl/parser.cc @@ -0,0 +1,75 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <functional> +#include <string> + +#include <ucl.h> + +module nihil.ucl; + +namespace nihil::ucl { + +parse_error::parse_error(std::string what) + : error(std::move(what)) +{ +} + +auto macro_handler::handle(unsigned char const *data, std::size_t len, void *ud) +-> bool +{ + auto handler = static_cast<macro_handler *>(ud); + auto string = std::string_view( + reinterpret_cast<char const *>(data), + len); + return handler->callback(string); +} + +parser::parser(int flags) +{ + if ((_parser = ::ucl_parser_new(flags)) != nullptr) + return; + + throw error("failed to create UCL parser"); +} + +parser::parser() + : parser(0) +{ +} + +parser::~parser() +{ + if (_parser) + ::ucl_parser_free(_parser); +} + +auto parser::register_value( + this parser &self, + std::string_view variable, + std::string_view value) +-> void +{ + ::ucl_parser_register_variable( + self._parser, + std::string(variable).c_str(), + std::string(value).c_str()); +} + +auto parser::top(this parser &self) -> map<object> +{ + if (self._parser == nullptr) + throw error("attempt to call top() on an empty parser"); + + auto obj = ::ucl_parser_get_object(self._parser); + if (obj == nullptr) + throw error("attempt to call top() on an empty parser"); + + // ucl_parser_get_objects() refs the object for us. + return {noref, obj}; +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/parser.ccm b/nihil.ucl/parser.ccm index 9b87773..f817b76 100644 --- a/nihil.ucl/parser.ccm +++ b/nihil.ucl/parser.ccm @@ -25,7 +25,7 @@ namespace nihil::ucl { * Exception thrown when an issue occurs parsing UCL. */ export struct parse_error : error { - parse_error(std::string what) : error(std::move(what)) {} + parse_error(std::string what); }; // UCL parser flags. @@ -33,8 +33,6 @@ export inline constexpr int parser_key_lower = UCL_PARSER_KEY_LOWERCASE; export inline constexpr int parser_zerocopy = UCL_PARSER_ZEROCOPY; export inline constexpr int parser_no_time = UCL_PARSER_NO_TIME; -export struct parser; - // A macro handler. This proxies the C API callback to the C++ API. using macro_callback_t = bool (std::string_view); @@ -42,39 +40,25 @@ struct macro_handler { std::function<macro_callback_t> callback; // Handle a callback from the C API. - static auto handle(unsigned char const *data, std::size_t len, void *ud) - -> bool - { - auto handler = static_cast<macro_handler *>(ud); - auto string = std::string_view( - reinterpret_cast<char const *>(data), - len); - return handler->callback(string); - } + static auto handle( + unsigned char const *data, + std::size_t len, void + *ud) + -> bool; }; /* * A UCL parser. This wraps the C ucl_parser API. */ export struct parser { - // Create a new parser with the given flags. - parser(int flags) { - if ((_parser = ::ucl_parser_new(flags)) != nullptr) - return; - - throw error("failed to create UCL parser"); - } + parser(int flags); // Create a new parser with the default flags. - parser() : parser(0) {} + parser(); // Destroy our parser when we're destroyed. - ~parser() - { - if (_parser) - ::ucl_parser_free(_parser); - } + ~parser(); // Add a parser macro. Unlike ucl_parser_register_macro, this doesn't // take a userdata parameter; it's assumed the user will use lambda @@ -99,17 +83,12 @@ export struct parser { // Add a parser variable. auto register_value(this parser &self, std::string_view variable, - std::string_view value) -> void - { - ::ucl_parser_register_variable(self._parser, - std::string(variable).c_str(), - std::string(value).c_str()); - } + std::string_view value) -> void; // Add data to the parser. auto add(this parser &self, std::ranges::contiguous_range auto &&data) - -> void + -> void // Only bytes (chars) are permitted. requires(sizeof(std::ranges::range_value_t<decltype(data)>) == 1) { @@ -125,7 +104,7 @@ export struct parser { } auto add(this parser &self, std::ranges::range auto &&data) - -> void + -> void requires (!std::ranges::contiguous_range<decltype(data)>) { auto cdata = std::vector<char>( @@ -135,18 +114,7 @@ export struct parser { } // Return the top object of this parser. - auto top(this parser &self) -> map<object> - { - if (self._parser == nullptr) - throw error("attempt to call top() on an empty parser"); - - auto obj = ::ucl_parser_get_object(self._parser); - if (obj == nullptr) - throw error("attempt to call top() on an empty parser"); - - // ucl_parser_get_objects() refs the object for us. - return {noref, obj}; - } + auto top(this parser &self) -> map<object>; private: // The parser object. Should never be null, unless we've been diff --git a/nihil.ucl/real.cc b/nihil.ucl/real.cc new file mode 100644 index 0000000..b371072 --- /dev/null +++ b/nihil.ucl/real.cc @@ -0,0 +1,79 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <cassert> +#include <compare> +#include <cstdlib> +#include <string> + +#include <ucl.h> + +module nihil.ucl; + +namespace nihil::ucl { + +real::real(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, uobj) +{ + if (type() != ucl_type) + throw type_mismatch(ucl_type, type()); +} + +real::real(noref_t, ::ucl_object_t *uobj) + : object(noref, uobj) +{ + if (type() != ucl_type) + throw type_mismatch(ucl_type, type()); +} + +real::real() + : real(0) +{ +} + +real::real(contained_type value) + : object(noref, ::ucl_object_fromdouble(value)) +{ + if (_object == nullptr) + throw error("failed to create UCL object"); +} + +auto real::value(this real const &self) -> contained_type +{ + auto v = contained_type{}; + auto const *uobj = self.get_ucl_object(); + + if (::ucl_object_todouble_safe(uobj, &v)) + return v; + + std::abort(); +} + +auto operator== (real const &a, real const &b) +-> bool +{ + return a.value() == b.value(); +} + +auto operator<=> (real const &a, real const &b) +-> std::partial_ordering +{ + return a.value() <=> b.value(); +} + +auto operator== (real const &a, real::contained_type b) +-> bool +{ + return a.value() == b; +} + +auto operator<=> (real const &a, real::contained_type b) +-> std::partial_ordering +{ + return a.value() <=> b; +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/real.ccm b/nihil.ucl/real.ccm index 260e993..e0ecbb5 100644 --- a/nihil.ucl/real.ccm +++ b/nihil.ucl/real.ccm @@ -4,9 +4,7 @@ module; -#include <cassert> -#include <cstdlib> -#include <string> +#include <compare> #include <ucl.h> @@ -22,72 +20,29 @@ export struct real final : object { inline static constexpr object_type ucl_type = object_type::real; // Create a new real from a UCL object. - real(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, uobj) - { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); - } - - real(noref_t, ::ucl_object_t *uobj) - : object(noref, uobj) - { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); - } + real(ref_t, ::ucl_object_t const *uobj); + real(noref_t, ::ucl_object_t *uobj); // Create a default-initialised real. - real() - : real(0) - {} + real(); // Create a new real from a value. - explicit real(contained_type value) - : object(noref, ::ucl_object_fromdouble(value)) - { - if (_object == nullptr) - throw error("failed to create UCL object"); - } + explicit real(contained_type value); // Return the value of this real. - auto value(this real const &self) -> contained_type - { - auto v = contained_type{}; - auto const *uobj = self.get_ucl_object(); - - if (::ucl_object_todouble_safe(uobj, &v)) - return v; - - std::abort(); - } + auto value(this real const &self) -> contained_type; }; /* * Comparison operators. */ -export auto operator== (real const &a, real const &b) - -> bool -{ - return a.value() == b.value(); -} +export auto operator== (real const &a, real const &b) -> bool; +export auto operator== (real const &a, real::contained_type b) -> bool; export auto operator<=> (real const &a, real const &b) - -> std::partial_ordering -{ - return a.value() <=> b.value(); -} - -export auto operator== (real const &a, real::contained_type b) - -> bool -{ - return a.value() == b; -} - + -> std::partial_ordering; export auto operator<=> (real const &a, real::contained_type b) - -> std::partial_ordering -{ - return a.value() <=> b; -} + -> std::partial_ordering; } // namespace nihil::ucl diff --git a/nihil.ucl/string.cc b/nihil.ucl/string.cc new file mode 100644 index 0000000..d2f4618 --- /dev/null +++ b/nihil.ucl/string.cc @@ -0,0 +1,139 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <cstdlib> +#include <string> + +#include <ucl.h> + +module nihil.ucl; + +namespace nihil::ucl { + +string::string(ref_t, ::ucl_object_t const *uobj) + : object(nihil::ucl::ref, uobj) +{ + if (type() != ucl_type) + throw type_mismatch(ucl_type, type()); +} + +string::string(noref_t, ::ucl_object_t *uobj) + : object(noref, uobj) +{ + if (type() != ucl_type) + throw type_mismatch(ucl_type, type()); +} + +string::string() + : string(std::string_view("")) +{} + +string::string(std::string_view value) + : object(nihil::ucl::ref, + ::ucl_object_fromstring_common( + value.data(), value.size(), + UCL_STRING_RAW)) +{ + if (_object == nullptr) + throw error("failed to create UCL object"); +} + +auto string::value(this string const &self) -> contained_type +{ + char const *dptr{}; + std::size_t dlen; + + auto const *uobj = self.get_ucl_object(); + if (::ucl_object_tolstring_safe(uobj, &dptr, &dlen)) + return {dptr, dlen}; + + // This should never fail. + std::abort(); +} + +auto string::size(this string const &self) -> size_type +{ + return self.value().size(); +} + +auto string::empty(this string const &self) -> bool +{ + return self.size() == 0; +} + +auto string::data(this string const &self) -> pointer +{ + char const *dptr{}; + + auto const *uobj = self.get_ucl_object(); + if (::ucl_object_tostring_safe(uobj, &dptr)) + return dptr; + + // This should never fail. + std::abort(); +} + +auto string::begin(this string const &self) -> iterator +{ + return self.data(); +} + +auto string::end(this string const &self) -> iterator +{ + return self.data() + self.size(); +} + +auto operator== (string const &a, string const &b) + -> bool +{ + return a.value() == b.value(); +} + +auto operator<=> (string const &a, string const &b) + -> std::strong_ordering +{ + return a.value() <=> b.value(); +} + +/* + * For convenience, allow comparison with C++ strings without having to + * construct a temporary UCL object. + */ + +auto operator==(string const &lhs, std::string_view rhs) -> bool +{ + return lhs.value() == rhs; +} + +auto operator<=>(string const &lhs, std::string_view rhs) + -> std::strong_ordering +{ + return lhs.value() <=> rhs; +} + +auto operator==(string const &lhs, std::string const &rhs) -> bool +{ + return lhs == std::string_view(rhs); +} + +auto operator<=>(string const &lhs, std::string const &rhs) + -> std::strong_ordering +{ + return lhs <=> std::string_view(rhs); +} + +auto operator==(string const &lhs, char const *rhs) -> bool +{ + return lhs == std::string_view(rhs); +} + +auto operator<=>(string const &lhs, char const *rhs) + -> std::strong_ordering +{ + return lhs <=> std::string_view(rhs); +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/string.ccm b/nihil.ucl/string.ccm index f92c82c..f8dc1cd 100644 --- a/nihil.ucl/string.ccm +++ b/nihil.ucl/string.ccm @@ -4,7 +4,6 @@ module; -#include <cassert> #include <cstdlib> #include <string> @@ -28,35 +27,14 @@ export struct string final : object { using iterator = pointer; // Create a new string from a UCL object. - string(ref_t, ::ucl_object_t const *uobj) - : object(nihil::ucl::ref, uobj) - { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); - } - - string(noref_t, ::ucl_object_t *uobj) - : object(noref, uobj) - { - if (type() != ucl_type) - throw type_mismatch(ucl_type, type()); - } + string(ref_t, ::ucl_object_t const *uobj); + string(noref_t, ::ucl_object_t *uobj); // Create a new empty string. - string() - : string(std::string_view("")) - {} + string(); // Create a new UCL string from a string. - explicit string(std::string_view value) - : object(nihil::ucl::ref, - ::ucl_object_fromstring_common( - value.data(), value.size(), - UCL_STRING_RAW)) - { - if (_object == nullptr) - throw error("failed to create UCL object"); - } + explicit string(std::string_view value); // Create a new UCL string from an iterator pair. template<std::contiguous_iterator Iterator> @@ -77,108 +55,44 @@ export struct string final : object { {} // Return the value of this string. - auto value(this string const &self) -> contained_type - { - char const *dptr{}; - std::size_t dlen; - - auto const *uobj = self.get_ucl_object(); - if (::ucl_object_tolstring_safe(uobj, &dptr, &dlen)) - return {dptr, dlen}; - - // This should never fail. - std::abort(); - } + auto value(this string const &self) -> contained_type; // Return the size of this string. - auto size(this string const &self) -> size_type - { - return self.value().size(); - } + auto size(this string const &self) -> size_type; // Test if this string is empty. - auto empty(this string const &self) -> bool - { - return self.size() == 0; - } + auto empty(this string const &self) -> bool; // Access this string's data - auto data(this string const &self) -> pointer - { - char const *dptr{}; - - auto const *uobj = self.get_ucl_object(); - if (::ucl_object_tostring_safe(uobj, &dptr)) - return dptr; - - // This should never fail. - std::abort(); - } + auto data(this string const &self) -> pointer; // Iterator access - auto begin(this string const &self) -> iterator - { - return self.data(); - } - - auto end(this string const &self) -> iterator - { - return self.data() + self.size(); - } + auto begin(this string const &self) -> iterator; + auto end(this string const &self) -> iterator; }; /* * Comparison operators. */ -export auto operator== (string const &a, string const &b) - -> bool -{ - return a.value() == b.value(); -} - +export auto operator== (string const &a, string const &b) -> bool; export auto operator<=> (string const &a, string const &b) - -> std::strong_ordering -{ - return a.value() <=> b.value(); -} + -> std::strong_ordering; /* * For convenience, allow comparison with C++ strings without having to * construct a temporary UCL object. */ -export auto operator==(string const &lhs, std::string_view rhs) -> bool -{ - return lhs.value() == rhs; -} +export auto operator==(string const &lhs, std::string_view rhs) -> bool; +export auto operator==(string const &lhs, std::string const &rhs) -> bool; +export auto operator==(string const &lhs, char const *rhs) -> bool; export auto operator<=>(string const &lhs, std::string_view rhs) - -> std::strong_ordering -{ - return lhs.value() <=> rhs; -} - -export auto operator==(string const &lhs, std::string const &rhs) -> bool -{ - return lhs == std::string_view(rhs); -} - + -> std::strong_ordering; export auto operator<=>(string const &lhs, std::string const &rhs) - -> std::strong_ordering -{ - return lhs <=> std::string_view(rhs); -} - -export auto operator==(string const &lhs, char const *rhs) -> bool -{ - return lhs == std::string_view(rhs); -} - + -> std::strong_ordering; export auto operator<=>(string const &lhs, char const *rhs) - -> std::strong_ordering -{ - return lhs <=> std::string_view(rhs); -} + -> std::strong_ordering; } // namespace nihil::ucl diff --git a/nihil.ucl/type.cc b/nihil.ucl/type.cc new file mode 100644 index 0000000..a008aa3 --- /dev/null +++ b/nihil.ucl/type.cc @@ -0,0 +1,62 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <format> + +module nihil.ucl; + +namespace nihil::ucl { + +auto str(object_type type) -> std::string_view { + using namespace std::literals; + + switch (type) { + case object_type::object: + return "object"sv; + case object_type::array: + return "array"sv; + case object_type::integer: + return "integer"sv; + case object_type::real: + return "real"sv; + case object_type::string: + return "string"sv; + case object_type::boolean: + return "boolean"sv; + case object_type::time: + return "time"sv; + case object_type::userdata: + return "userdata"sv; + case object_type::null: + return "null"sv; + default: + // Don't fail here, since UCL might add more types that we + // don't know about. + return "unknown"sv; + } +} + +type_mismatch::type_mismatch( + object_type expected_type, object_type actual_type) + : error(std::format("UCL type mismatch: expected type '{}' " + "!= actual type '{}'", + str(expected_type), str(actual_type))) + , _expected_type(expected_type) + , _actual_type(actual_type) +{ +} + +auto type_mismatch::expected_type(this type_mismatch const &self) -> object_type +{ + return self._expected_type; +} + +auto type_mismatch::actual_type(this type_mismatch const &self) -> object_type +{ + return self._actual_type; +} + +} // namespace nihil::ucl diff --git a/nihil.ucl/type.ccm b/nihil.ucl/type.ccm index bf6c6bc..088d196 100644 --- a/nihil.ucl/type.ccm +++ b/nihil.ucl/type.ccm @@ -30,34 +30,7 @@ export enum struct object_type { }; // Get the name of a type. -export auto str(object_type type) -> std::string_view { - using namespace std::literals; - - switch (type) { - case object_type::object: - return "object"sv; - case object_type::array: - return "array"sv; - case object_type::integer: - return "integer"sv; - case object_type::real: - return "real"sv; - case object_type::string: - return "string"sv; - case object_type::boolean: - return "boolean"sv; - case object_type::time: - return "time"sv; - case object_type::userdata: - return "userdata"sv; - case object_type::null: - return "null"sv; - default: - // Don't fail here, since UCL might add more types that we - // don't know about. - return "unknown"sv; - } -} +export auto str(object_type type) -> std::string_view; // Concept of a UCL data type. export template<typename T> @@ -69,25 +42,12 @@ concept datatype = requires(T o) { // Exception thrown when a type assertion fails. export struct type_mismatch : error { - type_mismatch(object_type expected_type, object_type actual_type) - : error(std::format("UCL type mismatch: expected type '{}' " - "!= actual type '{}'", - str(expected_type), str(actual_type))) - , _expected_type(expected_type) - , _actual_type(actual_type) - {} + type_mismatch(object_type expected_type, object_type actual_type); // The type we expected. - auto expected_type(this type_mismatch const &self) -> object_type - { - return self._expected_type; - } - + auto expected_type(this type_mismatch const &self) -> object_type; // The type we got. - auto actual_type(this type_mismatch const &self) -> object_type - { - return self._actual_type; - } + auto actual_type(this type_mismatch const &self) -> object_type; private: object_type _expected_type; diff --git a/nihil/CMakeLists.txt b/nihil/CMakeLists.txt index 6406294..6dcec2c 100644 --- a/nihil/CMakeLists.txt +++ b/nihil/CMakeLists.txt @@ -1,8 +1,8 @@ # This source code is released into the public domain. add_library(nihil STATIC) -target_sources(nihil PUBLIC - FILE_SET modules TYPE CXX_MODULES FILES +target_sources(nihil + PUBLIC FILE_SET modules TYPE CXX_MODULES FILES nihil.ccm argv.ccm command_map.ccm @@ -25,6 +25,14 @@ target_sources(nihil PUBLIC tabulate.ccm usage_error.ccm write_file.ccm + + PRIVATE + exec.cc + fd.cc + find_in_path.cc + getenv.cc + open_file.cc + process.cc ) if(NIHIL_TESTS) diff --git a/nihil/exec.cc b/nihil/exec.cc new file mode 100644 index 0000000..f29122a --- /dev/null +++ b/nihil/exec.cc @@ -0,0 +1,75 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <format> +#include <string> +#include <utility> + +#include <err.h> +#include <fcntl.h> +#include <unistd.h> + +extern char **environ; + +module nihil; + +namespace nihil { + +exec_error::exec_error(std::string what) + : generic_error(std::move(what)) +{} + +executable_not_found::executable_not_found(std::string_view filename) + : exec_error(std::format("{}: command not found", filename)) + , _filename(filename) +{ +} + +auto executable_not_found::filename(this executable_not_found const &self) +-> std::string_view +{ + return self._filename; +} + +fexecv::fexecv(fd &&execfd, argv &&args) noexcept + : _execfd(std::move(execfd)) + , _args(std::move(args)) +{ +} + +auto fexecv::exec(this fexecv &self) noexcept +-> void +{ + ::fexecve(self._execfd.get(), self._args.data(), environ); + ::err(1, "fexecve"); +} + +fexecv::fexecv(fexecv &&) noexcept = default; +auto fexecv::operator=(this fexecv &, fexecv &&) noexcept -> fexecv& = default; + +auto execv(std::string_view path, argv &&argv) -> fexecv +{ + auto cpath = std::string(path); + auto const ret = ::open(cpath.c_str(), O_EXEC); + if (ret == -1) + throw executable_not_found(path); + return {fd(ret), std::move(argv)}; +} + +auto execvp(std::string_view file, argv &&argv) -> fexecv +{ + auto execfd = find_in_path(file); + if (!execfd) + throw executable_not_found(file); + return {std::move(*execfd), std::move(argv)}; +} + +auto shell(std::string_view const &command) -> fexecv +{ + return execl("/bin/sh", "sh", "-c", command); +} + +} // namespace nihil diff --git a/nihil/exec.ccm b/nihil/exec.ccm index f91efdf..7dbf31e 100644 --- a/nihil/exec.ccm +++ b/nihil/exec.ccm @@ -8,15 +8,7 @@ module; * Exec providers, mostly used for spawn(). */ -#include <format> #include <string> -#include <utility> - -#include <err.h> -#include <fcntl.h> -#include <unistd.h> - -extern char **environ; export module nihil:exec; @@ -31,24 +23,16 @@ namespace nihil { * Generic error, what() should be descriptive. */ export struct exec_error : generic_error { - exec_error(std::string what) - : generic_error(std::move(what)) - {} + exec_error(std::string what); }; /* * We tried to execute a path or filename and the file was not found. */ export struct executable_not_found : exec_error { - executable_not_found(std::string_view filename) - : exec_error(std::format("{}: command not found", filename)) - , _filename(filename) - {} - - auto filename(this executable_not_found const &self) -> std::string_view - { - return self._filename; - } + executable_not_found(std::string_view filename); + auto filename(this executable_not_found const &self) + -> std::string_view; private: std::string _filename; @@ -77,20 +61,13 @@ concept executor = export struct fexecv final { using tag = exec_tag; - fexecv(fd &&execfd, argv &&args) noexcept - : _execfd(std::move(execfd)) - , _args(std::move(args)) - {} + fexecv(fd &&execfd, argv &&args) noexcept; - [[noreturn]] auto exec(this fexecv &self) noexcept -> void - { - ::fexecve(self._execfd.get(), self._args.data(), environ); - ::err(1, "fexecve"); - } + [[noreturn]] auto exec(this fexecv &self) noexcept -> void; // Movable - fexecv(fexecv &&) noexcept = default; - auto operator=(this fexecv &, fexecv &&) noexcept -> fexecv& = default; + fexecv(fexecv &&) noexcept; + auto operator=(this fexecv &, fexecv &&) noexcept -> fexecv&; // Not copyable (because we hold the open fd object) fexecv(fexecv const &) = delete; @@ -101,33 +78,18 @@ private: argv _args; }; -static_assert(executor<fexecv>); - /* * execv: equivalent to fexecv(), except the command is passed as * a pathname instead of a file descriptor. Does not search $PATH. */ -export auto execv(std::string_view path, argv &&argv) -> fexecv -{ - auto cpath = std::string(path); - auto const ret = ::open(cpath.c_str(), O_EXEC); - if (ret == -1) - throw executable_not_found(path); - return {fd(ret), std::move(argv)}; -} +export auto execv(std::string_view path, argv &&argv) -> fexecv; /* * execvp: equivalent to fexecv(), except the command is passed as * a filename instead of a file descriptor. If the filename is not * an absolute path, it will be searched for in $PATH. */ -export auto execvp(std::string_view file, argv &&argv) -> fexecv -{ - auto execfd = find_in_path(file); - if (!execfd) - throw executable_not_found(file); - return {std::move(*execfd), std::move(argv)}; -} +export auto execvp(std::string_view file, argv &&argv) -> fexecv; /* * execl: equivalent to execv, except the arguments are passed as a @@ -151,9 +113,6 @@ export auto execlp(std::string_view file, auto &&...args) -> fexecv * shell: run the process by invoking /bin/sh -c with the single argument, * equivalent to system(3). */ -export auto shell(std::string_view const &command) -> fexecv -{ - return execl("/bin/sh", "sh", "-c", command); -} +export auto shell(std::string_view const &command) -> fexecv; } // namespace nihil diff --git a/nihil/fd.cc b/nihil/fd.cc new file mode 100644 index 0000000..e25ba28 --- /dev/null +++ b/nihil/fd.cc @@ -0,0 +1,250 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <fcntl.h> +#include <unistd.h> + +#include <expected> +#include <format> +#include <stdexcept> +#include <system_error> + +module nihil; + +namespace nihil { + +fd_logic_error::fd_logic_error(std::string what) + : std::logic_error(std::move(what)) +{ +} + +fd::fd() noexcept = default; + +fd::fd(int fd_) noexcept + : _fd(fd_) +{ +} + +fd::~fd() +{ + if (*this) + this->close(); +} + +fd::fd(fd &&other) noexcept + : _fd(std::exchange(other._fd, _invalid_fd)) +{ +} + +auto fd::operator=(fd &&other) noexcept -> fd & +{ + if (this != &other) + _fd = std::exchange(other._fd, _invalid_fd); + return *this; +} + +fd::operator bool(this fd const &self) noexcept +{ + return self._fd != _invalid_fd; +} + +auto fd::close(this fd &self) -> std::expected<void, std::error_code> +{ + auto const ret = ::close(self.get()); + self._fd = _invalid_fd; + + if (ret == 0) + return {}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +auto fd::get(this fd const &self) -> int +{ + if (self) + return self._fd; + throw fd_logic_error("Attempt to call get() on invalid fd"); +} + +auto fd::release(this fd &&self) -> int +{ + if (self) + return std::exchange(self._fd, self._invalid_fd); + throw fd_logic_error("Attempt to release an invalid fd"); +} + +auto dup(fd const &self) -> std::expected<fd, std::error_code> +{ + auto thisfd = self.get(); + + auto const newfd = ::dup(thisfd); + if (newfd != -1) + return {newfd}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +auto dup(fd const &self, int newfd) -> std::expected<fd, std::error_code> +{ + auto thisfd = self.get(); + + auto const ret = ::dup2(thisfd, newfd); + if (ret != -1) + return {newfd}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +auto raw_dup(fd const &self) -> std::expected<int, std::error_code> +{ + auto thisfd = self.get(); + + auto const newfd = ::dup(thisfd); + if (newfd != -1) + return {newfd}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +auto raw_dup(fd const &self, int newfd) -> std::expected<int, std::error_code> +{ + auto thisfd = self.get(); + + auto const ret = ::dup2(thisfd, newfd); + if (ret != -1) + return {newfd}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +auto getflags(fd const &self) -> std::expected<int, std::error_code> +{ + auto const flags = ::fcntl(self.get(), F_GETFL); + if (flags != -1) + return {flags}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +auto replaceflags(fd &self, int newflags) + -> std::expected<void, std::error_code> +{ + auto const ret = ::fcntl(self.get(), F_SETFL, newflags); + if (ret == 0) + return {}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +auto setflags(fd &self, int newflags) + -> std::expected<int, std::error_code> +{ + auto flags = getflags(self); + if (!flags) + return flags; + + *flags |= newflags; + auto const ret = replaceflags(self, *flags); + if (!ret) + return std::unexpected(ret.error()); + + return {*flags}; +} + +auto clearflags(fd &self, int clrflags) + -> std::expected<int, std::error_code> +{ + auto flags = getflags(self); + if (!flags) + return flags; + + *flags &= ~clrflags; + auto const ret = replaceflags(self, *flags); + if (!ret) + return std::unexpected(ret.error()); + + return {*flags}; +} + +auto getfdflags(fd const &self) -> std::expected<int, std::error_code> +{ + auto const flags = ::fcntl(self.get(), F_GETFD); + if (flags != -1) + return {flags}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +auto replacefdflags(fd &self, int newflags) + -> std::expected<void, std::error_code> +{ + auto const ret = ::fcntl(self.get(), F_SETFD, newflags); + if (ret != -1) + return {}; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +auto setfdflags(fd &self, int newflags) + -> std::expected<int, std::error_code> +{ + auto flags = getfdflags(self); + if (!flags) + return flags; + + *flags |= newflags; + auto const ret = replacefdflags(self, *flags); + if (!ret) + return std::unexpected(ret.error()); + + return {*flags}; +} + +auto clearfdflags(fd &self, int clrflags) + -> std::expected<int, std::error_code> +{ + auto flags = getfdflags(self); + if (!flags) + return flags; + + *flags &= ~clrflags; + auto ret = replacefdflags(self, *flags); + if (!ret) + return std::unexpected(ret.error()); + + return {*flags}; +} + +auto pipe() -> std::expected<std::pair<fd, fd>, std::error_code> { + auto fds = std::array<int, 2>{}; + + if (auto const ret = ::pipe(fds.data()); ret != 0) + return std::unexpected(std::make_error_code(std::errc(errno))); + + return {{fd(fds[0]), fd(fds[1])}}; +} + +auto fd::write(this fd &self, std::span<std::byte const> buffer) + -> std::expected<std::size_t, std::error_code> +{ + auto const ret = ::write(self.get(), buffer.data(), buffer.size()); + if (ret >= 0) + return ret; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +auto fd::read(this fd &self, std::span<std::byte> buffer) + -> std::expected<std::size_t, std::error_code> +{ + auto const ret = ::read(self.get(), buffer.data(), buffer.size()); + if (ret >= 0) + return ret; + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +} // namespace nihil diff --git a/nihil/fd.ccm b/nihil/fd.ccm index ad96ea7..352eab3 100644 --- a/nihil/fd.ccm +++ b/nihil/fd.ccm @@ -4,11 +4,9 @@ module; -#include <fcntl.h> -#include <unistd.h> - #include <expected> -#include <format> +#include <ranges> +#include <span> #include <stdexcept> #include <system_error> @@ -23,9 +21,7 @@ namespace nihil { * to be caught, since it indicates an internal logic error in the caller. */ export struct fd_logic_error final : std::logic_error { - fd_logic_error(std::string what) - : std::logic_error(std::move(what)) - {} + fd_logic_error(std::string what); }; /* @@ -34,67 +30,43 @@ export struct fd_logic_error final : std::logic_error { export struct fd final { // Construct an empty (invalid) fd. - fd() noexcept = default; + fd() noexcept; // Construct an fd from an exising file destrictor, taking ownership. - fd(int fd_) noexcept : _fd(fd_) {} + fd(int fd_) noexcept; // Destructor. Close the fd, discarding any errors. - ~fd() - { - if (*this) - this->close(); - } + ~fd(); // Move from another fd, leaving the moved-from fd in an invalid state. - fd(fd &&other) noexcept - : _fd(std::exchange(other._fd, _invalid_fd)) - {} - - // Move assign from another fd. - auto operator=(fd &&other) noexcept -> fd & - { - if (this != &other) - _fd = std::exchange(other._fd, _invalid_fd); - return *this; - } + fd(fd &&other) noexcept; + auto operator=(fd &&other) noexcept -> fd &; // Not copyable. fd(fd const &) = delete; fd& operator=(fd const &) = delete; // Return true if this fd is valid (open). - explicit operator bool(this fd const &self) noexcept - { - return self._fd != _invalid_fd; - } + explicit operator bool(this fd const &self) noexcept; // Close the wrapped fd. - auto close(this fd &self) -> std::expected<void, std::error_code> - { - auto const ret = ::close(self.get()); - self._fd = _invalid_fd; - - if (ret == 0) - return {}; - - return std::unexpected(std::make_error_code(std::errc(errno))); - } + auto close(this fd &self) -> std::expected<void, std::error_code>; // Return the stored fd. - auto get(this fd const &self) -> int { - if (self) - return self._fd; - throw fd_logic_error("Attempt to call get() on invalid fd"); - } - + auto get(this fd const &self) -> int; // Release the stored fd and return it. The caller must close it. - auto release(this fd &&self) -> int { - if (self) - return std::exchange(self._fd, self._invalid_fd); - throw fd_logic_error("Attempt to release an invalid fd"); - } + auto release(this fd &&self) -> int; + + // Write data from the provided buffer to the fd. Returns the + // number of bytes written. + auto write(this fd &self, std::span<std::byte const>) + -> std::expected<std::size_t, std::error_code>; + + // Read data from the fd to the provided buffer. Returns the + // number of bytes read. + auto read(this fd &self, std::span<std::byte>) + -> std::expected<std::size_t, std::error_code>; private: static constexpr int _invalid_fd = -1; @@ -103,16 +75,7 @@ private: }; // Create a copy of this fd by calling dup(). -export auto dup(fd const &self) -> std::expected<fd, std::error_code> -{ - auto thisfd = self.get(); - - auto const newfd = ::dup(thisfd); - if (newfd != -1) - return {newfd}; - - return std::unexpected(std::make_error_code(std::errc(errno))); -} +export auto dup(fd const &self) -> std::expected<fd, std::error_code>; // Create a copy of this fd by calling dup2(). Note that because this results // in the existing fd and the new fd both being managed by an fd instance, @@ -124,171 +87,60 @@ export auto dup(fd const &self) -> std::expected<fd, std::error_code> // In both of these cases, either use raw_dup() instead, or immediately call // release() on the returned fd to prevent the fd instance from closing it. export auto dup(fd const &self, int newfd) - -> std::expected<fd, std::error_code> -{ - auto thisfd = self.get(); - - auto const ret = ::dup2(thisfd, newfd); - if (ret != -1) - return {newfd}; - - return std::unexpected(std::make_error_code(std::errc(errno))); -} + -> std::expected<fd, std::error_code>; // Create a copy of this fd by calling dup(). -export auto raw_dup(fd const &self) -> std::expected<int, std::error_code> -{ - auto thisfd = self.get(); - - auto const newfd = ::dup(thisfd); - if (newfd != -1) - return {newfd}; - - return std::unexpected(std::make_error_code(std::errc(errno))); -} +export auto raw_dup(fd const &self) + -> std::expected<int, std::error_code>; // Create a copy of this fd by calling dup2(). export auto raw_dup(fd const &self, int newfd) - -> std::expected<int, std::error_code> -{ - auto thisfd = self.get(); - - auto const ret = ::dup2(thisfd, newfd); - if (ret != -1) - return {newfd}; - - return std::unexpected(std::make_error_code(std::errc(errno))); -} + -> std::expected<int, std::error_code>; // Return the fnctl flags for this fd. -export auto getflags(fd const &self) -> std::expected<int, std::error_code> -{ - auto const flags = ::fcntl(self.get(), F_GETFL); - if (flags != -1) - return {flags}; - - return std::unexpected(std::make_error_code(std::errc(errno))); -} +export auto getflags(fd const &self) + -> std::expected<int, std::error_code>; // Replace the fnctl flags for this fd. export auto replaceflags(fd &self, int newflags) - -> std::expected<void, std::error_code> -{ - auto const ret = ::fcntl(self.get(), F_SETFL, newflags); - if (ret == 0) - return {}; - - return std::unexpected(std::make_error_code(std::errc(errno))); -} + -> std::expected<void, std::error_code>; // Add bits to the fcntl flags for this fd. Returns the new flags. export auto setflags(fd &self, int newflags) - -> std::expected<int, std::error_code> -{ - auto flags = getflags(self); - if (!flags) - return flags; - - *flags |= newflags; - auto const ret = replaceflags(self, *flags); - if (!ret) - return std::unexpected(ret.error()); - - return {*flags}; -} + -> std::expected<int, std::error_code>; // Remove bits from the fcntl flags for this fd. Returns the new flags. export auto clearflags(fd &self, int clrflags) - -> std::expected<int, std::error_code> -{ - auto flags = getflags(self); - if (!flags) - return flags; - - *flags &= ~clrflags; - auto const ret = replaceflags(self, *flags); - if (!ret) - return std::unexpected(ret.error()); - - return {*flags}; -} + -> std::expected<int, std::error_code>; // Return the fd flags for this fd. -export auto getfdflags(fd const &self) -> std::expected<int, std::error_code> -{ - auto const flags = ::fcntl(self.get(), F_GETFD); - if (flags != -1) - return {flags}; - - return std::unexpected(std::make_error_code(std::errc(errno))); -} +export auto getfdflags(fd const &self) + -> std::expected<int, std::error_code>; // Replace the fd flags for this fd. export auto replacefdflags(fd &self, int newflags) - -> std::expected<void, std::error_code> -{ - auto const ret = ::fcntl(self.get(), F_SETFD, newflags); - if (ret != -1) - return {}; - - return std::unexpected(std::make_error_code(std::errc(errno))); -} + -> std::expected<void, std::error_code>; // Add bits to the fd flags for this fd. Returns the new flags. export auto setfdflags(fd &self, int newflags) - -> std::expected<int, std::error_code> -{ - auto flags = getfdflags(self); - if (!flags) - return flags; - - *flags |= newflags; - auto const ret = replacefdflags(self, *flags); - if (!ret) - return std::unexpected(ret.error()); - - return {*flags}; -} + -> std::expected<int, std::error_code>; // Remove bits from the fd flags for this fd. Returns the new flags. export auto clearfdflags(fd &self, int clrflags) - -> std::expected<int, std::error_code> -{ - auto flags = getfdflags(self); - if (!flags) - return flags; - - *flags &= ~clrflags; - auto ret = replacefdflags(self, *flags); - if (!ret) - return std::unexpected(ret.error()); - - return {*flags}; -} + -> std::expected<int, std::error_code>; // Create two fds by calling pipe() and return them. -export auto pipe() -> std::expected<std::pair<fd, fd>, std::error_code> { - auto fds = std::array<int, 2>{}; - - if (auto const ret = ::pipe(fds.data()); ret != 0) - return std::unexpected(std::make_error_code(std::errc(errno))); - - return {{fd(fds[0]), fd(fds[1])}}; -} +export auto pipe() -> std::expected<std::pair<fd, fd>, std::error_code>; /* - * Write data to a file descriptor from the provided buffer. Returns the + * Write data to a file descriptor from the provided range. Returns the * number of bytes (not objects) written. Incomplete writes may cause a * partial object to be written. */ export auto write(fd &file, std::ranges::contiguous_range auto &&range) -> std::expected<std::size_t, std::error_code> { - auto const ret = ::write(file.get(), std::ranges::data(range), - std::ranges::size(range)); - if (ret >= 0) - return ret; - return std::unexpected(std::make_error_code(std::errc(errno))); + return file.write(as_bytes(std::span(range))); } /* @@ -299,11 +151,7 @@ export auto write(fd &file, std::ranges::contiguous_range auto &&range) export auto read(fd &file, std::ranges::contiguous_range auto &&range) -> std::expected<std::size_t, std::error_code> { - auto const ret = ::read(file.get(), std::ranges::data(range), - std::ranges::size(range)); - if (ret >= 0) - return ret; - return std::unexpected(std::make_error_code(std::errc(errno))); + return file.read(as_writable_bytes(std::span(range))); } } // namespace nihil diff --git a/nihil/find_in_path.cc b/nihil/find_in_path.cc new file mode 100644 index 0000000..2b8a57c --- /dev/null +++ b/nihil/find_in_path.cc @@ -0,0 +1,52 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <filesystem> +#include <optional> +#include <ranges> +#include <string> + +#include <fcntl.h> +#include <paths.h> + +module nihil; + +namespace nihil { + +auto find_in_path(std::filesystem::path const &file) +-> std::optional<fd> +{ + using namespace std::literals; + + auto try_open = + [](std::filesystem::path const &file) -> std::optional<fd> + { + auto ret = open_file(file, O_EXEC); + if (ret) + return {std::move(*ret)}; + return {}; + }; + + // Absolute pathname skips the search. + if (file.is_absolute()) + return try_open(file); + + auto path = getenv("PATH").value_or(_PATH_DEFPATH); + + for (auto &&dir : path | std::views::split(':')) { + // An empty $PATH element means cwd. + auto sdir = dir.empty() + ? std::filesystem::path(".") + : std::filesystem::path(std::string_view(dir)); + + if (auto ret = try_open(sdir / file); ret) + return ret; + } + + return {}; +} + +} // namespace lfjail diff --git a/nihil/find_in_path.ccm b/nihil/find_in_path.ccm index 1e72d0b..3c02ca5 100644 --- a/nihil/find_in_path.ccm +++ b/nihil/find_in_path.ccm @@ -4,57 +4,20 @@ module; -#include <format> -#include <ranges> -#include <string> - -#include <fcntl.h> -#include <paths.h> -#include <unistd.h> +#include <filesystem> +#include <optional> export module nihil:find_in_path; import :fd; -import :getenv; namespace nihil { /* * Find an executable in $PATH, open it with O_EXEC and return the fd. - * If $PATH is not set, uses _PATH_DEFPATH. + * If $PATH is not set, uses _PATH_DEFPATH. If the file can't be found + * or opened, returns std::nullopt. */ -auto find_in_path(std::string_view file) -> std::optional<fd> { - using namespace std::literals; - - auto try_open = [](std::string_view path) -> std::optional<fd> { - auto cpath = std::string(path); - auto const ret = ::open(cpath.c_str(), O_EXEC); - if (ret != -1) - return fd(ret); - return {}; - }; - - if (file.empty()) - return {}; - - // Absolute pathname skips the search. - if (file[0] == '/') - return try_open(file); - - auto path = getenv("PATH").value_or(_PATH_DEFPATH); - - for (auto &&dir : std::views::split(path, ':')) { - // An empty $PATH element means cwd. - auto sdir = std::string_view(dir); - if (sdir.empty()) - sdir = "."; - - auto const path = std::format("{}/{}", sdir, file); - if (auto ret = try_open(path); ret) - return ret; - } - - return {}; -} +auto find_in_path(std::filesystem::path const &file) -> std::optional<fd>; -} // namespace lfjail +} // namespace nihil diff --git a/nihil/getenv.cc b/nihil/getenv.cc new file mode 100644 index 0000000..8f28211 --- /dev/null +++ b/nihil/getenv.cc @@ -0,0 +1,44 @@ + +/* + * This source code is released into the public domain. + */ + +module; + +#include <cstdint> +#include <expected> +#include <string> +#include <system_error> +#include <vector> + +#include <unistd.h> + +module nihil; + +namespace nihil { + + auto getenv(std::string_view varname) +-> std::expected<std::string, std::error_code> +{ + // Start with a buffer of this size, and double it every iteration. + constexpr auto bufinc = std::size_t{1024}; + + auto cvarname = std::string(varname); + auto buf = std::vector<char>(bufinc); + for (;;) { + auto const ret = ::getenv_r(cvarname.c_str(), + buf.data(), buf.size()); + + if (ret == 0) + return {std::string(buf.data())}; + + if (ret == -1 && errno == ERANGE) { + buf.resize(buf.size() * 2); + continue; + } + + return std::unexpected(std::make_error_code(std::errc(errno))); + } +} + +} // namespace nihil diff --git a/nihil/getenv.ccm b/nihil/getenv.ccm index 7397b79..c1b99d7 100644 --- a/nihil/getenv.ccm +++ b/nihil/getenv.ccm @@ -4,13 +4,9 @@ module; -#include <cstdint> #include <expected> #include <string> #include <system_error> -#include <vector> - -#include <unistd.h> export module nihil:getenv; @@ -21,27 +17,6 @@ namespace nihil { */ export auto getenv(std::string_view varname) - -> std::expected<std::string, std::error_code> -{ - // Start with a buffer of this size, and double it every iteration. - constexpr auto bufinc = std::size_t{1024}; - - auto cvarname = std::string(varname); - auto buf = std::vector<char>(bufinc); - for (;;) { - auto const ret = ::getenv_r(cvarname.c_str(), - buf.data(), buf.size()); - - if (ret == 0) - return {std::string(buf.data())}; - - if (ret == -1 && errno == ERANGE) { - buf.resize(buf.size() * 2); - continue; - } - - return std::unexpected(std::make_error_code(std::errc(errno))); - } -} + -> std::expected<std::string, std::error_code>; } // namespace nihil diff --git a/nihil/open_file.cc b/nihil/open_file.cc new file mode 100644 index 0000000..d86478c --- /dev/null +++ b/nihil/open_file.cc @@ -0,0 +1,28 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <expected> +#include <filesystem> +#include <system_error> + +#include <fcntl.h> +#include <unistd.h> + +module nihil; + +namespace nihil { + +auto open_file(std::filesystem::path const &filename, int flags, int mode) +-> std::expected<fd, std::error_code> +{ + auto fdno = ::open(filename.c_str(), flags, mode); + if (fdno != -1) + return fd(fdno); + + return std::unexpected(std::make_error_code(std::errc(errno))); +} + +} // namespace nihil diff --git a/nihil/open_file.ccm b/nihil/open_file.ccm index 38fedbd..a399474 100644 --- a/nihil/open_file.ccm +++ b/nihil/open_file.ccm @@ -8,9 +8,6 @@ module; #include <filesystem> #include <system_error> -#include <fcntl.h> -#include <unistd.h> - export module nihil:open_file; import :fd; @@ -21,15 +18,7 @@ namespace nihil { * Open the given file and return an fd for it. */ auto open_file(std::filesystem::path const &filename, - int flags, - int mode = 0777) - -> std::expected<fd, std::error_code> -{ - auto fdno = ::open(filename.c_str(), flags, mode); - if (fdno != -1) - return fd(fdno); - - return std::unexpected(std::make_error_code(std::errc(errno))); -} + int flags, int mode = 0777) + -> std::expected<fd, std::error_code>; } // namespace nihil diff --git a/nihil/process.cc b/nihil/process.cc new file mode 100644 index 0000000..0122907 --- /dev/null +++ b/nihil/process.cc @@ -0,0 +1,111 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <cerrno> +#include <cstring> +#include <format> +#include <optional> +#include <system_error> +#include <utility> + +#include <sys/types.h> +#include <sys/wait.h> + +module nihil; + +namespace nihil { + +process_error::process_error(std::string what) + : generic_error(std::move(what)) +{ +} + +waitpid_error::waitpid_error(::pid_t pid, std::error_code error) + : process_error(std::format("waitpid({}): {}", + pid, error.message())) + , _pid(pid) + , _error(error) +{ +} + +auto waitpid_error::pid(this waitpid_error const &self) -> ::pid_t +{ + return self._pid; +} + +auto waitpid_error::error(this waitpid_error const &self) -> std::error_code +{ + return self._error; +} + +auto wait_result::okay(this wait_result const &self) -> bool +{ + return self.status() == 0; +} + +wait_result::operator bool(this wait_result const &self) +{ + return self.okay(); +} + +auto wait_result::status(this wait_result const &self) -> std::optional<int> +{ + if (WIFEXITED(self._status)) + return WEXITSTATUS(self._status); + return {}; +} + +auto wait_result::signal(this wait_result const &self) -> std::optional<int> +{ + if (WIFSIGNALED(self._status)) + return WTERMSIG(self._status); + return {}; +} + +wait_result::wait_result(int status) + : _status(status) +{} + +process::process(::pid_t pid) + : _pid(pid) +{} + +process::~process() { + if (_pid == -1) + return; + + auto status = int{}; + std::ignore = waitpid(_pid, &status, WEXITED); +} + +process::process(process &&) noexcept = default; +auto process::operator=(process &&) noexcept -> process& = default; + +// Get the child's process id. +auto process::pid(this process const &self) noexcept -> ::pid_t +{ + return self._pid; +} + +auto process::wait(this process &&self) -> wait_result +{ + auto status = int{}; + auto ret = waitpid(self._pid, &status, WEXITED); + if (ret != -1) + return wait_result(status); + + throw waitpid_error(self._pid, + std::make_error_code(std::errc(errno))); +} + +auto process::release(this process &&self) -> ::pid_t +{ + auto const ret = self.pid(); + self._pid = -1; + return ret; +} + +} // namespace nihil diff --git a/nihil/process.ccm b/nihil/process.ccm index ba8a399..db8b61a 100644 --- a/nihil/process.ccm +++ b/nihil/process.ccm @@ -4,15 +4,11 @@ module; -#include <cerrno> -#include <cstring> -#include <format> #include <optional> #include <system_error> #include <utility> #include <sys/types.h> -#include <sys/wait.h> export module nihil:process; @@ -24,29 +20,15 @@ namespace nihil { * Exception thrown when a process operation fails. */ export struct process_error : generic_error { - process_error(std::string what) - : generic_error(std::move(what)) - {} + process_error(std::string what); }; // A waitpid() call failed. export struct waitpid_error : process_error { - waitpid_error(::pid_t pid, std::error_code error) - : process_error(std::format("waitpid({}): {}", - pid, error.message())) - , _pid(pid) - , _error(error) - {} - - auto pid(this waitpid_error const &self) -> ::pid_t - { - return self._pid; - } - - auto error(this waitpid_error const &self) -> std::error_code - { - return self._error; - } + waitpid_error(::pid_t pid, std::error_code error); + + auto pid(this waitpid_error const &self) -> ::pid_t; + auto error(this waitpid_error const &self) -> std::error_code; private: ::pid_t _pid; @@ -59,31 +41,14 @@ private: export struct wait_result final { // Return true if the process exited normally with an exit code of // zero, otherwise false. - auto okay(this wait_result const &self) -> bool - { - return self.status() == 0; - } - - explicit operator bool(this wait_result const &self) - { - return self.okay(); - } + auto okay(this wait_result const &self) -> bool; + explicit operator bool(this wait_result const &self); // Return the exit status, if any. - auto status(this wait_result const &self) -> std::optional<int> - { - if (WIFEXITED(self._status)) - return WEXITSTATUS(self._status); - return {}; - } + auto status(this wait_result const &self) -> std::optional<int>; // Return the exit signal, if any. - auto signal(this wait_result const &self) -> std::optional<int> - { - if (WIFSIGNALED(self._status)) - return WTERMSIG(self._status); - return {}; - } + auto signal(this wait_result const &self) -> std::optional<int>; private: friend struct process; @@ -91,9 +56,7 @@ private: int _status; // Construct a new wait_result from the output of waitpid(). - wait_result(int status) - : _status(status) - {} + wait_result(int status); }; /* @@ -106,23 +69,15 @@ export struct process final { * Create a new process from a pid, which must be a child of the * current process. */ - process(::pid_t pid) - : _pid(pid) - {} + process(::pid_t pid); // When destroyed, we automatically wait for the process to // avoid creating zombie processes. - ~process() { - if (_pid == -1) - return; - - auto status = int{}; - std::ignore = waitpid(_pid, &status, WEXITED); - } + ~process(); // Movable. - process(process &&) noexcept = default; - auto operator=(process &&) noexcept -> process& = default; + process(process &&) noexcept; + auto operator=(process &&) noexcept -> process&; // Not copyable. process(process const &) = delete; @@ -136,26 +91,13 @@ export struct process final { * its exit status. This destroys the process state, leaving this * object in a moved-from state. */ - auto wait(this process &&self) -> wait_result - { - auto status = int{}; - auto ret = waitpid(self._pid, &status, WEXITED); - if (ret != -1) - return wait_result(status); - - throw waitpid_error(self._pid, - std::make_error_code(std::errc(errno))); - } + auto wait(this process &&self) -> wait_result; /* * Release this process so we won't try to wait for it when * destroying this object. */ - auto release(this process &&self) -> ::pid_t { - auto const ret = self._pid; - self._pid = -1; - return ret; - } + auto release(this process &&self) -> ::pid_t; private: ::pid_t _pid; |
