aboutsummaryrefslogtreecommitdiff
path: root/ttwhy/scanners
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@gmail.com>2026-09-22 22:47:33 +0200
committerFelix Morgner <felix.morgner@gmail.com>2026-09-22 22:47:33 +0200
commitd05b9343f69a0841043963088e0003368e205bd9 (patch)
tree8cde07ff86d27cbc808a0a149b89d0024b4a586c /ttwhy/scanners
parent22f6534081acfabdab4956d337604a21e7da5b64 (diff)
downloadttwhy-d05b9343f69a0841043963088e0003368e205bd9.tar.xz
ttwhy-d05b9343f69a0841043963088e0003368e205bd9.zip
lib: restructure into vertical slicesHEADmaster
Diffstat (limited to 'ttwhy/scanners')
-rw-r--r--ttwhy/scanners/concepts.cppm30
-rw-r--r--ttwhy/scanners/mod.cppm6
-rw-r--r--ttwhy/scanners/terminal_events.cppm100
-rw-r--r--ttwhy/scanners/terminal_policies.cppm161
-rw-r--r--ttwhy/scanners/terminal_scanner.cppm236
-rw-r--r--ttwhy/scanners/terminal_scanner.tests.cpp100
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));
- }
- }
- }
-}