Paolo Boldi

Progetto StringCalculator


Descrizione del progetto

Questo progetto consiste nella realizzazione di un semplice calcolatore interattivo orientato alle stringhe, chiamato StringCalculator. L'utente interagisce con lo StringCalculator introducendo (da standard input) dei comandi, i quali producono (su standard output) degli effetti: la sintassi dei comandi e l'effetto da essi prodotto è descritto nel seguito.

Si tenga presente che questo documento descrive esclusivamente le caratteristiche minimali che lo StringCalculator deve presentare: siete liberi di (e incoraggiati a) introdurre funzionalità addizionali di cui, ovviamente, si terrà conto in sede di valutazione.

Sintassi generale dei comandi

Ogni comando è costituito da una sequenza di token, separati l'uno dall'altro da (almeno uno) spazio, e terminati da un a-capo. Il primo di questi token, chiamato istruzione, determina il comando da eseguire.

Sono consentiti i seguenti tipi di token:

Variabili: istruzione def

Il calcolatore manipola delle variabili. Ogni variabile ha un nome e un tipo; esistono solo tre tipi di variabili: numeriche (il cui contenuto è un numero intero), stringa (il cui contenuto è una stringa alfanumerica) e insieme (il cui contenuto è un insieme di stringhe).

Ogni variabile, prima di essere utilizzata, deve essere definita. La definizione della variabile avviene attraverso l'istruzione def, la cui sintassi è:

	def <nome variabile> <tipo>
dove <tipo> può essere: num, str oppure set (indicanti rispettivamente una variabile numerica, stringa oppure insieme).

Il tentativo di usare (in una delle istruzioni sotto descritte) una variabile non ancora dichiarata deve produrre un errore. Anche il tentativo di definire (con def) una variabile già definita deve produrre un errore.

Una variabile, all'atto della sua definizione, ha il seguente contenuto:

L'istruzione def non prevede alcun output.

Istruzione print

L'istruzione print stampa il contenuto di una variabile; la sua sintassi è:

	print <nome variabile>

Se la variabile è una variabile numerica, verrà stampato il numero in essa contenuto; se è una variabile stringa, la stringa in essa contenuta; se è un insieme, la stampa avrà il seguente formato:

	{<stringa>, ..., <stringa>}
dove fra le parentesi graffe, separate da virgole, compaiono le stringhe contenute nell'insieme, senza ripetizioni, in un ordine arbitrario.

Assegnamenti di costanti e copie

L'istruzione assign serve per assegnare un valore costante a una variabile numerica o stringa. La sua sintassi è:

	assign <nome variabile> <valore costante>

Il valore costante deve essere del tipo corretto (una costante numerica se la variabile è di tipo numerico, una costante stringa se la variabile è di tipo stringa): in caso contrario, si produrrà un errore. Si produrrà altresì un errore se si tenta di usare quest'istruzione con una variabile di tipo insieme.

L'istruzione copy serve per copiare il contenuto di una variabile in un'altra variabile. La sua sintassi è:

	copy <nome variabile sorgente> <nome variabile destinazione>

L'istruzione ha successo se le due variabili sono dello stesso tipo. Inoltre, ha successo nei seguenti casi:

Operazioni

L'istruzione op consente di assegnare a una variabile (destinazione) un valore ottenuto applicando un qualche operatore ai contenuti di due variabili (chiamate operandi). Le variabili coinvolte nell'operazione possono non essere distinte.

La sintassi dell'istruzione è:

	op <operatore> <nome var. operando 1> [<nome var. operando 2>] <nome var. destinazione>
Notate che l'operando 2 può essere opzionale.

Alcuni degli operatori disponibili prevedono che tutte le variabili coinvolte siano dello stesso tipo, e hanno un significato diverso a seconda del tipo delle variabili coinvolte nell'operazione. Questi operatori (che richiedono tutti due operandi) sono:

Operatoresu var. numerichesu var. stringasu var. insieme
+sommaconcatenazioneunione
-differenzamassimo prefisso comunedifferenza insiemistica
*prodottointerleaving (vedi sotto)concatenazione insiemistica (vedi sotto)
/divisione interamassimo suffisso comuneintersezione

L'interleaving di due stringhe si definisce come la stringa ottenuta alternando i caratteri della prima ai caratteri della seconda; ad esempio: cane*pesce=cpaersncee, torquato*tasso=ttoarsqsuoato, ma*davvero=mdaavvero.

La concatenazione insiemistica di due insiemi di stringhe è l'insieme delle stringhe ottenibili concatenando una stringa del primo insieme con una stringa del secondo; ad esempio: {con,pre,in}/{testo,torto}={contesto,pretesto,intesto,contorto,pretorto,intorto}.

Altri operatori disponibili sono consentiti solo per certi tipi di variabili:

Operatoretipo operando 1 (x)tipo operando 2 (y)tipo risultatocontenuto risultato
!stringainsiemeinsiemey unione {x}
^stringanumericostringaxxx...x (y volte)
&insieme-numericocardinalità di x
&stringa-numericolunghezza di x
%stringastringainsiemeshuffle fra x e y (vedi sotto)

Lo shuffle fra due stringhe è l'insieme delle stringhe ottenibili alternando in qualunque modo possibile le lettere delle due stringhe. Una definizione ricorsiva di questa operazione è la seguente:

w%ε={w}
w%cv=Unione{ {w'c}*(w''%v), per ogni w',w'' tali che w=w'w''}
dove w e v sono stringhe, c è un carattere e ε indica la parola vuota.

Istruzione quit

L'istruzione quit termina l'esecuzione dello StringCalculator.


Esempio di sessione

Quello che segue è un esempio di sessione d'uso dello StringCalculatore: il simbolo $ rappresenta il prompt che indica che il calcolatore è pronto a ricevere un comando. Le righe che non iniziano con il prompt sono gli output emessi dallo StringCalculator.

$ def a str
$ def b str
$ def c str
$ def ai num
$ def bi num
$ def as set
$ assign a #mamma
$ assign b #marmo
$ assign c #asermo
$ def d str
$ op + a b d
$ print d
mammamarmo
$ op - a b d
$ print d
ma
$ op * a b d
$ print d
mmaamrmmao
$ op / a b d
$ print d
 
$ op / a c d
$ print d
 
$ op / b c d
$ print d
rmo
$ op & a ai
$ print ai
5
$ assign bi 3
$ print bi
3
$ def ci num
$ op + ai bi ci
$ print ci
8
$ op * ai bi ci
$ print ci
15
$ op - ai bi ci
$ print ci
2
$ op / ai bi ci
$ print ci
1
$ op ! a as as
$ op ! b as as
$ print as
{mamma,marmo}
$ def bs set
$ op ! c bs bs
$ print bs
{asermo}
$ def cs set
$ op + as bs cs
$ print cs
{mamma,asermo,marmo}
$ op - as bs cs
$ print cs
{mamma,marmo}
$ op - as as cs
$ print cs
{}
$ op * as bs cs
$ print cs
{mammaasermo,marmoasermo}
$ op / as bs cs
$ print cs
{}
$ op ^ a ai c
$ print c
mammamammamammamammamamma
$ op & cs bi
$ print bi
0
$ op & as bi
$ print bi
2
$ print a
mamma
$ print b
marmo
$ assign a #ciao
$assign b #treno
$ op % a b as
$ print as
{trecinoao,tcriaoeno,tcireaono,ctriaeono,tcreniaoo,ctreniaoo,ctriaenoo,ctrienaoo,trecinaoo,
ctiraeono,tcrienoao,ciatroeno,treciaono,tcireanoo,ctrienoao,ctireanoo,trencoiao,ctrieaono,
ctiaroeno,tciaoreno,trciaeono,trencioao,tcreianoo,ctreiaono,trceianoo,trceiaono,ciatreono,
citreaono,trecnoiao,trcienaoo,trenciaoo,tciraenoo,tcrieaono,trcenoiao,ciatoreno,tciraeono,
trecianoo,citraenoo,ctrieanoo,trecniaoo,citraoeno,trcieanoo,ctiarenoo,ctireaono,ctriaoeno,
ctreianoo,trciaoeno,trcieaono,tcreiaono,citrenaoo,ctreinoao,citaroeno,tcreinoao,tcirenaoo,
citaoreno,tciaroeno,tcriaenoo,ctiraenoo,citreanoo,ctiraoeno,trciaenoo,ctreinaoo,ciaotreno,
trceniaoo,tcirenoao,trecnioao,ctrenioao,ciatrenoo,ctirenaoo,tciarenoo,ctiareono,tcriaeono,
tcreinaoo,tciraoeno,ctrenoiao,tciareono,citarenoo,trcienoao,tcrenoiao,citrenoao,trceinoao,
ctiaoreno,tcrieanoo,tcrenioao,citareono,trceinaoo,trenociao,ctirenoao,citraeono,tcrienaoo,trcenioao}
$ quit

Demo

Potete scaricare un demo di soluzione di questo problema. Se volete sperimentare con questo demo, salvate il file .jar ed eseguitelo con il comando:

java -cp stringcalculator.jar it.unimi.mat.proj.stringcalculator.StringCalculator

Nota bene: se il file prog.jar non si trova nelle directory di libreria della JVM, dovrete metterlo nella directory corrente e scrivere

java -cp stringcalculator.jar:prog.jar it.unimi.mat.proj.stringcalculator.StringCalculator

Suggerimenti per la realizzazione

In questa sezione, vogliamo dare alcuni consigli su una possibile realizzazione del progetto. È evidente che si tratta solo di una delle possibili soluzioni del problema, e non è detto né che si tratti della migliore né che sia necessario in alcun senso seguire queste indicazioni.

In primo luogo, due classi di utilità che potrebbero rivelarsi preziose per risolvere il problema (oltre a quelle viste durante il corso) sono java.util.StringTokenizer e la gerarchia java.util.Set. In particolare:

Una possibile soluzione, consiste in primo luogo nel realizzare una classe astratta Variable le cui istanze rappresentano delle variabili. La classe astratta contiene un attributo che rappresenta il nome (unica caratteristica comune a tutte le variabili) e alcuni metodi di utilità: per esempio, il metodo equals (due variabili sono considerate uguali sse hanno lo stesso nome) e un metodo toString(). La classe contiene alcuni metodi astratti: per esempio, un metodo void assign(String x) che assegna alla variabile un valore costante descritto dalla stringa x, oppure un metodo void copy(Variable v) che copia il valore di v nella variabile (dopo eventuali conversioni). Altro metodo presente sarà Object op(Variable v, char operator) che "opera" questa variabile con v, usando l'operatore specificato, e restituisce il valore da assegnare alla variabile destinazione. Tutti questi metodi devono poter sollevare eccezioni (ad esempio, se l'operatore non esiste, o se i tipi delle variabili coinvolti non sono corretti ecc.).

La classe Variable ha tre sottoclassi concrete, NumericVariable, StringVariable e SetVariable per i tre tipi di variabili. Ciascuna di queste classi avrà un attributo per contenere il valore della variabile (si tratterà di un attributo di tipo int, String e Set, rispettivamente).

Una classe eseguibile provvede a leggere i comandi, a interpretarli e a invocare gli opportuni metodi. Questa classe gestisce una sequenza contenente le variabili fino a questo punto dichiarate (istanze di Variable). Ricordate che Sequenza ha un metodo find che, dato un oggetto, ne cerca uno uguale nella sequenza, e lo restituisce; quindi, per cercare una variabile con nome dato, basta che ne creiate una (fittizia) con il nome che vi interessa, e invochiate il metodo find.


Modalità e tempi di consegna