#include "kapi/cio.hpp" #include #include #include #include #include #include #include namespace teachos::cio { namespace { struct null_device final : public output_device { null_device static instance; auto write(std::string_view) -> void override {} auto writeln(std::string_view) -> void override {} auto write_error(std::string_view) -> void override {} auto writeln_error(std::string_view) -> void override {} }; constinit null_device null_device::instance; struct write_buffer { constexpr auto static size = 128uz; write_buffer(write_buffer const &) = delete; write_buffer(write_buffer &&) = delete; auto operator=(write_buffer const &) -> write_buffer & = delete; auto operator=(write_buffer &&) -> write_buffer & = delete; explicit write_buffer(output_device * device, bool write_to_error) : m_device{device} , m_write_to_error{write_to_error} {} ~write_buffer() noexcept { flush(); } auto flush() noexcept -> void { if (m_position > 0) { std::string_view chunk{m_buffer.data(), m_position}; if (m_write_to_error) { m_device->write_error(chunk); } else { m_device->write(chunk); } m_position = 0; } } auto static callback(void * object, std::string_view text) -> void { auto * self = static_cast(object); for (char const character : text) { if (self->m_position >= size) { self->flush(); } self->m_buffer.at(self->m_position++) = character; } } private: output_device * m_device; bool m_write_to_error; std::array m_buffer{}; std::size_t m_position{}; }; } // namespace // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) constinit auto active_device = static_cast(&null_device::instance); auto set_output_device(output_device & device) -> std::optional { if (&device == active_device) { return {}; } return std::exchange(active_device, &device); } auto print(std::string_view text) -> void { active_device->write(text); } auto println(std::string_view text) -> void { active_device->writeln(text); } auto print_error(std::string_view text) -> void { active_device->write_error(text); } auto println_error(std::string_view text) -> void { active_device->writeln_error(text); } namespace { auto static vprint_impl(std::string_view fmt, kstd::format_args args, bool write_to_error) -> void { if (!active_device) return; auto writer = write_buffer{active_device, write_to_error}; auto context = kstd::format_context{.writer = write_buffer::callback, .user_data = &writer}; auto current = fmt.begin(); auto end = fmt.end(); auto next_automatic_index = 0uz; while (current != end) { if (*current != '{') { auto start = current; while (current != end && *current != '{') { std::advance(current, 1); } context.push(std::string_view(start, current - start)); continue; } if (std::next(current) != end && *(std::next(current)) == '{') { context.push('{'); std::advance(current, 2); continue; } std::advance(current, 1); auto index = 0uz; if (current != end && *current >= '0' && *current <= '9') { while (current != end && *current >= '0' && *current <= '9') { // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers) index = index * 10 + static_cast(*current - '0'); std::advance(current, 1); } } else { index = next_automatic_index++; } auto remaining_fmt = std::string_view{current, static_cast(std::distance(current, end))}; auto const arg = args.get(index); if (arg.format) { auto const after_specs = arg.format(arg.value, remaining_fmt, context); auto const consumed = remaining_fmt.size() - after_specs.size(); std::advance(current, consumed); } else { context.push("{?}"); while (current != end && *current != '}') std::advance(current, 1); } if (current != end && *current == '}') { std::advance(current, 1); } else { context.push("{fmt-err}"); while (current != end && *current != '}') std::advance(current, 1); if (current != end) std::advance(current, 1); } } } } // namespace auto vprint(std::string_view fmt, kstd::format_args args) -> void { vprint_impl(fmt, args, false); } auto vprint_error(std::string_view fmt, kstd::format_args args) -> void { vprint_impl(fmt, args, true); } } // namespace teachos::cio