// This source code is released into the public domain. module; #include export module nihil.ucl:parser; import nihil.std; import nihil.util; import :object; import :map; namespace nihil::ucl { // UCL parser flags. 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; // A macro handler. This proxies the C API callback to the C++ API. using macro_callback_t = bool(std::string_view); struct macro_handler { std::function 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(ud); auto string = std::string_view(reinterpret_cast(data), len); return handler->callback(string); } }; // A UCL parser. This wraps the C ucl_parser API. // // parser itself is not exported; use make_parser() to create one. struct parser { // Create a parser from a UCL parser. explicit parser(::ucl_parser *uclp) : m_parser(uclp) { } // Destroy our parser when we're destroyed. ~parser() { if (m_parser != nullptr) ::ucl_parser_free(m_parser); } // Not copyable. parser(parser const &) = delete; auto operator=(this parser &, parser const &) -> parser & = delete; // Movable. parser(parser &&other) noexcept : m_parser(std::exchange(other.m_parser, nullptr)) , m_macros(std::move(other.m_macros)) { } auto operator=(this parser &self, parser &&other) noexcept -> parser & { if (&self != &other) { if (self.m_parser != nullptr) ::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; // NOLINT } // 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 F> auto register_macro(this parser &self, std::string_view name, F &&func) -> void requires(std::same_as>) { auto handler = std::make_unique(std::forward(func)); auto cname = std::string(name); ::ucl_parser_register_macro(self.get_parser(), cname.c_str(), ¯o_handler::handle, handler.get()); 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 { ::ucl_parser_register_variable(self.get_parser(), std::string(variable).c_str(), std::string(value).c_str()); } // Add data to the parser. [[nodiscard]] auto add(this parser &self, std::ranges::contiguous_range auto &&data) -> std::expected // Only bytes (chars) are permitted. requires(sizeof(std::ranges::range_value_t) == 1) { auto *p = self.get_parser(); auto dptr = reinterpret_cast(std::ranges::data(data)); auto ret = ::ucl_parser_add_chunk(p, dptr, std::ranges::size(data)); if (ret == true) return {}; return std::unexpected(error(::ucl_parser_get_error(p))); } [[nodiscard]] auto add(this parser &self, std::ranges::range auto &&data) -> std::expected requires(!std::ranges::contiguous_range) { auto cdata = std::vector(std::from_range, std::forward(data)); co_await self.add(std::move(cdata)); co_return {}; } // Return the top object of this parser. [[nodiscard]] auto top(this parser &self) -> map { auto *obj = ::ucl_parser_get_object(self.get_parser()); if (obj != nullptr) // ucl_parser_get_object() refs the object for us. return {noref, obj}; throw std::logic_error("attempt to call top() on an invalid ucl::parser"); } // Return the stored parser object. [[nodiscard]] auto 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; } private: // The parser object. Should never be null, unless we've been // moved-from. 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> m_macros; }; // Create a parser with the given flags. export [[nodiscard]] auto make_parser(int flags = 0) -> std::expected { auto *p = ::ucl_parser_new(flags); if (p != nullptr) return {parser(p)}; // TODO: Is there a way to get the actual error here? return std::unexpected(error("failed to create parser")); } // Utility function to parse something and return the top-level object. export [[nodiscard]] auto parse(int flags, std::ranges::range auto &&data) -> std::expected, error> { auto p = co_await make_parser(flags); co_await p.add(std::forward(data)); co_return p.top(); } export [[nodiscard]] auto parse(std::ranges::range auto &&data) -> std::expected, error> { co_return co_await parse(0, std::forward(data)); } } // namespace nihil::ucl