Durante tutta la progettazione si è sempre
avuta particolare attenzione nel creare software modulare. Ci è
stato di notevole aiuto visto che il feedback è stato notevole,
infatti era la nostra prima esperienza nella costruzione di un interprete.
Un passo più avanti della modularità c'è l'estensibilità.
Per chiarirne la differenza:
Modularità
La modularità è la proprietà
che permette ad un progettista di slegare le varie parti di software in
piccoli pacchetti o moduli, ognuno relativamente indipendente dagli altri.
Questo facilita l'incrementabilità e la modificabilità del
codice, oltre la testabilità.
Estensibilità
L'estensibilità è la proprietà
che permette ad un utente diverso dal progettista di modificare il comportamento
del programma senza dover mettere mano ai codici già scritti. Magari
aggiungendo nuove funzionalità non previste o non realizzate da
chi ha progettato il software.
Di solito un utente non ha a disposizione il codice
di un software ed anche se lo avesse a disposizione non vorrebbe perdere
tempo per studiarlo e correggerlo.
Prendiamo per esempio proprio il sistema java. La Sun è stata sempre attenta nel fare componenti estensibili. Se si vuole costruire un editor di testo non si modificano i codici di java, ma si deriva una nuova classe dalla JTextArea e si aggiungono le nuove funzionalità. Tutto ciò è possibile anche senza possedere i codici JTextArea.java.
Nello stesso modo se vogliamo aggiungere una nuova
operazione o comando ai nostri interpreti è sufficiente derivare
qualche classe e scrivere il codice per la funzione aggiunta.
Anche l'interfaccia grafica è stata progettata
per essere riutilizzata, così come l'help dei comandi.
LispPer vedere come è stato possibile ottenere l'estensibilità si possono leggere gli approfonimenti sull'implementazione relativa ad ogni parte: Lisp, Prolog.
Aggiungere un nuovo comando all'interprete lisp per shell DOS è veramente banale: basta derivare la classe EvalSexpVisitor in cui si inseriscono il costruttore e la funzione che contiene il codice per il comando. Nel costruttore si aggiungerà il comando alla lista dei comandi primitivi tramite il metodo setPrimitive del parser.
Prolog
Per aggiungere un nuovo comando all'interprete prolog per shell DOS fondamentalmente ci si comporta come per l'interprete lisp: al posto dell'EvalSexpVisitor avremo l'EngineSexpVisitor.
Sono però necessari due passi aggiuntivi:Interfaccia
- bisogna prima creare la classe corrispondente al comando derivandola da una classe della gerarchia.
- se il comando è un operatore aritmetico deve essere trattato dall'EvalVisitror e nell'EngineSexpVisitor deve essere lanciata una eccezione (nel caso si cerchi di applicare la risoluzione a questo comando)
Forse è la parte più facile:
- Si crea una classe contenente il main e dall'interno del main si eseguono i punti seguenti.
- Si crea una istanza applet della classe InterpreterApplet.
- Si utilizzano i metodi setExtendedLisp e setExtendedProlog dell'applet per passargli le classi dei visitor estesi (lisp, prolog o entrambi).
- Si utilizzano i metodi addLisp e addProlog dell'applet se si vuole anche aggiungere l'help dei nuovi comandi.
- Si crea una nuova istanza frame della classe MyFrame passandogli l'applet.
- Si invoca il metodo setVisible del frame con parametro true.
Esempio
Come esempio abbiamo esteso entrambi gli interpreti
con il comando sqr (quadrato di un numero). In prolog abbiamo creato un
nuovo operatore postfisso associativo a sinistra. Di seguito è riportato
il codice essenziale (a parte gli import e la definizione del package).
Lisp
public class ExtendedLispEvalSexpVisitor extends
EvalSexpVisitor {
public ExtendedLispEvalSexpVisitor(InputBuffer
In,OutputBuffer Out,Parser parser) {
super(In,Out,parser);
parser.setPrimitive("sqr");
// Aggiungo la primitiva al set dei comandi
}
// Qui abbiamo l'implementazione
del comando
protected boolean applyPrimitive(PrimitiveToken
prim, Sexp argList) throws InterpreterException {
if
(super.applyPrimitive(prim,argList)) return true;
if
(prim.equals("sqr")) {
if (checkEvalArgs(argList)!=1) ErrorMessage(prim,argList);
double val = stack.popNum().getVal();
stack.pushSexp(new NumToken(val*val));
return true;
}
return
false;
}
}
Prolog
// La classe dell'oggetto
public class SqrSexp extends PostfixExprSexp
implements Expr {
final static OperationToken
sqr = new OperationToken("sqr");
public SqrSexp(Sexp arg) {
super(sqr,new
ArgsSexp(arg,nil));
priority
= 300;
Separators
= Spaces; // Per stampare uno spazio tra l'operatore
e l'argomento
}
public void accept(SexpVisitor
e) throws InterpreterException {
if
(e instanceof ExtendedPrologEvalVisitor) ((ExtendedPrologEvalVisitor)e).visit(this);
else
((StructureSexpVisitor)e).visit(this);
}
}
// Il motore di ricerca
public class ExtendedPrologEngineVisitor extends
EngineSexpVisitor {
public ExtendedPrologEngineVisitor(InputBuffer
In,OutputBuffer Out,NewParser parser,Class extEval) throws InterpreterException
{
super(In,Out,parser,extEval);
//
L'istruzione seguente aggiunge il comando al set delle primitive
try
{ parser.setPrimitive(new OpSexp("sqr",300,OpSexp.OP_YF,Class.forName("Interpreter.Extension.SqrSexp")));
}
catch(Exception
e) { throw new InterpreterException("Prolog: " + e); }
}
protected void visit(SqrSexp
sqr) throws InterpreterException { // Lancia
semplicemente una eccezione
throw
new InterpreterException("EngineSexpVisitor: arithmetic expression not
allowed here.",sqr);
}
}
// Il valutatore di espressioni
public class ExtendedPrologEvalVisitor extends
EvalVisitor {
public ExtendedPrologEvalVisitor(SexpHashtable
subs,SexpStack EngineStack) {
super(subs,EngineStack);
}
// Qui abbiamo l'implementazione
del comando
protected void visit(SqrSexp
sqr) throws InterpreterException {
sqr.first().accept(this);
double
val = stack.popNum().getVal();
stack.pushSexp(new
NumToken(val*val));
}
}
Main
public class Main {
public Main() {
InterpreterApplet
applet = new InterpreterApplet();
try
{ // Parte lisp
Class lispEvalVisitor = Class.forName("Interpreter.Extension.ExtendedLispEvalSexpVisitor");
applet.setExtendedLisp(lispEvalVisitor);
applet.addLisp("sqr","(sqr <EXPR>)\n\nCalcola il quadrato dell'argomento.");
//
Help
}
catch(Exception e) { System.out.println("Main: error while setting lisp
extension."); }
try
{ // Parte prolog
Class prologEngineVisitor = Class.forName("Interpreter.Extension.ExtendedPrologEngineVisitor");
Class prologEvalVisitor = Class.forName("Interpreter.Extension.ExtendedPrologEvalVisitor");
applet.setExtendedProlog(prologEngineVisitor,prologEvalVisitor);
applet.addProlog("sqr","Postfix: sqr <EXPR>.\n\nCalcola il quadrato
dell'argomento."); // Help
}
catch(Exception e) { System.out.println("Main: error while setting prolog
extension."); }
MyFrame
frame = new MyFrame(applet);
frame.setVisible(true);
}
//
funzione main
public static void main(String[]
args) { new Main(); }
}