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:
parent
1b347298f1
commit
be519022c3
11 changed files with 707 additions and 571 deletions
|
@ -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
|
||||
)~~~");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue