Linguaggi di Programmazione: Paradigmi di Programmazione.
Il linguaggio ProLOGO.

Luca Cinti - Lorenzo Masetti


Home page ProLOGO

Indice

Introduzione

Il progetto riguarda la costruzione di un interprete, scritto in Prolog, per un linguaggio LOGO-like. Il linguaggio da noi implementato è un sottoinsieme del LOGO, con la particolarità che i comandi possono essere sia in inglese che in italiano.

Il linguaggio LOGO

Il LOGO è un linguaggio di programmazione pensato per essere usato da principianti, ed in particolar modo da bambini e ragazzi. Infatti il LOGO permette di effettuare le tipiche operazioni consentite da altri linguaggi, ma fornisce in più un insieme di comandi chiamati Turtle Graphics (Grafica della tartaruga) finalizzati al disegno di figure geometriche realizzate comandando opportunamente un cursore, detto tartaruga, sullo schermo.

La geometria della tartaruga si differenzia dal modo tradizionale di disegnare al computer perché descrive i percorsi "dall'interno" piuttosto che "dall'esterno" o "dall'alto". Ad esempio dicendo "gira a destra" non si esprime una direzione assoluta, ma una direzione relativa all'orientamento corrente della tartaruga, dicendo "vai avanti di 10 passi" ci si riferisce alla posizione e alla direzione correnti. Questo approccio ha molti vantaggi, ad esempio disegnare un quadrato inclinato è facile come disegnare un quadrato con i lati orizzontali e verticali: la sequenza delle istruzioni sarà la stessa, cambierà solo la posizione iniziale della tartaruga. Un altro vantaggio è di carattere pedagogico, piuttosto che computazionale, perché questo modo di disegnare è consono all'esperienza del ragazzo, perchè è analogo al modo di muoversi nello spazio.

Il LOGO è stato utilizzato con vantaggio nelle scuole elementari e medie inferiori perché permette anche a un pincipiante di ottenere subito risultati visibili. Dal punto di vista didattico, Il LOGO insegnava un metodo di programmazione più strutturato rispetto al più famoso BASIC in cui anche i programmi più banali costringono ad un uso massiccio del goto.

Il LOGO incoraggia la programmazione modulare con uso intensivo di procedure e offre molta estendibilità per gli utenti più esperti. Al programmatore principiante vengono proposte in modo naturale le basi della programmazione per contratto. Ad esempio in un manuale di LOGO per la scuola media, [3], si legge: "È bene abituarsi scrivere i programmi in modo tale che alla fine la tartaruga risulti rivolta verso l'alto"

Seymour Papert

Il professor Seymour Papert. Il linguaggio LOGO è anche stato utilizzato per far muovere piccoli robot costruiti con il LEGO (LEGO/LOGO).

Il linguaggio LOGO fu ideato e realizzato negli anni '60 dal professor Seymour Papert del Massachusetts Institute of Tecnology (cfr. [1]). Ereditava le tecniche di calcolo simbolico del Lisp, dal quale riprende parte della sintassi ed il modo di gestire le liste.

In origine il LOGO fu utilizzato per muovere un semplice robot, al quale si potevano dare comandi del tipo FORWARD 50 per andare avanti di 50 passi o RIGHT 90 per girare a destra di 90 gradi. Il primo di questi robot aveva una corazza simile a quella di una tartaruga, da cui il nome del cursore (che nelle prime versioni su schermo era semplicemente un piccolo triangolo). Con lo sviluppo dei monitor il linguaggio LOGO divenne più accessibile e negli anni '80 ne vennero realizzate versioni per personal computer, ad esempio l'Apple II e il Commodore 64, utilizzate a scopi didattici, spesso per il laboratorio di geometria.

Al giorno d'oggi esistono molte versioni del linguaggio LOGO con caratteristiche anche sofisticate (gestione della grafica tridimensionale, possibilità di comandare più tartarughe contemporaneamente). All'estremo opposto si pone invece il Berkley LOGO scritto da Brian Harvey, con possibilità grafiche molto limitate ma con l'ambizione di trasformare il LOGO in un linguaggio di programmazione "serio". Il manuale per l'utente del Berkley LOGO, [2], presenta la semantica dei comandi LOGO ed è stato un punto di riferimento per la nostra implementazione.

Negli anni '80 fu realizzata anche una versione del LOGO con i comandi in italiano (descritta sommariamente in [4]) alla quale ci siamo ispirati per i nomi dei comandi in italiano nella nostra versione bilingue. Una delle caratteristiche del LOGO è che ogni comando ha una forma lunga e una abbreviata. Estendendo questa idea abbiamo dato per ogni comando la forma inglese e italiana, abbreviata e non.

Il linguaggio da noi implementato è un sottoinsieme del linguaggio LOGO, e realizza tutti i comandi fondamentali. Non abbiamo implementato le procedure e, dei vari tipi di dato forniti dal LOGO, abbiamo realizzato gli interi, le parole (word) e le liste, ma non sono disponibili i numeri reali. Altre differenze rispetto al LOGO classico sono la presenza di un istruzione per leggere un intero e la possibilità di specificare i colori con i codici RGB esadecimali.

Nave

Uno screenshot del ProLOGO.

Scelte di progetto

Per la realizzazione del progetto abbiamo utilizzato le libreria grafiche XPCE, che forniscono un framework ad oggetti facilmente integrabile in Prolog. L'interfaccia grafica consiste in una casella di testo per inserire i comandi, mentre l'output è diviso, secondo la tradizione del LOGO, in due aree dello schermo. Quella inferiore presenta l'output testuale, quella superiore l'output di tipo grafico.



L'interfaccia grafica del ProLOGO

L'interfaccia grafica permette di eseguire un comando o di aprire ed eseguire uno script memorizzato in un file.

Abbiamo realizzato la memoria come una lista di coppie (variabile, valore). Lo stato della memoria, che chiamiamo ambiente, viene mantenuto dopo l'esecuzione di ogni comando andando a modificare il contesto del comando successivo. A questo scopo l'interprete crea inizialmente un ambiente vuoto che viene modificato con l'esecuzione di ogni comando. L'ambiente viene passato da un comando all'altro durante l'esecuzione, e alla fine viene salvato asserendo il fatto ambiente(EnvF)

exec(Command,Env,EnvF):-
string_...
...,Env,EnvF),
retractall(ambiente(X)),
assertz(ambiente(EnvF)).

La grammatica dei comandi è realizzata con le DCG Grammars fornite dal Prolog. In pratica i predicati con il separatore -> vengono automaticamente espansi aggiungendo due argomenti che sono il minuendo e il sottraendo di una lista differenza che rappresenta il comando da eseguire. In alcuni casi è stato necessario esplicitare questi due parametri per realizzare i comandi più complessi.

Le varianti dei nomi dei diversi comandi sono realizzate facilmente associando nella grammatica terminali diversi al simbolo che rappresenta il nome del comando (che abbiamo stabilito di chiamare con la forma inglese seguita da un underscore). Ad esempio il seguente frammento di codice Prolog associa a fd_ le quattro forme possibili del comando

fd_ --> [forward].
fd_ --> [fd].
fd_ --> [a].
fd_ --> [avanti].

Grafica

Per memorizzare lo stato della tartaruga abbiamo definito una classe XPCE, chiamata turtle.


/*** CLASSE PER LA GESTIONE DELLA TARTARUGA ***/
:- pce_begin_class(turtle,class).
variable(x,int:=0,both).
variable(y,int:=0,both).
variable(angle,int:=0,both).
variable(pen,int:=1,both).
variable(color,string:='#000000',both).
variable(visible,int:=1,both).
:- pce_end_class.

Questa dichiarazione della classe definisce un tipo di dato con gli attributi x, y, angle, che definiscono la posizione e la direzione della tartaruga sullo schermo, pen, che indica se la tartaruga lascerà il segno o no mentre si muove, color, che definisce il colore della linea e infine visible, che stabilisce se la tartaruga è visibile.

Per questa classe vengono automaticamente definiti, grazie all'opzione both, i metodi accessori e mutatori per ogni attributo, che hanno lo stesso nome dell'attributo a cui fanno riferimento.

Le coordinate della tartaruga sono espresse nel sistema di riferimento del LOGO che è abbastanza diverso da quello standard usato per individuare le coordinate di un punto in una finestra. In LOGO il punto di coordinate (0,0) è il punto centrale dello schermo grafico (la home). Gli angoli sono misurati in senso orario a partire dall'asse y. Con queste convenzioni la posizione iniziale della tartaruga, nel punto centrale con la testa rivolta in alto corrisponde a x=0, y=0, angle=0. Queste coordinate vanno trasformate nel riferimento standard secondo il quale il punto (0,0) è l'angolo in alto a sinistra, la coordinata y aumenta spostandosi verso il basso e gli angoli si misurano a partire dall'orizzontale e crescono in senso antiorario.

Queste trasformazioni sono realizzate dai seguenti predicati che utilizzano l'oggetto globale @graphics che rappresenta la finestra grafica


/*** trasforma dalle coordinate logo in coordinate della finestra ***/
trasf(X,Y,X1,Y1) :- get(@graphics,width,W), get(@graphics,height,H),
        X1 is (W/2+X), Y1 is (H/2-Y).

/*** trasformazioni dell'angolo ***/
trasfAngle(Angle,Angle2) :- AngleTemp is ((90-Angle) mod 360),
   ifthenelse(AngleTemp>=0,Angle2=AngleTemp,
        Angle2 is AngleTemp+360).

Della classe turtle viene istanziato un oggetto globale di nome @myturtle_class al quale si fa riferimento per leggere e scrivere i vari attributi, come in questo frammento di codice. I predicati send e get sono predicati speciali che permettono di invocare metodi sugli oggetti XPCE.

/*** imposta la tartaruga ***/
setTurtle(X,Y,Angle) :- send(@myturtle_class,x(X)),send(@myturtle_class,y(Y)),send(@myturtle_class,angle(Angle)).

/*** legge i valori della tartaruga ***/
getTurtle(X,Y,Angle,Pen,Visible) :- get(@myturtle_class,x,X),get(@myturtle_class,y,Y),get(@myturtle_class,angle,Angle),get(@myturtle_class,pen,Pen),get(@myturtle_class,visible,Visible).

Grazie alle librerie XPCE la realizzazione del disegno è estremamente semplice. Il predicato line/5 disegna una linea della quale i primi quattro parametri specificano le coordinate del punto iniziale e finale. L'ultimo parametro indica lo stato della penna: se è 0 la penna è su e non si disegna niente, se invece è 1 la linea viene disegnata. Per disegnare la linea è necessario trasformare le coordinate dei due punti, ottenenere il colore corrente, poi, con il predicato speciale new, creare un oggetto della classe line con i parametri opportuni e infine chiamare il metodo display sull'oggetto globale @graphics per visualizzare la linea.

/*** se la penna e' su non si disegna niente ***/
line(_,_,_,_,0).
line(X0,Y0,X1,Y1,1) :- trasf(X0,Y0,X0v,Y0v),trasf(X1,Y1,X1v,Y1v),new(Line,line(X0v,Y0v,X1v,Y1v)),getColor(Color),send(Line,colour,Color),send(@graphics,display(Line)).


La memoria

La memoria è realizzata come lista di coppie (nome_variabile, valore). Ogni coppia è rappresentata con il termine val(Var,Val).

Il predicato readmem legge dalla memoria la variabile con il nome indicato nel secondo parametro e il cui valore è il terzo parametro. L'uso del cut evita che vengano fornite soluzioni sbagliate in caso di backtracking.

readmem([val(Nome,N) | L],Nome,N) :- !.
readmem([ Head | Tail ],Nome,N) :- readmem(Tail,Nome,N).
readmem([],Nome,0). /*** variabili indefinite valgono 0 ***/

Il predicato writemem scrive nella variabile, il cui nome è specificato nel secondo parametro, il valore specificato nel terzo parametro. Il primo parametro è la memoria prima della scrittura, l'ultimo è la memoria dopo la scrittura.

writemem([],Nome,X,[val(Nome,X)]) :- !.
writemem([val(Nome,OldValue) | Tail],Nome,X,[val(Nome,X) | Tail]) :- !.
writemem([ Head | Tail ],Nome,X,[Head | NewMem]) :- 
	writemem(Tail,Nome,X,NewMem).


Divisione in token

La prima operazione da effettuare per eseguire un programma è la trasformazione di una lista di caratteri in una lista di token.

Per far questo ci siamo serviti di una semplice grammatica. Il predicato parser produce una lista di token. Gli spazi, l'invio e la tabulazione vengono ignorati grazie al predicato spazio. Un token può essere formato da più caratteri se questi caratteri soddisfano il predicato is_char (verificato solo dai simboli alfanumerici, dalle cifre e dai simboli # e _).

Ogni altro simbolo è considerato un token di un solo carattere, e viene restituito il suo nome. In questo modo, ad esempio, la stringa "fd 50 if :b = 0 [ bk 90 ]" viene trasformata nella lista [ fd, 50, if, ':', b, '=', 0, '[', bk, 90, ']' ].


parser([]) --> [],!.
parser([H | L]) --> token(H),spazio,!,parser(L).

spazio --> [32],spazio,!.
spazio --> [13],spazio,!.
spazio --> [10],spazio,!.
spazio --> [9],spazio,!.

spazio --> [],!.

token(N) --> [X],{not(is_char(X)),name(N,[X])}.
token(W) --> chars(L), {name(W,L)}.

chars([H | T]) --> char(H),!,chars(T).
chars([]) --> [].

char(X) --> [X], {is_char(X)}.


is_char(X) :- code_type(X,digit).
is_char(X) :- code_type(X,alnum).
is_char(35). /* # */
is_char(95). /* _ */

Espressioni

Espressioni intere

Per realizzare le espressioni intere in modo corretto abbiamo realizzato in Prolog la grammatica delle espressioni. Abbiamo scartato l'ipotesi di implementare le espressioni "delegando" al Prolog la loro valutazione globale perché questo metodo non avrebbe permesso di gestire le variabili in maniera semplice.

La grammatica delle espressioni intere deve essere chiaramente non ambigua e deve realizzare correttamente le precedenze tra gli operatori secondo le regole standard. Inoltre gli operatori binari devono associare a sinistra, questa proprietà è importante per gli operatori non associativi (sottrazione, divisione intera e modulo).

La grammatica utilizzata è la seguente, che è ottenuta dalla grammatica non ambigua per le espressioni intere eliminando la ricorsione sinistra, che crea loop infiniti nella esecuzione del programma.

\begin{displaymath}
\textrm{E}_{I} \: \to \: \textrm{T}_{I}  \textrm{E}_{I}^{\prime}
\end{displaymath}


\begin{displaymath}
\textrm{E}_{I}^{\prime} \to + \textrm{T}_{I} \textrm{E}_{I} ...
... \textrm{T}_{I} \textrm{E}_{I} ^{\prime} \; \vert \; \epsilon
\end{displaymath}


\begin{displaymath}
\textrm{T}_{I} \to \textrm{F}_{I} \textrm{T}_{I}^{\prime}
\end{displaymath}


\begin{displaymath}
\textrm{T}_{I}^{\prime} \to * \textrm{F}_{I} \textrm{T}_{I} ...
... \textrm{F}_{I} \textrm{T}_{I} ^{\prime} \; \vert \; \epsilon
\end{displaymath}


\begin{displaymath}
\textrm{F}_{I} \to n \; \vert \; (\textrm{E}_{I}) \; \vert \; -\textrm{F}_{I} \; \vert \; VAR
\end{displaymath}

Il codice Prolog presentato sopra è la naturale traduzione della grammatica, dove il predicato intexpr corrisponde al simbolo non terminale $E_I$, intexpr2 corrisponde a $E_I^\prime$, ed analogamente per quanto rigurda termine e fattore.

intexpr(Env,X) --> termine(Env,T),intexpr2(Env,E),!, {X is T+E}.

intexpr2(Env,X)--> ['+'],termine(Env,T),intexpr2(Env,E),!,{X is T+E}.
intexpr2(Env,X)--> ['-'],termine(Env,T),intexpr2(Env,E),!,{X is -T+E}.
intexpr2(Env,0) --> [].

termine(Env,X) --> fattore(Env,F),termine2(Env,X,F).
termine(Env,X) --> fattore(Env,F).

termine2(Env,X,Parziale) --> ['*'],fattore(Env,F),{Ris is Parziale*F},termine2(Env,X,Ris).
termine2(Env,X,Parziale) --> ['/'],fattore(Env,F),{F\=0,Ris is Parziale//F},termine2(Env,X,Ris).
termine2(Env,X,Parziale) --> ['/'],fattore(Env,F),{F=0},errorMsg('Division by zero').

termine2(Env,X,Parziale) --> ['\%'],fattore(Env,F),{F\=0,Ris is Parziale mod F},termine2(Env,X,Ris).
termine2(Env,X,Parziale) --> ['\%'],fattore(Env,F),{F=0},errorMsg('Division by zero').
termine2(Env,Parziale,Parziale) --> []. /*** Quando non ci sono ulteriori termini viene restituito il risultato finale ***/

fattore(Env,X) --> read_,  list_string_(Mess), {readinput(X,Mess)}.
fattore(Env,X) --> read_,!,{readinput(X,'?')}.

fattore(Env,X) --> ['('],intexpr(Env,X),[')'].
fattore(Env,X) --> [N], {integer(N), X is N}.
fattore(Env,X) --> ['-'],fattore(Env,E),!, {X is -E}.
fattore(Env,X) --> variable_r(Nome), { readmem(Env,Nome,X),integer(X)}.

fattore(Env,X) --> first_, listexpr(Env,[X|_]),{integer(X),!}.
fattore(Env,0) --> first_, listexpr(Env,[]), errorMsg('First doesn\'t like [] as input').

fattore(Env,0) --> last_, listexpr(Env,[]), errorMsg('Last doesn\'t like [] as input').
fattore(Env,X) --> last_, listexpr(Env,L), {last(X,L), integer(X)}.

fattore(Env,X) --> xcor_, {get(@myturtle_class,x,X)}.
fattore(Env,X) --> ycor_, {get(@myturtle_class,y,X)}.
fattore(Env,X) --> heading_, {get(@myturtle_class,angle,Angle),trasfAngleInv(Angle,X)}.

fattore(Env,X) --> length_, listexpr(Env,L), !,{length(L,X)}.

fattore(Env,X) --> length_, wordexpr(Env,Str), !,{atom_codes(Str,L),length(L,X)}.

Per calcolare somme e differenze tra termini abbiamo utilizzato lo stratagemma di sommare sempre quanto restituito in X da intexpr2 (che restituisce un numero negativo nel caso della sottrazione). Per moltiplicazioni divisioni è stato necessario aggiungere al predicato termine2 un parametro che rappresenta il risultato parziale.

Sono espressioni intere anche:

read
(leggi). Questa funzione (che può prendere come parametro un messaggio da visualizzare), restituisce un intero specificato dall'utente. Esempio: make "x read [ inserisci un numero ]. Il costrutto read non esiste nel LOGO standard nel quale si possono leggere solo liste con la funzione readlist descritta in seguito.

first <lista>
(primo) Restituisce il primo elemento di una lista (se è un intero)
last <lista>
(ultimo) Restituisce l'ultimo elemento di una lista
xcor
Restituisce la coordinata x corrente della tartaruga
ycor
Restituisce la coordinata y corrente della tartaruga
heading
(direzione) Restituisce l'angolo formato dalla testa della tartaruga con la verticale. Gli angoli sono misurati seguendo il senso orario.
length <lista>, length <word>
(lunghezza, lungh) Restituisce la lunghezza di una lista o di una word.

Espressioni lista

Per la gestione delle liste il LOGO adotta una filosofia del tutto simile al Prolog: una lista ha una testa che è un elemento di un tipo base (intero, booleano, word) e una coda che è ricorsivamente una lista. Le liste sono specificate all'interno di parentesi quadre e i vari elementi sono separati da spazi. L'operatore first restituisce l'elemento in testa alla lista, mentre butfirst restituisce la coda della lista. È presente anche l'operatore last che restituisce l'ultimo elemento. Gli elementi dentro le liste non vengono in nessun modo interpretati. Per questo motivo la lista [1+2] è una lista contenente i tre elementi 1, + e 2 e non la lista formata dall'unico elemento 3.

Questa scelta non è un problema perché è possibile aggiungere un nuovo elemento (intero, word o booleano) in testa alle liste grazie all'operatore push (che prende un elemento e una lista e restituisce una lista) ed è possibile unire due liste con l'operatore concat che restituisce l'append di due liste.

Questi sono i predicati Prolog che realizzano la grammatica delle liste:

listexpr(Env,L) --> list_(L).
listexpr(Env,L) --> variable_r(Nome), {readmem(Env,Nome,L),
                is_list(L)}.
listexpr(Env,L) --> variable_r(Nome), {readmem(Env,Nome,X),
                L=[X]}.
listexpr(Env,L) --> butfirst_, listexpr(Env,[_ | L]).
listexpr(Env,[]) --> butfirst_, listexpr(Env,[]),
                 errorMsg('butfirst doesn\'t like [] as input'). 

listexpr(Env,L) --> readlist_, list_string_(Mess),!, {readList(L,Mess)}.
listexpr(Env,L) --> readlist_,!, {readList(L,'')}.

listexpr(Env,[X | L]) --> push_,intexpr(Env,X),listexpr(Env,L).
listexpr(Env,[X | L]) --> push_,boolexpr(Env,X),listexpr(Env,L).
listexpr(Env,[X | L]) --> push_,wordexpr(Env,X),listexpr(Env,L).

listexpr(Env,L) --> concat_,listexpr(Env,L1),listexpr(Env,L2),
                {append(L1,L2,L)}.


atom_list([]) --> [].
atom_list([Head | Tail ]) --> [Head],atom_list(Tail).

list_(X) --> square_open_, atom_list(X), square_close_.

Un'espressione di tipo lista può essere quindi una lista specificata tra parentesi quadre, una variabile contenente una lista, oppure una lista ottenuta dai seguenti operatori:

readlist
(leggilista) Questo operatore, che può prendere anche un parametro che specifica un messaggio da visualizzare, restituisce una lista introdotta dall'utente. Nel LOGO standard anche per leggere un intero o una word si deve utilizzare readlist in combinazione con first, ad esempio scrivendo make "x first readlist. Anche in ProLOGO è possibile utilizzare first readlist in alternativa a read (che però ha il vantaggio di garantire che l'utente inserisca proprio un intero).

butfirst <lista>
(coda) Restituisce la coda di una lista
push <int/bool/word> <lista>
(metti) Restituisce la lista ottenuta inserendo in testa alla lista passata come secondo argomento il primo argomento
concat <lista1> <lista2>
Restituisce la lista ottenuta concatenando i due argomenti

Espressioni word

In LOGO una word è, in pratica, una stringa che non contiene spazi. Le stringhe con spazi sono sostituite da liste.

Una word si indica aprendo i doppi apici ma senza chiuderli (es. "stringa). Questa notazione è possibile perché le stringhe non contengono spazi.

La grammatica delle espressioni di tipo word è realizzata dal predicato wordexpr.

quote --> ['\"'].
word(Nome) --> quote,[Nome],!.
word('') --> quote.

wordexpr(Env,X) --> word(X).
wordexpr(Env,X) --> variable_r(Env,Nome),{readmem(Env,Nome,X),not(integer(X))}.
wordexpr(Env,X) --> first_, listexpr(Env,[X|_]).
wordexpr(Env,X) --> last_, listexpr(Env,L), {last(X,L), not(integer(X))}.
wordexpr(Env,X) --> first_, wordexpr(Env,Str), {name(Str,[ F | T ]),name(X,[F])}.
wordexpr(Env,X) --> butfirst_, wordexpr(Env,Str), {name(Str,[ F | T ]),name(X,T)}.
wordexpr(Env,X) --> last_, wordexpr(Env,Str), {name(Str,L),last(Last,L),name(X,[Last])}.



Sulle word è possibile usare gli operatori first, butfirst e last che operano sulla stringa vista come lista di caratteri. Inoltre è possibile ottenere una word usando gli operatori first e last su una lista, nel caso che l'elemento non sia un intero.

Espressioni booleane

La realizzazione della espressioni booleane è stata più semplice di quella per le espressioni intere, perchè tutti gli operatori sono associativi, e quindi si può far associare gli operatori a destra senza cambiare il risultato. Abbiamo usato questa semplice grammatica non ambigua ricorsiva a destra, dove con E$_I$ indichiamo la grammatica delle espressioni intere, con E$_L$ e E$_W$ le grammatiche per le espressioni lista e word.


\begin{displaymath}
\textrm{E}_{B} \to \; \textrm{T}_{B} \: or \: \textrm{E}_{B} \; \vert \; \textrm{T}_{B}
\end{displaymath}


\begin{displaymath}
\textrm{T}_{B} \to \; \textrm{F}_{B} \: and \: \textrm{T}_{B} \; \vert \; \textrm{F}_{B}
\end{displaymath}


\begin{displaymath}
\textrm{F}_{B} \to true \; \vert \; false \; \vert \; not (\...
..._L \; \vert \; empty \: \textrm{E}_W \; \vert \; VAR \; \vert
\end{displaymath}


\begin{displaymath}
\vert \; \textrm{E}_{I} = \textrm{E}_{I} \; \vert \; \textrm...
...; \textrm{E}_{I} < \textrm{E}_{I} \; \vert \; (\textrm{E}_{B})
\end{displaymath}

Di seguito presentiamo il codice Prolog che implementa la grammatica e che fa uso della classica definizione del predicato ifthenelse ternario e dei predicati and, or e not che definiscono la semantica delle funzioni and, or e not.

ifthenelse(A,B,C) :- call(A),!,B.
ifthenelse(A,B,C) :- C.


and(true,true,true).
and(false,_,false).
and(_,false,false).

or(false,false,false).
or(true,_,true).
or(_,true,true).

not(true,false).
not(false,true).


boolexpr(Env,X) --> boolterm(Env,F1), or_, boolexpr(Env,F2), {or(F1,F2,X)}.
boolexpr(Env,X) --> boolterm(Env,X).

boolterm(Env,X) --> boolfatt(Env,F1), and_, boolterm(Env,F2), {and(F1,F2,X)}.
boolterm(Env,X) --> boolfatt(Env,X).

boolfatt(Env,true) --> true_.
boolfatt(Env,false) --> false_.
boolfatt(Env,X) --> not_,['('],boolexpr(Env,Y),[')'], {not(Y,X)}.

boolfatt(Env,X) --> intexpr(Env,E1),['='],intexpr(Env,E2),{ifthenelse(E1=E2,X=true,X=false) }.
boolfatt(Env,X) --> intexpr(Env,E1),['>'],intexpr(Env,E2),{ifthenelse(E1>E2,X=true,X=false) }.
boolfatt(Env,X) --> intexpr(Env,E1),['<'],intexpr(Env,E2),{ifthenelse(E1 intexpr(Env,E1),['>'],['='],intexpr(Env,E2),{ifthenelse(E1>=E2,X=true,X=false) }.
boolfatt(Env,X) --> intexpr(Env,E1),['<'],['='],intexpr(Env,E2),{ifthenelse(E1= intexpr(Env,E1),['!'],['='],intexpr(Env,E2),{ifthenelse(E1\=E2,X=true,X=false) }.

boolfatt(Env,X) --> ['('],boolexpr(Env,X),[')'].

boolfatt(Env,true) --> variable_r(Env,Nome),{ readmem(Env,X,true)}.
boolfatt(Env,false) --> variable_r(Env,Nome), { readmem(Env,X,false)}.

boolfatt(Env,X) --> empty_, listexpr(Env,L), {ifthenelse(length(L,0),X=true,X=false)}.
boolfatt(Env,X) --> empty_, wordexpr(Env,Str), {atom_codes(Str,L),ifthenelse(length(L,0),X=true,X=false)}.






Ricapitolando, un espressione booleana è data dai valori di verità true e false, dalle relazioni tra numeri interi $=,<,<=,>,>=,!=$, dall'operatore empty applicato a una lista. Le espressioni booleane si possono combinare con gli operatori and, or e not. Naturalmente sono presenti anche le forme italiane che sono rispettivamente vero, falso, vuota, e, o, non oltre alla variante emptyp per empty.

Comandi

La grammatica dei comandi LOGO è realizzata dal predicato command. Un comando viene semplicemente definito come una lista di istruzioni. Il predicato command viene chiamato sulla lista ottenuta dal parser descritto nella sezione 6 che divide una stringa in token.


exec(Command,Env,EnvF):-
        string_to_list(Command,Comm),parser(C,Comm,[]),
        command(Env,EnvF,C,[]).

command(Env,Env) --> [].
command(Env0,EnvF) --> instr(Env0,Env1),!,command(Env1,EnvF).
command(Env0,Env1) --> square_open_,!, command(Env0,Env1),
                 square_close_.

Ogni istruzione viene chiamata con due parametri di ambiente. Il primo rappresenta l'ambiente prima della esecuzione dell'istruzione, mentre il secondo è l'ambiente dopo l'esecuzione dell'istruzione.

Comandi elementari

Le grammatiche che definiscono le istruzioni elementari sono molto semplici e non richiedono particolare commento. Ad esempio riportiamo la grammatica per l'istruzione fd.


instr(Env,Env) --> fd_,intexpr(Env,X),!, {forward(X)}.

L'implementazione dell'input e dell'output è affidata alla chiamata di predicati ausiliari. Nell'esempio si usa il predicato forward che fa avanzare la tartaruga.

forward(N) :- getTurtle(X,Y,Angle,Pen),radianti(Angle,Radianti),X1 is round(X+N*cos(Radianti)),Y1 is round(Y+N*sin(Radianti)),setTurtle(X1,Y1,Angle),line(X,Y,X1,Y1,Pen),paintTurtle.

Qui riportiamo tutte le istruzioni elementari supportate dal ProLOGO.

forward n
(fd, avanti, a) fa avanzare la tartaruga di n pixel.
back n
(bk, indietro, i) fa indietreggiare la tartaruga di n pixel.
rightturn n
(right, rt, destra, d) fa girare la tartaruga verso destra di n gradi
leftturn n
(left, lt, sinistra, s) fa girare la tartaruga verso sinistra di n gradi
home
(tana) Fa tornare la tartaruga al centro dello schermo (punto (0,0)) con la testa rivolta in alto.
clearscreen
(cs, puliscischermo, ps) Cancellla lo schermo grafico e porta la tartaruga in posizione home
clean
(pulisci) Pulisce lo schermo senza spostare la tartaruga
cleartext
(ct, puliscitesto, pt) Cancella lo schermo testuale
print <valore>
(pr, stampa) Visualizza sullo schermo testuale un valore
make "var <valore>
(assegna, as) Assegna a una variabile un valore. Questo comando è descritto in dettaglio nella sezione 8.2
penup
(pu, sulapenna, su) Sposta in su la penna. La tartaruga si sposterà senza lasciare traccia
pendown
(pd, giulapenna, giu) Sposta in giù la penna. La tartaruga lascerà traccia
showturtle
(st, mostarta, mt) Mostra la tartaruga
hideturtle
(ht, nastarta, nt) Nasconde la tartaruga
setx x
(vaix) Sposta la tartaruga alla x specificata (senza cambiare la y)
sety y
(vaiy) Sposta la tartaruga alla y specificata (senza cambiare la x)

setxy x y, setpos lista
(vaixy - vaipos) Sposta la tartaruga alla posizione indicata (per setpos la lista deve essere di due elementi)
setheading n
(seth, angolo) Determina la direzione della tartaruga: l'angolo è dato in gradi dalla verticale in senso orario.
setpencolor 0-7 | "nomecolore
(pencolor, setpc, color, colorepenna, colore) Setta il colore della penna. Il colore può essere un numero da 0 a 7, oppure un nome ad esempio "black oppure una descrizione esadecimale ad esempio "#000000
setbgcolor 0-7 | "nomecolore
(setbackgroundcolor, bgcolor, coloresfondo, sfondo) Setta il colore di sfondo
defaultcolors
(coloristandard) Imposta i colori standard (nero su bianco)
printoutnames
(pons, ponames, showvars, mostravar) Mostra su schermo testuale il contenuto di tutte le variabili
eraseall
(erall, cancellatutto) Cancella il contenuto della memoria
label <word / lista>
(etichetta) Scrive un testo sulla finestra grafica, nella posizione corrente della tartaruga
about
(informazioni) Visualizza le informazioni sul ProLOGO


Assegnamento e lettura del valore di una variabile

In LOGO per assegnare un valore a una variabile si utilizza il comando make la cui sintassi più comune è make "var valore. In realtà però il primo parametro può essere una qualsiasi espressione di tipo word che restituisce il nome di una variabile.

Per accedere al valore di una variabile si usa l'operatore thing, ad esempio thing "x restituisce il valore di x. Per semplicità invece di scrivere thing "x si può utilizzare la forma abbreviata :x, tuttavia l'utilizzo dell'operatore thing può risultare utile in alcuni casi, vediamo un esempio:

make "nome_var "x
make :nome_var 10
pr thing :nome_var

In questo esempio con il primo comando alla variabile nome_var viene assegnata la stringa (word) x. Il secondo comando assegna alla variabile x il valore 10, il terzo comando stampa il contenuto della variabile il cui nome è il contenuto di nome_var, cioè il contenuto di x, 10. In questo caso l'utilizzo di thing è necessario, infatti non è possibile scrivere pr ::nome_var.

La definizione della istruzione make è questa

instr(Env0,EnvF) --> make_, wordexpr(Env0,Nome), wordexpr(Env0,X),!,{writemem(Env0,Nome,X,EnvF)}.
instr(Env0,EnvF) --> make_, wordexpr(Env0,Nome), intexpr(Env0,X),!,{writemem(Env0,Nome,X,EnvF)}.
instr(Env0,EnvF) --> make_, wordexpr(Env0,Nome), boolexpr(Env0,X),!,{writemem(Env0,Nome,X,EnvF)}.
instr(Env0,EnvF) --> make_, wordexpr(Env0,Nome), listexpr(Env0,X),!,{writemem(Env0,Nome,X,EnvF)}.

Come si vede il primo parametro è una espressione word che restituisce il nome della variabile, il secondo parametro può essere un'espressione di tipo word, intero, lista o booleano. L'assegnamento è eseguito richiamando il predicato writemem descritto nella sezione 5.

L'accesso al valore di una variabile avviene ottenendo il nome della variabile con il predicato variable_r che restituisce il nome di una variabile.

dots --> [':'].
thing_ --> [thing].
thing_ --> [cosa].

variable_r(Env,Nome) --> dots,[Nome].
variable_r(Env,Nome) --> thing_,wordexpr(Env,Nome).


Controllo di flusso

Il LOGO offre tradizionalmente due costrutti per il controllo del flusso, il REPEAT e l'IF. A questi abbiamo aggiunto il costrutto WHILE che non è presente in tutte le versioni di LOGO.

Nell'implementazione di queste istruzioni si è posto il problema di come gestire il caso in cui una parte di codice non debba essere eseguita perché la condizione non è verificata. Se il risultato dell'esecuzione di una parte di codice fosse solo una modifica dell'ambiente, sarebbe stato facile ignorare l'ambiente risultante dalla parte di codice che non deve essere eseguita. Tuttavia la parte di input/output è realizzata da side-effect che verrebbero comunque proposti anche se si ignorasse l'ambiente ottenuto. Per questo motivo abbiamo scelto di controllare emplicemente che la parte di codice da non eseguire fosse formata da una stringa qualsiasi con parentesi quadre bilanciate e ignorando qualsiasi token diverso da [ e ]. Questa scelta è ammissibile perché il ProLOGO è un interprete e non un compilatore e quindi può evitare di controllare la correttezza sintattica della parte di codice che non viene eseguita.

La seguente definizione di openclose viene chiamata con parametro 1 dopo aver controllato la presenza di una parentesi aperta. Il parametro viene incrementato quando si trova una parentesi aperta e decrementato quando si trova una parentesi chiusa.

openclose(0) --> [],!.
openclose(X) --> square_open_,!,{X1 is X+1},openclose(X1).
openclose(X) --> square_close_,!,{X1 is X-1},openclose(X1).
openclose(X) --> [I],openclose(X).
openclose(X,[],[]) :- X\=0,errorDialog('Syntax Error').

L'istruzione REPEAT

Questo costrutto prevede di ripetere una sequenza di istruzioni per un numero di volte prefissato. È particolarmente utile per il disegno di figure geometriche, ad esempio questo semplice programma ProLOGO disegna un poligono regolare prendendo in input la lunghezza del lato e il numero di lati.

make "lato read [ inserisci la lunghezza del lato ]
make "n read [ inserisci il numero di lati ]
cs

if :n<3 [
   pr [ Non esistono poligoni con meno di 3 lati ]
   ]
   [
   make "angolo 360/:n
   repeat :n [
       fd :lato rt :angolo ]
   ]    

Poligono Il risultato dell'esecuzione dello script per disegnare un poligono. Nel ProLOGO la tartaruga può trasformarsi in un pinguino.

Per implementare l'istruzione REPEAT (in italiano RIPETI) è stato necessario, nella prima regola, esplicitare gli ultimi due argomenti del predicato instr che sono la lista differenza che rappresenta il programma da interpretare. La prima regola riguarda il caso in cui si abbia un repeat per un numero di volte maggiore di 1. In questo caso bisogna eseguire la sequenza di istruzioni una volta e continuare richiamando l'istruzione repeat con il parametro decrementato di 1. La seconda regola riguarda il caso base, REPEAT 1. In questo caso si esegue semplicemente il codice. Nel caso particolare REPEAT 0 il codice viene ignorato grazie alla chiamata di openclose. La seconda regola poteva essere evitata, ma abbiamo preferito lasciarla per rendere più veloce l'interpretazione.

instr(Env0,EnvF,L,C) :- repeat_(L,Lr), intexpr(Env0,X,Lr,Le),X>1,!,square_open_(Le,Los),command(Env0,Env1,Los,Lc),square_close_(Lc,Lcs),X1 is X-1,instr(Env1,EnvF,[repeat, X1 | Le],C).

instr(Env0,EnvF) --> repeat_, intexpr(Env0,1),!,square_open_,command(Env0,EnvF),square_close_.
 
instr(Env0,Env0) --> repeat_, intexpr(Env0,0),!,square_open_,openclose(1).

L'istruzione IF

In LOGO l'IF (in italiano SE) è definito sempre come if ... then ... else con questa sintassi: if condizione [istruzioni1][istruzioni2]. La nostra implementazione ricalca questa scelta. Si devono quindi distinguere solo due situazioni: la condizione è vera, la condizione è falsa. Nel primo caso si eseguono le istruzioni1 e si richiama openclose per il secondo blocco. Se la condizione è falsa ci si comporta in maniera simmetrica.

instr(Env0,EnvF) -->if_, boolexpr(Env0,true),!,square_open_,command(Env0,EnvF),square_close_,square_open_,openclose(1),!.

instr(Env0,EnvF) --> if_, boolexpr(Env0,false),!,square_open_,openclose(1),square_open_,command(Env0,EnvF),square_close_.

L'istruzione WHILE

La sintassi del WHILE (in italiano MENTRE) che abbiamo utilizzato è la seguente: while condizione [istruzioni]. Nel caso che la condizione sia vera si esegue il blocco di istruzioni e si richiama la stessa istruzione WHILE con l'ambiente iniziale modificato. Nel caso che la condizione sia falsa il blocco viene ignorato con l'usuale chiamata a openclose.

instr(Env0,EnvF,L,C) :- while_(L,L1), boolexpr(Env0,true,L1,L2),!,square_open_(L2,L3),command(Env0,Env1,L3,L4),square_close_(L4,L5),instr(Env1,EnvF,L,C).

instr(Env0,Env0) --> while_, boolexpr(Env0,false),!,square_open_,openclose(1).

Come esempio dell'uso del WHILE riportiamo il programma ProLOGO per calcolare il massimo comun divisore di due numeri

make "a read [ Inserisci il primo numero ]
make "b read [ Inserisci il secondo numero ]

while :b!=0 [
      make "temp :b
      make "b :a%:b
      make "a :temp
      ]
      
pr [ MCD = ]
pr :a

Gestione degli errori

Le situazioni di errore previste sono di due tipi. La prima si ha quando la lista che rappresenta la porzione di programma ancora da eseguire non può essere unificata con nessuna regola. In questo caso si visualizza il classico messaggio di errore del LOGO: "I don't know how to...". Questa regola è presentata alla fine di tutte le altre regole per instr in modo che sia eseguita solo nel caso in cui non sia possibile l'unificazione con le regole precedenti.

instr(Env,Env) --> [X],!, {atom_concat('I don\'t know how to ',X,Mess)},errorMsg(Mess).

errorMsg ha l'effetto di visualizzare il messaggio d'errore specificato e di scorrere tutta la lista che rappresenta il programma. In questo modo si evita di continuare ad eseguire il programma dopo un errore

toend --> [X], toend.
toend(_,[]).

errorMsg(Message) --> {errorDialog(Message)},toend.

Altri tipi di errore previsti sono la divisione per 0 o l'uso di first o butfirst su una lista vuota. Anche in questi casi si richiama errorMsg con il messaggio opportuno.

termine2(Env,X,Parziale) --> ['/'],fattore(Env,F),{F=0},errorMsg('Division by zero').

fattore(Env,0) --> first_, listexpr(Env,[]), errorMsg('First doesn\'t like [] as input').

Bibliografia

1
Seymour Papert, Mindstorms: Children, Computers and Powerful Ideas. Basic Books, 1980.

2
Brian Harvey, Berkley Logo User Manual, 1993.

3
Ermanno Soncini, Quaderno di Informatica. Laboratorio di esperienze formative per il triennio della scuola media. Istituto Geografico De Agostini, 1989.

4
Sandra Marchi, Il LOGO per un laboratorio di geometria. Zanichelli, 1992.

About this document ...

Linguaggi di Programmazione: Paradigmi di Programmazione.
Il linguaggio ProLOGO.

This document was generated using the LaTeX2HTML translator Version 2K.1beta (1.48)

Copyright © 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds.
Copyright © 1997, 1998, 1999, Ross Moore, Mathematics Department, Macquarie University, Sydney.

The command line arguments were:
latex2html -dir html -split 0 -no_navigation relazione.tex

The translation was initiated by Lorenzo Masetti on 2003-06-16


Lorenzo Masetti 2003-06-16