|
Boost : |
From: Andrey Semashev (andrey.semashev_at_[hidden])
Date: 2008-08-18 12:40:09
Phil Endecott wrote:
> Dear All,
>
> Here's another FSM example which I hope is a bit less trivial than the
> last one. It's for the escape sequence handling in a terminal
> emulator. If you're curious you can see the real code here:
>
> http://svn.anyterm.org/anyterm/trunk/common/Terminal.cc
[snip]
> My view is that the ad-hoc version of the code is sufficiently readable
> and efficient for my needs, and that the Boost.FSM version does not
> improve on it.
Actually, the ad-hoc code is a good example for what is known as
spagetti-style, which I always try to avoid. And no, I don't find that
code readable.
Although I admit that Boost.FSM is more verbose in general, I find its
code more readable and maintainable. Below is an improved version of the
terminal, with corrected mistakes and categorized events.
// Events
struct event_base
{
char c;
};
struct control_char : event_base {};
struct printable_char : event_base {};
struct digit : printable_char {};
// States
struct Normal;
struct Esc;
struct CSI;
typedef mpl::vector<Normal,Esc,CSI>::type StateList;
// Root class
struct Common
{
screen_t screen;
int cursor_col, cursor_row;
void carriage_return()
{
cursor_col = 0;
}
void write_normal_char(char c)
{
if (cursor_col>=cols)
{
cursor_col = 0;
cursor_line_down();
}
screen(cursor_row,cursor_col) = c;
cursor_col++;
}
};
// Base class for states
template< typename StateT >
struct TermState :
fsm::state< StateT, StateList >,
virtual Common
{
void on_process(control_char const& evt)
{
switch (evt.c)
{
....
case 13: carriage_return(); break;
....
case 26: // This code abandons any escape sequence that is in
progress
this->switch_to<Normal>(); break;
case 27: // Escape
this->switch_to<Esc>(); break;
....
}
};
};
// States
struct Normal :
TermState< Normal >
{
using TermState< Normal >::on_process;
void on_process(printable_char const& evt)
{
write_normal_char(evt.c);
}
};
struct Esc :
TermState< Esc >
{
using TermState< Esc >::on_process;
void on_process(printable_char const& evt)
{
switch (evt.c)
{
....
case 'M': cursor_line_up(); switch_to<Normal>(); break;
....
case '[': switch_to<CSI>(); break;
default: switch_to<Normal>(); break;
}
}
};
struct CSI :
TermState< CSI >
{
using TermState< CSI >::on_process;
void on_process(digit const& evt)
{
if (nparams==0)
{
nparams=1;
params[0]=0;
}
params[nparams-1] = params[nparams-1]*10 + (c-'0');
}
void on_process(printable_char const& evt)
{
switch (evt.c)
{
case ';':
if (nparams < MAX_PARAMS)
{
nparams++;
params[nparams-1] = 0;
}
return;
....
case 'A': csi_CUU(); break;
....
}
switch_to<Normal>();
}
};
// Terminal implementation
struct Terminal
{
fsm::state_machine< StateList > m_FSM;
void process_char(char c)
{
if (c <= 31)
m_FSM.process(control_char(c));
else if (isdigit(c))
m_FSM.process(digit(c));
else
m_FSM.process(printable_char(c));
}
};
I'm not familiar with the domain, but maybe other easily detectable
event categories can be defined, which would further offload those
switch-cases left.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk