diff options
Diffstat (limited to 'ttwhy/scanners/terminal_scanner.cppm')
| -rw-r--r-- | ttwhy/scanners/terminal_scanner.cppm | 236 |
1 files changed, 236 insertions, 0 deletions
diff --git a/ttwhy/scanners/terminal_scanner.cppm b/ttwhy/scanners/terminal_scanner.cppm new file mode 100644 index 0000000..9284b84 --- /dev/null +++ b/ttwhy/scanners/terminal_scanner.cppm @@ -0,0 +1,236 @@ +module; + +#include <algorithm> +#include <boost/sml.hpp> + +#include <queue> +#include <span> +#include <string> + +export module ttwhy.scanners:terminal_scanner; + +import :concepts; +import :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 >= 0x20 && e.value <= 0x7e; + }; + + constexpr auto is_c0_chord = [](byte_received e) { + return e.value >= 0x00 && e.value <= 0x1f && !is_backspace(e) && !is_tab(e) && !is_escape(e); + }; + + constexpr auto is_fe = [](byte_received e) { + return e.value >= 0x40 && e.value <= 0x5f; + }; + + constexpr auto is_csi = [](c1_received e) { + return e.value == 0x9b; + }; + + constexpr auto is_ss3 = [](c1_received e) { + return e.value == 0x8f; + }; + + 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 >= 0x20 && e.value <= 0x3f; + }; + + constexpr auto is_vt100_terminator = [](byte_received e) { + return e.value >= 0x40 && e.value <= 0x7d; + }; + + constexpr auto is_vt220_terminator = [](byte_received e) { + return e.value == '~'; + }; + + /// Transitions + + template<ansi_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 >= 0x20 && event.value <= 0x7e) + { + 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, 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<ansi_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<ansi_sink Sink> + using ansi = terminal_scanner<Sink, ansi_policy>; + +} // namespace ttwhy::scanners |
