Implementazione dell'interprete Prolog
Introduzione - Analisi lessicale - Analisi sintattica - Valutazione - Estensione - Demo

Introduzione

    Nell'implementare l'interprete Scheme si era deciso di costruire tutte le espressioni come ConsSexp e poi distinguere il tipo di operatore in base alla testa della ConsSexp, testa che costituiva l'operatore stesso. Nel caso Prolog si è pensato di seguire l'altra via, ovvero quella di definire una sottoclasse di ConsSexp per ogni operatore. Si è costruita quindi un'intera gerarchia sottostante la classe ConsSexp. Questa gerarchia è sostanzialmente basata su quattro rami:

    Da quanto scritto risulta più corretto dire che in effetti la radice della gerarchia dedicata al Prolog è la classe TermSexp più che ConsSexp. Infatti in Prolog ogni "istruzione" è un termine, tuttavia per facilitare la scrittura del codice si introduce la possibilità di costruire tramite operatori i termini corrispondenti ad operazioni predefinite. Per capirci basti osservare che l'or fra due termini atomici a e b non è altro che il termine di funtore ";" applicato a 2 argomenti di nome a e b, ovvero è il termine ;(a,b), però per comodità è data la possibilità di utilizzare la scrittura equivalente a;b, in cui il ; è usato come operatore infisso. Lo stesso discorso vale per tutti gli operatori, compreso il ":-" con cui si avvia la risoluzione Prolog o si definiscono clausole. Nel nostro caso abbiamo tenuti distinti i termini corrispondenti agli operatori dagli altri. Gli altri sono tutti quelli costruiti scrivendo una stringa seguita da una parentesi aperta (senza separatori in mezzo), dall'elenco degli argomenti separati con virgole e da una parentesi chiusa. Fra questi ve ne sono alcuni predefiniti, come clause/2 (ovvero clause a due argomenti), functor/2, atom/1, e molti altri. Per ognuno dei termini predefiniti è stata definita una classe dedicata, costruita come sottoclasse di TermSexp. Ogni termine definito dall'utente nasce come TermSexp.
    La scelta di usare una gerarchia dettagliata (e numerosa) è stata fatta appositamente per evidenziare le differenze con il caso Scheme in cui la gerarchia era assolutamente "spartana" in quanto limitata a quattro classi ed in cui si faceva uso esclusivo della ConsSexp per tutte le espressioni non atomiche. L'utilizzo di una classe per ogni tipo di operazione consente in fase di costruzione del Visitor di non dover utilizzare degli if per stabilire il tipo di operazione. Infatti ogni operazione corrisponde ad una classe e quindi all'invocazione del metodo accept() viene automaticamente chiamato (tramite il meccanismo del double dispatch) il metodo che si occupa di quell'operazione.
    Inizialmente si era pensato di costruire un interprete Prolog semplificato, in cui fossero disponibili pochi operatori (solo quelli essenziali) e pochi predicati predefiniti. In questo modo si sarebbero costruite poche classi e quindi l'utilizzo della gerarchia con radice TermSexp sarebbe stato semplice. Tuttavia in corso d'opera si è deciso di aggiungere via via nuove funzionalità finendo per inserire quasi tutti gli operatori e quasi tutti i termini predefiniti, così da ottenere un interprete Prolog quasi completo. La conseguenza è stata quella di avere una gerarchia piuttosto numerosa che ha provocato qualche scomodità nell'utilizzo del Visitor. La scomodità si manifesta allorché si voglia aggiungere un'operatore, poiché in quel caso oltre che creare la nuova classe si deve aggiungere la dichiarazione del metodo visit() nel Visitor astratto e la definizione dello stesso metodo in ogni Visitor concreto, mentre nel caso Scheme era sufficiente aggiungere un semplice if nel metodo applyPrimitive().