aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-06-23 18:34:18 +0100
committerLexi Winter <lexi@le-fay.org>2025-06-23 18:34:18 +0100
commit32b4443ba2ec5c3f7c09221ab9b21911a3126ef9 (patch)
treecb6346997078626dc512e5e46e95796e375690ee
parentd5963532328ce5f1c9f266bf7e760b7d18a60c15 (diff)
downloadnihil-32b4443ba2ec5c3f7c09221ab9b21911a3126ef9.tar.gz
nihil-32b4443ba2ec5c3f7c09221ab9b21911a3126ef9.tar.bz2
add separate module implementation files
-rw-r--r--nihil.config/CMakeLists.txt10
-rw-r--r--nihil.config/option.cc67
-rw-r--r--nihil.config/option.ccm50
-rw-r--r--nihil.config/store.cc89
-rw-r--r--nihil.config/store.ccm68
-rw-r--r--nihil.config/string.cc57
-rw-r--r--nihil.config/string.ccm42
-rw-r--r--nihil.ucl/CMakeLists.txt17
-rw-r--r--nihil.ucl/boolean.cc77
-rw-r--r--nihil.ucl/boolean.ccm60
-rw-r--r--nihil.ucl/emit.cc21
-rw-r--r--nihil.ucl/emit.ccm15
-rw-r--r--nihil.ucl/error.cc19
-rw-r--r--nihil.ucl/error.ccm5
-rw-r--r--nihil.ucl/integer.cc76
-rw-r--r--nihil.ucl/integer.ccm65
-rw-r--r--nihil.ucl/object.cc123
-rw-r--r--nihil.ucl/object.ccm198
-rw-r--r--nihil.ucl/parser.cc75
-rw-r--r--nihil.ucl/parser.ccm58
-rw-r--r--nihil.ucl/real.cc79
-rw-r--r--nihil.ucl/real.ccm65
-rw-r--r--nihil.ucl/string.cc139
-rw-r--r--nihil.ucl/string.ccm122
-rw-r--r--nihil.ucl/type.cc62
-rw-r--r--nihil.ucl/type.ccm48
-rw-r--r--nihil/CMakeLists.txt12
-rw-r--r--nihil/exec.cc75
-rw-r--r--nihil/exec.ccm63
-rw-r--r--nihil/fd.cc250
-rw-r--r--nihil/fd.ccm234
-rw-r--r--nihil/find_in_path.cc52
-rw-r--r--nihil/find_in_path.ccm49
-rw-r--r--nihil/getenv.cc44
-rw-r--r--nihil/getenv.ccm27
-rw-r--r--nihil/open_file.cc28
-rw-r--r--nihil/open_file.ccm15
-rw-r--r--nihil/process.cc111
-rw-r--r--nihil/process.ccm90
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;