diff options
| author | Felix Morgner <felix.morgner@gmail.com> | 2026-09-22 22:47:33 +0200 |
|---|---|---|
| committer | Felix Morgner <felix.morgner@gmail.com> | 2026-09-22 22:47:33 +0200 |
| commit | d05b9343f69a0841043963088e0003368e205bd9 (patch) | |
| tree | 8cde07ff86d27cbc808a0a149b89d0024b4a586c /ttwhy/scanners | |
| parent | 22f6534081acfabdab4956d337604a21e7da5b64 (diff) | |
| download | ttwhy-d05b9343f69a0841043963088e0003368e205bd9.tar.xz ttwhy-d05b9343f69a0841043963088e0003368e205bd9.zip | |
Diffstat (limited to 'ttwhy/scanners')
| -rw-r--r-- | ttwhy/scanners/concepts.cppm | 30 | ||||
| -rw-r--r-- | ttwhy/scanners/mod.cppm | 6 | ||||
| -rw-r--r-- | ttwhy/scanners/terminal_events.cppm | 100 | ||||
| -rw-r--r-- | ttwhy/scanners/terminal_policies.cppm | 161 | ||||
| -rw-r--r-- | ttwhy/scanners/terminal_scanner.cppm | 236 | ||||
| -rw-r--r-- | ttwhy/scanners/terminal_scanner.tests.cpp | 100 |
6 files changed, 0 insertions, 633 deletions
diff --git a/ttwhy/scanners/concepts.cppm b/ttwhy/scanners/concepts.cppm deleted file mode 100644 index 5c29b5f..0000000 --- a/ttwhy/scanners/concepts.cppm +++ /dev/null @@ -1,30 +0,0 @@ -module; - -#include <concepts> -#include <span> - -export module ttwhy.scanners:concepts; - -import :terminal_events; - -namespace ttwhy -{ - - export template<typename Candidate> - concept scanner = requires(Candidate a, std::span<char const> buffer) { - { a.process(buffer) } -> std::same_as<void>; - { a.timeout() } -> std::same_as<void>; - { a.is_pending() } -> std::same_as<bool>; - }; - - export template<typename Sink, typename Event> - concept event_sink = requires(Sink sink, Event event) { - { sink(event) } -> std::same_as<void>; - }; - - export template<typename Sink> - concept terminal_event_sink = - event_sink<Sink, scanners::character_event> && event_sink<Sink, scanners::control_event> && - event_sink<Sink, scanners::navigation_event>; - -} // namespace ttwhy diff --git a/ttwhy/scanners/mod.cppm b/ttwhy/scanners/mod.cppm deleted file mode 100644 index b422a38..0000000 --- a/ttwhy/scanners/mod.cppm +++ /dev/null @@ -1,6 +0,0 @@ -export module ttwhy.scanners; - -export import :concepts; -export import :terminal_events; -export import :terminal_policies; -export import :terminal_scanner; diff --git a/ttwhy/scanners/terminal_events.cppm b/ttwhy/scanners/terminal_events.cppm deleted file mode 100644 index 3e4c849..0000000 --- a/ttwhy/scanners/terminal_events.cppm +++ /dev/null @@ -1,100 +0,0 @@ -module; - -#include <cstdint> -#include <utility> -#include <variant> - -export module ttwhy.scanners:terminal_events; - -export namespace ttwhy::scanners -{ - enum struct control_key : std::uint8_t - { - enter, - escape, - tab, - backspace, - }; - - enum struct navigation_key : std::uint8_t - { - up, - down, - left, - right, - home, - end, - delete_key, - insert_key, - page_up, - page_down, - }; - - enum struct modifier : std::uint8_t - { - none = 0, - shift = 1, - alt = 2, - ctrl = 4, - meta = 8, - }; - - enum struct modifier_set : std::uint8_t - { - }; - - constexpr auto operator|(modifier lhs, modifier rhs) -> modifier_set - { - return static_cast<modifier_set>(std::to_underlying(lhs) | std::to_underlying(rhs)); - } - - constexpr auto operator|(modifier_set lhs, modifier rhs) -> modifier_set - { - return static_cast<modifier_set>(std::to_underlying(lhs) | std::to_underlying(rhs)); - } - - constexpr auto operator&(modifier lhs, modifier rhs) -> modifier_set - { - return static_cast<modifier_set>(std::to_underlying(lhs) & std::to_underlying(rhs)); - } - - constexpr auto operator&(modifier_set lhs, modifier rhs) -> modifier_set - { - return static_cast<modifier_set>(std::to_underlying(lhs) & std::to_underlying(rhs)); - } - - template<std::same_as<modifier>... Modifier> - constexpr auto has_all(modifier_set mask, Modifier... flag) -> bool - { - return (true && ... && (mask & flag)); - } - - template<std::same_as<modifier>... Modifier> - constexpr auto has_any(modifier_set mask, Modifier... flag) -> bool - { - return (false || ... || (mask & flag)); - } - - struct character_event - { - char value{}; - }; - - struct control_event - { - control_key key{}; - }; - - struct navigation_event - { - navigation_key key{}; - modifier_set modifiers{}; - }; - - struct ctrl_chord_event - { - char key{}; - }; - - using input_event = std::variant<character_event, control_event, navigation_event, ctrl_chord_event>; -} // namespace ttwhy::scanners diff --git a/ttwhy/scanners/terminal_policies.cppm b/ttwhy/scanners/terminal_policies.cppm deleted file mode 100644 index e3c889c..0000000 --- a/ttwhy/scanners/terminal_policies.cppm +++ /dev/null @@ -1,161 +0,0 @@ -module; - -#include <optional> -#include <string> - -export module ttwhy.scanners:terminal_policies; - -import :concepts; -import :terminal_events; - -namespace ttwhy::scanners -{ - - namespace detail - { - constexpr auto map_vt220_keypad(char terminator) -> std::optional<navigation_key> - { - switch (terminator) - { - case '1': - case '7': - return navigation_key::home; - case '2': - return navigation_key::insert_key; - case '3': - return navigation_key::delete_key; - case '4': - case '8': - return navigation_key::end; - case '5': - return navigation_key::page_up; - case '6': - return navigation_key::page_down; - default: - return std::nullopt; - } - } - - constexpr auto map_vt100_cursor(char terminator) -> std::optional<navigation_key> - { - switch (terminator) - { - case 'A': - return navigation_key::up; - case 'B': - return navigation_key::down; - case 'C': - return navigation_key::right; - case 'D': - return navigation_key::left; - case 'H': - return navigation_key::home; - case 'F': - return navigation_key::end; - default: - return std::nullopt; - } - } - - } // namespace detail - - export struct ansi_policy - { - constexpr auto static resolve_vt220_keypad(std::string & buffer, ttwhy::terminal_event_sink auto & sink) -> void - { - if (buffer.empty()) - { - return; - } - - if (auto key = detail::map_vt220_keypad(buffer.front())) - { - sink(navigation_event{*key}); - } - buffer.clear(); - } - - constexpr auto static resolve_vt100_cursor(char terminator, std::string &, ttwhy::terminal_event_sink auto & sink) - -> void - { - if (auto key = detail::map_vt100_cursor(terminator)) - { - sink(navigation_event{*key}); - } - } - - constexpr auto static resolve_ss3(char terminator, ttwhy::terminal_event_sink auto & sink) -> void - { - if (auto key = detail::map_vt100_cursor(terminator)) - { - sink(navigation_event{*key}); - } - } - }; - - export struct xterm_policy - { - constexpr auto static parse_modifiers(std::string_view buffer) -> modifier_set - { - auto delimiter = buffer.find(';'); - if (delimiter == std::string_view::npos) - { - return {}; - } - - auto modifier_string = buffer.substr(delimiter + 1); - auto modifier_value = 0u; - - for (auto character : modifier_string) - { - if (character >= '0' && character <= '9') - { - modifier_value = modifier_value * 10 + static_cast<unsigned int>(character - '0'); - } - } - - if (modifier_value <= 1) - { - return {}; - } - - return static_cast<modifier_set>(modifier_value - 1); - } - - constexpr auto static resolve_vt220_keypad(std::string & buffer, ttwhy::terminal_event_sink auto & sink) -> void - { - if (buffer.empty()) - { - return; - } - - auto modifiers = parse_modifiers(buffer); - - if (auto key = detail::map_vt220_keypad(buffer.front())) - { - sink(navigation_event{*key, modifiers}); - } - buffer.clear(); - } - - constexpr auto static resolve_vt100_cursor(char terminator, std::string & buffer, - ttwhy::terminal_event_sink auto & sink) -> void - { - auto modifiers = parse_modifiers(buffer); - - if (auto key = detail::map_vt100_cursor(terminator)) - { - sink(navigation_event{*key, modifiers}); - } - } - - constexpr auto static resolve_ss3(char terminator, ttwhy::terminal_event_sink auto & sink) -> void - { - if (auto key = detail::map_vt100_cursor(terminator)) - { - sink(navigation_event{*key}); - } - } - }; - -} // namespace ttwhy::scanners diff --git a/ttwhy/scanners/terminal_scanner.cppm b/ttwhy/scanners/terminal_scanner.cppm deleted file mode 100644 index a791909..0000000 --- a/ttwhy/scanners/terminal_scanner.cppm +++ /dev/null @@ -1,236 +0,0 @@ -module; - -#include <algorithm> -#include <boost/sml.hpp> - -#include <queue> -#include <span> -#include <string> - -export module ttwhy.scanners:terminal_scanner; - -import :concepts; -import :terminal_events; -import :terminal_policies; - -namespace ttwhy::scanners::detail -{ - - /// Events - - struct byte_received - { - char value; - }; - - struct c1_received - { - unsigned char value; - }; - - struct timeout_expired - { - }; - - /// States - - constexpr auto idle = boost::sml::state<class idle>; - constexpr auto escape_sequence = boost::sml::state<class escape_sequence>; - constexpr auto csi_sequence = boost::sml::state<class csi_sequence>; - constexpr auto ss3_sequence = boost::sml::state<class ss3_sequence>; - - /// 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 >= ' ' && e.value <= '~'; - }; - - constexpr auto is_c0_chord = [](byte_received e) { - return e.value >= '\0' && e.value <= '\x1f' && !is_backspace(e) && !is_tab(e) && !is_enter(e) && !is_escape(e); - }; - - constexpr auto is_fe = [](byte_received e) { - return e.value >= '@' && e.value <= '_'; - }; - - constexpr auto is_csi = [](c1_received e) { - return e.value == u'\x9b'; - }; - - constexpr auto is_ss3 = [](c1_received e) { - return e.value == u'\x8f'; - }; - - constexpr auto is_unhandled_c1 = [](c1_received e) { - return !(is_csi(e) || is_ss3(e)); - }; - - constexpr auto is_csi_param = [](byte_received e) { - return e.value >= ' ' && e.value <= '?'; - }; - - constexpr auto is_vt100_terminator = [](byte_received e) { - return e.value >= '@' && e.value <= '}'; - }; - - constexpr auto is_vt220_terminator = [](byte_received e) { - return e.value == '~'; - }; - - /// Transitions - - template<terminal_event_sink Sink, typename TerminalPolicy> - struct transition_table - { - auto operator()() const noexcept - { - using namespace boost::sml; - - constexpr auto push_character = [](byte_received event, Sink & sink) { - sink(character_event{event.value}); - }; - - constexpr auto push_backspace = [](Sink & sink) { - sink(control_event{control_key::backspace}); - }; - - constexpr auto push_tab = [](Sink & sink) { - sink(control_event(control_key::tab)); - }; - - constexpr auto push_enter = [](Sink & sink) { - sink(control_event(control_key::enter)); - }; - - constexpr auto push_c0_chord = [](byte_received event, Sink & sink) { - sink(ctrl_chord_event{static_cast<char>(event.value + 0x40)}); - }; - - constexpr auto dispatch_c1 = [](byte_received event, back::process<c1_received> process) { - process(c1_received{static_cast<unsigned char>(event.value + 0x40)}); - }; - - constexpr auto fallback_fe = [](c1_received event, Sink & sink) { - sink(control_event{control_key::escape}); - sink(character_event{static_cast<char>(event.value - 0x40)}); - }; - - constexpr auto emit_timeout_escape = [](Sink & sink, std::string & buffer) { - sink(control_event{control_key::escape}); - buffer.clear(); - }; - - constexpr auto fallback_escape = [](byte_received event, Sink & sink) { - sink(control_event{control_key::escape}); - if (event.value >= ' ' && event.value <= '~') - { - sink(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_vt100_cursor = [](byte_received event, std::string & buffer, Sink & sink) { - TerminalPolicy::resolve_vt100_cursor(event.value, buffer, sink); - buffer.clear(); - }; - - constexpr auto resolve_vt220_keypad = [](std::string & buffer, Sink & sink) { - TerminalPolicy::resolve_vt220_keypad(buffer, sink); - buffer.clear(); - }; - - constexpr auto resolve_ss3 = [](byte_received event, Sink & sink) { - TerminalPolicy::resolve_ss3(event.value, sink); - }; - - // 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_tab] / push_tab = idle, - idle + event<byte_received>[is_enter] / push_enter = idle, - idle + event<byte_received>[is_c0_chord] / push_c0_chord = idle, - idle + event<byte_received>[is_printable] / push_character = idle, - - escape_sequence + event<byte_received>[is_fe] / dispatch_c1 = idle, - escape_sequence + event<byte_received>[!is_fe] / fallback_escape = idle, - escape_sequence + event<timeout_expired> / emit_timeout_escape = idle, - - idle + event<c1_received>[is_csi] / clear_csi = csi_sequence, - idle + event<c1_received>[is_ss3] = ss3_sequence, - idle + event<c1_received>[is_unhandled_c1] / fallback_fe = idle, - - csi_sequence + event<byte_received>[is_csi_param] / push_csi_parameter = csi_sequence, - csi_sequence + event<byte_received>[is_vt220_terminator] / resolve_vt220_keypad = idle, - csi_sequence + event<byte_received>[is_vt100_terminator] / resolve_vt100_cursor = idle, - csi_sequence + event<timeout_expired> / emit_timeout_escape = idle, - - ss3_sequence + event<byte_received> / resolve_ss3 = idle, - ss3_sequence + event<timeout_expired> / emit_timeout_escape = idle - ); - // clang-format on - } - }; - -} // namespace ttwhy::scanners::detail - -export namespace ttwhy::scanners -{ - - template<terminal_event_sink Sink, typename TerminalPolicy> - struct terminal_scanner - { - explicit terminal_scanner(Sink & sink) - : m_state_machine{sink, m_csi_buffer} - {} - - auto process(std::span<char const> 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<detail::transition_table<Sink, TerminalPolicy>, boost::sml::process_queue<std::queue>> - m_state_machine; - }; - - template<terminal_event_sink Sink> - using ansi = terminal_scanner<Sink, ansi_policy>; - -} // namespace ttwhy::scanners diff --git a/ttwhy/scanners/terminal_scanner.tests.cpp b/ttwhy/scanners/terminal_scanner.tests.cpp deleted file mode 100644 index f4ea6ad..0000000 --- a/ttwhy/scanners/terminal_scanner.tests.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include <catch2/catch_test_macros.hpp> - -#include <string_view> -#include <variant> -#include <vector> - -import ttwhy.scanners; - -using namespace std::string_view_literals; - -[[nodiscard]] constexpr auto static is_character(ttwhy::scanners::input_event & event, char expected) -> bool -{ - auto const * data = std::get_if<ttwhy::scanners::character_event>(&event); - return data != nullptr && data->value == expected; -} - -[[nodiscard]] constexpr auto static is_control(ttwhy::scanners::input_event & event, - ttwhy::scanners::control_key expected) -> bool -{ - auto const * data = std::get_if<ttwhy::scanners::control_event>(&event); - return data != nullptr && data->key == expected; -} - -SCENARIO("The ANSI scanner processes printable ASCII and standard C0 control characters", "[scanner][ansi]") -{ - GIVEN("An initialized scanner and event sink") - { - auto queue = std::vector<ttwhy::scanners::input_event>{}; - auto sink = [&queue](auto const & event) { - queue.push_back(event); - }; - auto scanner = ttwhy::scanners::ansi{sink}; - - WHEN("Processing a standard printable character") - { - scanner.process("A"sv); - - THEN("It yields a single character event") - { - REQUIRE(queue.size() == 1); - CHECK(is_character(queue.at(0), 'A')); - } - } - - WHEN("Processing a BS byte (\\x08)") - { - scanner.process("\x08"sv); - - THEN("It yields a backspace control event") - { - REQUIRE(queue.size() == 1); - CHECK(is_control(queue.at(0), ttwhy::scanners::control_key::backspace)); - } - } - - WHEN("Processing a DEL byte (\\x7f as BS)") - { - scanner.process("\x7f"sv); - - THEN("It yields a backspace control event") - { - REQUIRE(queue.size() == 1); - CHECK(is_control(queue.at(0), ttwhy::scanners::control_key::backspace)); - } - } - - WHEN("Processing a TAB byte") - { - scanner.process("\x09"sv); - - THEN("It yields a tab control event") - { - REQUIRE(queue.size() == 1); - CHECK(is_control(queue.at(0), ttwhy::scanners::control_key::tab)); - } - } - - WHEN("Processing an LF byte") - { - scanner.process("\x0a"sv); - - THEN("It yields an enter control event") - { - REQUIRE(queue.size() == 1); - CHECK(is_control(queue.at(0), ttwhy::scanners::control_key::enter)); - } - } - - WHEN("Processing an CR byte") - { - scanner.process("\x0d"sv); - - THEN("It yields an enter control event") - { - REQUIRE(queue.size() == 1); - CHECK(is_control(queue.at(0), ttwhy::scanners::control_key::enter)); - } - } - } -} |
