1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 10:08:12 +00:00

LibVT: Implement new ANSI escape sequence parser

This commit replaces the former, hand-written parser with a new one that
can be generated automatically according to a state change diagram.

The new `EscapeSequenceParser` class provides a more ergonomic interface
to dealing with escape sequences. This interface has been inspired by
Alacritty's [vte library](https://github.com/alacritty/vte/).

I tried to avoid changing the application logic inside the `Terminal`
class. While this code has not been thoroughly tested, I can't find
regressions in the basic command line utilities or `vttest`.

`Terminal` now displays nicer debug messages when it encounters an
unknown escape sequence. Defensive programming and bounds checks have
been added where we access parameters, and as a result, we can now
endure 4-5 seconds of `cat /dev/urandom`. :D

We generate EscapeSequenceStateMachine.h when building the in-kernel
LibVT, and we assume that the file is already in place when the userland
library is being built. This will probably cause problems later on, but
I can't find a way to do it nicely.
This commit is contained in:
Daniel Bertalan 2021-05-08 20:37:43 +02:00 committed by Andreas Kling
parent 1b347298f1
commit be519022c3
11 changed files with 707 additions and 571 deletions

View file

@ -211,18 +211,15 @@ parse_state_machine(StringView input)
}
void output_header(const StateMachine&, SourceGenerator&);
void output_cpp(const StateMachine&, SourceGenerator&);
int main(int argc, char** argv)
{
Core::ArgsParser args_parser;
const char* path = nullptr;
bool header_mode = false;
args_parser.add_option(header_mode, "Generate .h file", "header", 'H');
args_parser.add_positional_argument(path, "Path to parser description", "input", Core::ArgsParser::Required::Yes);
args_parser.parse(argc, argv);
auto file_or_error = Core::File::open(path, Core::IODevice::ReadOnly);
auto file_or_error = Core::File::open(path, Core::OpenMode::ReadOnly);
if (file_or_error.is_error()) {
fprintf(stderr, "Cannot open %s\n", path);
}
@ -232,10 +229,7 @@ int main(int argc, char** argv)
StringBuilder builder;
SourceGenerator generator { builder };
if (header_mode)
output_header(*state_machine, generator);
else
output_cpp(*state_machine, generator);
output_header(*state_machine, generator);
outln("{}", generator.as_string_view());
return 0;
}
@ -340,9 +334,66 @@ public:
typedef Function<void(Action, u8)> Handler;
@class_name@(Handler);
@class_name@(Handler handler)
: m_handler(move(handler))
{
}
void advance(u8);
void advance(u8 byte)
{
auto next_state = lookup_state_transition(byte);
bool state_will_change = next_state.new_state != m_state && next_state.new_state != State::_Anywhere;
// only run exit directive if state is being changed
if (state_will_change) {
switch (m_state) {
)~~~");
for (auto s : machine.states) {
auto state_generator = generator.fork();
if (s.exit_action.has_value()) {
state_generator.set("state_name", s.name);
state_generator.set("action", s.exit_action.value());
state_generator.append(R"~~~(
case State::@state_name@:
m_handler(Action::@action@, byte);
break;
)~~~");
}
}
generator.append(R"~~~(
default:
break;
}
}
if (next_state.action != Action::_Ignore)
m_handler(next_state.action, byte);
m_state = next_state.new_state;
// only run entry directive if state is being changed
if (state_will_change)
{
switch (next_state.new_state)
{
)~~~");
for (auto state : machine.states) {
auto state_generator = generator.fork();
if (state.entry_action.has_value()) {
state_generator.set("state_name", state.name);
state_generator.set("action", state.entry_action.value());
state_generator.append(R"~~~(
case State::@state_name@:
m_handler(Action::@action@, byte);
break;
)~~~");
}
}
generator.append(R"~~~(
default:
break;
}
}
}
private:
enum class State : u8 {
@ -370,7 +421,21 @@ private:
Handler m_handler;
StateTransition lookup_state_transition(u8);
ALWAYS_INLINE StateTransition lookup_state_transition(u8 byte)
{
VERIFY((u8)m_state < @state_count@);
)~~~");
if (machine.anywhere.has_value()) {
generator.append(R"~~~(
auto anywhere_state = STATE_TRANSITION_TABLE[0][byte];
if (anywhere_state.new_state != State::_Anywhere || anywhere_state.action != Action::_Ignore)
return anywhere_state;
else
)~~~");
}
generator.append(R"~~~(
return STATE_TRANSITION_TABLE[(u8)m_state][byte];
}
)~~~");
auto table_generator = generator.fork();
@ -385,111 +450,3 @@ private:
)~~~");
}
}
void output_cpp(const StateMachine& machine, SourceGenerator& generator)
{
VERIFY(!machine.name.is_empty());
generator.set("class_name", machine.name);
generator.set("state_count", String::number(machine.states.size() + 1));
generator.append(R"~~~(
#include "@class_name@.h"
#include <AK/Function.h>
#include <AK/Types.h>
)~~~");
if (machine.namespaces.has_value()) {
generator.set("namespace", machine.namespaces.value());
generator.append(R"~~~(
namespace @namespace@ {
)~~~");
}
generator.append(R"~~~(
@class_name@::@class_name@(Handler handler)
: m_handler(move(handler))
{
}
ALWAYS_INLINE @class_name@::StateTransition @class_name@::lookup_state_transition(u8 byte)
{
VERIFY((u8)m_state < @state_count@);
)~~~");
if (machine.anywhere.has_value()) {
generator.append(R"~~~(
auto anywhere_state = STATE_TRANSITION_TABLE[0][byte];
if (anywhere_state.new_state != @class_name@::State::_Anywhere || anywhere_state.action != @class_name@::Action::_Ignore)
return anywhere_state;
else
)~~~");
}
generator.append(R"~~~(
return STATE_TRANSITION_TABLE[(u8)m_state][byte];
}
)~~~");
generator.append(R"~~~(
void @class_name@::advance(u8 byte)
{
auto next_state = lookup_state_transition(byte);
bool state_will_change = next_state.new_state != m_state && next_state.new_state != @class_name@::State::_Anywhere;
// only run exit directive if state is being changed
if (state_will_change)
{
switch (m_state)
{
)~~~");
for (auto s : machine.states) {
auto state_generator = generator.fork();
if (s.exit_action.has_value()) {
state_generator.set("state_name", s.name);
state_generator.set("action", s.exit_action.value());
state_generator.append(R"~~~(
case @class_name@::State::@state_name@:
m_handler(Action::@action@, byte);
break;
)~~~");
}
}
generator.append(R"~~~(
default:
break;
}
}
if (next_state.action != @class_name@::Action::_Ignore)
m_handler(next_state.action, byte);
m_state = next_state.new_state;
// only run entry directive if state is being changed
if (state_will_change)
{
switch (next_state.new_state)
{
)~~~");
for (auto state : machine.states) {
auto state_generator = generator.fork();
if (state.entry_action.has_value()) {
state_generator.set("state_name", state.name);
state_generator.set("action", state.entry_action.value());
state_generator.append(R"~~~(
case @class_name@::State::@state_name@:
m_handler(Action::@action@, byte);
break;
)~~~");
}
}
generator.append(R"~~~(
default:
break;
}
}
}
)~~~");
if (machine.namespaces.has_value()) {
generator.append(R"~~~(
} // end namespace
)~~~");
}
}