diff options
| author | Felix Morgner <felix.morgner@gmail.com> | 2026-06-18 16:42:03 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@gmail.com> | 2026-06-18 17:04:51 +0200 |
| commit | a7aadafc8d6842fb960786d4840dc29c490d3071 (patch) | |
| tree | 50fd136e3d88807dc9e0bce8899712be5325b10e /ttwhy | |
| parent | c6262346b45b6c84fd1a009f21465d3af1b63593 (diff) | |
| download | ttwhy-a7aadafc8d6842fb960786d4840dc29c490d3071.tar.xz ttwhy-a7aadafc8d6842fb960786d4840dc29c490d3071.zip | |
lib: add simple echo router
Diffstat (limited to 'ttwhy')
| -rw-r--r-- | ttwhy/event.cppm | 1 | ||||
| -rw-r--r-- | ttwhy/io.cppm | 67 | ||||
| -rw-r--r-- | ttwhy/main.cpp | 13 | ||||
| -rw-r--r-- | ttwhy/routers.cppm | 3 | ||||
| -rw-r--r-- | ttwhy/routers/echo.cppm | 84 | ||||
| -rw-r--r-- | ttwhy/scanner.cppm | 127 |
6 files changed, 241 insertions, 54 deletions
diff --git a/ttwhy/event.cppm b/ttwhy/event.cppm index 3a81bff..b37cc51 100644 --- a/ttwhy/event.cppm +++ b/ttwhy/event.cppm @@ -8,7 +8,6 @@ namespace ttwhy { export enum class control_key { - del, enter, escape, tab, diff --git a/ttwhy/io.cppm b/ttwhy/io.cppm index 230f180..fd84dfd 100644 --- a/ttwhy/io.cppm +++ b/ttwhy/io.cppm @@ -2,11 +2,15 @@ module; #include <asio.hpp> -#include <expected> #include <format> +#include <span> +#include <vector> export module ttwhy:io; +import :event; +import :scanner; + namespace ttwhy::io { @@ -25,58 +29,35 @@ namespace ttwhy::io co_return error; } - export template<typename InputStream, typename ErrorStream> - auto read(std::span<char> data, InputStream & input_stream, ErrorStream & error_stream) - -> asio::awaitable<std::expected<std::size_t, asio::error_code>> - { - auto [error, read] = co_await input_stream.async_read_some(asio::buffer(data), asio::as_tuple(asio::use_awaitable)); - - if (error) - { - auto message = std::format("{}, exiting ...\n", error.message()); - co_await asio::async_write(error_stream, asio::buffer(message), asio::use_awaitable); - co_return std::unexpected{error}; - } - - co_return read; - } - - export template<typename OutputStream, typename ErrorStream> - auto write(std::span<char const> string, OutputStream & output_stream, ErrorStream & error_stream) - -> asio::awaitable<asio::error_code> + export template<typename InputStream, typename AppRouter> + auto read_events(InputStream & stream, AppRouter & router) -> asio::awaitable<void> { - auto [error, written] = - co_await asio::async_write(output_stream, asio::buffer(string), asio::as_tuple(asio::use_awaitable)); + auto queue = std::vector<input_event>{}; + queue.reserve(16); - if (error) - { - auto message = std::format("{}, exiting ...\n", error.message()); - co_await asio::async_write(error_stream, asio::buffer(message), asio::use_awaitable); - } - - co_return error; - } - - export template<typename InputStream, typename OutputStream, typename ErrorStream> - auto echo(InputStream & input_stream, OutputStream & output_stream, ErrorStream & error_stream) - -> asio::awaitable<asio::error_code> - { - auto input = std::array<char, 16>{}; + auto scanner = ansi_scanner{queue}; + auto raw_buffer = std::array<char, 64>{}; while (true) { - auto read_result = co_await read(input, input_stream, error_stream); - if (!read_result.has_value()) + auto [error, bytes_read] = + co_await stream.async_read_some(asio::buffer(raw_buffer), asio::as_tuple(asio::use_awaitable)); + + if (error) { - co_return read_result.error(); + co_return; } - auto to_write = std::span{input.data(), read_result.value()}; - auto write_error = co_await write(to_write, output_stream, error_stream); - if (write_error) + auto const byte_span = std::span<char const>{raw_buffer.data(), bytes_read}; + + scanner.process(byte_span); + + for (auto const & event : queue) { - co_return write_error; + co_await router.process(event); } + + queue.clear(); } } diff --git a/ttwhy/main.cpp b/ttwhy/main.cpp index 94c1330..6a813c0 100644 --- a/ttwhy/main.cpp +++ b/ttwhy/main.cpp @@ -3,9 +3,8 @@ #include <stdio.h> -#include <format> - import ttwhy; +import ttwhy.routers; auto app(int in, int out, int error) -> asio::awaitable<void> { @@ -17,15 +16,9 @@ auto app(int in, int out, int error) -> asio::awaitable<void> auto output_stream = asio::posix::stream_descriptor{executor, out}; auto error_stream = asio::posix::stream_descriptor{executor, error}; - auto result = - co_await (ttwhy::io::handle_signals(error_stream) || ttwhy::io::echo(input_stream, output_stream, error_stream)); - auto final_error = result.index() == 0 ? std::get<0>(result) : std::get<1>(result); + auto router = ttwhy::routers::echo{input_stream}; - if (final_error && final_error != asio::error::operation_aborted) - { - auto message = std::format("Terminated with error: {}\n", final_error.message()); - co_await asio::async_write(error_stream, asio::buffer(message), asio::use_awaitable); - } + co_await (ttwhy::io::handle_signals(error_stream) || ttwhy::io::read_events(input_stream, router)); } auto main() -> int diff --git a/ttwhy/routers.cppm b/ttwhy/routers.cppm new file mode 100644 index 0000000..fbab282 --- /dev/null +++ b/ttwhy/routers.cppm @@ -0,0 +1,3 @@ +export module ttwhy.routers; + +export import :echo; diff --git a/ttwhy/routers/echo.cppm b/ttwhy/routers/echo.cppm new file mode 100644 index 0000000..41b7b51 --- /dev/null +++ b/ttwhy/routers/echo.cppm @@ -0,0 +1,84 @@ +module; + +#include <asio.hpp> + +#include <format> +#include <string_view> + +export module ttwhy.routers:echo; + +import ttwhy; + +namespace ttwhy::routers +{ + + using namespace std::string_view_literals; + + constexpr auto vte_backspace_sequence = "\b \b"sv; + + export template<typename StreamType> + struct echo + { + explicit echo(StreamType & stream) + : m_output_stream{stream} + {} + + auto process(ttwhy::input_event event) -> asio::awaitable<void> + { + co_await std::visit( + [&](auto && event) -> asio::awaitable<void> { + using event_type = std::decay_t<decltype(event)>; + + if constexpr (std::same_as<character_event, event_type>) + { + co_await process_character(event.value); + } + else if constexpr (std::same_as<control_event, event_type>) + { + co_await process_control_key(event.key); + } + else if constexpr (std::same_as<navigation_event, event_type>) + { + co_await process_navigation_key(event.key); + } + }, + event); + } + + private: + auto process_character(char character) -> asio::awaitable<void> + { + co_await asio::async_write(m_output_stream, asio::buffer(&character, 1), asio::as_tuple(asio::use_awaitable)); + } + + auto process_control_key(control_key key) -> asio::awaitable<void> + { + switch (key) + { + case control_key::backspace: + co_await asio::async_write(m_output_stream, asio::buffer(vte_backspace_sequence), + asio::as_tuple(asio::use_awaitable)); + break; + default: + co_await asio::async_write(m_output_stream, asio::buffer(std::format("{{CTRL:{}}}", std::to_underlying(key))), + asio::as_tuple(asio::use_awaitable)); + }; + } + + auto process_navigation_key(navigation_key key) -> asio::awaitable<void> + { + switch (key) + { + case navigation_key::delete_key: + co_await asio::async_write(m_output_stream, asio::buffer("{DEL}"), asio::as_tuple(asio::use_awaitable)); + break; + default: + co_await asio::async_write(m_output_stream, asio::buffer(std::format("{{NAV:{}}}", std::to_underlying(key))), + asio::as_tuple(asio::use_awaitable)); + } + } + + StreamType & m_output_stream; + }; + +} // namespace ttwhy::routers diff --git a/ttwhy/scanner.cppm b/ttwhy/scanner.cppm new file mode 100644 index 0000000..86a8493 --- /dev/null +++ b/ttwhy/scanner.cppm @@ -0,0 +1,127 @@ +module; + +#include <algorithm> +#include <boost/sml.hpp> + +#include <cctype> +#include <span> +#include <vector> + +export module ttwhy:scanner; + +import :event; + +namespace ttwhy::detail +{ + + /// Events + + struct byte_received + { + char value; + }; + + /// States + + auto const idle = boost::sml::state<class idle>; + auto const escape_sequence = boost::sml::state<class escape_sequence>; + auto const csi_sequence = boost::sml::state<class csi_sequence>; + + /// Actions + + auto push_character = [](byte_received const & event, std::vector<input_event> & queue) { + queue.push_back(character_event{event.value}); + }; + + auto push_backspace = [](std::vector<input_event> & queue) { + queue.push_back(control_event{control_key::backspace}); + }; + + auto push_delete = [](std::vector<input_event> & queue) { + queue.push_back(navigation_event{navigation_key::delete_key}); + }; + + auto fallback_escape = [](byte_received const & event, std::vector<input_event> & queue) { + queue.push_back(control_event{control_key::escape}); + if (event.value >= 0x20 && event.value <= 0x7e) + { + queue.push_back(character_event{event.value}); + } + }; + + /// Guards + + auto is_backspace = [](byte_received e) { + return e.value == '\x08' || e.value == '\x7f'; + }; + + auto is_escape = [](byte_received e) { + return e.value == '\x1b'; + }; + + auto is_printable = [](byte_received e) { + return e.value >= 0x20 && e.value <= 0x7e; + }; + + auto is_csi_introducer = [](byte_received e) { + return e.value == '['; + }; + + auto is_csi_param = [](byte_received e) { + return e.value >= 0x20 && e.value <= 0x3f; + }; + + auto is_csi_terminator = [](byte_received e) { + return e.value >= 0x40 && e.value <= 0x7e; + }; + + auto is_tilde = [](byte_received e) { + return e.value == '~'; + }; + + /// Transitions + + struct transition_table + { + auto operator()() const noexcept + { + using namespace boost::sml; + + // clang-format off + return make_transition_table( + *idle + event<byte_received>[is_escape] = escape_sequence, + idle + event<byte_received>[is_backspace] / push_backspace = idle, + idle + event<byte_received>[is_printable] / push_character = idle, + + escape_sequence + event<byte_received>[is_csi_introducer] = csi_sequence, + escape_sequence + event<byte_received>[!is_csi_introducer] / fallback_escape = idle, + + csi_sequence + event<byte_received>[is_csi_param] = csi_sequence, + csi_sequence + event<byte_received>[is_tilde] / push_delete = idle, + csi_sequence + event<byte_received>[is_csi_terminator] = idle + ); + // clang-format on + } + }; + +} // namespace ttwhy::detail + +export namespace ttwhy +{ + + struct ansi_scanner + { + explicit ansi_scanner(std::vector<input_event> & queue) + : m_state_machine{queue} + {} + + auto process(std::span<char const> buffer) -> void + { + std::ranges::for_each(buffer, [&](auto byte) { m_state_machine.process_event(detail::byte_received{byte}); }); + } + + private: + boost::sml::sm<detail::transition_table> m_state_machine; + }; + +} // namespace ttwhy |
