diff options
Diffstat (limited to 'nihil.ucl/emit.ccm')
| -rw-r--r-- | nihil.ucl/emit.ccm | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/nihil.ucl/emit.ccm b/nihil.ucl/emit.ccm new file mode 100644 index 0000000..b88f8e7 --- /dev/null +++ b/nihil.ucl/emit.ccm @@ -0,0 +1,209 @@ +/* + * This source code is released into the public domain. + */ + +module; + +#include <array> +#include <charconv> +#include <cstdlib> +#include <format> +#include <iterator> +#include <iosfwd> +#include <span> +#include <string> +#include <utility> + +#include <ucl.h> + +export module nihil.ucl:emit; + +import :object; + +namespace nihil::ucl { + +export enum struct emitter { + configuration = UCL_EMIT_CONFIG, + compact_json = UCL_EMIT_JSON_COMPACT, + json = UCL_EMIT_JSON, + yaml = UCL_EMIT_YAML, +}; + +/* + * Wrap ucl_emitter_functions for a particular output iterator type. + * + * 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 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 { + emit_wrapper(Iterator iterator_) + : iterator(std::move(iterator_)) + {} + + static auto append_character(unsigned char c, std::size_t nchars, + void *ud) + noexcept -> int + try { + auto *self = static_cast<emit_wrapper *>(ud); + + while (nchars--) + *self->iterator++ = static_cast<char>(c); + + return 0; + } catch (...) { + return 0; + } + + static auto append_len(unsigned char const *str, std::size_t len, + void *ud) + noexcept -> int + try { + auto *self = static_cast<emit_wrapper *>(ud); + + for (auto c : std::span(str, len)) + *self->iterator++ = static_cast<char>(c); + + return 0; + } catch (...) { + return 0; + } + + static auto append_int(std::int64_t value, void *ud) + noexcept -> int + try { + auto constexpr bufsize = + std::numeric_limits<std::int64_t>::digits10; + auto buf = std::array<char, bufsize>(); + + auto *self = static_cast<emit_wrapper *>(ud); + auto result = std::to_chars(buf.data(), buf.data() + buf.size(), + value, 10); + + if (result.ec == std::errc()) + for (auto c : std::span(buf.data(), result.ptr)) + *self->iterator++ = c; + + return 0; + } catch (...) { + return 0; + } + + static auto append_double(double value, void *ud) + noexcept -> int + try { + auto constexpr bufsize = + std::numeric_limits<double>::digits10; + auto buf = std::array<char, bufsize>(); + + auto *self = static_cast<emit_wrapper *>(ud); + auto result = std::to_chars(buf.data(), buf.data() + buf.size(), + value); + + if (result.ec == std::errc()) + for (auto c : std::span(buf.data(), result.ptr)) + *self->iterator++ = c; + + return 0; + } catch (...) { + return 0; + } + + auto get_functions(this emit_wrapper &self) -> ucl_emitter_functions + { + auto ret = ucl_emitter_functions{}; + + ret.ucl_emitter_append_character = &emit_wrapper::append_character; + ret.ucl_emitter_append_len = &emit_wrapper::append_len; + ret.ucl_emitter_append_int = &emit_wrapper::append_int; + ret.ucl_emitter_append_double = &emit_wrapper::append_double; + ret.ud = &self; + + return ret; + } + +private: + Iterator iterator{}; +}; + +export auto emit(object const &object, emitter format, + std::output_iterator<char> auto &&it) + -> void +{ + auto ucl_format = static_cast<ucl_emitter>(format); + auto wrapper = emit_wrapper(it); + auto functions = wrapper.get_functions(); + + ::ucl_object_emit_full(object.get_ucl_object(), ucl_format, + &functions, nullptr); +} + +/* + * Basic ostream printer for UCL; default to JSON since it's probably what + * most people expect. + */ +export auto operator<<(std::ostream &, object const &) -> std::ostream &; + +} // namespace nihil::ucl + +/* + * Specialisation of std::formatter<> for object. + */ +template<std::derived_from<nihil::ucl::object> T> +struct std::formatter<T, char> +{ + nihil::ucl::emitter emitter = nihil::ucl::emitter::json; + + template<class ParseContext> + constexpr ParseContext::iterator parse(ParseContext& ctx) + { + auto it = ctx.begin(); + auto end = ctx.end(); + + while (it != end) { + switch (*it) { + case 'j': + emitter = nihil::ucl::emitter::json; + break; + case 'J': + emitter = nihil::ucl::emitter::compact_json; + break; + case 'c': + emitter = nihil::ucl::emitter::configuration; + break; + case 'y': + emitter = nihil::ucl::emitter::yaml; + break; + case '}': + return it; + default: + throw std::format_error("Invalid format string " + "for UCL object"); + } + + ++it; + } + + return it; + } + + template<class FmtContext> + FmtContext::iterator format(nihil::ucl::object const &o, + FmtContext& ctx) const + { + // We can't use emit() here since the context iterator is not + // an std::output_iterator. + + auto out = ctx.out(); + + auto ucl_format = static_cast<::ucl_emitter>(emitter); + auto wrapper = nihil::ucl::emit_wrapper(out); + auto functions = wrapper.get_functions(); + + ::ucl_object_emit_full(o.get_ucl_object(), ucl_format, + &functions, nullptr); + return out; + } +}; |
