Boost logo

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