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

Analisi lessicale

    Per quel che riguarda l'AL molto è già stato detto nella sezione dedicata all'interprete Scheme. La soluzione è la stessa, ovvero l'AL è al solito composto da un lexer detto PrologLexer (che costituisce una sottoclasse della generica classe Lexer definita nel package Interpreter.Sexp.Utils.Lexer) e dai soliti quattro set (PrimitiveSet, OperationalSet, RelationalSet e SpecialSet). L'AL Prolog è quindi organizzato esattamente allo stesso modo dell'AL Scheme.

    Vi sono però due osservazioni da fare.

    La prima osservazione da fare riguarda il fatto che in Prolog le parentesi tonde hanno una duplice funzione: se si scrive "append(L1,L2,R)" si intende indicare il termine append/3 e quindi le parentesi tonde racchiudono i suoi argomenti, mentre se si scrive "3*(5-2)" le parentesi servono a far eseguire la sottrazione prima della moltiplicazione, ovvero intervengono sulla precedenza fra gli operatori. Tuttavia in Prolog un operatore è anche un funtore, quindi l'espressione aritmetica precedente può essere anche scritta come *(3, -(5, 2)), ovvero termine con funtore '*' (mettiamo gli apici solo per evidenziare che si tratta di funtori) e due argomenti, di cui il primo è il numero 3 ed il secondo è il termine con funtore '-' e due argomenti dati dai numeri 5 e 2. Allora nasce un problema nel caso di operatori prefissi. Per capire il problema chiediamoci come deve interpretare l'interprete il seguente codice:

:-(a,b).
    Ci sono due possibilità, la prima è di interpretarlo come un termine di tipo ':-'/2 con argomenti a e b, la seconda è di intenderlo come termine ':-'/1 con argomento l'and di a e b. Nel primo caso quella scrittura rappresenta una clausola con antecedente "a" e conseguente "b", nel secondo rappresenta una query con body l'and di "a" e "b". L'espressione è in effetti ambigua e quindi può portare a degli errori. La soluzione adottata in SICStus (http://www.sics.se/sicstus.html) è stata quella di imporre una restrizione sintattica per superare l'ambiguità. Si tratta semplicemente di imporre che allorché si voglia scrivere un termine nella forma "funtore" + "(" + "elenco argomenti" + ")" non si devono interporre dei caratteri di separazione (come ad esempio lo space) fra il nome del funtore e la "(". In questo modo l'esempio sopra deve essere inteso come termine di tipo :-/2, poiché l'aperta parentesi non è separata dal funtore. Se si scrive quindi:
    :-(a,b),c.
l'interprete restituisce errore perché quella scrittura corrisponde all'and fra un il termine :-(a,b) e c, e questo è sbagliato perché il termine :- corrisponde all'operatore ':-'/2 che ha precedenza tale da non poter costituire un argomento dell'operatore ','. La scrittura corretta è:
    :- (a,b),c.
che corrisponde effettivamente alla query con body dato dall'and fra ','(a,b) e c.

    Nel nostro caso non era semplice ottenere la stessa cosa. Questo fatto è ancora una volta imputabile allo StreamTokenizer di Java. Infatti il vantaggio di usare lo StreamTokenizer è che si può delegare ad esso il compito di costruire i token indicandogli semplicemente quali sono i separatori (space, new line, tab...), ma così facendo non è più possibile sapere se due token successivi sono o no intercalati da qualche separatore. In particolare non è possibile sapere se una stringa ed un'aperta parentesi tonda sono separati o no. L'unico modo per saperlo è di farsi restituire dallo StreamTokenizer anche i separatori e trattarli nel proprio lexer. Ciò che si dovrebbe fare è di definire una sottoclasse dello StreamTokenizer ed in essa aggiungere un flag, che si potrebbe chiamare nospaces, e ridefinire il metodo next(). All'interno di next() si dovrebbe inizialmente resettare flag, poi effettuare un ciclo in cui per prima cosa si chiama super.next() e se viene restituito un separatore allora lo si "scarta" e si setta il flag. Il ciclo terminerebbe allorché super.next() restituisce un token che non sia un separatore. A questo punto si può restituire quel token e terminare. Quel flag sarà settato se sono stati incontrati dei separatori prima del token, oppure non settato in caso contrario. Se si vuole a questo punto sapere se il token corrente (questo potrebbe essere appunto l'aperta parentesi) era o no preceduto da separatori è sufficiente controllare quel flag. La scomodità di questa soluzione è che si deve ridefinire la classe StreamTokenizer e riscrivere la gestione degli spazi solo per poter avere quel flag. La soluzione migliore sarebbe avere quel flag direttamente nello StreamTokenizer.

    La seconda osservazione da fare riguardo all'AL è legata al riconoscimento di token come ":-", oppure "=:=", etc. Si tratta di tokens costruiti unendo caratteri particolari che possono aver significato anche singolarmente. Per riconoscerli l'ideale sarebbe che lo StreamTokenizer mettesse a disposizione un metodo atom(String) tramite cui fosse possibile segnalargli quali sono gli atomi particolari che deve riconoscere.

Per questi motivi e per il problema legato al riconoscimento dei numeri (vedi sezione AL nella pagina dedicata all'implementazione dell'interprete Scheme) abbiamo pensato di definire una classe chiamata StreamTokenizerWithAtom, il cui codice è esattamente il codice della classe StreamTokenizer Java modificato aggiungendogli tutte le funzionalità che ci servivano, ovvero:

    Invece di utilizzare lo StreamTokenizer Java abbiamo usato questa classe.
    Abbiamo poi inviato il codice e le motivazioni dei cambiamenti alla Sun Microsystems nella speranza che queste modifiche vengano in futuro apportate direttamente al codice dello StreamTokenizer Java. Purtroppo però ci è stato risposto che tali modifiche, di cui pur era già sentita l'esigenza, non possono essere realizzate per questioni di compatibilità con le versioni precedenti di Java.