aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nihil.config/CMakeLists.txt1
-rw-r--r--nihil.config/error.ccm23
-rw-r--r--nihil.config/nihil.config.ccm1
-rw-r--r--nihil.config/option.cc70
-rw-r--r--nihil.config/option.ccm61
-rw-r--r--nihil.config/read.cc35
-rw-r--r--nihil.config/read.ccm6
-rw-r--r--nihil.config/store.cc92
-rw-r--r--nihil.config/store.ccm41
-rw-r--r--nihil.config/string.cc42
-rw-r--r--nihil.config/string.ccm34
-rw-r--r--nihil.config/tests/string.cc22
-rw-r--r--nihil.config/write.cc27
-rw-r--r--nihil.config/write.ccm6
-rw-r--r--nihil.ucl/object_cast.ccm29
-rw-r--r--nihil.ucl/parser.cc71
-rw-r--r--nihil.ucl/parser.ccm101
-rw-r--r--nihil.ucl/tests/array.cc53
-rw-r--r--nihil.ucl/tests/boolean.cc18
-rw-r--r--nihil.ucl/tests/emit.cc24
-rw-r--r--nihil.ucl/tests/integer.cc16
-rw-r--r--nihil.ucl/tests/object.cc15
-rw-r--r--nihil.ucl/tests/parse.cc14
-rw-r--r--nihil.ucl/tests/real.cc17
-rw-r--r--nihil.ucl/tests/string.cc15
25 files changed, 508 insertions, 326 deletions
diff --git a/nihil.config/CMakeLists.txt b/nihil.config/CMakeLists.txt
index 2aa0dae..fb2c1db 100644
--- a/nihil.config/CMakeLists.txt
+++ b/nihil.config/CMakeLists.txt
@@ -5,7 +5,6 @@ target_link_libraries(nihil.config PUBLIC nihil nihil.ucl)
target_sources(nihil.config
PUBLIC FILE_SET modules TYPE CXX_MODULES FILES
nihil.config.ccm
- error.ccm
read.ccm
store.ccm
write.ccm
diff --git a/nihil.config/error.ccm b/nihil.config/error.ccm
deleted file mode 100644
index 4e7131a..0000000
--- a/nihil.config/error.ccm
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * This source code is released into the public domain.
- */
-
-module;
-
-#include <format>
-#include <utility>
-
-export module nihil.config:error;
-
-import nihil;
-
-namespace nihil::config {
-
-/*
- * Exception thrown when an issue occurs with the configuration.
- */
-export struct error : generic_error {
- error(std::string what) : generic_error(std::move(what)) {}
-};
-
-} // namespace nihil::config
diff --git a/nihil.config/nihil.config.ccm b/nihil.config/nihil.config.ccm
index d20c3fe..8957305 100644
--- a/nihil.config/nihil.config.ccm
+++ b/nihil.config/nihil.config.ccm
@@ -6,7 +6,6 @@ module;
export module nihil.config;
-export import :error;
export import :option;
export import :read;
export import :store;
diff --git a/nihil.config/option.cc b/nihil.config/option.cc
index 9bf77c9..588a48f 100644
--- a/nihil.config/option.cc
+++ b/nihil.config/option.cc
@@ -4,58 +4,86 @@
module;
+#include <coroutine>
+#include <expected>
#include <iostream>
#include <string>
module nihil.config;
+import nihil;
+import nihil.ucl;
+
namespace nihil::config {
+option::option(std::string_view name, std::string_view description)
+ : m_name(name)
+ , m_description(description)
+{
+ auto okay = store::get().register_option(this);
+ if (okay)
+ return;
+
+ std::print(std::cerr,
+ "INTERNAL ERROR: failed to register "
+ "configuration option '{}': {}",
+ m_name, okay.error());
+ std::exit(1);
+}
+
+option::~option()
+{
+ std::ignore = store::get().unregister_option(this);
+}
+
auto option::name(this option const &self) noexcept
--> std::string_view
+ -> std::string_view
{
- return self._name;
+ return self.m_name;
}
-// Human-readable description of this option.
auto option::description(this option const &self) noexcept
--> std::string_view
+ -> std::string_view
{
- return self._description;
+ return self.m_description;
}
-// If true, this option is set to its default value.
auto option::is_default(this option const &self) noexcept
--> bool
+ -> bool
+{
+ return self.m_is_default;
+}
+
+auto option::is_default(this option &self, bool b) -> void
{
- return self._is_default;
+ self.m_is_default = b;
}
-// Get or set this option as a string.
-auto option::string(this option const &self)
--> std::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
+ -> std::expected<void, error>
{
- self.set_string(value);
- self._is_default = false;
+ co_await self.set_string(value);
+ self.is_default(false);
+ co_return {};
}
-option::option(std::string_view name,
- std::string_view description)
- : _name(name)
- , _description(description)
+auto option::ucl(this option const &self)
+ -> std::expected<nihil::ucl::object, error>
{
+ return self.get_ucl();
}
-auto option::is_default(bool b)
--> void
+auto option::ucl(this option &self, nihil::ucl::object const &value)
+ -> std::expected<void, error>
{
- _is_default = b;
+ co_await self.set_ucl(value);
+ self.is_default(false);
+ co_return {};
}
auto operator<<(std::ostream &strm, option const &opt)
diff --git a/nihil.config/option.ccm b/nihil.config/option.ccm
index c6a8329..c095d34 100644
--- a/nihil.config/option.ccm
+++ b/nihil.config/option.ccm
@@ -4,46 +4,67 @@
module;
+#include <expected>
#include <iosfwd>
#include <string>
export module nihil.config:option;
+import nihil;
import nihil.ucl;
-import :error;
namespace nihil::config {
/*
* Base class for options; this is what config_store interacts with.
+ *
+ * Base classes should override the four protected functions:
+ *
+ * get_string()
+ * set_string()
+ * get_ucl()
+ * set_ucl()
+ *
+ * Overriding any other members is not permitted.
*/
export struct option
{
+ virtual ~option();
+
// Short name of this option.
- auto name(this option const &self) noexcept -> std::string_view;
+ [[nodiscard]] auto name(this option const &) noexcept
+ -> std::string_view;
// Human-readable description of this option.
- auto description(this option const &self) noexcept -> std::string_view;
+ [[nodiscard]] auto description(this option const &) noexcept
+ -> std::string_view;
// If true, this option is set to its default value.
- auto is_default(this option const &self) noexcept -> bool;
+ [[nodiscard]] auto is_default(this option const &) noexcept
+ -> bool;
- // Get or set this option as a string.
- auto string(this option const &self) -> std::string;
- auto string(this option &self, std::string_view value) -> void;
+ /*
+ * Get or set this option as a string. The specific conversion
+ * method depends on the derived option type.
+ */
+ [[nodiscard]] auto string(this option const &) -> std::string;
+ [[nodiscard]] auto string(this option &, std::string_view value)
+ -> std::expected<void, error>;
/*
* Return this object as a UCL object. This is used when writing the
* configuration file.
*/
- virtual auto to_ucl() const -> ucl::object = 0;
+ [[nodiscard]] auto ucl(this option const &)
+ -> std::expected<ucl::object, error>;
/*
* Set this object from a UCL object. This is used when reading the
* configuration file.
*/
- virtual auto from_ucl(ucl::object const &) -> void = 0;
+ [[nodiscard]] auto ucl(this option &, ucl::object const &)
+ -> std::expected<void, error>;
// Not copyable or movable.
option(option const &) = delete;
@@ -52,18 +73,28 @@ export struct option
protected:
option(std::string_view name, std::string_view description);
- auto is_default(bool b) -> void;
+ auto is_default(this option &, bool) -> void;
/*
* Get or set this option as a string.
*/
- virtual auto get_string() const -> std::string = 0;
- virtual auto set_string(std::string_view) -> void = 0;
+ virtual auto get_string() const
+ -> std::string = 0;
+ virtual auto set_string(std::string_view)
+ -> std::expected<void, error> = 0;
+
+ /*
+ * Get or set this option as a UCL object.
+ */
+ virtual auto get_ucl() const
+ -> std::expected<ucl::object, error> = 0;
+ virtual auto set_ucl(ucl::object const &)
+ -> std::expected<void, error> = 0;
private:
- std::string _name;
- std::string _description;
- bool _is_default = true;
+ std::string m_name;
+ std::string m_description;
+ bool m_is_default = true;
};
/*
diff --git a/nihil.config/read.cc b/nihil.config/read.cc
index 0a5fcad..e7def91 100644
--- a/nihil.config/read.cc
+++ b/nihil.config/read.cc
@@ -11,15 +11,15 @@ module;
#include <iterator>
#include <string>
+module nihil.config;
+
import nihil;
import nihil.ucl;
-module nihil.config;
-
namespace nihil::config {
auto read_from(std::filesystem::path const &filename)
- -> std::expected<void, nihil::error>
+ -> std::expected<void, error>
{
// TODO: nihil.ucl should have a way to load UCL from a filename.
@@ -29,33 +29,20 @@ auto read_from(std::filesystem::path const &filename)
// Ignore ENOENT, it simply means we haven't created the
// config file yet, so default values will be used.
if (err.error().root_cause() == std::errc::no_such_file_or_directory)
- return {};
+ co_return {};
auto errstr = std::format("cannot read {}", filename.string());
- return std::unexpected(nihil::error(errstr, err.error()));
+ co_return std::unexpected(error(errstr, err.error()));
}
// Parse the UCL.
- try {
- auto uclconfig = ucl::parse(config_text);
-
- for (auto &&[key, value] : uclconfig) {
- auto &opt = store::get().fetch(key);
- opt.from_ucl(value);
- }
- } catch (unknown_option const &) {
- // This is probably an old option which has been removed;
- // ignore it, and we'll remove the bad option next time
- // we write the config.
- } catch (error const &err) {
- // Include the filename in any other config errors.
- throw error(std::format("{}: {}", filename.string(),
- err.what()));
- } catch (ucl::error const &err) {
- throw error(std::format("{}: {}", filename.string(),
- err.what()));
+ auto uclconfig = co_await ucl::parse(config_text);
+
+ for (auto &&[key, value] : uclconfig) {
+ auto opt = co_await store::get().fetch(key);
+ co_await opt->ucl(value);
}
- return {};
+ co_return {};
}
} // namespace nihil::config
diff --git a/nihil.config/read.ccm b/nihil.config/read.ccm
index 18f7213..fca78eb 100644
--- a/nihil.config/read.ccm
+++ b/nihil.config/read.ccm
@@ -7,16 +7,16 @@ module;
#include <expected>
#include <filesystem>
-import nihil;
-
export module nihil.config:read;
+import nihil;
+
namespace nihil::config {
/*
* Load the configuration from a file.
*/
export [[nodiscard]] auto read_from(std::filesystem::path const &filename)
- -> std::expected<void, nihil::error>;
+ -> std::expected<void, error>;
} // namespace nihil::config
diff --git a/nihil.config/store.cc b/nihil.config/store.cc
index 2ec8ade..6f93677 100644
--- a/nihil.config/store.cc
+++ b/nihil.config/store.cc
@@ -5,6 +5,7 @@
module;
#include <coroutine>
+#include <expected>
#include <filesystem>
#include <format>
#include <map>
@@ -15,75 +16,78 @@ 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)
-{
-}
+store::store() = default;
-auto unknown_option::option_name(this unknown_option const &self)
--> std::string_view
+auto store::get() -> store &
{
- return self._option_name;
-}
-
-auto store::get()
--> store&
-{
- if (instance == nullptr)
- instance = new store;
-
- return *instance;
+ static auto instance = store();
+ return instance;
}
auto store::register_option(this store &self, option *object)
--> void
+ -> std::expected<void, error>
{
- auto [it, okay] = self.options.insert(
+ auto [it, okay] = self.m_options.insert(
std::pair{object->name(), object});
- if (!okay)
- throw error(std::format(
- "INTERNAL ERROR: attempt to register "
- "duplicate config value '{0}'",
- object->name()));
+ if (okay)
+ return {};
+
+ return std::unexpected(error(std::format(
+ "attempt to register duplicate "
+ "configuration option '{0}'",
+ object->name())));
}
auto store::unregister_option(this store &self, option *object)
--> void
+ -> std::expected<void, error>
{
- 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 it = self.m_options.find(object->name());
+ if (it == self.m_options.end())
+ return std::unexpected(error(std::format(
+ "attempt to unregister non-existent "
+ "configuration option '{}'",
+ object->name())));
+
+ self.m_options.erase(it);
+ return {};
}
auto store::fetch(this store const &self, std::string_view name)
--> option &
+ -> std::expected<option const *, error>
{
- if (auto it = self.options.find(name); it != self.options.end())
- return *it->second;
+ if (auto it = self.m_options.find(name); it != self.m_options.end())
+ return it->second;
- throw unknown_option(name);
+ return std::unexpected(error(std::format(
+ "unknown configuration option '{}'",
+ name)));
+}
+
+auto store::fetch(this store &self, std::string_view name)
+ -> std::expected<option *, error>
+{
+ auto opt = co_await static_cast<store const &>(self).fetch(name);
+ co_return const_cast<option *>(opt);
+}
+
+auto store::all(this store const &self) -> nihil::generator<option const *>
+{
+ for (auto &&it : self.m_options)
+ co_yield it.second;
}
-auto store::all(this store const &self)
--> nihil::generator<option const &>
+auto store::all(this store &self) -> nihil::generator<option *>
{
- for (auto &&it : self.options)
- co_yield *it.second;
+ for (auto &&it : self.m_options)
+ co_yield it.second;
}
auto get_option(std::string_view option_name)
--> option &
+ -> std::expected<option *, error>
{
- return store::get().fetch(option_name);
+ co_return co_await store::get().fetch(option_name);
}
} // namespace nihil::config
diff --git a/nihil.config/store.ccm b/nihil.config/store.ccm
index 77b44b5..03d09d3 100644
--- a/nihil.config/store.ccm
+++ b/nihil.config/store.ccm
@@ -9,51 +9,50 @@ module;
*/
#include <coroutine>
+#include <expected>
#include <string>
#include <map>
export module nihil.config:store;
import nihil;
-import :error;
import :option;
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);
- auto option_name(this unknown_option const &self) -> std::string_view;
-
-private:
- std::string _option_name;
-};
-
struct store final {
/*
* Get the global config store.
*/
- static auto get() -> store &;
+ [[nodiscard]] static auto get() -> store &;
/*
* Register a new value with the config store.
*/
- auto register_option(this store &self, option *object) -> void;
+ [[nodiscard]] auto register_option(this store &, option *object)
+ -> std::expected<void, error>;
/*
* Remove a value from the config store.
*/
- auto unregister_option(this store &self, option *object) -> void;
+ [[nodiscard]] auto unregister_option(this store &, option *object)
+ -> std::expected<void, error>;
/*
* Fetch an existing value in the config store.
*/
- auto fetch(this store const &self, std::string_view name) -> option &;
+ [[nodiscard]] auto fetch(this store const &, std::string_view name)
+ -> std::expected<option const *, error>;
+ [[nodiscard]] auto fetch(this store &, std::string_view name)
+ -> std::expected<option *, error>;
/*
* Fetch all values in the configuration store.
*/
- auto all(this store const &self) -> nihil::generator<option const &>;
+ [[nodiscard]] auto all(this store const &self)
+ -> nihil::generator<option const *>;
+ [[nodiscard]] auto all(this store &self)
+ -> nihil::generator<option *>;
// Not movable or copyable.
store(store const &) = delete;
@@ -62,19 +61,15 @@ struct store final {
store& operator=(store &&) = delete;
private:
- /*
- * The global configuration store, created by init() and accessed via
- * get().
- */
- inline static store *instance = nullptr;
+ store();
- std::map<std::string_view, option *> options;
- store() = default;
+ std::map<std::string_view, option *> m_options;
};
/*
* The public API.
*/
-export auto get_option(std::string_view option_name) -> option &;
+export auto get_option(std::string_view option_name)
+ -> std::expected<option *, error>;
} // namespace nihil::config
diff --git a/nihil.config/string.cc b/nihil.config/string.cc
index 6b201ae..7d0c038 100644
--- a/nihil.config/string.cc
+++ b/nihil.config/string.cc
@@ -4,11 +4,14 @@
module;
+#include <coroutine>
+#include <expected>
#include <format>
#include <string>
module nihil.config;
+import nihil;
import nihil.ucl;
namespace nihil::config {
@@ -18,40 +21,41 @@ string::string(
std::string_view name,
std::string_view description) noexcept
: option(name, description)
- , _storage(storage)
+ , m_storage(storage)
{
- store::get().register_option(this);
}
-string::~string()
-{
- store::get().unregister_option(this);
-}
+string::~string() = default;
auto string::get_string() const -> std::string
{
- return _storage;
+ return m_storage;
}
-auto string::set_string(std::string_view new_value) -> void
+auto string::set_string(std::string_view new_value)
+ -> std::expected<void, error>
{
- _storage = new_value;
+ m_storage = new_value;
+ return {};
}
-auto string::to_ucl() const -> ucl::object
+auto string::get_ucl() const -> std::expected<ucl::object, error>
{
- return ucl::string(_storage);
+ return ucl::string(m_storage);
}
-auto string::from_ucl(ucl::object const &uclobj) -> void
+auto string::set_ucl(ucl::object const &uclobj) -> std::expected<void, error>
{
- 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 obj = co_await object_cast<ucl::string>(uclobj)
+ .transform_error([&] (ucl::type_mismatch const &m) {
+ return error(std::format(
+ "'{}': expected string, not {}",
+ name(), str(m.actual_type())));
+ });
+
+ m_storage = obj.value();
+ is_default(false);
+ co_return {};
}
} // namespace nihil::config
diff --git a/nihil.config/string.ccm b/nihil.config/string.ccm
index ae5efb9..b6a7e05 100644
--- a/nihil.config/string.ccm
+++ b/nihil.config/string.ccm
@@ -4,16 +4,21 @@
module;
+#include <expected>
#include <format>
#include <string>
export module nihil.config:string;
+import nihil;
import nihil.ucl;
import :option;
namespace nihil::config {
+/*
+ * A string option. The backing type is std::string.
+ */
export struct string final : option
{
string(std::string &storage,
@@ -22,14 +27,31 @@ export struct string final : option
~string();
- auto get_string() const -> std::string override;
- auto set_string(std::string_view new_value) -> void override;
-
- auto to_ucl() const -> ucl::object override;
- auto from_ucl(ucl::object const &uclobj) -> void override;
+ /*
+ * Get this option as a string; simply returns the storage.
+ */
+ [[nodiscard]] auto get_string() const -> std::string override;
+
+ /*
+ * Set this option to a string value; assigns to the storage.
+ */
+ [[nodiscard]] auto set_string(std::string_view new_value)
+ -> std::expected<void, error> override;
+
+ /*
+ * Convert this option to a UCL object.
+ */
+ [[nodiscard]] auto get_ucl() const
+ -> std::expected<ucl::object, error> override;
+
+ /*
+ * Set this option from a UCL object.
+ */
+ [[nodiscard]] auto set_ucl(ucl::object const &uclobj)
+ -> std::expected<void, error> override;
private:
- std::string &_storage;
+ std::string &m_storage;
};
} // namespace nihil::config
diff --git a/nihil.config/tests/string.cc b/nihil.config/tests/string.cc
index 7e95190..aeb1ef8 100644
--- a/nihil.config/tests/string.cc
+++ b/nihil.config/tests/string.cc
@@ -12,23 +12,25 @@ TEST_CASE("nihil.config: string option", "[nihil][nihil.config]")
{
std::string storage;
- REQUIRE_THROWS_AS(nihil::config::get_option("test_option"),
- nihil::config::unknown_option);
+ auto opt = nihil::config::get_option("test_option");
+ REQUIRE(!opt);
{
auto string_option = nihil::config::string(
storage, "test_option", "This is a test option");
- auto &opt = nihil::config::get_option("test_option");
- REQUIRE(opt.name() == "test_option");
- REQUIRE(opt.description() == "This is a test option");
- REQUIRE(opt.is_default() == true);
- REQUIRE(opt.string() == "");
+ auto opt = nihil::config::get_option("test_option");
+ REQUIRE(opt);
- opt.string("testing");
+ REQUIRE((*opt)->name() == "test_option");
+ REQUIRE((*opt)->description() == "This is a test option");
+ REQUIRE((*opt)->is_default() == true);
+ REQUIRE((*opt)->string() == "");
+
+ REQUIRE((*opt)->string("testing"));
REQUIRE(storage == "testing");
}
- REQUIRE_THROWS_AS(nihil::config::get_option("test_option"),
- nihil::config::unknown_option);
+ opt = nihil::config::get_option("test_option");
+ REQUIRE(!opt);
}
diff --git a/nihil.config/write.cc b/nihil.config/write.cc
index 2b451bd..8c02c43 100644
--- a/nihil.config/write.cc
+++ b/nihil.config/write.cc
@@ -4,43 +4,36 @@
module;
+#include <coroutine>
#include <expected>
#include <filesystem>
#include <format>
#include <utility>
+module nihil.config;
+
import nihil;
import nihil.ucl;
-module nihil.config;
-
namespace nihil::config {
auto write_to(std::filesystem::path const &filename)
- -> std::expected<void, nihil::error>
-try {
+ -> std::expected<void, error>
+{
auto uclconfig = ucl::map<ucl::object>();
// Add all the options to the UCL object.
for (auto const &option : store::get().all()) {
- if (option.is_default())
+ if (option->is_default())
continue;
- uclconfig.insert({option.name(), option.to_ucl()});
+ auto uobj = co_await option->ucl();
+ uclconfig.insert({option->name(), uobj});
}
auto ucl_text = std::format("{:c}", uclconfig);
- auto ret = safe_write_file(filename, ucl_text);
- if (!ret)
- return std::unexpected(nihil::error(
- std::format("cannot write {}", filename.string()),
- ret.error()));
-
- return {};
-} catch (ucl::error const &exc) {
- return std::unexpected(nihil::error(
- "failed to serialize configuration",
- nihil::error(exc.what())));
+ co_await safe_write_file(filename, ucl_text);
+ co_return {};
}
};
diff --git a/nihil.config/write.ccm b/nihil.config/write.ccm
index 1a07dd7..cb35d76 100644
--- a/nihil.config/write.ccm
+++ b/nihil.config/write.ccm
@@ -7,16 +7,16 @@ module;
#include <expected>
#include <filesystem>
-import nihil;
-
export module nihil.config:write;
+import nihil;
+
export namespace nihil::config {
/*
* Write all config values (except defaults) to disk.
*/
auto write_to(std::filesystem::path const &filename) ->
- std::expected<void, nihil::error>;
+ std::expected<void, error>;
};
diff --git a/nihil.ucl/object_cast.ccm b/nihil.ucl/object_cast.ccm
index b10ffbc..07588a1 100644
--- a/nihil.ucl/object_cast.ccm
+++ b/nihil.ucl/object_cast.ccm
@@ -4,12 +4,15 @@
module;
+#include <coroutine>
#include <cstdlib>
+#include <expected>
#include <ucl.h>
export module nihil.ucl:object_cast;
+import nihil;
import :type;
import :object;
import :array;
@@ -25,21 +28,22 @@ namespace nihil::ucl {
template<datatype To>
struct convert_check
{
- auto check(::ucl_object_t const *from) -> void
+ [[nodiscard]] auto check(::ucl_object_t const *from)
+ -> std::expected<void, type_mismatch>
{
auto from_type = static_cast<object_type>(::ucl_object_type(from));
auto to_type = To::ucl_type;
// Converting from anything to object is permitted.
if (to_type == object_type::object)
- return;
+ return {};
// Converting between two equal types is permitted.
if (from_type == to_type)
- return;
+ return {};
// Otherwise, this is an error.
- throw type_mismatch(to_type, from_type);
+ return std::unexpected(type_mismatch(to_type, from_type));
}
};
@@ -47,7 +51,8 @@ struct convert_check
template<typename T>
struct convert_check<array<T>>
{
- auto check(::ucl_object_t const *from) -> void
+ [[nodiscard]] auto check(::ucl_object_t const *from)
+ -> std::expected<void, type_mismatch>
{
using To = array<T>;
auto from_type = static_cast<object_type>(::ucl_object_type(from));
@@ -55,13 +60,17 @@ struct convert_check<array<T>>
// If the source type is not an array, this is an error.
if (from_type != object_type::array)
- throw type_mismatch(to_type, from_type);
+ co_return std::unexpected(
+ type_mismatch(to_type, from_type));
for (std::size_t i = 0, size = ::ucl_array_size(from);
i < size; ++i) {
auto const *arr_obj = ::ucl_array_find_index(from, i);
- convert_check<typename To::value_type>{}.check(arr_obj);
+ co_await convert_check<typename To::value_type>{}
+ .check(arr_obj);
}
+
+ co_return {};
}
};
@@ -69,12 +78,12 @@ struct convert_check<array<T>>
* Convert a UCL object to another type.
*/
export template<datatype To>
-auto object_cast(object const &from) -> To
+auto object_cast(object const &from) -> std::expected<To, type_mismatch>
{
auto uobj = from.get_ucl_object();
- convert_check<To>{}.check(uobj);
- return To(nihil::ucl::ref, uobj);
+ co_await convert_check<To>{}.check(uobj);
+ co_return To(nihil::ucl::ref, uobj);
}
} // namespace nihil::ucl
diff --git a/nihil.ucl/parser.cc b/nihil.ucl/parser.cc
index 816116d..611fe50 100644
--- a/nihil.ucl/parser.cc
+++ b/nihil.ucl/parser.cc
@@ -4,6 +4,7 @@
module;
+#include <expected>
#include <functional>
#include <string>
@@ -11,15 +12,23 @@ module;
module nihil.ucl;
+import nihil;
+
namespace nihil::ucl {
-parse_error::parse_error(std::string what)
- : error(std::move(what))
+auto make_parser(int flags) -> std::expected<parser, nihil::error>
{
+ auto *p = ::ucl_parser_new(flags);
+ if (p != nullptr)
+ return p;
+
+ // TODO: Is there a way to get the actual error here?
+ return std::unexpected(nihil::error("failed to create parser"));
}
-auto macro_handler::handle(unsigned char const *data, std::size_t len, void *ud)
--> bool
+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(
@@ -28,48 +37,66 @@ auto macro_handler::handle(unsigned char const *data, std::size_t len, void *ud)
return handler->callback(string);
}
-parser::parser(int flags)
+parser::parser(::ucl_parser *uclp)
+ : m_parser(uclp)
{
- if ((_parser = ::ucl_parser_new(flags)) != nullptr)
- return;
+}
- throw error("failed to create UCL parser");
+parser::~parser()
+{
+ if (m_parser)
+ ::ucl_parser_free(m_parser);
}
-parser::parser()
- : parser(0)
+parser::parser(parser &&other) noexcept
+ : m_parser(std::exchange(other.m_parser, nullptr))
+ , m_macros(std::move(other.m_macros))
{
}
-parser::~parser()
+auto parser::operator=(this parser &self, parser &&other) noexcept
+ -> parser &
{
- if (_parser)
- ::ucl_parser_free(_parser);
+ if (&self != &other) {
+ if (self.m_parser)
+ ::ucl_parser_free(self.m_parser);
+
+ self.m_parser = std::exchange(other.m_parser, nullptr);
+ self.m_macros = std::move(other.m_macros);
+ }
+
+ return self;
}
auto parser::register_value(
this parser &self,
std::string_view variable,
std::string_view value)
--> void
+ -> void
{
::ucl_parser_register_variable(
- self._parser,
+ self.get_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.get_parser());
+ if (obj != nullptr)
+ // ucl_parser_get_object() refs the object for us.
+ return {noref, obj};
- auto obj = ::ucl_parser_get_object(self._parser);
- if (obj == nullptr)
- throw error("attempt to call top() on an empty parser");
+ throw std::logic_error(
+ "attempt to call top() on an invalid ucl::parser");
+}
- // ucl_parser_get_objects() refs the object for us.
- return {noref, obj};
+auto parser::get_parser(this parser &self) -> ::ucl_parser *
+{
+ if (self.m_parser == nullptr)
+ throw std::logic_error("attempt to fetch a null ucl::parser");
+
+ return self.m_parser;
}
} // namespace nihil::ucl
diff --git a/nihil.ucl/parser.ccm b/nihil.ucl/parser.ccm
index f817b76..efddd5f 100644
--- a/nihil.ucl/parser.ccm
+++ b/nihil.ucl/parser.ccm
@@ -4,6 +4,8 @@
module;
+#include <coroutine>
+#include <expected>
#include <format>
#include <functional>
#include <memory>
@@ -21,13 +23,6 @@ import :map;
namespace nihil::ucl {
-/*
- * Exception thrown when an issue occurs parsing UCL.
- */
-export struct parse_error : error {
- parse_error(std::string what);
-};
-
// UCL parser flags.
export inline constexpr int parser_key_lower = UCL_PARSER_KEY_LOWERCASE;
export inline constexpr int parser_zerocopy = UCL_PARSER_ZEROCOPY;
@@ -49,88 +44,118 @@ struct macro_handler {
/*
* A UCL parser. This wraps the C ucl_parser API.
+ *
+ * parser itself is not exported; use make_parser() to create one.
*/
-export struct parser {
- // Create a new parser with the given flags.
- parser(int flags);
-
- // Create a new parser with the default flags.
- parser();
+struct parser {
+ // Create a parser from a UCL parser.
+ parser(::ucl_parser *);
// Destroy our parser when we're destroyed.
~parser();
+ // Not copyable.
+ parser(parser const &) = delete;
+ auto operator=(this parser &, parser const &) -> parser & = delete;
+
+ // Movable.
+ parser(parser &&) noexcept;
+ auto operator=(this parser &, parser &&) noexcept -> 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
// capture or similar if needed.
template<std::invocable<std::string_view> F>
auto register_macro(this parser &self,
std::string_view name,
- F &&func) -> void
+ F &&func)
+ -> void
requires (std::same_as<bool, std::invoke_result<F>>)
{
auto handler = std::make_unique<macro_handler>(
std::move(func));
auto cname = std::string(name);
- ::ucl_parser_register_macro(self._parser, cname.c_str(),
- &macro_handler::handle,
- handler.get());
+ ::ucl_parser_register_macro(
+ self.get_parser(), cname.c_str(),
+ &macro_handler::handle, handler.get());
- self._macros.emplace_back(std::move(handler));
+ self.m_macros.emplace_back(std::move(handler));
}
// Add a parser variable.
auto register_value(this parser &self,
std::string_view variable,
- std::string_view value) -> void;
+ std::string_view value)
+ -> void;
// Add data to the parser.
- auto add(this parser &self,
- std::ranges::contiguous_range auto &&data)
- -> void
+ [[nodiscard]] auto add(this parser &self,
+ std::ranges::contiguous_range auto &&data)
+ -> std::expected<void, nihil::error>
// Only bytes (chars) are permitted.
requires(sizeof(std::ranges::range_value_t<decltype(data)>) == 1)
{
- // UCL accepts unsigned chars, but this is quite unhelpful
- // when reading from files or strings.
+ auto *p = self.get_parser();
auto dptr = reinterpret_cast<unsigned char const *>(
std::ranges::data(data));
- auto ret = ::ucl_parser_add_chunk(self._parser, dptr,
- std::ranges::size(data));
- if (ret == false)
- throw parse_error(::ucl_parser_get_error(self._parser));
+ auto ret = ::ucl_parser_add_chunk(
+ p, dptr, std::ranges::size(data));
+
+ if (ret == true)
+ return {};
+
+ return std::unexpected(nihil::error(::ucl_parser_get_error(p)));
}
- auto add(this parser &self, std::ranges::range auto &&data)
- -> void
+ [[nodiscard]] auto add(this parser &self,
+ std::ranges::range auto &&data)
+ -> std::expected<void, nihil::error>
requires (!std::ranges::contiguous_range<decltype(data)>)
{
auto cdata = std::vector<char>(
std::from_range,
std::forward<decltype(data)>(data));
- return self.add(std::move(cdata));
+ co_await self.add(std::move(cdata));
+ co_return {};
}
// Return the top object of this parser.
- auto top(this parser &self) -> map<object>;
+ [[nodiscard]] auto top(this parser &self) -> map<object>;
+
+ // Return the stored parser object.
+ [[nodiscard]] auto get_parser(this parser &self) -> ::ucl_parser *;
private:
// The parser object. Should never be null, unless we've been
// moved-from.
- ucl_parser *_parser = nullptr;
+ ucl_parser *m_parser;
// Functions added by register_macro. We have to store these as
// pointers because we pass the address to libucl.
- std::vector<std::unique_ptr<macro_handler>> _macros;
+ std::vector<std::unique_ptr<macro_handler>> m_macros;
};
+// Create a parser with the given flags.
+export [[nodiscard]] auto
+make_parser(int flags = 0) -> std::expected<parser, nihil::error>;
+
// Utility function to parse something and return the top-level object.
-export auto parse(std::ranges::range auto &&data) -> map<object> {
- auto p = parser();
- p.add(std::forward<decltype(data)>(data));
- return p.top();
+export [[nodiscard]] auto
+parse(int flags, std::ranges::range auto &&data)
+ -> std::expected<map<object>, nihil::error>
+{
+ auto p = co_await make_parser(flags);
+ co_await p.add(std::forward<decltype(data)>(data));
+ co_return p.top();
+}
+
+export [[nodiscard]] auto
+parse(std::ranges::range auto &&data)
+ -> std::expected<map<object>, nihil::error>
+{
+ co_return co_await parse(0, std::forward<decltype(data)>(data));
}
} // namespace nihil::ucl
diff --git a/nihil.ucl/tests/array.cc b/nihil.ucl/tests/array.cc
index ce0976f..fb23178 100644
--- a/nihil.ucl/tests/array.cc
+++ b/nihil.ucl/tests/array.cc
@@ -4,12 +4,14 @@
#include <algorithm>
#include <concepts>
+#include <expected>
#include <ranges>
#include <string>
#include <catch2/catch_test_macros.hpp>
#include <ucl.h>
+import nihil;
import nihil.ucl;
TEST_CASE("ucl: array: invariants", "[ucl]")
@@ -154,8 +156,14 @@ TEST_CASE("ucl: array: parse", "[ucl]")
using namespace std::literals;
using namespace nihil::ucl;
- auto obj = parse("value = [1, 42, 666]"sv);
- auto arr = object_cast<array<integer>>(obj["value"]);
+ auto obj_err = parse("value = [1, 42, 666]"sv);
+ REQUIRE(obj_err);
+ auto obj = *obj_err;
+
+ auto err = object_cast<array<integer>>(obj["value"]);
+ REQUIRE(err);
+
+ auto arr = *err;
REQUIRE(arr.size() == 3);
REQUIRE(arr[0] == 1);
REQUIRE(arr[1] == 42);
@@ -167,7 +175,9 @@ TEST_CASE("ucl: array: emit", "[ucl]")
using namespace nihil::ucl;
auto ucl = parse("array = [1, 42, 666];");
- auto output = std::format("{:c}", ucl);
+ REQUIRE(ucl);
+
+ auto output = std::format("{:c}", *ucl);
REQUIRE(output ==
"array [\n"
" 1,\n"
@@ -211,7 +221,8 @@ TEST_CASE("ucl: array: bad object_cast", "[ucl]")
auto arr = array<integer>();
- REQUIRE_THROWS_AS(object_cast<integer>(arr), type_mismatch);
+ auto cast_ok = object_cast<integer>(arr);
+ REQUIRE(!cast_ok);
}
TEST_CASE("ucl: array: heterogeneous elements", "[ucl]")
@@ -219,19 +230,27 @@ TEST_CASE("ucl: array: heterogeneous elements", "[ucl]")
using namespace std::literals;
using namespace nihil::ucl;
- auto obj = parse("array [ 42, true, \"test\" ];");
- auto arr = object_cast<array<>>(obj["array"]);
+ auto obj_err = parse("array [ 42, true, \"test\" ];");
+ REQUIRE(obj_err);
+ auto obj = *obj_err;
+ auto err = object_cast<array<>>(obj["array"]);
+ REQUIRE(err);
+
+ auto arr = *err;
REQUIRE(arr.size() == 3);
auto int_obj = object_cast<integer>(arr[0]);
- REQUIRE(int_obj == 42);
+ REQUIRE(int_obj);
+ REQUIRE(*int_obj == 42);
auto bool_obj = object_cast<boolean>(arr[1]);
- REQUIRE(bool_obj == true);
+ REQUIRE(bool_obj);
+ REQUIRE(*bool_obj == true);
auto string_obj = object_cast<string>(arr[2]);
- REQUIRE(string_obj == "test");
+ REQUIRE(string_obj);
+ REQUIRE(*string_obj == "test");
}
TEST_CASE("ucl: array: heterogenous cast", "[ucl]")
@@ -243,10 +262,14 @@ TEST_CASE("ucl: array: heterogenous cast", "[ucl]")
arr.push_back(boolean(true));
// Converting to an array<integer> should fail.
- REQUIRE_THROWS_AS(object_cast<array<integer>>(arr), type_mismatch);
+ auto cast_ok = object_cast<array<integer>>(arr);
+ REQUIRE(!cast_ok);
// Converting to array<object> should succeed.
- auto obj_arr = object_cast<array<object>>(arr);
+ auto err = object_cast<array<object>>(arr);
+ REQUIRE(err);
+
+ auto obj_arr = *err;
REQUIRE(obj_arr[0] == integer(42));
}
@@ -261,10 +284,14 @@ TEST_CASE("ucl: array: homogeneous cast", "[ucl]")
auto obj = object(ref, arr.get_ucl_object());
// Converting to array<string> should fail.
- REQUIRE_THROWS_AS(object_cast<array<string>>(obj), type_mismatch);
+ auto cast_ok = object_cast<array<string>>(obj);
+ REQUIRE(!cast_ok);
// Converting to an array<integer> should succeed.
- auto obj_arr = object_cast<array<integer>>(obj);
+ auto err = object_cast<array<integer>>(obj);
+ REQUIRE(err);
+
+ auto obj_arr = *err;
REQUIRE(obj_arr[0] == 1);
REQUIRE(obj_arr[1] == 42);
}
diff --git a/nihil.ucl/tests/boolean.cc b/nihil.ucl/tests/boolean.cc
index 49dc408..495071d 100644
--- a/nihil.ucl/tests/boolean.cc
+++ b/nihil.ucl/tests/boolean.cc
@@ -62,8 +62,11 @@ TEST_CASE("ucl: boolean: key()", "[ucl]")
{
using namespace nihil::ucl;
- auto obj = parse("a_bool = true");
- REQUIRE(object_cast<boolean>(obj["a_bool"]).key() == "a_bool");
+ auto err = parse("a_bool = true");
+ REQUIRE(err);
+
+ auto obj = *err;
+ REQUIRE(object_cast<boolean>(obj["a_bool"])->key() == "a_bool");
auto b = nihil::ucl::boolean(true);
REQUIRE(b.key() == "");
@@ -95,10 +98,14 @@ TEST_CASE("ucl: boolean: parse", "[ucl]")
{
using namespace std::literals;
- auto obj = nihil::ucl::parse("value = true"sv);
+ auto err = nihil::ucl::parse("value = true"sv);
+ REQUIRE(err);
+
+ auto obj = *err;
+
auto v = obj["value"];
REQUIRE(v.key() == "value");
- REQUIRE(object_cast<nihil::ucl::boolean>(v) == true);
+ REQUIRE(*object_cast<nihil::ucl::boolean>(v) == true);
}
TEST_CASE("ucl: boolean: emit", "[ucl]")
@@ -111,9 +118,10 @@ TEST_CASE("ucl: boolean: emit", "[ucl]")
TEST_CASE("ucl: boolean: parse and emit", "[ucl]")
{
auto ucl = nihil::ucl::parse("bool = true;");
+ REQUIRE(ucl);
auto output = std::string();
- emit(ucl, nihil::ucl::emitter::configuration,
+ emit(*ucl, nihil::ucl::emitter::configuration,
std::back_inserter(output));
REQUIRE(output == "bool = true;\n");
diff --git a/nihil.ucl/tests/emit.cc b/nihil.ucl/tests/emit.cc
index a8487c6..d75255b 100644
--- a/nihil.ucl/tests/emit.cc
+++ b/nihil.ucl/tests/emit.cc
@@ -15,11 +15,13 @@ TEST_CASE("ucl: emit to std::ostream", "[ucl]")
using namespace std::literals;
auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv);
+ REQUIRE(obj);
+
auto strm = std::ostringstream();
- strm << obj;
+ strm << *obj;
// The ostream emitter produces JSON.
- REQUIRE(strm.str() == std::format("{:j}", obj));
+ REQUIRE(strm.str() == std::format("{:j}", *obj));
}
TEST_CASE("ucl: emit JSON with std::format", "[ucl]")
@@ -27,7 +29,9 @@ TEST_CASE("ucl: emit JSON with std::format", "[ucl]")
using namespace std::literals;
auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv);
- auto str = std::format("{:j}", obj);
+ REQUIRE(obj);
+
+ auto str = std::format("{:j}", *obj);
REQUIRE(str ==
"{\n"
@@ -39,7 +43,7 @@ TEST_CASE("ucl: emit JSON with std::format", "[ucl]")
"}");
// Make sure JSON is the default format.
- auto str2 = std::format("{}", obj);
+ auto str2 = std::format("{}", *obj);
REQUIRE(str == str2);
}
@@ -48,7 +52,9 @@ TEST_CASE("ucl: emit compact JSON with std::format", "[ucl]")
using namespace std::literals;
auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv);
- auto str = std::format("{:J}", obj);
+ REQUIRE(obj);
+
+ auto str = std::format("{:J}", *obj);
REQUIRE(str == "{\"int\":[1,42,666]}");
}
@@ -58,7 +64,9 @@ TEST_CASE("ucl: emit configuration with std::format", "[ucl]")
using namespace std::literals;
auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv);
- auto str = std::format("{:c}", obj);
+ REQUIRE(obj);
+
+ auto str = std::format("{:c}", *obj);
REQUIRE(str ==
"int [\n"
@@ -73,7 +81,9 @@ TEST_CASE("ucl: emit YAML with std::format", "[ucl]")
using namespace std::literals;
auto obj = nihil::ucl::parse("int = [1, 42, 666]"sv);
- auto str = std::format("{:y}", obj);
+ REQUIRE(obj);
+
+ auto str = std::format("{:y}", *obj);
REQUIRE(str ==
"int: [\n"
diff --git a/nihil.ucl/tests/integer.cc b/nihil.ucl/tests/integer.cc
index 811a864..05647fe 100644
--- a/nihil.ucl/tests/integer.cc
+++ b/nihil.ucl/tests/integer.cc
@@ -63,8 +63,11 @@ TEST_CASE("ucl: integer: key()", "[ucl]")
{
using namespace nihil::ucl;
- auto obj = parse("an_int = 42");
- REQUIRE(object_cast<integer>(obj["an_int"]).key() == "an_int");
+ auto err = parse("an_int = 42");
+ REQUIRE(err);
+
+ auto obj = *err;
+ REQUIRE(object_cast<integer>(obj["an_int"])->key() == "an_int");
auto i = nihil::ucl::integer(42);
REQUIRE(i.key() == "");
@@ -96,7 +99,11 @@ TEST_CASE("ucl: integer: parse", "[ucl]")
{
using namespace std::literals;
- auto obj = nihil::ucl::parse("value = 42"sv);
+ auto err = nihil::ucl::parse("value = 42"sv);
+ REQUIRE(err);
+
+ auto obj = *err;
+
auto v = obj["value"];
REQUIRE(v.key() == "value");
REQUIRE(object_cast<nihil::ucl::integer>(v) == 42);
@@ -112,9 +119,10 @@ TEST_CASE("ucl: integer: emit", "[ucl]")
TEST_CASE("ucl: integer: parse and emit", "[ucl]")
{
auto ucl = nihil::ucl::parse("int = 42;");
+ REQUIRE(ucl);
auto output = std::string();
- emit(ucl, nihil::ucl::emitter::configuration,
+ emit(*ucl, nihil::ucl::emitter::configuration,
std::back_inserter(output));
REQUIRE(output == "int = 42;\n");
diff --git a/nihil.ucl/tests/object.cc b/nihil.ucl/tests/object.cc
index 1bbcf4f..3ad180e 100644
--- a/nihil.ucl/tests/object.cc
+++ b/nihil.ucl/tests/object.cc
@@ -26,12 +26,19 @@ TEST_CASE("ucl object: compare", "[ucl]")
using namespace std::literals;
auto obj_41 = nihil::ucl::parse("int = 41;"sv);
+ REQUIRE(obj_41);
+
auto obj_42 = nihil::ucl::parse("int = 42;"sv);
+ REQUIRE(obj_42);
+
auto obj_42_2 = nihil::ucl::parse("int = 42;"sv);
+ REQUIRE(obj_42_2);
+
auto obj_43 = nihil::ucl::parse("int = 43;"sv);
+ REQUIRE(obj_43);
- REQUIRE(obj_42 == obj_42_2);
- REQUIRE(obj_42 != obj_43);
- REQUIRE(obj_42 < obj_43);
- REQUIRE(obj_42 > obj_41);
+ REQUIRE(*obj_42 == *obj_42_2);
+ REQUIRE(*obj_42 != *obj_43);
+ REQUIRE(*obj_42 < *obj_43);
+ REQUIRE(*obj_42 > *obj_41);
}
diff --git a/nihil.ucl/tests/parse.cc b/nihil.ucl/tests/parse.cc
index 3cf5742..c56974e 100644
--- a/nihil.ucl/tests/parse.cc
+++ b/nihil.ucl/tests/parse.cc
@@ -15,13 +15,18 @@ TEST_CASE("ucl parse: iterate array", "[ucl]")
using namespace std::literals;
using namespace nihil::ucl;
- auto obj = parse("value = [1, 42, 666];"sv);
+ auto err = parse("value = [1, 42, 666];"sv);
+ REQUIRE(err);
+
+ auto obj = *err;
auto arr = obj["value"];
REQUIRE(arr.key() == "value");
- auto vec = std::vector(std::from_range,
- object_cast<array<integer>>(arr));
+ auto ints = object_cast<array<integer>>(arr);
+ REQUIRE(ints);
+
+ auto vec = std::vector(std::from_range, *ints);
REQUIRE(vec.size() == 3);
REQUIRE(vec[0] == 1);
@@ -36,8 +41,9 @@ TEST_CASE("ucl parse: iterate hash", "[ucl]")
auto input = "int = 42; bool = true; str = \"test\";"sv;
auto obj = parse(input);
+ REQUIRE(obj);
- for (auto &&[key, value] : obj) {
+ for (auto &&[key, value] : *obj) {
REQUIRE(key == value.key());
if (key == "int")
diff --git a/nihil.ucl/tests/real.cc b/nihil.ucl/tests/real.cc
index b11c113..be4e213 100644
--- a/nihil.ucl/tests/real.cc
+++ b/nihil.ucl/tests/real.cc
@@ -31,7 +31,7 @@ TEST_CASE("ucl: real: invariants", "[ucl]")
TEST_CASE("ucl: real: construct", "[ucl]")
{
auto obj = nihil::ucl::real(42.1);
- REQUIRE_THAT(object_cast<nihil::ucl::real>(obj).value(),
+ REQUIRE_THAT(object_cast<nihil::ucl::real>(obj)->value(),
Catch::Matchers::WithinRel(42.1));
}
@@ -80,19 +80,26 @@ TEST_CASE("ucl: real: parse", "[ucl]")
{
using namespace std::literals;
- auto obj = nihil::ucl::parse("value = 42.1"sv);
+ auto err = nihil::ucl::parse("value = 42.1"sv);
+ REQUIRE(err);
+
+ auto obj = *err;
+
auto v = obj["value"];
REQUIRE(v.key() == "value");
- REQUIRE_THAT(object_cast<nihil::ucl::real>(v).value(),
+ REQUIRE_THAT(object_cast<nihil::ucl::real>(v)->value(),
Catch::Matchers::WithinRel(42.1));
}
TEST_CASE("ucl: real: emit", "[ucl]")
{
- auto ucl = nihil::ucl::parse("real = 42.2");
+ auto err = nihil::ucl::parse("real = 42.2");
+ REQUIRE(err);
+
+ auto obj = *err;
auto output = std::string();
- emit(ucl, nihil::ucl::emitter::configuration,
+ emit(obj, nihil::ucl::emitter::configuration,
std::back_inserter(output));
REQUIRE(output == "real = 42.2;\n");
diff --git a/nihil.ucl/tests/string.cc b/nihil.ucl/tests/string.cc
index e7eb0ad..995e95a 100644
--- a/nihil.ucl/tests/string.cc
+++ b/nihil.ucl/tests/string.cc
@@ -109,8 +109,11 @@ TEST_CASE("ucl: string: key()", "[ucl]")
{
using namespace nihil::ucl;
- auto obj = parse("a_string = \"test\"");
- REQUIRE(object_cast<string>(obj["a_string"]).key() == "a_string");
+ auto err = parse("a_string = \"test\"");
+ REQUIRE(err);
+
+ auto obj = *err;
+ REQUIRE(object_cast<string>(obj["a_string"])->key() == "a_string");
auto s = nihil::ucl::string("test");
REQUIRE(s.key() == "");
@@ -188,7 +191,10 @@ TEST_CASE("ucl: string: parse", "[ucl]")
{
using namespace std::literals;
- auto obj = nihil::ucl::parse("value = \"te\\\"st\""sv);
+ auto err = nihil::ucl::parse("value = \"te\\\"st\""sv);
+ REQUIRE(err);
+
+ auto obj = *err;
auto v = obj["value"];
REQUIRE(v.key() == "value");
REQUIRE(object_cast<nihil::ucl::string>(v) == "te\"st");
@@ -204,9 +210,10 @@ TEST_CASE("ucl: string: emit", "[ucl]")
TEST_CASE("ucl: string: parse and emit", "[ucl]")
{
auto ucl = nihil::ucl::parse("str = \"te\\\"st\";");
+ REQUIRE(ucl);
auto output = std::string();
- emit(ucl, nihil::ucl::emitter::configuration,
+ emit(*ucl, nihil::ucl::emitter::configuration,
std::back_inserter(output));
REQUIRE(output == "str = \"te\\\"st\";\n");