Siamo arrivati all'ultimo articolo di una serie di tre, che hanno avuto come obiettivo lo sviluppo di un interprete di espressioni. Nei due precedenti abbiamo trattato le fasi di analisi lessicale [\[1\]](#BIBLIO1) e analisi sintattica [\[2\]](#BIBLIO1), nonché una descrizione generale del progetto. Lo scopo del presente articolo è l'esame della fase di analisi semantica, generazione, esecuzione e debugging del codice applicate all'interprete di espressioni numeriche descritto negli articoli precedenti ([\[1\]\[2\]](#BIBLIO1)); verranno inoltre descritte alcune classi del package "java.util", che saranno impiegate nel corso dell'implementazione: HashTable, Vector e Stack.

In genere l'analisi semantica compie principalmente la verifica di compatibilità tra operandi (typechecking) e la conversione automatica di tipi (typecasting). Nel nostro caso questa fase si limiterà soltanto alla verifica dell'esistenza delle variabili utilizzate all'interno delle espressioni, visto e considerato che che il tipo numerico è uguale per tutti gli operandi.

La verifica delle variabili utilizza una tabella di simboli, tabella che in altri casi non contiene soltanto gli identificatori di variabili ma altresì gli identificatori di funzione, metodo, oggetto, ecc. Le variabili vengono registrate nella tabella di simboli quando esse sono dichiarate. Oltre all'identificatore di variabile possono venire registrate altre informazioni come ad esempio il tipo, il valore, il dominio, le proprietà (public, private), ecc. Nel nostro caso verrà registrato soltanto l'identificatore e il valore.

La struttura più utilizzata per la tabella di simboli è stata e continua ad essere la Hashtable. Questa struttura permette di registrare degli insiemi (chiave, valore); la ricerca di un valore si esegue attraverso la sua chiave. L'algoritmo di ricerca utilizza come indice il valore numerico (hashcode), ottenuto con l'applicazione alla chiave di una funzione di Hash. Le funzioni possono essere svariate e dipendono dal tipo di chiave; per le stringhe è molto utilizzata la somma dei valori interi dei caratteri. Tra le classi Java del package "java.util" troviamo la classe Hashtable, questa classe contiene due metodi essenziali che descriviamo di seguito.

  • public synchronized Object put(Object key, Object value)

> Inserisce nella tabella l'elemento value, utilizzando la chiave key. Il hashcode per qualsiasi oggetto Java viene calcolato con il metodo native java.lang.Object.hashCode(). Nessuno dei due parametri può essere null. Questo metodo restituisce il precedente valore associato alla chiave key, o riporta null nel caso questa non sia stata mai inserita.

  • public synchronized Object get(Object key)

> Restituisce l'oggetto associato alla chiave contenuta in key. Se la chiave non si trova riporta null.

Ora vediamo l'implementazione della tabella di variabili come estensione della classe Hashtable.

File ExpressionAnalizer.java

72    class VariablesTable extends Hashtable {

73        public VariablesTable() {

74            super();

75        }

77        public VariablesTable( Applet app ) {

78            super();

79    

80            StringTokenizer st;

81            String varname,s = app.getParameter("variables").toLowerCase();

82    

83            if (s==null)

84                app.getAppletContext().showStatus("Avvertenza: No sono state definite variabili");

85            else {

86                st = new StringTokenizer(s);

87                while (st.hasMoreTokens()) {

88                    varname = st.nextToken().toLowerCase();

89                    s = app.getParameter("_"+varname);

90                    if (s==null)

91                        app.getAppletContext().showStatus("Variabile \""+varname+"\" non dichiarata");

92                    else

93                        put(varname,new Double(s));

94                }

95            }

96        }

98        public void toList( List li ) {

99            Enumeration en = keys();

100           String sKey;

101   

102           while (en.hasMoreElements()) {

103               sKey = (String)en.nextElement();

104               li.addItem(sKey+"="+get(sKey));

105           }

106       }

107   }

Il secondo costruttore (riga 77) permette di inserire le variabili, con i loro valori, a partire dai parametri dell'Applet. Nel parametro "variables" (r.81) si specificano tutti gli identificatori delle variabili separati da spazio (es. x y alfa). L'oggetto StringTokenizer st (r.86) è utilizzato per estrarre gli identificatori delle variabili (r.88) dalla stringa del parametro "variables". I valori delle variabili vengono presi dai parametri che hanno il nome della variabile preceduto dal carattere '\' (r.89 - es. \x). L'identificatore della variabile e il suo valore sono aggiunti alla tabella con il metodo Hashtable.put (r.93). La verifica delle variabili e il recupero dei propri valori verranno esposti più avanti quando analizzeremo il codice di analisi sintattico, semantico e di generazione di codice.

Infine abbiamo il metodo toList (r.98) che ci fornisce una rappresentazione della tabella attraverso una istanza della classe java.awt.List contenente tutte le variabili e i suoi valori.

Le espressioni analizzate, in genere, vengono rappresentate con la notazione Postfix. Questo tipo di notazione rispetta la corretta precedenza degli operatori ed elimina le parentesi.

La notazione Postfix per una espressione E può essere induttivamente definita come segue:

1. Se E è una variabile o una costante, la notazione per E è E stessa. 2. Se E è una espressione della forma E<sub>1</sub> op E<sub>2</sub>, dove op è un operatore binario, la notazione per E è E<sub>1</sub>' E<sub>2</sub>' op, dove E<sub>1</sub>' e E<sub>2</sub>' sono le notazioni postfix per E<sub>1</sub> e E<sub>2</sub> rispettivamente. 3. Se E è una espressione della forma op E<sub>1</sub>, dove op è un operatore unitario, la notazione per E è E<sub>1</sub>' op, dove E<sub>1</sub>' è la notazione postfix per E<sub>1</sub>. 4. Se E è una espressione della forma ( E<sub>1</sub> ), la notazione per E è E<sub>1</sub>.

Se prendiamo come esempio la espressione -2+x\(y-7)la notazione postfix è 2-xy7-\+ (il primo operatore '-' è l'operatore di negazione unitaria).

L'esecuzione di una espressione registrata in questo modo è abbastanza semplice e ha bisogno soltanto di uno stack per registrare i valori temporanei delle operazioni. Uno stack è una struttura di tipo LIFO (Last In First Out), vale a dire una struttura dove l'ultimo elemento inserito diventa il primo elemento da prelevare. L'algoritmo di esecuzione consiste nel prelevare uno ad uno gli elementi della notazione ed eseguire le seguenti diverse operazioni secondo il tipo di elemento.

1. Se l'elemento è un numero, inserire tale numero nello stack. 2. Se l'elemento è una variabile, prelevare il suo valore e inserirlo nello stack. 3. Se l'elemento è un operatore unitario, estrarre l'ultimo valore dello stack, applicare l'operatore e rimettere il risultato nello stack. 4. Se l'elemento è un operatore binario, estrarre l'ultimo e il penultimo valore dallo stack, applicare l'operatore tra i due e rimettere il risultato nello stack.

Al termine della notazione lo stack contiene come unico elemento il risultato dellespressione. Per mostrare il funzionamento di quest'algoritmo prendiamo come esempio lespressione in notazione postfix ottenuta precedentemente : 2-xy7-\*+ con i valori x=2 e y=5.

| Passo | Operazione | Notazione rimanente | Stack | |---:|---|---|---| | 1 | | 2 - x y 7 - + | | | 2 | 2 | - x y 7 - + | 2 | | 3 | - 2 | x y 7 - + | -2 | | 4 | x | y 7 - + | 2, -2 | | 5 | y | 7 - + | 5, 2, -2 | | 6 | 7 | - + | 7, 5, 2, -2 | | 7 | 5 - 7 | + | -2, 2, -2 | | 8 | 2 -2 | + | -4, -2 | | 9 | -4 + -2 | | -6 |

La classe Java che più si adatta a rappresentare la notazione Postfix è la classe java.util.Vector. Questa classe ha il vantaggio della ridimensionabilità dinamica automatica, caratteristica questa che non è disponibili con gli array. Utilizzeremo come elementi del Vector i medesimi oggetti tokens prodotti dall'analisi lessicale [\[1\]](#BIBLIO1). Vediamo di seguito la dichiarazione della classe ExprCode che rappresenterà il codice come notazione Postfix.

File ExpressionAnalizer.java

40    class ExprCode extends Vector {

42        public ExprCode() {

43            super();

44        }

46        public void add( GenericToken o ) {

47            addElement(o);

48        }

50        public GenericToken peek() throws ExprCodeError {

51            GenericToken o;

52    

53            try {

54                o = (GenericToken)firstElement();

55                removeElementAt(0);

56            }

57            catch (NoSuchElementException e) throw new ExprCodeError();

58            catch (ArrayIndexOutOfBoundsException e) throw new ExprCodeError();

59    

60            return o;

61        }

63        public void toList( List li ) {

64            int i;

65    

66            for (i=0;i<size();i++)

67                li.addItem(elementAt(i).toString());

68        }

70    }

Sulla base dell'algoritmo di esecuzione spiegato in precedenza, e la generazione lineare del codice, notiamo che le uniche operazioni necessarie per gestire il codice sono le due seguenti:

  • add (r.46): Aggiunge un token alla fine della notazione utilizzando il metodo di Vector:addElement (r.47): Aggiunge un oggetto alla fine della struttura.

> Sarà utilizzato in fase di generazione del codice.

  • peek (r.50): Preleva e rimuove il primo token della notazione utilizzando la combinazione dei due metodi di

Vector:firstElement (r.54): Restituisce il primo elemento della struttura.

RemoveElementAt (r.55): Rimuove l'elemento che si trova nella posizione specificata come parametro. Nel nostro caso si tratta del primo (0).

Sarà utilizzato in fase di esecuzione e debugging del codice.

Il metodo toList (r.63) ci fornisce una rappresentazione della notazione tramite una istanza della classe java.awt.List contenente tutte le descrizioni dei tokens.

Il tentativo di prelevare un token in un ExprCode vuoto solleva leccezione ExprCodeError (r.57). Questa eccezione è stata definita come una semplice estensione di Exception, e ci servirà a distinguere la provenienza degli errori.

L'analisi semantica e la generazione del codice possono avvenire a partire dall'albero sintattico [\[5\]](#BIBLIO1) o direttamente nei metodi corrispondenti ai non terminali [\[2\]](#BIBLIO1). La scelta, nel nostro caso, è ricaduta sulla seconda possibilità poiché non abbiamo creato l'albero sintattico. Basandoci sulla classe SyntaxAnalizer, implementata nell'articolo precedente [\[2\]](#BIBLIO1), passiamo ora alla definizione della classe ExpressionAnalizer. Questa classe incorporerà all'interno dell'analisi sintattica le istruzioni per svolgere i nuovi compiti.

File ExpressionAnalizer.java

109   class ExpressionAnalizer {

110       static final String factorErr = "Aspetto un numero, una variabile, \'-\' o \'(\'";

111       LexicalAnalizer la;

112       VariablesTable vt;

113       ExprCode ec;

114       OperatorToken lastOp;

115       List sList;

116       int indent=0;

117   

118       public ExpressionAnalizer( InputStream is, List _sList, VariablesTable _vt ) {

119           la = new LexicalAnalizer(is);

120           sList = _sList;

121           vt = _vt;

122           lastOp = null;

123       }

125       void show(String s) {

126           int i;

127           StringBuffer sb = new StringBuffer(indent);

128   

129           for (i=0;i<indent;i++) sb.append(' ');

130   

131           sList.addItem(sb+s);

132       }

134       public ExprCode Parse() throws SemanticError, SyntaxError, LexicalError, IOException {

135           GenericToken gt;

136   

137           ec = new ExprCode();

138   

139           Expression();

140   

141           gt = la.nextToken();

142           if (gt.getType()!=GenericToken.EOE) {

143               throw new SyntaxError("Aspetto un operatore.");

144           } else

145             return ec;

146       }

148       boolean checkVariable( String varname ) {

149           return vt.containsKey(varname);

150       }

152       void Expression() throws SemanticError, SyntaxError, LexicalError, IOException {

153           GenericToken gt;

154   

155           show("<Espressione>");

156           indent++;

157           Term();

158           while (true) {

159               gt = la.nextToken();

160               if (gt.getType()==GenericToken.OPERATOR

161                   && (((OperatorToken)gt).getOp()==OperatorToken.SUB ||

162                       ((OperatorToken)gt).getOp()==OperatorToken.ADD)) {

163                  show(gt.toString());

164                  Term();

165                  ec.add((OperatorToken)gt);

166               } else {

167                   la.pushBack();

168                   break;

169               }

170           }

171           indent--;

172       }

174       void Term() throws SemanticError, SyntaxError, LexicalError, IOException {

175           GenericToken gt;

176   

177           show("<Termine>");

178           indent++;

179           Factor();

180           while (true) {

181               gt = la.nextToken();

182               if (gt.getType()==GenericToken.OPERATOR

183                   && (((OperatorToken)gt).getOp()==OperatorToken.MUL ||

184                       ((OperatorToken)gt).getOp()==OperatorToken.DIV)) {

185                  show(gt.toString());

186                  Factor();

187                  ec.add((OperatorToken)gt);

188               } else {

189                   la.pushBack();

190                   break;

191               }

192           }

193           indent--;

194       }

196       void Factor() throws SemanticError, SyntaxError, LexicalError, IOException {

197           GenericToken gt;

198           OperatorToken ot;

199   

200           show("<Fattore>");

201           indent++;

202           gt = la.nextToken();

203           switch (gt.getType()) {

204               case GenericToken.NUMBER:

205                   show(gt.toString());

206                   ec.add((NumberToken)gt);

207                   break;

208               case GenericToken.VARIABLE:

209                   show(gt.toString());

210                   if (checkVariable(((VariableToken)gt).getName()))

211                       ec.add((VariableToken)gt);

212                   else

213                       throw new SemanticError("Variable \""+((VariableToken)gt).getName()+"\" non trovata");

214                   break;

215               case GenericToken.OPERATOR:

216                   if (((OperatorToken)gt).getOp()==OperatorToken.SUB) {

217                       ot = new OperatorToken(OperatorToken.MINUS);

218                       show(ot.toString());

219                       Factor();

220                       ec.add(ot);

221                       break;

222                  } else

223                       throw new SyntaxError(factorErr);

224               case GenericToken.SYMBOL:

225                   if (((SymbolToken)gt).getSymbol()=='(') {

226                       show(gt.toString());

227                       Expression();

228                       gt = la.nextToken();

229                       if (gt.getType()==GenericToken.SYMBOL

230                           && ((SymbolToken)gt).getSymbol()==')') {

231                           show(gt.toString());

232                           break;

233                       } else

234                           throw new SyntaxError("Aspetto \')\'");

235                   }

236               default:

237                   throw new SyntaxError(factorErr);

238          }

239           indent--;

240       }

242   }

La prima modifica si trova nel costruttore della classe (r.118), al quale viene passata un'istanza di VariablesTable contenente tutte le variabili dichiarate. Nel metodo Parse (r.134) viene creata un'istanza di ExprCode (r.137) dove sarà immesso il codice generato; se il parsing finisce senza errore, il metodo restituisce l'istanza creata (r.145). Ogni volta che si processa un token, esso viene aggiunto al codice seguendo le regole della notazione Postfix (r.165, 187, 206, 211, 220). Quando un token di tipo VariableToken viene processato (r.208) si verifica l'esistenza della variabile col metodo checkVariable (r.148). All'interno di quest'ultimo si controlla, col metodo Hashtable.containsKey (r.149) l'esistenza di una chiave con lo stesso nome della variabile. Il metodo containsKey restituisce true se il parametro "key" si trova della struttura.

Il codice generato dal metodo Parse, se non si verificano degli errori, è pronto per l'esecuzione con la classe ExpressionRunner, che descriveremo di seguito.

File ExpressionRunner.java

15    class ValuesStack extends Stack {

17        public ValuesStack() {

18            super();

19        }

21        public void toList( List li ) {

22            GenericToken gt;

23            Stack aux = new Stack();

24    

25            while (!empty()) {

26                gt = (GenericToken)pop();

27                li.addItem(gt.toString());

28                aux.push(gt);

29            }

30    

31            while (!aux.empty()) {

32                gt = (GenericToken)aux.pop();

33                push(gt);

34            }

35        }

36    }

37    

39    class ExpressionRunner {

40        public ExprCode ec;

41        public ValuesStack ts;

42        VariablesTable vt;

43    

44        public ExpressionRunner( ExprCode _ec, VariablesTable _vt ) {

45            ec = (ExprCode)_ec.clone();

46            ts = new ValuesStack();

47            vt = _vt;

48        }

50        double getVarValue( VariableToken vtok ) throws ExprCodeError {

51            Double v = (Double)vt.get(vtok.getName());

52    

53            if (v==null)

54              throw new ExprCodeError("Variabile \""+vtok.getName()+"\" non trovata");

55            else

56              return v.doubleValue();

57        }

59        public boolean Step() throws ExprCodeError {

60            GenericToken gt;

61            OperatorToken ot;

62            NumberToken n1,n2;

63    

64            gt = ec.peek();

65    

66            switch (gt.getType()) {

67                case GenericToken.NUMBER:   ts.push(gt); break;

68                case GenericToken.VARIABLE: ts.push(new NumberToken(getVarValue((VariableToken)gt))); break;

69                case GenericToken.OPERATOR:

70                    ot = (OperatorToken)gt;

71                    try {

72                        n1 = (NumberToken)ts.pop();

73                        if (ot.getArity()==2)

74                            n2 = (NumberToken)ts.pop();

75                        else

76                            n2 = n1;

77                        try

78                            ts.push(new NumberToken(ot.doOp(n2,n1)));

79                        catch (ArithmeticException e) throw new ExprCodeError("Divisione per zero");

80                    }

81                    catch (EmptyStackException e) throw new ExprCodeError();

82                    break;

83            }

85            return !ec.isEmpty();

87        }

89        public double Execute() throws ExprCodeError {

90            while (Step());

91            return ((NumberToken)ts.pop()).getValue();

92        }

94    }

La classe ValuesStack (r.15) ci servirà come stack temporaneo dei valori nell'esecuzione del codice. L'unico metodo aggiunto, toList (r.21), fornisce una rappresentazione visuale del contenuto dello stack tramite una istanza della classe java.awt.List. Questo metodo utilizza uno stack ausiliare temporaneo (aux - r.23) per risistemare i valori che sono stati estratti ai fini della visualizzazione. Ogni valore estratto (r. 25-29) viene aggiunto alla List li e allo stack ausiliare aux; dopodiché vengono rimessi nello stack originale (r. 31-34). Per inserire i valori vengono impiegati i metodi push e pop, ereditati da java.util.Stack. Il metodo push (r. 28, 33, 67, 68, 78) inserisce un qualsiasi oggetto nello stack; nel nostro interprete questi oggetti saranno di tipo NumberToken. Il metodo pop (r. 26, 32, 72, 74, 91) invece restituisce l'ultimo oggetto inserito.

Nel costruttore della classe ExpressionRunner (r.44), il codice, passato come parametro nella struttura \_ec di tipo ExprCode, viene clonato (r.45) col metodo Vector.clone (interface Clonable). Questa clonazione ha come obiettivo di preservare il contenuto del codice originale, dato che l'algoritmo di esecuzione estrae tutti gli elementi dalla struttura ExprCode.

Il metodo Execute (r.89) esegue ordinatamente tutte le operazioni della notazione ExprCode ec; per la realizzazione di ogni singola operazione viene utilizzato il metodo Step. Arrivati alla fine del codice, il risultato dell'esecuzione viene prelevato dallo stack temporaneo ts.

Il metodo Step (r.59) estrae un token dalla notazione, e in base al tipo esegue una delle operazioni descritte in precedenza. Se prendiamo come riferimento i numeri assegnati nell'elenco delle operazioni, le righe corrispondenti a queste ultime sono:

1. (riga 67) - Inserisce il NumberToken nello stack. 2. (riga 68) - Inserisce un NumberToken creato con il valore della variabile. Il valore viene preso col metodo getVarValue, il quale a sua volta lo ottiene col metodo get della hashtable. Una eccezione di tipo ExprCodeError viene generata nel caso in cui la variabile non venga reperita nella tabella. 3. e 4. (righe 70-81) Sulla base del numero di operandi dell'operatore sono estratti uno o due NumberToken dallo stack temporaneo ts. Il numero di operandi ci viene fornito attraverso il metodo OperatorToken.getArity (r.73). L'esecuzione dell'operazione è svolta dal metodo OperatorToken.doOp (r.78), il cui risultato viene inserito nello stack.

Dopo un'approfondita descrizione delle classi ExpressionAnalizer ed ExpressionRunner, è possibile vederne il suo utilizzo nell'applet che segue. La espressione -2+x\*(y-7), analizzata in precedenza, può servire come un primo esempio di compilazione ed esecuzione.

Di seguito analizzeremo le parti più importanti del codice sorgente di questo applet.

File ex3.java

13    public class ex3 extends java.applet.Applet {

14        static final String DO_BUTTON = "Compila ed esegui";

15        static final String EXEC_BUTTON = "Esegui";

16        static final String STEP_BUTTON = "Passo a passo";

17        List    li,codeList,stackList,varsList;

18        TextField edExpr,edRes;

19        boolean success = false;

20        VariablesTable vt;

21        ExprCode ec;

22        ExpressionRunner er;

23    

24        public void init() {

25            /* Creazione e disposizione dei componenti nell'Applet.

26               I sorgento possono essere scaricati come indicato

27               alla fine dell'articolo */

183   

184           vt = new VariablesTable(this);

185           vt.toList(varsList);

186       }

207       void addError( Exception e ) {

208           li.addItem(e.toString());

209           li.select(li.countItems()-1);

210       }

212       void doCompile() {

213           ExpressionAnalizer ea;

214   

215           success = false;

216           try {

217               ea = new ExpressionAnalizer(new StringBufferInputStream(edExpr.getText()),li,vt);

218               li.clear();

219               codeList.clear();

220               stackList.clear();

221               edRes.setText("");

222               ec = ea.Parse();

223               ec.toList(codeList);

224               success = true;

225           }

226           catch (IOException e) getAppletContext().showStatus(e.toString());

227           catch (LexicalError e) addError(e);

228           catch (SyntaxError e) addError(e);

229           catch (SemanticError e) addError(e);

230           if (success) {

231               getAppletContext().showStatus("Operazione completata con successo");

232               doExecute();

233           } else

234               getAppletContext().showStatus("Operazione interrotta per errore");

235       }

237       void doExecute() {

238   

239           if (success) {

240               try {

241                   er = new ExpressionRunner(ec,vt);

242                   stackList.clear();

243                   codeList.clear();

244                   ec.toList(codeList);

245                   edRes.setText(String.valueOf(er.Execute()));

246               }

247               catch (ExprCodeError e) {

248                   getAppletContext().showStatus(e.toString());

249               }

250               er = null;

251           }

252       }

254       void doStep() {

255   

256           if (success) {

257               try {

258                   if (er==null) {

259                       er = new ExpressionRunner(ec,vt);

260                       edRes.setText("");

261                   }

262                   stackList.clear();

263                   codeList.clear();

264                   if (er.Step()) {

265                       er.ts.toList(stackList);

266                       er.ec.toList(codeList);

267                   } else {

268                       ec.toList(codeList);

269                       edRes.setText(String.valueOf(((NumberToken)er.ts.pop()).getValue()));

270                       er = null;

271                   }

272               }

273               catch (ExprCodeError e) {

274                   getAppletContext().showStatus(e.toString());

275                   er = null;

276                   codeList.clear();

277                   ec.toList(codeList);

278               }

279           }

280       }

282       public boolean handleEvent(Event evt) {

284           switch (evt.id) {

285               case Event.ACTION_EVENT: {

286                   if (DO_BUTTON.equals(evt.arg)) doCompile();

287                   else if (EXEC_BUTTON.equals(evt.arg)) doExecute();

288                   else if (STEP_BUTTON.equals(evt.arg)) doStep();

289                   break;

290               }

291               case Event.KEY_PRESS: {

292                   if (evt.target instanceof TextField &&

293                       evt.key == '\n' ) doCompile();

294                   break;

295               }

296           }

297           return false;

298       }

299   }

Il costruttore dell'applet (r.24) crea e dispone i diversi componenti (non inseriti in questo listato per ragioni di spazio) e crea una istanza della classe VariablesTable (r.184) a partire dai parametri dello stesso applet. Nella riga 185 vengono inseriti all'interno della List varList (casella di elenco "Variabili") gli identificatori e i valori della VariablesTable vt.

Nel metodo handleEvent (r.282) vengono catturati gli eventi di pressione del tasto Invio e del pulsante "Compila ed esegui" per eseguire tutta lanalisi, la generazione e l'esecuzione del codice nel metodo doCompile (r.61). Il debugging del codice può essere fatto utilizzando il pulsante "Passo a passo", il quale esegue una singola istruzione del codice e aggiorna le caselle "Valori temporanei" e "Codice". Il pulsante "Esegui" può essere utile nel caso si voglia arrivare alla fine nella modalità passo a passo. La pressione dei tasti "Esegui" e "Passo a passo" eseguono rispettivamente i metodi doExecute e doStep.

Il metodo doCompile (r.217) crea una istanza della classe ExpressionAnalizer usando come InputStream il contenuto della casella "Espressione"; gli altri parametri sono la List li (casella di elenco "Tracce semantiche, sintattiche e lessicali") per visualizzare i risultati dell'analisi sintattica e semantica, e la tabella delle variabili vt. Nelle righe seguenti (r. 218-221) viene pulito il contenuto delle caselle "Tracce semantiche, sintattiche e lessicali", "Valori temporanei", "Codice" e "Risultato". La chiamata al metodo Parse (r.222) esegue la compilazione e restituisce il codice generato. La rappresentazione visuale del codice viene fatta attraverso il metodo toList della istanza ExprCode ea. La visualizzazione degli errori (r.227-229) avviene tramite il metodo addError (r.207), il quale aggiunge (r.208) il testo dell'errore nella casella "Tracce semantiche, sintattiche e lessicali" selezionando (r.209) la riga inserita per garantire la sua visibilità. Le istruzioni finali del metodo doCompile (r.230-234) notificano, nella barra di stato, il successo delloperazione. In caso di successo positivo, il codice prodotto viene eseguito mediante il metodo doExecute.

Il metodo doExecute (r.237) crea una istanza della classe ExpressionRunner con il codice generato (ec) e la tabella di variabili (vt), e lo esegue con il metodo Execute (r.245). Il valore risultante dall'esecuzione è inserito della casella "Risultato". Se si verifica un errore, il testo viene visualizzato nella barra di status (r.248).

Il metodo doStep (r. 254) crea, se non esiste (r.258), una istanza della classe ExpressionRunner con il codice generato (ec) e la tabella di variabili (vt), ed esegue una singola istruzione del codice ec con il metodo Step (r.264). Quest'ultimo restituisce true se non viene raggiunta la fine del codice, quindi vengono aggiornate le caselle "Codice" e "Valori temporanei". L'ultimo valore dello stack temporaneo, quando si è arrivati all'ultima istruzione, viene messo nella casella "Risultato" e il codice originale reinserito nella casella "Codice". Se si verifica un errore, il testo verrà visualizzato nella barra di status (r.274) e il codice originale reinserito nella casella "Codice" (r.277).

Benché le tematiche di teoria di linguaggi e tecniche di compilazione siano così vaste, abbiamo cercato di fornire le basi per lo sviluppo di applicazioni più complesse in questo campo. Una di queste applicazioni potrebbe essere lo sviluppo di un motore di ricerca; il quale interpreti, in query SQL, le espressioni d'interrogazione inserite dall'utente. Questi tre articoli sono serviti inoltre a dimostrare nella pratica l'utilizzo delle classi Java StreamTokenizer, HashTable, Vector e Stack. In futuro potrebbero essere affrontate altre fasi come il controllo di tipo (type checking) e l'ottimizzazione del codice, tutto ciò "religiosamente" implementato in Java.

Download

I sorgenti esposti possono essere scaricati ai seguenti indirizzi:

Bibliografia

  • 1. *"Interprete di espressioni: analisi lessicale con lausilio della classe java.io.StreamTokenizer"*, Rafael Andrés Marín de la Cruz, MokaByte Numero 7 Aprile 1997. URL:
  • [http://www.programmers.net/Riviste/moka/0497/interpre.htm](../../../1997/04/interpre.htm)
  • 2. *"Interprete di espressioni: analisi sintattica utilizzando un algoritmo di discesa ricorsiva"*, Rafael Andrés Marín de la Cruz, MokaByte Numero 8 Maggio 1997. URL:
  • [http://www.programmers.net/Riviste/moka/0597/interpre.htm](../../../1997/05/interprete.htm)

3. Documentazione API JDK 1.0.2. URL: <http://java.sun.com/products/jdk/1.0.2/api> 4. "The theory of parsing, translation, and compiling", A.V. Aho and J.D. Ullman, 1973; ISBN 0-13-914564-8 5. "Compilers Principles, Techniques and Tools", A.V. Aho, R. Sethi and J.D. Ullman, 1986; ISBN 0-201-10088-6 6. "Peter Nortons Guide to Java Programming", Peter Norton and William Stanek, 1996; ISBN 1-57521-088-6

Rafael Andrés Marín de la Cruz

  • Laureato in Matematica-Cibernetica allUniversità dellAvana (Cuba) nel 1990, da quattro anni vive e lavora a Milano. Con la sua società ha sviluppato software per non vedenti sotto DOS e un tool di installazione per Windows. Svolge inoltre consulenza, in qualità di progettista, nel campo delle telecomunicazioni. Ha programmato un gioco-applet *GeniusMind* valutato
  • ["JARS Top 25%"](http://www.jars.com); la versione italiana può essere vista all'indirizzo <http://www.galactica.it/dialectica/genius/geniusmind.html>.

Il suo sito si trova al seguente indirizzo: <http://www.galactica.it/dialectica>.

Può essere contattato tramite la redazione o direttamente all'indirizzo: <dialectica.sas@galactica.it>