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