|
In questo articolo descriviamo un limitato compilatore
sviluppato con Lex e Yacc, chiamato b2c. In realtà si tratta
di un convertitore di formato tra due noti linguaggi di
programmazione: da BASIC a C. Il traduttore è molto limitato
nelle sue funzionalità: tratta solo un successive
sottoinsieme del BASIC. Un compilatore in generale è un
traduttore di programmi in un linguaggio in programmi in un altro
linguaggio. Il vantaggio di questa operazione è insita nelle
ottimizzazioni che vengono effettuate dal compilatore.
Lex e Yacc comunque sono utili in qualunque contesto sia
richiesta una conversione di dati da un formato ad un altro: per
esempio, conversioni di formato di testi (ricordiamo i tanti
traduttori da qualche formato in HTML), compilatori ad alto
livello da un linguaggio ad un altro, (per esempio esistono
compilatori da Pascal, Fortran, Lisp e Prolog in C), interpreti
(l'interprete Perl usa una sostanziosa grammatica Yacc), editor
guidati dalla sintassi e via discorrendo.
Illustriamo il BASIC giocattolo che b2c supporta. Ribadisco
che si tratta di un linguaggio talmente limitato da essere
inutilizzabile per qualsiasi contesto pratico (anche se può
essere un'ottima base per dei progetti concreti). Le limitazioni
sono volute per mantenere il tutto ad un puro livello didattico
ed evitare qualunque complicazione che possa oscurare i concetti.
La sintassi del linguaggio è data nel seguente listato:
/* ESPRESSIONI */
constant: INTEGER ;
primary.expression:
VARIABLE
| constant
| '(' expression ')'
;
unary.expression:
primary.expression
| '+' primary.expression
| '-' primary.expression
| NOT primary.expression
;
multiplicative.expression:
unary.expression
| multiplicative.expression '*' unary.expression
| multiplicative.expression '/' unary.expression
;
additive.expression:
multiplicative.expression
| additive.expression '+' multiplicative.expression
| additive.expression '-' multiplicative.expression
;
relational.expression:
additive.expression
| relational.expression '<' additive.expression
| relational.expression '>' additive.expression
| relational.expression LE additive.expression
| relational.expression GE additive.expression
;
equality.expression:
relational.expression
| equality.expression EQ relational.expression
| equality.expression NE relational.expression
;
AND.expression:
equality.expression
| AND.expression AND equality.expression
;
OR.expression:
AND.expression
| OR.expression OR AND.expression
;
expression:
OR.expression
;
/* COMANDI */
sep.list:
SEP
| sep.list SEP
;
sep.list.opt : | sep.list ;
statement.list:
statement
| statement.list sep.list statement
;
statement:
assignment.statement
| for.statement
| while.statement
| if.statement
| print.statement
| input.statement
;
assignment.statement:
LET VARIABLE '=' expression
| VARIABLE '=' expression
;
for.statement:
FOR VARIABLE '=' expression TO expression
sep.list statement.list sep.list NEXT
;
while.statement:
WHILE expression sep.list statement.list sep.list WEND
;
if.statement:
IF expression THEN
sep.list.opt statement.list sep.list.opt
else.statement.opt
;
else.statement.opt:
ENDIF
| ELSE sep.list.opt statement.list sep.list.opt ENDIF
print.statement:
PRINT
| PRINT VARIABLE
| PRINT VARIABLE ';'
| PRINT STRING
| PRINT STRING ';'
;
input.statement:
INPUT VARIABLE
;
program:
sep.list.opt statement.list sep.list.opt
;
I simboli terminali (i token) sono racchiusi tra virgolette
singole oppure sono scritti interamente in maiuscolo. Tutti gli
altri sono simboli non-terminali. Il nostro BASIC ammette
soltanto espressioni aritmetiche: le costanti possono essere
soltanto intere. Sono ammesse le 4 operazioni, le classiche
operazioni relazionali (=, >, >=, <=, <>), AND, OR
e NOT. Le variabili possono essere solo di tipo intero,
equivalenti se minuscole o maiuscole e significative solo per il
primo carattere. Questa limitazione (che può sembrare
eccessiva, ma del resto tutto il linguaggio è solo un
prototipo) ci consentirà di allocare staticamente tutte le
variabili; per una implementazione un po' meno limitativa sarebbe
necessario costruire esplicitamente l'albero di sintassi
astratta, scandirlo per trovare le variabili e generare delle
dichiarazioni prima di convertire il resto del codice.
Per quanto riguarda i comandi, avremo FOR...NEXT,
IF...THEN...ELSE...ENDIF, WHILE...WEND, PRINT e INPUT. Sarà
possibile solo il PRINT di una stringa o una variabile,
eventualmente seguita da un ";" nel caso si voglia la
soppressione del newline prodotto automaticamente dalla PRINT. La
grammatica può sembrare qua e là più complicata
del necessario, ma è la vera grammatica utilizzata dallo
Yacc per il b2c, e come tale richiede qualche compromesso e
complicazione per rientrare nell'insieme delle grammatiche da
esso supportate.
|