Progetto di Linguaggi e Traduttori

Titolo

Un interprete per un semplice linguaggio

Autore

Zavaroni Andrea matr. 2006 43339

Sommario

Un interprete di un semplice linguaggio particolarmente dedicato ad un primissimo contatto con la programmazione.

Indice del contenuto

Introduzione

Questo semplice interprete si propone di realizzare una interfaccia user-friendly con il programmatore che puo' costruire un semplice programma utilizzando comandi con un linguaggio a lui famigliare (scrivi il file ..., apri l'archivio ..., ecc.).
L'interprete da me realizzato e' scritto in Java (vers. 1.1.5) ed e' volutamente semplice per mettere in evidenza tutta una serie di problemi legati a questo tipo di programmazione, come ad esempio il parsing di un file di testo, il controllo degli errori, ecc... . Tale interprete e' comunque espandibile, attraverso l'aggiunta di ulteriori comandi.
Inoltre, e' interessante notare che e' naturalmente possibile usare altri linguaggi di programmazione per codificare l'interprete. Ad esempio io ho impostato in Prolog (SICStus 3) alcune operazioni.
Tale interprete si presenterebbe utilissimo ad esempio per avvicinare i bambini alla programmazione: con un linguaggio simile al loro (ed in italiano) possono costruire semplici programmi, imparando i rudimenti essenziali della programmazione (come la lettura e scrittura di file, lettura e scrittura di variabili, operazioni con variabili e costanti, ecc...).


Grammatica

La grammatica G = (VN,VT,S,P) dove :

VN = {PROG, ISTRUZ, COMANDO, CICLO, ISTRUZCICLO, COMANDO1, COMANDO2, COMANDO3, COMANDO4, REST1, REST2, REST3, REST4, SPECIFICA, ORIGINE, PREP, OPERANDO, OP, RIS, STR, NUM, COST, COSTN},

VT = {a, apri, archivio, da, dalla, diviso, fai, file, fine, il, in, l', leggi, meno, per, piu, ripeti, scrivi, su, video, volte, "},

S = PROG,

P = {
PROG ::= {ISTRUZ} fine
ISTRUZ ::= COMANDO | [CICLO]
CICLO ::= ripeti ISTRUZCICLO volte NUM
ISTRUZCICLO ::= {COMANDO}
COMANDO ::= COMANDO1 | COMANDO2 | COMANDO3 | COMANDO4
COMANDO1 ::= apri REST1
COMANDO2 ::= leggi REST2
COMANDO3 ::= scrivi REST3
COMANDO4 ::= fai REST4
REST1 ::= identfile | SPECIFICA identfile
SPECIFICA ::= l'archivio | il file
REST2 ::= REST1 | identvar
REST3 ::= ORIGINE PREP identfile | ORIGINE a video
ORIGINE ::= identvar | identfile | COST
PREP ::= in | su
REST4 ::= OPERANDO OP OPERANDO in RIS
OPERANDO ::= identvarn | COSTN
OP ::= piu | meno | per | diviso
RIS ::= identvar
STR ::= stringalfanumerica
NUM ::= numint
COST ::= " NUM " | " STR "
COSTN ::= " NUM "


NOTA :

identvar e' il nome di una variabile
identfile e' il nome di un file
identvarn e' il nome di una variabile istanziata a un numero intero
numint e' un numero intero
stringalfanumerica e' una stringa (di caratteri)


La grammatica G descritta appartiene alla classe LL(1) : infatti e' sempre possibile decidere la transizione di stato utilizzando un solo simbolo (terminale) di ingresso.
Come si vede dalle regole di produzione, che definiscono la sintassi, e' possibile estendere la grammatica con ulteriori comandi : l'estendibilita' e' semplice, basta aggiungere, ad esempio, COMANDO5 nella riga :
COMANDO ::= COMANDO1 | ... | COMANDO4
che diventa cosi' :
COMANDO ::= COMANDO1 | ... | COMANDO4 | COMANDO5
e poi definire COMANDO5, analogamente agli altri 4 comandi.


I comandi

Ho pensato di mettere a disposizione del programmatore tutta una serie di comandi fondamentali (che ripeto puo' essere ampliata).


Analisi del progettto e considerazioni

Rispetto all'interprete piu' complesso per SCHEME (in Java), anche qui e' rispettata una certa estensibilita', permettendo cosi' al programmatore di sviluppare nuove istruzioni (nuovi comandi) per rendere piu' "potente" l'interprete.
Ma l'aspetto ancora piu' fondamentale e' quello di generalizzare il procedimento di costruzione che vede il passaggio del problema iniziale a una sua formalizzazione sintattica, in quanto la codifica con l'uno o l'altro linguaggio e' si importante, ma secondario rispetto alla generalizzazione suddetta.
Questa "generalizzazione" si ottiene appunto formulando una sintassi che sia chiara e che permetta di realizzare una grammatica non ambigua di classe LL(1).
Fatto questo esistono meccanismi (come i riconoscitori a tabelle di linguaggi LL(1) ) che permettono di codificare (automaticamente) la sintassi in esame attraverso un'analisi deterministica discendente con uno stile di programmazione guidato dai dati. (Uso di tabelle dette "parsing table" che indicano il comportamento dell'automa riconoscitore).
Per scrivere la sintassi adeguata ho utilizzato del formalismo BNF (Bakus Naus Form). Tale formalismo permette di isolare le categorie sintattiche al fine di definire (e successivamente di costruire) l'interprete vero e proprio.
Nella grammatica in esame le categorie sono legate alle istruzioni di ciclo (ripeti ... volte ...) e ai quattro comandi (apri, leggi, scrivi e fai), mentre, ad esempio, le categorie del meta-interprete LISP visto a lezione sono 8.
La realizzazione dell'interprete mediante il linguaggio Java ha messo in luce gli aspetti positivi legati all'adozione di tale linguaggio, come ad esempio la possibilita' di avere a disposizione classi e metodi gia' pronti (librerie di classi e metodi di Java) per il parsing di file di testo (StreamTokenizer()). Inoltre, per la realizzazione di un environment, ho potuto usare i metodi legati alla classe HashTable().
In Prolog, ad esempio, non si hanno meccanismi particolari per il parsing e per realizzare un environment.
Esistono pero' dei predicati (predicati definiti built-in) che permettono di realizzare in modo opportuno queste e altre operazioni. In particolare, per aprire un file in lettura (aprire il canale di input) esiste il predicato see(X).
Usando tali predicati e sfruttando le potenzialita' proprie del linguaggio Prolog e' possibile definire un interprete del mio linguaggio in Prolog invece che in Java.
Ovviamente la scelta del linguaggio di programmazione dipende da molti fattori : una ditta di sw puo' voler utilizzare un dato linguaggio ( o un insieme di linguaggi), oppure il programmatore conosce e preferisce programmare con un linguaggio particolare invece che un altro.
Supponendo pero' di avere una scelta libera e illimitata riguardo al linguaggio da utilizzare, la scelta cadra' naturalmente su quello che presenta piu' vantaggi.
Io ho scelto Java perche' tra le altre cose e' fornito di una vastissima libreria in grado di aiutare il programmatore a risolvere molti problemi, oltre ad essere un linguaggio particolarmente adatto alla programmazione su rete (vedi l'uso di applet su Internet). Infatti uno sviluppo interessante di un interprete e' quello di essere "portabile" su Internet e reso disponibile a chiunque ne abbia bisogno.
Da parte sua il Prolog permette un approccio piu' conversazionale, quindi piu' vicino alle esigenze umane, permettendo, grazie alle sua caratteristica dichiarativa, di concentrarsi sulle specifiche del problema ed a risolverlo, senza preoccuparsi molto dei dettagli della realizzazione in se. ( vedi E in Prolog?).
Cio' che e' importante osservare e' che qualsiasi sia il linguaggio scelto, la grammatica e in particolare le produzioni (sintassi) non cambiano. Quindi individuata una sintassi, la codifica puo' avvenire in un secondo momento e con il linguaggio scelto.
Occorre quindi definire in modo opportuno la grammatica, come piu' volte detto.
Volendo passare dall'interprete da me realizzato ad un interprete Prolog, ad esempio, basta modificare il componente software che effettua la valutazione di una frase F. Occorre poi introdurre una diversa interpretazione dei simboli di F, attraverso l'unificazionee la risoluzione, che rappresentano le caratteristiche peculiari (ed essenziali) del linguaggio Prolog.


Codifica in Java

Struttura dell'interprete e breve descrizione delle classi

L'interprete e' realizzato attraverso 3 classi appartenenti al package interp.
Ogni classe e' definita all'interno di un file. Il relativo file deve (e in effetti ha) lo stesso nome della classe che contiene.


Limitazioni dell'interprete
Utilizzo dell'interprete

Per poter utilizzare l'interprete occorre avere Java ed un editor di testo.
Dopo aver scritto il programma da interpretare (nomefile.z) basta digitare al prompt dei comandi di MS-DOS java interp.Interp nomeFile.zav ricordando di mettersi nel direttorio ...\interp.
Se alla fine dell'interpretazione a video compare il messaggio "Elaborazione terminata" allora tutto e' andato a buon fine, altrimenti viene visualizzato un particolare messaggio legato al tipo di errore riscontrato e viene interrotta l'interpretazione.
Oltre al messaggio "Elaborazione terminata" vengono visualizzati a video anche il numero di linee elaborate del file sorgente e l'indicazione dei file utilizzati e chiusi.


Il Codice Java

FileF.java (contattatemi per avere il codice)

Frasi.java (contattatemi per avere il codice)

Interp.java (contattatemi per avere il codice)


E in Prolog ?

Come esempio ho implementato le 4 operazioni, rese possibili dal comando fai, attraverso il linguaggio Prolog (SICStus 3).
prolint.pro.
Come si evince dal codice l'approccio e' totalmente diverso rispetto al linguaggio Java. In particolare il Prolog si basa su chiamate ricorsive e definizioni di regole, fatti e goal.


Conclusioni

La formalizzazione detta all'inizio del progetto e' quindi estrinsecata dalla stesura di una grammatica che racchiuda in se tutte le proprieta' dell'interprete che si vuole realizzare.
L'interprete da me creato mi ha permesso di sviluppare concretamente alcune potenzialita' del linguaggio Java.
Inoltre la stesura e soprattutto la prova dell'interprete su numerosi file sorgente hanno messo in luce l'importanza di dotare il software creato di ogni sorta di controlli degli errori : il controllo degli errori prende una parte preponderante di tutto il programma e se questo e' vero in teoria ho potuto ancora una volta constatare che cio' e' vero anche nella pratica.
Un software e' "vincente" se e' sicuro, affidabile ed estendibile. Il linguaggio Java permette tali caratteristiche.
Aver costruito questo interprete mi ha reso inoltre possibile sviluppare il concetto di oggetto calato in una realta' che si scontra con i vecchi problemi della programmazione : controllo di errori, scelta delle strutture dati, scelta dello stesso linguaggio di programmazione, ecc... .



Torna alla pagina iniziale




Back to Curriculum