Estensione dell'interprete


    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à.

    Modularità significa ridurre un problema di grosse dimensioni in tanti sotto problemi più facilmente gestibili.
    La metodologia di progettazione ad oggetti è nata per facilitare la modularità ed introdurre l'information hiding.
 

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.
 

Passi necessari

Lisp
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:
  1. bisogna prima creare la classe corrispondente al comando derivandola da una classe della gerarchia.
  2. 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)
Interfaccia
Forse è la parte più facile:
    Per vedere come è stato possibile ottenere l'estensibilità si possono leggere gli approfonimenti sull'implementazione relativa ad ogni parte: Lisp, Prolog.
    Tutto si risolve in poche righe di codice scritte.
 

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(); }
}