aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt4
-rw-r--r--ttwhy/event.cppm1
-rw-r--r--ttwhy/io.cppm67
-rw-r--r--ttwhy/main.cpp13
-rw-r--r--ttwhy/routers.cppm3
-rw-r--r--ttwhy/routers/echo.cppm84
-rw-r--r--ttwhy/scanner.cppm127
7 files changed, 245 insertions, 54 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b50768a..00318e7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -51,7 +51,11 @@ target_sources("ttwhy-core" PUBLIC
"ttwhy/event.cppm"
"ttwhy/io.cppm"
"ttwhy/lib.cppm"
+ "ttwhy/scanner.cppm"
"ttwhy/scoped_attributes.cppm"
+
+ "ttwhy/routers.cppm"
+ "ttwhy/routers/echo.cppm"
)
target_include_directories("ttwhy-core" PUBLIC
diff --git a/ttwhy/event.cppm b/ttwhy/event.cppm
index 3a81bff..b37cc51 100644
--- a/ttwhy/event.cppm
+++ b/ttwhy/event.cppm
@@ -8,7 +8,6 @@ namespace ttwhy
{
export enum class control_key
{
- del,
enter,
escape,
tab,
diff --git a/ttwhy/io.cppm b/ttwhy/io.cppm
index 230f180..fd84dfd 100644
--- a/ttwhy/io.cppm
+++ b/ttwhy/io.cppm
@@ -2,11 +2,15 @@ module;
#include <asio.hpp>
-#include <expected>
#include <format>
+#include <span>
+#include <vector>
export module ttwhy:io;
+import :event;
+import :scanner;
+
namespace ttwhy::io
{
@@ -25,58 +29,35 @@ namespace ttwhy::io
co_return error;
}
- export template<typename InputStream, typename ErrorStream>
- auto read(std::span<char> data, InputStream & input_stream, ErrorStream & error_stream)
- -> asio::awaitable<std::expected<std::size_t, asio::error_code>>
- {
- auto [error, read] = co_await input_stream.async_read_some(asio::buffer(data), asio::as_tuple(asio::use_awaitable));
-
- if (error)
- {
- auto message = std::format("{}, exiting ...\n", error.message());
- co_await asio::async_write(error_stream, asio::buffer(message), asio::use_awaitable);
- co_return std::unexpected{error};
- }
-
- co_return read;
- }
-
- export template<typename OutputStream, typename ErrorStream>
- auto write(std::span<char const> string, OutputStream & output_stream, ErrorStream & error_stream)
- -> asio::awaitable<asio::error_code>
+ export template<typename InputStream, typename AppRouter>
+ auto read_events(InputStream & stream, AppRouter & router) -> asio::awaitable<void>
{
- auto [error, written] =
- co_await asio::async_write(output_stream, asio::buffer(string), asio::as_tuple(asio::use_awaitable));
+ auto queue = std::vector<input_event>{};
+ queue.reserve(16);
- if (error)
- {
- auto message = std::format("{}, exiting ...\n", error.message());
- co_await asio::async_write(error_stream, asio::buffer(message), asio::use_awaitable);
- }
-
- co_return error;
- }
-
- export template<typename InputStream, typename OutputStream, typename ErrorStream>
- auto echo(InputStream & input_stream, OutputStream & output_stream, ErrorStream & error_stream)
- -> asio::awaitable<asio::error_code>
- {
- auto input = std::array<char, 16>{};
+ auto scanner = ansi_scanner{queue};
+ auto raw_buffer = std::array<char, 64>{};
while (true)
{
- auto read_result = co_await read(input, input_stream, error_stream);
- if (!read_result.has_value())
+ auto [error, bytes_read] =
+ co_await stream.async_read_some(asio::buffer(raw_buffer), asio::as_tuple(asio::use_awaitable));
+
+ if (error)
{
- co_return read_result.error();
+ co_return;
}
- auto to_write = std::span{input.data(), read_result.value()};
- auto write_error = co_await write(to_write, output_stream, error_stream);
- if (write_error)
+ auto const byte_span = std::span<char const>{raw_buffer.data(), bytes_read};
+
+ scanner.process(byte_span);
+
+ for (auto const & event : queue)
{
- co_return write_error;
+ co_await router.process(event);
}
+
+ queue.clear();
}
}
diff --git a/ttwhy/main.cpp b/ttwhy/main.cpp
index 94c1330..6a813c0 100644
--- a/ttwhy/main.cpp
+++ b/ttwhy/main.cpp
@@ -3,9 +3,8 @@
#include <stdio.h>
-#include <format>
-
import ttwhy;
+import ttwhy.routers;
auto app(int in, int out, int error) -> asio::awaitable<void>
{
@@ -17,15 +16,9 @@ auto app(int in, int out, int error) -> asio::awaitable<void>
auto output_stream = asio::posix::stream_descriptor{executor, out};
auto error_stream = asio::posix::stream_descriptor{executor, error};
- auto result =
- co_await (ttwhy::io::handle_signals(error_stream) || ttwhy::io::echo(input_stream, output_stream, error_stream));
- auto final_error = result.index() == 0 ? std::get<0>(result) : std::get<1>(result);
+ auto router = ttwhy::routers::echo{input_stream};
- if (final_error && final_error != asio::error::operation_aborted)
- {
- auto message = std::format("Terminated with error: {}\n", final_error.message());
- co_await asio::async_write(error_stream, asio::buffer(message), asio::use_awaitable);
- }
+ co_await (ttwhy::io::handle_signals(error_stream) || ttwhy::io::read_events(input_stream, router));
}
auto main() -> int
diff --git a/ttwhy/routers.cppm b/ttwhy/routers.cppm
new file mode 100644
index 0000000..fbab282
--- /dev/null
+++ b/ttwhy/routers.cppm
@@ -0,0 +1,3 @@
+export module ttwhy.routers;
+
+export import :echo;
diff --git a/ttwhy/routers/echo.cppm b/ttwhy/routers/echo.cppm
new file mode 100644
index 0000000..41b7b51
--- /dev/null
+++ b/ttwhy/routers/echo.cppm
@@ -0,0 +1,84 @@
+module;
+
+#include <asio.hpp>
+
+#include <format>
+#include <string_view>
+
+export module ttwhy.routers:echo;
+
+import ttwhy;
+
+namespace ttwhy::routers
+{
+
+ using namespace std::string_view_literals;
+
+ constexpr auto vte_backspace_sequence = "\b \b"sv;
+
+ export template<typename StreamType>
+ struct echo
+ {
+ explicit echo(StreamType & stream)
+ : m_output_stream{stream}
+ {}
+
+ auto process(ttwhy::input_event event) -> asio::awaitable<void>
+ {
+ co_await std::visit(
+ [&](auto && event) -> asio::awaitable<void> {
+ using event_type = std::decay_t<decltype(event)>;
+
+ if constexpr (std::same_as<character_event, event_type>)
+ {
+ co_await process_character(event.value);
+ }
+ else if constexpr (std::same_as<control_event, event_type>)
+ {
+ co_await process_control_key(event.key);
+ }
+ else if constexpr (std::same_as<navigation_event, event_type>)
+ {
+ co_await process_navigation_key(event.key);
+ }
+ },
+ event);
+ }
+
+ private:
+ auto process_character(char character) -> asio::awaitable<void>
+ {
+ 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<void>
+ {
+ switch (key)
+ {
+ case control_key::backspace:
+ co_await asio::async_write(m_output_stream, asio::buffer(vte_backspace_sequence),
+ asio::as_tuple(asio::use_awaitable));
+ break;
+ default:
+ co_await asio::async_write(m_output_stream, asio::buffer(std::format("{{CTRL:{}}}", std::to_underlying(key))),
+ asio::as_tuple(asio::use_awaitable));
+ };
+ }
+
+ auto process_navigation_key(navigation_key key) -> asio::awaitable<void>
+ {
+ switch (key)
+ {
+ case navigation_key::delete_key:
+ co_await asio::async_write(m_output_stream, asio::buffer("{DEL}"), asio::as_tuple(asio::use_awaitable));
+ break;
+ default:
+ co_await asio::async_write(m_output_stream, asio::buffer(std::format("{{NAV:{}}}", std::to_underlying(key))),
+ asio::as_tuple(asio::use_awaitable));
+ }
+ }
+
+ StreamType & m_output_stream;
+ };
+
+} // namespace ttwhy::routers
diff --git a/ttwhy/scanner.cppm b/ttwhy/scanner.cppm
new file mode 100644
index 0000000..86a8493
--- /dev/null
+++ b/ttwhy/scanner.cppm
@@ -0,0 +1,127 @@
+module;
+
+#include <algorithm>
+#include <boost/sml.hpp>
+
+#include <cctype>
+#include <span>
+#include <vector>
+
+export module ttwhy:scanner;
+
+import :event;
+
+namespace ttwhy::detail
+{
+
+ /// Events
+
+ struct byte_received
+ {
+ char value;
+ };
+
+ /// States
+
+ auto const idle = boost::sml::state<class idle>;
+ auto const escape_sequence = boost::sml::state<class escape_sequence>;
+ auto const csi_sequence = boost::sml::state<class csi_sequence>;
+
+ /// Actions
+
+ auto push_character = [](byte_received const & event, std::vector<input_event> & queue) {
+ queue.push_back(character_event{event.value});
+ };
+
+ auto push_backspace = [](std::vector<input_event> & queue) {
+ queue.push_back(control_event{control_key::backspace});
+ };
+
+ auto push_delete = [](std::vector<input_event> & queue) {
+ queue.push_back(navigation_event{navigation_key::delete_key});
+ };
+
+ auto fallback_escape = [](byte_received const & event, std::vector<input_event> & queue) {
+ queue.push_back(control_event{control_key::escape});
+ if (event.value >= 0x20 && event.value <= 0x7e)
+ {
+ queue.push_back(character_event{event.value});
+ }
+ };
+
+ /// Guards
+
+ auto is_backspace = [](byte_received e) {
+ return e.value == '\x08' || e.value == '\x7f';
+ };
+
+ auto is_escape = [](byte_received e) {
+ return e.value == '\x1b';
+ };
+
+ auto is_printable = [](byte_received e) {
+ return e.value >= 0x20 && e.value <= 0x7e;
+ };
+
+ auto is_csi_introducer = [](byte_received e) {
+ return e.value == '[';
+ };
+
+ auto is_csi_param = [](byte_received e) {
+ return e.value >= 0x20 && e.value <= 0x3f;
+ };
+
+ auto is_csi_terminator = [](byte_received e) {
+ return e.value >= 0x40 && e.value <= 0x7e;
+ };
+
+ auto is_tilde = [](byte_received e) {
+ return e.value == '~';
+ };
+
+ /// Transitions
+
+ struct transition_table
+ {
+ auto operator()() const noexcept
+ {
+ using namespace boost::sml;
+
+ // 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_printable] / push_character = idle,
+
+ escape_sequence + event<byte_received>[is_csi_introducer] = csi_sequence,
+ escape_sequence + event<byte_received>[!is_csi_introducer] / fallback_escape = idle,
+
+ csi_sequence + event<byte_received>[is_csi_param] = csi_sequence,
+ csi_sequence + event<byte_received>[is_tilde] / push_delete = idle,
+ csi_sequence + event<byte_received>[is_csi_terminator] = idle
+ );
+ // clang-format on
+ }
+ };
+
+} // namespace ttwhy::detail
+
+export namespace ttwhy
+{
+
+ struct ansi_scanner
+ {
+ explicit ansi_scanner(std::vector<input_event> & queue)
+ : m_state_machine{queue}
+ {}
+
+ auto process(std::span<char const> buffer) -> void
+ {
+ std::ranges::for_each(buffer, [&](auto byte) { m_state_machine.process_event(detail::byte_received{byte}); });
+ }
+
+ private:
+ boost::sml::sm<detail::transition_table> m_state_machine;
+ };
+
+} // namespace ttwhy