SML - State Machine Language

Alberto Bellina
1 Gennaio 1996
Versione 1.0

INTRODUZIONE

SML e' l'acronimo di State Machine Language.
SML e' un ambiente operativo che consente la gestione di eventi di diversa provenienza in un unico e standardizzato modo.
SML e' un linguaggio che consente mediante una sintassi semplicata la generazione di macchine stati multiple, con gestione automatica del colloquio delle stesse.
Le parti in cui e' composto l'ambiente operativo SML sono evidenziate dal seguente disegno:
                               +------+
      +------+    *******    +------+ |
      |      |    *     *    |      | |
      | .sml |--->* SML *--->|      | |   files generati
      |      |    *     *    |      |-+      .c e .h
      +------+    *******    +------+

CONVENZIONI DEL DOCUMENTO

Di seguito le definizioni utilizzate:
CHANNEL canale di comunicazione tra automi
STATEMACHINE macchina a stati o automa
EVENT tipo evento
SML State Machine Language
SMLGEN programma generato da SML
SMLTEST programma di test generato da SML

FUNZIONAMENTO

I programmi generati a seguito della sintassi espressa nel linguaggio SML, permettono di gestire gli eventi relativi alle State Machine implementate.
Esistono due tipi di modalita' di funzionamento delle State Machine:
Queste modalita' differiscono in quento nella modalita' wait event le chiamate alle funzioni utente all'arrivo di un evento sono effettuate nel codice generato, mentre nella modalita' dispatcher le chiamate alle funzioni utente sono gestite dal codice di libreria mediante la funzione Dispatcher().
E' possibile selezionare la modalita' di funzionamento in uno dei seguenti modi:

MODALITA' WAIT EVENT

   Codice Generato                 Codice Libreria
   main
     EVENTINIT()
     main_SM1()
       while( EVENTWAIT(SM1) )     Attende evento per la StateMachine SM1
                                   quando evento ritorna
         esegue funzione utente()
     EVENTEND()

MODALITA' DISPATCHER

   Codice Generato                 Codice Libreria
   main
     EVENTINIT()
     while( DISPATCHER(CH1) )
                                   while
                                     Attende eventi sul canale CH1
                                     quando evento esegue funzione utente()
     EVENTEND()

TESTING

Vi sono due programmi che vengono generati e che operano sulle code degli eventi, e sono il programma di gestione vero e proprio SMLGEN ed un programma di test che consente di simulare gli eventi in coda in modo semplificato.

***********                         **********
*         *       +---------+       *        *
* SMLTEST *  -->  | | | | | |  -->  * SMLGEN *
*         *       +---------+       *        *
***********       event queue       **********

FILES GENERATI

Sono automaticamente generati a partire dalla grammatica utente i seguenti files:
Questi files generano mediante il comando make -f smlgen.mak i seguenti programmi:

MODULARITA'

La struttura modulare dei sorgenti generati consente di utilizzare i sorgenti generati in qualsiasi ambiente operativo, in quanto le chiamate eseguite verso le code degli eventi possono essere sostituite mediante il cambio delle definizioni hei files di include, oppure sostituendo i moduli events.o e queue.o con dei propri files.
La struttura modulare della libreria consente di utilizzare solamente le parti di codice che si ritengono utili per raggiungere lo scopo prefissato.
Per esempio se non si necessita di contesti multipli, la parte context non viene utilizzata.
Si puo' addirittura utilizzare solamente la parte input e timer e creare un proprio dispatcher per gestire questi eventi.
             +------------------+ +------------------+
             |  generated code  | |    user code     |
             +------------------+ +------------------+
                       |                 |   |
                       +---------+-------+   |
                                 |           |
......................................................................
. LIBRARY SML                    |           |                       .
.                       +------------------+ |                       .
.                       |     events       | |                       .
.                       +------------------+ |                       .
.                                |           |                       .
.       +-----------+------------+-----------+                       .
.       |           |            |           |                       .
.  +---------+ +---------+  +---------+ +---------+  +---------+     .
.  | userevt | |  timer  |--|  input  | |   sm    |--| context |     .
.  +---------+ +---------+  +---------+ +---------+  +---------+     .
.                                |                                   .
.                     +----------+-----------+                       .
.                     |          |           |                       .
.                 +--------+ +--------+ +--------+                   .
.                 | queues | |  pipe  | | stream |                   .
.                 +--------+ +--------+ +--------+                   .
.                                                                    .
......................................................................

PORTABILITA'

La portabilita' di SML, grazie alla sua struttura modulare e' quasi assoluta per quanto riguarda il linguaggio, mentre per la parte relativa alle code fornite per ora e' limitata alle macchine Unix, la tabella di seguito illustra le portabilita' esistenti:
Piattaforma LinguaggioCodeTimer
HP-UX SI SI SI
IBM-AIX SI SI SI
SCO-UNIX SI SI SI
MS-DOS SI NO NO
WIN 3.1 SI NO NO
WIN95 SI NO NO

CODICE UTENTE

E' possibile inserire codice utente inserendolo tra i caratteri:
%{
    /* ...user code here... */
}%
Il codice utente puo' essere inserito in alcuni punti particolari della grammatica ed evita l'intervento manuale sui files generati.
In questo modo si puo' personalizzare le strutture dati, inserire codice all'interno delle funzioni di gestione del codice utente.
Si puo' per esempio inserire nella dischiarazione di una funzione:
   ...
   FUNCTIONS {
    NAME SM1Error BEGIN
      %{ UsrLogFunction( "Errore" ); }%
      EMIT( "SM1: error" )
      NEXTSTATE( SM1_S_IDLE )
    END
    NAME SM1fun1 {
    ...
Attenzione: Il codice utente inserito puo' rendere non portabili i files generati. Si puo' migliorare il problema utilizzando per il linguaggio 'C' di #include invece che codice puro.

ESEMPIO

La figura sottostante permette di evidenziare le fasi necessarie per la creazione di una o piu' State Machine.
Come si puo' notare l'unica parte che l'utente deve eseguire manualmente e' la fase di definizione delle State Machine.
definizione    generazione     generazione    test   verifica
StateMachine      SML          eseguibili            funzionamento

***********
* editor  *
***********
     |
     v
+---------+
|         |    ***********
| XXX.sml | -> *   SML   *
|         |    ***********
+---------+         |          +---------+
                    V          | smllib  |
                +---------+    +---------+
               +---------+|         |
              +---------+||         V
              |         |||    ***********
              |smlgen.X ||+ -> *   CC    *
              |         |+     ***********
              +---------+           |
                                    V
                               +---------+
                               |         |
                               | smltest | -> +-+
                               |         |    +-+
                               +---------+    +-+
                               +---------+    +-+
                               |         |    +-+
                               |   sml   | <- +-+    +---------+
                               |         | --------> | sml.log |
                               +---------+           +---------+

DICHIARAZIONI

Devono essere dichiarate le seguenti parti:

MODULO

La dichiarazione di modulo non ha una grande importanza ai fini della generazione della macchina a stati, ma consente di dichiarare lo scopo del contenuto del file.
Inoltre in questa fase e' possibile introdurre linee di codice 'C' che saranno inserite nel codice generato.
Le linee di codice da inserire devono essere precedute dal carattere >, dopo l'ultima linea che si desidera inserire di deve mettere la keywork END.
MODULE Example1 %{
\#include 

static flag = 1;

}%
Attenzione: Se si deve utilizzare in carattere '#' precederlo con un carattere di escape '\', cosicche' non sia interpretato dal precompilatore.

DATI

Gli unici dati che devono essere dichiarati sono quelli spediti assieme ad un evento verso una State Machine.
Tutti i dati sono mappati in una struct indipendente con il nome indicato. Tutte le struct sono poi inserite in una union, che le racchiude tutte. Nel codice generato e' possibile inserire parti utente.
DATA TYPE BEGIN
  NAME SM1data
  NAME SM2data
  NAME SM1toSM2
  NAME SM2toSM1
END
Dalla definizione illustrata vengono generate le seguenti dichiarazioni:
typedef struct {
    int         state;
    /* FIELDS ADDED by USER... */
    } SM1data; /*C1*/
/* ...omissis... */
typedef struct {
    int         state;
    /* FIELDS ADDED by USER... */
    } SM2toSM1; /*C1*/

/* data sended on channels */
typedef struct {
  Evt   evt;       /* event field */
  long  len;
  union data {
    SM1data SM1data;
    SM2data SM2data;
    SM1toSM2 SM1toSM2;
    SM2toSM1 SM2toSM1;
    } data;
  } Data, *DataP;

CHANNELS

Il channel si puo' definire come un canale di comunicazione ove transitano gli eventi.
Una StateMachine puo' essere associata ad un solo channel.
Questa StateMachine puo' avere associati piu' context.
Su un unico channel possono esistere piu' StateMachine di tipo diverso.
Possono essere utilizzati come channels i seguenti canali:

CONTEXT

Viene definito il nome di un contesto che viene tradotto in una struct 'C' dove si possono aggiungere files utenti.
Il contesto permette di avere piu' istanze di una state machine attive allo stesso tempo.
Questo permette di gestire situazioni dove, per esempio in state machine in ambienti di comunicazione, ogni state machine segue il percorso di un contesto da gestire.
Di seguito il legame tra Channels, StateMachine e Context:
Legenda:   {N}  Channels
           [N]  StateMachine
           (N)  Context

  -------> {1} >>>> [1] ---> [5] ---> [9]
                     |        |        |
                    (1)      (1)      (1)
                     |        |
                    (2)      (2)
                              |
                             (3)

  -------> {2} >>>> [2] ---> [8] ---> [7] ---> [6]
                     |        |        |
            .       (1)      (1)      (1)
            .        |
            .       (2)

  -------> {N} >>>> [3] ---> [4]
                     |        |
                    (1)      (1)
                              |
                             (2)

EVENTI

Gli eventi possono provenire da vari tipi di trasporti: Un evento ha associato una certo blocco dati.
Un evento puo' essere interno ad una macchina a stati, oppure spedito (SEND) ad un altra macchina a stati.
Per eventuali implementazioni diverse l'utilizzo delle Message Queue e' stato mascherato dalle funzioni EvtXXX() generate come sorgente, quindi modificandole e' possibile utizzare altri metodi per spedire e/o ricevere eventi.
Esistono alcuni eventi predefiniti che consentono di far eseguire alle State Machine alcune operazioni particolari:

EVENTI DA SORGENTI MULTIPLE

La tabella seguente evidenzia le tipologie di portatori di eventi che possono essere gestite.
                              +------------+     +---------------+
rete locale  -->  streams --> |            |     |               |
files        -->  pipe    --> | dispatcher | --> | State Machine |
timer        -->  timer   --> |            |     |               |
                              +------------+     +---------------+

PASSAGGIO DATI

Sono definiti i tipi necessari per il passaggio dei dati tra le varie entita' del modulo.
Il passaggio dei dati tra entita' della stessa State Machine viene definita mediante una struct.
Il passaggio dati tra diverse State Machine viene definito canale (CHANNEL) e corrisponde ad una struct.

GESTIONE EVENTI

Tutte le State Machine sono in ascolto ad una Message Queue e richiedono alla stessa solamente gli eventi a loro pertinenti.
Se vi sono piu' State Machine in ascolto da diverse Message Queue e' possibile inviare eventi ad una qualsiasi State Machine (caso A).
                      +-------------+
             +------->| WaitEvent() |
             |        |             | StateMachine1
Unix         |  +----<|  EvtSend(2) |
MsgQueue     |  |     +-------------+
+----+       |  |
|    |>------+  |
|    |<--+---|--+
+----+   |   |  |
         |   |  |
         |   |  |     +-------------+
         |   +------->| WaitEvent() |
         |      |     |             | StateMachine2
         |      +----<|  EvtSend(1) |
         |            +-------------+
         |(A)
         |            +-------------+
         |   +------->| WaitEvent() |
         |   |        |             | StateMachine3
         |   |  +----<|  EvtSend(4) |
Unix     +---|--|----<|  EvtSend(2) |
MsgQueue     |  |     +-------------+
+----+       |  |
|    |>------+  |
|    |<------|--+
+----+       |  |
             |  |
             |  |     +-------------+
             +------->| WaitEvent() |
                |     |             | StateMachine4
                +----<|  EvtSend(3) |
                      +-------------+
Esempio di coda di eventi in una Message Queue:
  OLD  -  Event TX for SM1 -----+-- estratti da WaitEvent di StateMachine1
   .   -  Event RX for SM2 --+  |
   .   -  Event CK for SM1 --|--+
   .   -  Event TX for SM1 --|--+
   .   -  Event CK for SM2 --+
  NEW  -  Event RX for SM2 --+----- estratti da WaitEvent di StateMachine2

ORDINE DI DICHIARAZIONE

DICHIARAZIONE COMPONENTE

E' un semplice identificatore del progetto e del prefisso da utilizzare per i nomi dei files generati.
Puo' contenere anche una parte di codice che deve essere inserita nei files generati.
    MODULE ident PREFIX ident BEGIN ... END
Tutto cio' che compare tra BEGIN ed END e ha come primo carattere '>' viene riportato cosi' come e' sul codice generato.
Esempio:
    MODULE prova PREFIX prova BEGIN
    >#include 
    >
    >int debug = 0;
    >
    END

DICHIARAZIONE TIPI

La seguente sintassi consente di definire il tipo di dato scambiato su un canale di comunicazione.
DATA TYPE BEGIN
  NAME name
  ...
END

DICHIARAZIONE STATE MACHINE

La seguente sintassi consente di dichiarare una state machine, il canale che utilizzera', il tipo di dato che viene scambiato sul canale ed infine se la SM e' attiva.
STATEMACHINE DEFINITION BEGIN
  NAME name CHANNEL number TYPE datatype {ON|OFF}
  ...
END

DICHIARAZIONE GERARCHIA AUTOMI

DICHIARAZIONE CANALI

DEFINIZIONE AUTOMI

La sintassi seguente permette di definire fisicamente la state machine, infatti di definiscono gli stati e gli eventi che la compongono.
  STATEMACHINE smname {
    STATES { state1, state2, ...,  stateN }
    EVENTS { event1, event2, ...,  eventN }
    FUNCTIONS {
      NAME funname {
        instructions...
      }
      NAME funname {
        instructions...
      }
      ...
    }
    INIT = funname
    SET ( state,  event )  = funname
    SET ( state1, event1 ) = funname
    ...
    SET ( stateN, eventN ) = funname
  }

ISTRUZIONI

Le seguenti sono le istruzioni che possono essere inserite:
  EMIT( string )
  NEXSTATE( state )
  SEND( channel, sm, event, datatype )

FILES

I seguenti sono i files generati automaticamente:
  smlmain.c  .. contiene un main di partenza programma
                un main_XXX per ogni SM utilizzata
  smlgen.c   .. contiene le funzioni delle macchine a stati
                e le stringhe di intentificazione di eventi e stati
  smlgen.h   .. contiene le definizioni dei tipi: dati, contesti
                define per eventi e stati
                prototipi funzioni
  smlgen.doc .. contiene la documentazione delle SM generate

LOG

Viene automaticamente gestito il log delle operazioni.

PREPROCESSOR

Viene utilizzato il preprocessore del linguaggio 'C' con la sua sintassi completa.

BNF

Convenzioni:    XX            .. keyword
                        .. identificatore
                        .. simbolo non terminale
                +        .. presenza di almeno un elemento (da 1 a N)
                *        .. presenza di elementi (da 0 a N)
                [xx]        .. xx e' facoltativo


sml                :=    
                         
                         
                         
                         +

module_def         :=    MODULE 
data_def           :=    DATA TYPE BEGIN
                          +
                         END
single_data_def    :=    NAME 
machines_def       :=    STATEMACHINE DEFINITION BEGIN
                           +
                         END
single_sm_def      :=    NAME  TYPE  ON|OFF

VERSIONE DEMO

La versione demo di slm ha le seguenti limitazioni:

TIMER

Le possibili operazioni sui timer sono:
  int   TimerInit( void )
  int   TimerDisplay( int id )
  int   TimerAdd( int seconds, int step, int (*fun)(int id) )
  int   TimerSet( int id, int seconds, int step, int (*fun)(int id) )
  int   TimerDel( int id )
  Timer TimerGet( int id )
  int   TimerSetStat( int id, int stat )
  int   TimerCheck( int mode, int *id )
  int   TimerManage( int mode, int *id )
  int   TimerSelect( int seconds )

INPUT

Le possibili operazioni sui canali di input sono:
  int   InputInit( void )
  int   InputDisplay( int id )
  int   InputAdd( int fd, int (*fun)(int id) )
  int   InputSet( int id, int fd, int (*fun)(int id) )
  int   InputDel( int id )
  Input InputGet( int id )
  int   InputGetN( int id )
  int   InputManage( int mode, int *id )
  int   InputSelect( int seconds )

USER EVENTS

Le possibili operazioni sui canali utente sono:
  int     UserEvtInit( int (*checkfun)(int *id) )
  int     UserEvtDisplay( int id )
  int     UserEvtAdd( int fd, int (*fun)(int id) )
  int     UserEvtSet( int id, int fd, int (*fun)(int id) )
  int     UserEvtDel( int id )
  UserEvt UserEvtGet( int id )
  int     UserEvtGetN( int id )
  int     UserEvtManage( int mode, int *id )