
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:
[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.