ePrometeusCorsoLinuxLinux
testi articoli
Testi Articoli  Download
Home | OpenSource | PhpNuke | Programming | SysAdm | 
CorsoJava è ora Video! Free for all!
Clicca Qui!
UN COMPILATORE CON LEX E YACC
Usare Lex e Yacc per un traduttore da BASIC a C
Grammatiche ed alberi di sintassi concreta e astratta
Lex e Yacc
Un traduttore elementare da BASIC a C
Il processo della compilazione
Analisi lessicale con Lex
Analisi sintattica con Yacc
Azioni semantiche di traduzione
Conclusioni
Riferimenti
Download
L'Autore


<<< Grammatiche ed alberi di sintassi concreta e astratta >>>

Una grammatica è un formalismo che descrive tutti i programmi esprimibili in un dato linguaggio. In questo contesto per programma si intende l'aspetto sintattico del programma, una stringa. Poiché generalmente i programmi esprimibili con un linguaggio sono infiniti, la grammatica li descrive tutti dando delle regole per generarli: sarebbe impensabile che li elencasse tutti. Per esempio:

Grammatica 1
(a) lang : '(' ')'
(b) lang : '(' lang ')'
è una grammatica che descrive tutte le stringhe che cominciano con n parentesi tonde aperte e finiscono con altrettante parentesi tonde chiuse. In questa notazione abbiamo due tipi di simboli: i terminali come '(' e ')', (che si distinguono perché racchiusi in virgolette singole) e i non-terminali (come lang); le regole, come (a) e (b), si dicono produzioni. I terminali sono simboli “definitivi”, cioè se compaiono non possono essere più rimpiazzati; i non-terminale possono invece essere rimpiazzati dal corpo di una regola che ha quel non-terminale in testa. Il processo di generazione di un linguaggio è una semplice applicazione delle regole, a partire da un non-terminale che viene normalmente chiamato simbolo iniziale e da cui si derivano tutti i programmi descritti da quella grammatica. Nel nostro caso il simbolo iniziale sarà necessariamente lang (non abbiamo molta scelta) e possiamo cominciare una generazione. La generazione (che potrebbe anche non terminare mai) termina quando nel programma generato non ci sono più simboli non terminali. Per esempio:
lang
(b) => '(' lang ')'
(a) => '(' '(' ')' ')'

Quindi (()) fa parte del linguaggio generato dalla nostra grammatica. In pratica le grammatiche non vengono utilizzate in questo modo ma a ritroso: cioè dato un programma si cerca di stabilire se la grammatica è in grado di generare quel programma (il che vuol dire che il programma è grammaticalmente corretto) e, altrettanto importante, la sequenza di regole che hanno consentito di generare quel programma. Un problema teorico che è stato studiato a fondo è quello della costruzione del parser di una grammatica. Il parser è quel particolare programma in grado di ricostruire la derivazione del programma dal simbolo iniziale applicando le regole della grammatica. Il che significa sostanzialmente ricostruire la struttura grammaticale di un programma, primo passo della compilazione. Il problema della costruzione del parser data una grammatica non è risolvibile nella sua formulazione più generale. È risolvibile invece solo per particolari grammatiche, con determinare limitazioni nel formato delle regole. La casistica è ampia e variegata, e non entreremo nei dettagli. Diciamo solo che Yacc è un generatore di parser: data una grammatica di tipo LALR(1) non ambigua è in grado di generare il sorgente C di un parser per il linguaggio descritto da quella grammatica. Facciamo un altro esempio per chiarire meglio il concetto di derivazione (estendiamo la notazione: il '|' abbrevia in un unica regola una serie di regole con la stessa testa, CIFRA in maiuscolo sta per una cifra da 0 a 9).

Grammatica 2
(c) e : CIFRA | t | f
(d) t : f '+' f
(e) f : e '*' e

Consideriamo la stringa '3+5*7': l'intera derivazione può essere ottenuta con la sequenza rappresentata da questo albero (in figura le foglie sono i terminali, mentre i figli di un non-terminale sono i simboli in cui si espande il non-terminale applicando una delle regole):

Albero 1
  e
  |
  t
/ | \
f + f
|  /|\
e e * e
| | |
3 5 7

I parser (e Yacc in particolare) generano il risultato dell'analisi sintattica in questo formato. La grammatica 2 è più complicata della grammatica 3, che può sembrare equivalente ma più semplice:

Grammatica 3
e : CIFRA | e '+' e | e '*' e

ma questa ha dei seri problemi. Innanzitutto soffre del peggior difetto che possa avere una grammatica: è ambigua. Infatti è possibile derivare il nostro programma in più di un modo :

  e     e
/ | \ / | \
3 + e e * 7
 /|\ /|\
e * e e + e
| | | |
5 7 3 5

quindi non è possibile stabilire univocamente la sequenza di applicazioni di regole che genera il programma. Di una grammatica come la 3 non è possibile generare automaticamente il parser: Yacc riporta vari errori. Una delle conseguenze di questo problema è che la grammatica non riflette la maggiore associatività della moltiplicazione rispetto all'addizione. La grammatica 2 infatti è scritta in modo tale che tenga conto del fatto che il prodotto ha priorità sulla somma. Infatti ogni algoritmo di ricostruzione della grammatica a partire dalla stringa in presenza di moltiplicazione applicherà la regola (e) prima delle altre, risolvendo così implicitamente il problema di stabilire la priorità.

Ritorniamo all'albero 1. Questo è il risultato dell'analisi ed è detto albero di sintassi concreta. Non è particolarmente comodo da utilizzare, perché mantiene traccia di una serie di dettagli sintattici, utili per risolvere problemi come la priorità della moltiplicazione sull'addizione, ma inutili per il resto del lavoro. Quello che realmente è utile è una forma semplificata di questo albero, il cosiddetto albero di sintassi astratta, per esempio:

Albero 2
   +
  / \
 3   *
/ \
5 7

Questo albero viene costruito dall'albero di sintassi concreta, semplificando tutti i dettagli sintattici non necessari ed è ciò che realmente si cerca di ricavare dall'analisi sintattica. In un certo senso è il “succo semantico”, sul quale si può comodamente operare per le fasi successive della compilazione.

ePrometeus s.r.l. - Web Software House & Open Source System Integrator
MILANO - SAN BENEDETTO DEL TRONTO(AP)
Contatti: info@eprometeus.com