From 7da39cd68d2bed2f49452ac5f9a3c01c52d0c5ba Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 19 Jun 2026 19:25:30 +0200 Subject: lib: clean up structure --- CMakeLists.txt | 9 +- ttwhy/event.cppm | 47 -------- ttwhy/io.cppm | 7 +- ttwhy/lib.cppm | 1 - ttwhy/routers.cppm | 3 - ttwhy/routers/echo.cppm | 21 ++-- ttwhy/routers/mod.cppm | 3 + ttwhy/scanner.cppm | 262 ------------------------------------------- ttwhy/scanners/ansi.cppm | 262 +++++++++++++++++++++++++++++++++++++++++++ ttwhy/scanners/concepts.cppm | 18 +++ ttwhy/scanners/events.cppm | 47 ++++++++ ttwhy/scanners/mod.cppm | 5 + 12 files changed, 355 insertions(+), 330 deletions(-) delete mode 100644 ttwhy/event.cppm delete mode 100644 ttwhy/routers.cppm create mode 100644 ttwhy/routers/mod.cppm delete mode 100644 ttwhy/scanner.cppm create mode 100644 ttwhy/scanners/ansi.cppm create mode 100644 ttwhy/scanners/concepts.cppm create mode 100644 ttwhy/scanners/events.cppm create mode 100644 ttwhy/scanners/mod.cppm diff --git a/CMakeLists.txt b/CMakeLists.txt index 00318e7..bb68832 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,14 +48,17 @@ target_sources("ttwhy-core" PUBLIC FILE_SET CXX_MODULES FILES - "ttwhy/event.cppm" "ttwhy/io.cppm" "ttwhy/lib.cppm" - "ttwhy/scanner.cppm" "ttwhy/scoped_attributes.cppm" - "ttwhy/routers.cppm" "ttwhy/routers/echo.cppm" + "ttwhy/routers/mod.cppm" + + "ttwhy/scanners/ansi.cppm" + "ttwhy/scanners/concepts.cppm" + "ttwhy/scanners/mod.cppm" + "ttwhy/scanners/events.cppm" ) target_include_directories("ttwhy-core" PUBLIC diff --git a/ttwhy/event.cppm b/ttwhy/event.cppm deleted file mode 100644 index b37cc51..0000000 --- a/ttwhy/event.cppm +++ /dev/null @@ -1,47 +0,0 @@ -module; - -#include - -export module ttwhy:event; - -namespace ttwhy -{ - export enum class control_key - { - enter, - escape, - tab, - backspace, - }; - - export enum class navigation_key - { - up, - down, - left, - right, - home, - end, - delete_key, - insert_key, - page_up, - page_down, - }; - - export struct character_event - { - char value; - }; - - export struct control_event - { - control_key key; - }; - - export struct navigation_event - { - navigation_key key; - }; - - export using input_event = std::variant; -} // namespace ttwhy diff --git a/ttwhy/io.cppm b/ttwhy/io.cppm index 09f16c8..5fd5e50 100644 --- a/ttwhy/io.cppm +++ b/ttwhy/io.cppm @@ -10,8 +10,7 @@ module; export module ttwhy:io; -import :event; -import :scanner; +import ttwhy.scanners; namespace ttwhy::io { @@ -69,10 +68,10 @@ namespace ttwhy::io auto executor = co_await asio::this_coro::executor; auto timer = asio::steady_timer{executor}; - auto queue = std::vector{}; + auto queue = std::vector{}; queue.reserve(16); - auto scanner = ansi_scanner{queue}; + auto scanner = scanners::ansi{queue}; auto raw_buffer = std::array{}; while (true) diff --git a/ttwhy/lib.cppm b/ttwhy/lib.cppm index b7766bd..14e0d5c 100644 --- a/ttwhy/lib.cppm +++ b/ttwhy/lib.cppm @@ -1,5 +1,4 @@ export module ttwhy; -export import :event; export import :io; export import :scoped_attributes; diff --git a/ttwhy/routers.cppm b/ttwhy/routers.cppm deleted file mode 100644 index fbab282..0000000 --- a/ttwhy/routers.cppm +++ /dev/null @@ -1,3 +0,0 @@ -export module ttwhy.routers; - -export import :echo; diff --git a/ttwhy/routers/echo.cppm b/ttwhy/routers/echo.cppm index fb392d6..40e68d6 100644 --- a/ttwhy/routers/echo.cppm +++ b/ttwhy/routers/echo.cppm @@ -8,6 +8,7 @@ module; export module ttwhy.routers:echo; import ttwhy; +import ttwhy.scanners; namespace ttwhy::routers { @@ -25,21 +26,21 @@ namespace ttwhy::routers : m_output_stream{stream} {} - auto process(ttwhy::input_event event) -> asio::awaitable + auto process(scanners::input_event event) -> asio::awaitable { co_await std::visit( [&](auto && event) -> asio::awaitable { using event_type = std::decay_t; - if constexpr (std::same_as) + if constexpr (std::same_as) { co_await process_character(event.value); } - else if constexpr (std::same_as) + else if constexpr (std::same_as) { co_await process_control_key(event.key); } - else if constexpr (std::same_as) + else if constexpr (std::same_as) { co_await process_navigation_key(event.key); } @@ -53,19 +54,19 @@ namespace ttwhy::routers 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 + auto process_control_key(scanners::control_key key) -> asio::awaitable { switch (key) { - case control_key::backspace: + case scanners::control_key::backspace: co_await asio::async_write(m_output_stream, asio::buffer(vte_backspace_sequence), asio::as_tuple(asio::use_awaitable)); break; - case control_key::enter: + case scanners::control_key::enter: co_await asio::async_write(m_output_stream, asio::buffer(vte_newline_sequence), asio::as_tuple(asio::use_awaitable)); break; - case control_key::tab: + case scanners::control_key::tab: co_await asio::async_write(m_output_stream, asio::buffer(vte_horizontal_tab_sequqnce), asio::as_tuple(asio::use_awaitable)); break; @@ -76,11 +77,11 @@ namespace ttwhy::routers }; } - auto process_navigation_key(navigation_key key) -> asio::awaitable + auto process_navigation_key(scanners::navigation_key key) -> asio::awaitable { switch (key) { - case navigation_key::delete_key: + case scanners::navigation_key::delete_key: co_await asio::async_write(m_output_stream, asio::buffer("{DEL}"), asio::as_tuple(asio::use_awaitable)); break; default: diff --git a/ttwhy/routers/mod.cppm b/ttwhy/routers/mod.cppm new file mode 100644 index 0000000..fbab282 --- /dev/null +++ b/ttwhy/routers/mod.cppm @@ -0,0 +1,3 @@ +export module ttwhy.routers; + +export import :echo; diff --git a/ttwhy/scanner.cppm b/ttwhy/scanner.cppm deleted file mode 100644 index 2257f5f..0000000 --- a/ttwhy/scanner.cppm +++ /dev/null @@ -1,262 +0,0 @@ -module; - -#include -#include - -#include -#include -#include - -export module ttwhy:scanner; - -import :event; - -namespace ttwhy::detail -{ - - /// Events - - struct byte_received - { - char value; - }; - - struct timeout_expired - { - }; - - /// States - - constexpr auto idle = boost::sml::state; - constexpr auto escape_sequence = boost::sml::state; - constexpr auto csi_sequence = boost::sml::state; - constexpr auto ss3_sequence = boost::sml::state; - - /// Actions - - constexpr auto push_character = [](byte_received const & event, std::vector & queue) { - queue.push_back(character_event{event.value}); - }; - - constexpr auto push_backspace = [](std::vector & queue) { - queue.push_back(control_event{control_key::backspace}); - }; - - constexpr auto push_tab = [](std::vector & queue) { - queue.push_back(control_event(control_key::tab)); - }; - - constexpr auto push_enter = [](std::vector & queue) { - queue.push_back(control_event(control_key::enter)); - }; - - constexpr auto emit_timeout_escape = [](std::vector & queue, std::string & buffer) { - queue.push_back(control_event{control_key::escape}); - buffer.clear(); - }; - - constexpr auto fallback_escape = [](byte_received const & event, std::vector & queue) { - queue.push_back(control_event{control_key::escape}); - if (event.value >= 0x20 && event.value <= 0x7e) - { - queue.push_back(character_event{event.value}); - } - }; - - constexpr auto clear_csi = [](std::string & buffer) { - buffer.clear(); - }; - - constexpr auto push_csi_parameter = [](byte_received const & event, std::string & buffer) { - buffer.push_back(event.value); - }; - - constexpr auto resolve_csi = [](byte_received const & event, std::string & buffer, std::vector & queue) { - if (event.value == '~') - { - switch (buffer.at(0)) - { - case '1': - case '7': - queue.push_back(navigation_event{navigation_key::home}); - break; - case '2': - queue.push_back(navigation_event{navigation_key::insert_key}); - break; - case '3': - queue.push_back(navigation_event{navigation_key::delete_key}); - break; - case '4': - case '8': - queue.push_back(navigation_event{navigation_key::end}); - break; - case '5': - queue.push_back(navigation_event{navigation_key::page_up}); - break; - case '6': - queue.push_back(navigation_event{navigation_key::page_down}); - break; - default: - } - } - else - { - switch (event.value) - { - case 'A': - queue.push_back(navigation_event{navigation_key::up}); - break; - case 'B': - queue.push_back(navigation_event{navigation_key::down}); - break; - case 'C': - queue.push_back(navigation_event{navigation_key::right}); - break; - case 'D': - queue.push_back(navigation_event{navigation_key::left}); - break; - case 'H': - queue.push_back(navigation_event{navigation_key::home}); - break; - case 'F': - queue.push_back(navigation_event{navigation_key::end}); - break; - default: - } - } - buffer.clear(); - }; - - constexpr auto resolve_ss3 = [](byte_received const & event, std::vector & queue) { - switch (event.value) - { - case 'A': - queue.push_back(navigation_event{navigation_key::up}); - break; - case 'B': - queue.push_back(navigation_event{navigation_key::down}); - break; - case 'C': - queue.push_back(navigation_event{navigation_key::right}); - break; - case 'D': - queue.push_back(navigation_event{navigation_key::left}); - break; - case 'H': - queue.push_back(navigation_event{navigation_key::home}); - break; - case 'F': - queue.push_back(navigation_event{navigation_key::end}); - break; - default: - } - }; - - /// Guards - - constexpr auto is_backspace = [](byte_received e) { - return e.value == '\x08' || e.value == '\x7f'; - }; - - constexpr auto is_tab = [](byte_received e) { - return e.value == '\x09'; - }; - - constexpr auto is_enter = [](byte_received e) { - return e.value == '\x0a' || e.value == '\x0d'; - }; - - constexpr auto is_escape = [](byte_received e) { - return e.value == '\x1b'; - }; - - constexpr auto is_printable = [](byte_received e) { - return e.value >= 0x20 && e.value <= 0x7e; - }; - - constexpr auto is_csi_introducer = [](byte_received e) { - return e.value == '['; - }; - - constexpr auto is_ss3_introducer = [](byte_received e) { - return e.value == 'O'; - }; - - constexpr auto is_invalid_introducer = [](byte_received e) { - return !(is_ss3_introducer(e) || is_csi_introducer(e)); - }; - - constexpr auto is_csi_param = [](byte_received e) { - return e.value >= 0x20 && e.value <= 0x3f; - }; - - constexpr auto is_csi_terminator = [](byte_received e) { - return e.value >= 0x40 && e.value <= 0x7e; - }; - - /// Transitions - - struct transition_table - { - auto operator()() const noexcept - { - using namespace boost::sml; - - // clang-format off - return make_transition_table( - *idle + event[is_escape] = escape_sequence, - idle + event[is_backspace] / push_backspace = idle, - idle + event[is_tab] / push_tab = idle, - idle + event[is_enter] / push_enter = idle, - idle + event[is_printable] / push_character = idle, - - escape_sequence + event[is_csi_introducer] / clear_csi = csi_sequence, - escape_sequence + event[is_ss3_introducer] = ss3_sequence, - escape_sequence + event[is_invalid_introducer] / fallback_escape = idle, - escape_sequence + event / emit_timeout_escape = idle, - - csi_sequence + event[is_csi_param] / push_csi_parameter = csi_sequence, - csi_sequence + event[is_csi_terminator] / resolve_csi = idle, - csi_sequence + event / emit_timeout_escape = idle, - - ss3_sequence + event / resolve_ss3 = idle, - ss3_sequence + event / emit_timeout_escape = idle - ); - // clang-format on - } - }; - -} // namespace ttwhy::detail - -export namespace ttwhy -{ - - struct ansi_scanner - { - explicit ansi_scanner(std::vector & queue) - : m_state_machine{queue, m_csi_buffer} - {} - - auto process(std::span buffer) -> void - { - std::ranges::for_each(buffer, [&](auto byte) { m_state_machine.process_event(detail::byte_received{byte}); }); - } - - auto timeout() - { - m_state_machine.process_event(detail::timeout_expired{}); - } - - [[nodiscard]] auto is_pending() const -> bool - { - return m_state_machine.is(detail::escape_sequence) || // - m_state_machine.is(detail::csi_sequence) || // - m_state_machine.is(detail::ss3_sequence); - } - - private: - std::string m_csi_buffer{}; - boost::sml::sm m_state_machine; - }; - -} // namespace ttwhy diff --git a/ttwhy/scanners/ansi.cppm b/ttwhy/scanners/ansi.cppm new file mode 100644 index 0000000..b248753 --- /dev/null +++ b/ttwhy/scanners/ansi.cppm @@ -0,0 +1,262 @@ +module; + +#include +#include + +#include +#include +#include + +export module ttwhy.scanners:ansi; + +import :events; + +namespace ttwhy::scanners::detail +{ + + /// Events + + struct byte_received + { + char value; + }; + + struct timeout_expired + { + }; + + /// States + + constexpr auto idle = boost::sml::state; + constexpr auto escape_sequence = boost::sml::state; + constexpr auto csi_sequence = boost::sml::state; + constexpr auto ss3_sequence = boost::sml::state; + + /// Actions + + constexpr auto push_character = [](byte_received const & event, std::vector & queue) { + queue.push_back(character_event{event.value}); + }; + + constexpr auto push_backspace = [](std::vector & queue) { + queue.push_back(control_event{control_key::backspace}); + }; + + constexpr auto push_tab = [](std::vector & queue) { + queue.push_back(control_event(control_key::tab)); + }; + + constexpr auto push_enter = [](std::vector & queue) { + queue.push_back(control_event(control_key::enter)); + }; + + constexpr auto emit_timeout_escape = [](std::vector & queue, std::string & buffer) { + queue.push_back(control_event{control_key::escape}); + buffer.clear(); + }; + + constexpr auto fallback_escape = [](byte_received const & event, std::vector & queue) { + queue.push_back(control_event{control_key::escape}); + if (event.value >= 0x20 && event.value <= 0x7e) + { + queue.push_back(character_event{event.value}); + } + }; + + constexpr auto clear_csi = [](std::string & buffer) { + buffer.clear(); + }; + + constexpr auto push_csi_parameter = [](byte_received const & event, std::string & buffer) { + buffer.push_back(event.value); + }; + + constexpr auto resolve_csi = [](byte_received const & event, std::string & buffer, std::vector & queue) { + if (event.value == '~') + { + switch (buffer.at(0)) + { + case '1': + case '7': + queue.push_back(navigation_event{navigation_key::home}); + break; + case '2': + queue.push_back(navigation_event{navigation_key::insert_key}); + break; + case '3': + queue.push_back(navigation_event{navigation_key::delete_key}); + break; + case '4': + case '8': + queue.push_back(navigation_event{navigation_key::end}); + break; + case '5': + queue.push_back(navigation_event{navigation_key::page_up}); + break; + case '6': + queue.push_back(navigation_event{navigation_key::page_down}); + break; + default: + } + } + else + { + switch (event.value) + { + case 'A': + queue.push_back(navigation_event{navigation_key::up}); + break; + case 'B': + queue.push_back(navigation_event{navigation_key::down}); + break; + case 'C': + queue.push_back(navigation_event{navigation_key::right}); + break; + case 'D': + queue.push_back(navigation_event{navigation_key::left}); + break; + case 'H': + queue.push_back(navigation_event{navigation_key::home}); + break; + case 'F': + queue.push_back(navigation_event{navigation_key::end}); + break; + default: + } + } + buffer.clear(); + }; + + constexpr auto resolve_ss3 = [](byte_received const & event, std::vector & queue) { + switch (event.value) + { + case 'A': + queue.push_back(navigation_event{navigation_key::up}); + break; + case 'B': + queue.push_back(navigation_event{navigation_key::down}); + break; + case 'C': + queue.push_back(navigation_event{navigation_key::right}); + break; + case 'D': + queue.push_back(navigation_event{navigation_key::left}); + break; + case 'H': + queue.push_back(navigation_event{navigation_key::home}); + break; + case 'F': + queue.push_back(navigation_event{navigation_key::end}); + break; + default: + } + }; + + /// Guards + + constexpr auto is_backspace = [](byte_received e) { + return e.value == '\x08' || e.value == '\x7f'; + }; + + constexpr auto is_tab = [](byte_received e) { + return e.value == '\x09'; + }; + + constexpr auto is_enter = [](byte_received e) { + return e.value == '\x0a' || e.value == '\x0d'; + }; + + constexpr auto is_escape = [](byte_received e) { + return e.value == '\x1b'; + }; + + constexpr auto is_printable = [](byte_received e) { + return e.value >= 0x20 && e.value <= 0x7e; + }; + + constexpr auto is_csi_introducer = [](byte_received e) { + return e.value == '['; + }; + + constexpr auto is_ss3_introducer = [](byte_received e) { + return e.value == 'O'; + }; + + constexpr auto is_invalid_introducer = [](byte_received e) { + return !(is_ss3_introducer(e) || is_csi_introducer(e)); + }; + + constexpr auto is_csi_param = [](byte_received e) { + return e.value >= 0x20 && e.value <= 0x3f; + }; + + constexpr auto is_csi_terminator = [](byte_received e) { + return e.value >= 0x40 && e.value <= 0x7e; + }; + + /// Transitions + + struct transition_table + { + auto operator()() const noexcept + { + using namespace boost::sml; + + // clang-format off + return make_transition_table( + *idle + event[is_escape] = escape_sequence, + idle + event[is_backspace] / push_backspace = idle, + idle + event[is_tab] / push_tab = idle, + idle + event[is_enter] / push_enter = idle, + idle + event[is_printable] / push_character = idle, + + escape_sequence + event[is_csi_introducer] / clear_csi = csi_sequence, + escape_sequence + event[is_ss3_introducer] = ss3_sequence, + escape_sequence + event[is_invalid_introducer] / fallback_escape = idle, + escape_sequence + event / emit_timeout_escape = idle, + + csi_sequence + event[is_csi_param] / push_csi_parameter = csi_sequence, + csi_sequence + event[is_csi_terminator] / resolve_csi = idle, + csi_sequence + event / emit_timeout_escape = idle, + + ss3_sequence + event / resolve_ss3 = idle, + ss3_sequence + event / emit_timeout_escape = idle + ); + // clang-format on + } + }; + +} // namespace ttwhy::scanners::detail + +export namespace ttwhy::scanners +{ + + struct ansi + { + explicit ansi(std::vector & queue) + : m_state_machine{queue, m_csi_buffer} + {} + + auto process(std::span buffer) -> void + { + std::ranges::for_each(buffer, [&](auto byte) { m_state_machine.process_event(detail::byte_received{byte}); }); + } + + auto timeout() + { + m_state_machine.process_event(detail::timeout_expired{}); + } + + [[nodiscard]] auto is_pending() const -> bool + { + return m_state_machine.is(detail::escape_sequence) || // + m_state_machine.is(detail::csi_sequence) || // + m_state_machine.is(detail::ss3_sequence); + } + + private: + std::string m_csi_buffer{}; + boost::sml::sm m_state_machine; + }; + +} // namespace ttwhy::scanners diff --git a/ttwhy/scanners/concepts.cppm b/ttwhy/scanners/concepts.cppm new file mode 100644 index 0000000..003874c --- /dev/null +++ b/ttwhy/scanners/concepts.cppm @@ -0,0 +1,18 @@ +module; + +#include +#include + +export module ttwhy.scanners:concepts; + +namespace ttwhy +{ + + export template + concept scanner = requires(Candidate a, std::span buffer) { + { a.process(buffer) } -> std::same_as; + { a.timeout() } -> std::same_as; + { a.is_pending() } -> std::same_as; + }; + +} // namespace ttwhy diff --git a/ttwhy/scanners/events.cppm b/ttwhy/scanners/events.cppm new file mode 100644 index 0000000..3e2f4f0 --- /dev/null +++ b/ttwhy/scanners/events.cppm @@ -0,0 +1,47 @@ +module; + +#include + +export module ttwhy.scanners:events; + +namespace ttwhy::scanners +{ + export enum class control_key + { + enter, + escape, + tab, + backspace, + }; + + export enum class navigation_key + { + up, + down, + left, + right, + home, + end, + delete_key, + insert_key, + page_up, + page_down, + }; + + export struct character_event + { + char value; + }; + + export struct control_event + { + control_key key; + }; + + export struct navigation_event + { + navigation_key key; + }; + + export using input_event = std::variant; +} // namespace ttwhy::scanners diff --git a/ttwhy/scanners/mod.cppm b/ttwhy/scanners/mod.cppm new file mode 100644 index 0000000..8dc3009 --- /dev/null +++ b/ttwhy/scanners/mod.cppm @@ -0,0 +1,5 @@ +export module ttwhy.scanners; + +export import :ansi; +export import :concepts; +export import :events; -- cgit v1.2.3