Avanti Indietro Indice

3. Lex

Il programma Lex genera un cosiddetto `Lexer' o `Analizzatore lessicale'. Questa è una funzione che ha come input un flusso di caratteri e che, quando riconosce che un gruppo di caratteri corrisponde a una chiave, esegue un'azione specificata. Un esempio molto semplice:

%{
#include <stdio.h>
%}

%%
stop    printf("Ricevuto comando Stop\n");
start   printf("Ricevuto comando Start\n");
%%

La prima sezione tra la coppia %{ e %} è inclusa direttamente nell'output del programma. È necessario inserirla perché in seguito si usa "printf" il quale è definito in stdio.h.

Le sezioni si separano usando '%%', quindi la prima linea della seconda sezione comincia con la chiave 'stop'. Ogni volta che viene incontrata in input la chiave 'stop' viene eseguito il resto della linea (una chiamata a printf()).

Oltre a 'stop', si è definita anche 'start', il cui comportamento è analogo a quello di 'stop'.

La sezione di codice termina ancora con '%%'.

Per compilare Esempio 1, fare questo:

lex esempio1.l
cc lex.yy.c -o esempio1 -ll

NOTA: se si sta usando flex, invece che lex, è probabile sia necessario cambiare '-ll' in '-lfl' negli script di compilazione. In RedHat 6.x e SuSE è necessario questo cambiamento anche se si invoca 'flex' come 'lex'!

Ciò genera il file 'esempio1'. Se lo si esegue, attenderà la scrittura in input di qualche carattere. Quando viene inserito in input qualcosa che non corrisponde a nessuna delle chiavi definite (cioè 'stop' e 'start') esso viene scritto in output nuovamente. Se venisse immesso 'stop' verrebbe stampato in uscita 'Ricevuto comando stop';

Chiudere il programma con un EOF (^D) [CONTROL-d].

Ci si potrebbe chiedere come mai il programma funzioni, visto che non è stata definita una funzione main(). Questa funzione viene definita implicitamente per voi in libl (liblex) che è stata inclusa nella compilazione con l'opzione -ll.

3.1 Espressioni regolari in corrispondenze

Questo esempio non è particolarmente utile in sé stesso, e neppure il prossimo. Comunque mostrerà come usare le espressioni regolari in Lex che risulteranno essere molto utili in seguito.

Esempio 2:

%{
#include <stdio.h>
%}

%%
[0123456789]+           printf("NUMERO\n");
[a-zA-Z][a-zA-Z0-9]*    printf("PAROLA\n");
%%

Questo file Lex descrive due tipi di corrispondenze (categorie): di tipo PAROLA e di tipo NUMERO. Le espressioni regolari possono essere scoraggianti, ma con un piccolo sforzo è facile capirle. Esaminiamo la combinazione del tipo NUMERO:

[0123456789]+

Questo dice: una sequenza di uno o più caratteri fra quelli del gruppo 0123456789. Avremmo potuto scriverli in maniera più breve:

[0-9]+

Ora, la combinazione del tipo PAROLA è in qualche modo più complicata:

[a-zA-Z][a-zA-Z0-9]*

La prima parte corrisponde a 1 e 1 solo carattere compreso tra 'a' e 'z', o tra 'A' e 'Z'. In altre parole, una lettera. Questa lettera iniziale deve poi essere seguita da zero o più caratteri che possono essere o una lettera o una cifra. Perché usare qui un asterisco? Il '+' significa 1 o più corrispondenze, ma una PAROLA potrebbe consistere benissimo di un solo carattere, che è già stato considerato. Quindi la seconda parte potrebbe avere zero corrispondenze, ed è per questo che si scrive un '*'.

In questo modo, si è imitato il comportamento di molti linguaggi di programmazione che richiedono che il nome di una variabile *debba* cominciare con una lettera, ma possa poi contenere anche delle cifre. In altre parole, 'temperatura1' è un nome valido, ma '1temperatura' non lo è.

Si cerchi di compilare Esempio 2, allo stesso modo di Esempio 1, e si inserisca del testo. Ecco un esempio di sessione:

$ ./esempio2
foo
PAROLA

bar
PAROLA

123
NUMERO

bar123
PAROLA

123bar
NUMERO
PAROLA

Ci si può chiedere da dove vengano tutti questi spazi bianchi nell'output. La ragione è semplice: erano nell'input, e non si è stabilita alcuna corrispondenza per essi in nessun luogo, quindi vengono restituiti ancora (come sono).

La manpage di Flex descrive le sue espressioni regolari in dettaglio. Sono in molti a ritenere che la manpage delle espressioni regolari di perl (perlre) sia molto utile, anche se Flex non implementa tutto quello che si può fare con perl.

Si deve stare attenti a non creare corrispondenze di lunghezza zero come '[0-9]*' - altrimenti l'Analizzatore lessicale potrebbe andare in confusione cominciando a riconoscere ripetutamente stringhe vuote.

3.2 Un esempio più complicato con sintassi simile a quella del C

Mettiamo si voglia analizzare un file di questo tipo:

logging {
        category lame-servers { null; };
        category cname { null; };
};

zone "." {
        type hint;
        file "/etc/bind/db.root";
};

Si vedono chiaramente delle categorie (token) in questo file:

Il file corrispondente per Lex è Esempio 3:

%{
#include <stdio.h>
%}

%%
[a-zA-Z][a-zA-Z0-9]*    printf("PAROLA ");
[a-zA-Z0-9\/.-]+        printf("NOMEFILE ");
\"                      printf("DOPPIOAPICE ");
\{                      printf("APERTAGRAFFA ");
\}                      printf("CHIUSAGRAFFA ");
;                       printf("PUNTOEVIRGOLA ");
\n                      printf("\n");
[ \t]+                  /* ignora spazi bianchi */;
%%

Quando si dà come input al programma generato da questo file per Lex il nostro file (usando esempio3.compile), si ottiene:

PAROLA APERTAGRAFFA 
PAROLA NOMEFILE APERTAGRAFFA PAROLA PUNTOEVIRGOLA CHIUSAGRAFFA PUNTOEVIRGOLA 
PAROLA PAROLA APERTAGRAFFA PAROLA PUNTOEVIRGOLA CHIUSAGRAFFA PUNTOEVIRGOLA 
CHIUSAGRAFFA PUNTOEVIRGOLA 

PAROLA DOPPIOAPICE NOMEFILE DOPPIOAPICE APERTAGRAFFA 
PAROLA PAROLA PUNTOEVIRGOLA 
PAROLA DOPPIOAPICE NOMEFILE DOPPIOAPICE PUNTOEVIRGOLA 
CHIUSAGRAFFA PUNTOEVIRGOLA 

Quando paragonato al file di configurazione di cui sopra, è chiaro che lo si è nitidamente 'categorizzato'. Ogni parte del file di configurazione è stato associato e convertito in una categoria (token).

E questo è esattamente quel che serve per poter utilizzare bene YACC.

3.3 Che cosa si è visto

Si è visto che Lex è in grado di leggere input arbitrari, e di determinare che cosa sia ogni parte dell'input. Questo è chiamato 'categorizzazione' (tokenizing).


Avanti Indietro Indice