$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
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.