Sei sulla pagina 1di 11

A.A.

2018/19
Corso di Linguaggi
● Che cos’è un Linguaggio di Programmazione?
● Descrivere i linguaggi di programmazione.
● Categorie sintattiche: espressioni, dichiarazioni, comandi.
● Astrazione del controllo e dei dati.
● Gestione della memoria.
● (Cenni) Paradigmi di programmazione.

Linguaggi di Programmazione

Un Linguaggio di Programmazione (Programming Language) è una notazione


(insieme di costrutti e regole) per descrivere algoritmi e dati.
Un Programma è una frase di un linguaggio di programmazione.
Un Algoritmo è un concetto astratto che trova forma concreta nel
programma: sequenza finita di passi (primitivi) di computazione (o calcolo)
descritti mediante un programma.

Per poter essere effettivamente eseguiti gli algoritmi devono essere opportunamente
formalizzati in termini dei costrutti di un linguaggio di programmazione, cioè devono
essere rappresentati mediante le istruzioni di un opportuno linguaggio di
programmazione. Questo linguaggio sarà definito formalmente da una specifica sintassi
e da una precisa semantica.

Un programma è un metodo, con rappresentazione finita, per calcolare


funzioni/relazioni potenzialmente infinite.
- Non è sufficiente rappresentare funzioni per implementare un algoritmo
ma si richiede di poter descrivere anche il metodo di calcolo, ovvero i
passi primitivi di computazione necessari per calcolare la funzione.
Un programma scritto nel linguaggio L è un insieme di istruzioni di L.
Linguaggio matematico: notazione rigorosa per rappresentare funzioni
(linguaggio formale che permette di descrivere funzioni) ma non computazioni,
ovvero passi elementari.
Linguaggio logico: le regole e gli assiomi rendono possibile specificare
il processo di computazione (ancora in modo implicito), inoltre permettono di
rappresentare formalmente oggetti (quindi numeri) infiniti in modo finito (ma
non computazioni infinite).

Un linguaggio di programmazione permette di specificare in modo accurato


esattamente le primitive del processo di computazione, con la rigorosità e la
potenza della logica. Quindi permette di scrivere istruzioni finite ed
computazioni infinite.
A.A. 18/19 Linguaggi - JEREGHI

I linguaggi di programmazione sono nati per distinguere dati ed istruzioni. La


potenza di un linguaggio è di rendere finito computazioni potenzialmente infinite.

Sulla progettazione dei linguaggi di programmazione ha influenzato


maggiormente l’architettura dei computer (von Neumann) e le metodologie di
sviluppo software. I linguaggi imperativi sono i più diffusi a causa dei
computer basati sull’architettura di von Neumann: variabili che modellano
celle di memoria, assegnamenti che modellano la modifica, iterazione
efficiente.

Macchina Astratta: dato un linguaggio di programmazione L la macchina


astratta ML è un insieme di strutture dati ed algoritmi che permettono di
memorizzare ed eseguire programmi scritti in L.
- una generica macchina astratta è composta da una memoria (per salvare
dati e programmi) e da un interprete (componente che esegue le
istruzioni dei programmi) quindi l’interprete con tutto il supporto per
eseguire un programma.

Linguaggio macchina: Data una macchina astratta ML, il linguaggio L è


detto linguaggio macchina se ha come stringhe legali tutte quelle “comprese”
dall’interprete di ML.

L’interprete di una macchina hardware è realizzato da un insieme di


dispositivi fisici che sostanzialmente costituiscono l’unità di controllo e
che, usando anche le operazioni per il controllo di sequenza, permette di
eseguire il cosiddetto ciclo fetch-decode-execute.

Una macchina astratta corrisponde univocamente ad un linguaggio, il suo


linguaggio macchina. Dato un linguaggio L esistono infinite macchine astratte
che hanno come linguaggio macchina L: tali macchine differiscono nel modo in
cui l’interprete viene realizzato e nelle strutture dati che utilizzano,
mentre tutte coincidono nel linguaggio interpretato, appunto L.

Implementare un linguaggio di programmazione L significa realizzare una


macchina astratta che abbia L come linguaggio macchina.

Le varie possibilità teoriche di realizzazione di una macchina astratta


si possono ricondurre ai seguenti tre casi e alle loro combinazioni:
1. Realizzazione in hardware;
2. Simulazione mediante software;
3. Simulazione (emulazione) mediante firmware.

In particolare:
1. In linea di principio la realizzazione di ML direttamente in hardware è
sempre possibile ed è concettualmente abbastanza semplice: si tratta di
realizzare mediante dispositivi fisici una macchina fisica tale che il
suo linguaggio macchina coincida con L.

1
A.A. 18/19 Linguaggi - JEREGHI

2. Questa possibilità consiste nella realizzazione delle strutture dati e


degli algoritmi di M di L mediante programmi scritti in un altro
linguaggio L’ che possiamo supporre già implementato. Avendo a
disposizione una macchina M’ di L’ possiamo realizzare la macchina M di
L mediano opportuni programmi scritti in L1 che interpreteranno i
costrutti di L simulando le funzionalità di ML. In questo caso la
realizzazione di M di L passa attraverso quella di un’altra macchina
astratta M’ di L’ che a sua volta dovrà essere realizzata aggiungendo un
ulteriore livello di interpretazione.
3. La terza possibilità è intermedia fra la realizzazione in hardware e
quella software. Consiste nella simulazione delle strutture dati e degli
algoritmi di ML mediante microprogrammi.
a. La microprogrammazione è nata per permettere a diversi elaboratori
di condividere lo stesso insieme di istruzioni e quindi di avere
lo stesso linguaggio - Assembly (primi calcolatori della serie IBM
360 chiamati anche Mainframe).
b. Il linguaggio macchina consiste di microistruzioni che specificano
semplici operazioni di trasferimento dati:
i. tra registri;
ii. da e per la memoria principale (Random Access Memory);
iii. eventualmente attraverso i circuiti logici che realizzano le
operazioni aritmetiche.
c. Ogni istruzione viene simulata mediante uno specifico insieme di
microistruzioni. L’insieme delle microistruzioni costituiscono un
microprogramma che risiede su memorie di sola lettura (Read Only
Memory) e realizza l’interprete del linguaggio comune (Assembly)
per i vari calcolatori con hardware diverso.
d. Nel caso di simulazione mediante microprogrammi si parla di
emulazione ed il livello della microprogrammazione è detto
firmware.
Concettualmente questa soluzione è simile alla simulazione mediante
software: in entrambi i casi ML è simulata mediante opportuni programmi
che sono poi eseguiti da una macchina fisica. Tuttavia, nel caso della
emulazione firmware tali programmi sono microprogrammi invece che
programmi di un linguaggio di alto livello. Perché questa soluzione sia
possibile la macchina fisica che si ha a disposizione deve essere
micro-programmabile.

Considerazioni:
- La realizzazione di ML in hardware offre massima velocità ma
flessibilità nulla.
- La realizzazione mediante software offre massima flessibilità e minima
velocità.
- Quella mediante firmware è intermedia fra le due.

L’implementazione di L sulla macchina ospite M0 avviene mediante una qualche


“traduzione” di L in L0 di M0. Possiamo distinguere due modalità di
implementazione a seconda del fatto che si abbia una traduzione implicita
2
A.A. 18/19 Linguaggi - JEREGHI

realizzata dalla simulazione dei costrutti ML mediante programmi scritti in L0


oppure una traduzione esplicita dei programmi in L in corrispondenti programmi
in L0.

L’esistenza dell’interprete e del compilatore è garantita a patto che il


linguaggio L0 usato per l’implementazione sia sufficientemente espressivo
rispetto al linguaggio L che si vuole implementare.

Implementazione interpretativa pura

Viene realizzato l’interprete di ML mediante un insieme di istruzioni in


L0 cioè un programma scritto in L0 che interpreta tutte le possibili
istruzioni di L (interprete).

Un interprete per il linguaggio L, scritto nel linguaggio L0 (qualsiasi), è un


programma che realizza una funzione parziale.

Questa implementazione risulta poco efficiente perché l’interprete deve


decodificare, al momente dell’esecuzione, dei costrutti del linguaggio L. Le
nuove occorrenze di comandi necessitano anch’esse di essere de-codificate il
che peggiora ancora di più l’efficienza.

Implementazione compilativa pura

L’implementazione di L avviene traducendo esplicitamente i programmi


scritti in L in programmi scritti in L0. La traduzione è eseguita da un
opportuno programma detto compilatore. In questo concetto il linguaggio L è
detto sorgente mentre L0 è il linguaggio oggetto.
Passaggi per la compilazione di un programma scritto in L con un dato di input
D: va mandato in esecuzione il compilatore scritto in L0 con in input il
programma scritto in L, il risultato diventa un programma compilato, cioè
scritto nel linguaggio del compilatore quindi L0. A questo punto è possibile
eseguire il programma compilato sulla macchina M di L0 con il dato di input D.

3
A.A. 18/19 Linguaggi - JEREGHI

Uno degli svantaggi maggiori dell’approccio compilativo risiede nella perdità


di informazioni riguardo la struttura del programma sorgente e ciò rende più
difficile l’interazione con il programma a tempo di esecuzione.

Trasformazioni di programmi e valutazioni parziali


Si tratta di trasformazioni di programmi all’interno di uno stesso
linguaggio con lo scopo di migliorare l’efficienza. La valutazione parziale è
una di queste tecniche che consiste nel valutare un programma del quale sia
nota una parte dell’input, in modo tale da ottenere un programma specializzato
rispetto a tale input, che sia più efficiente del programma originale. Il
programma ottenuto sarà dipendente dall’input non ancora noto.
Specializzare quindi significa inglobare la parte di input noto. Un
compilatore non altro che un interprete

[TEOREMA - 1^ proiezione di FUTAMURA] Dalla combinazione di interpreti e


specializzatori otteniamo programmi complessi ed utili nella programmazione
moderna, quali i compilatori.

4
A.A. 18/19 Linguaggi - JEREGHI

Descrivere un linguaggio di programmazione


Un linguaggio va descritto in tre grandi ambiti: quello della
grammatica, semantica e pragmatica.
La grammatica di un linguaggio definisce quali frasi sono corrette, quindi
definisce l’alfabeto ed il suo lessico.
Il lessico definisce le sequenze corrette di simboli che formano le
parole (token) del linguaggio.
La sintassi descrive quali sequenze di parole costituiscono frasi
legali.
La semantica attribuisce un significato od ogni frase corretta.
La pragmatica definisce come usare una frase corretta e sensata.

Linguaggio procedurale: le cui frasi corrette specificano azioni.

Si vuole comprendere mediante quale processo le frasi “operative” del


linguaggio realizzano lo stato di cose di cui parlano. Tutto questo viene
descritto dall’implementazione - il quarto livello da aggiungere per i
linguaggi di programmazione.

Grammatica e sintassi
Come viene descritta la sintassi per i linguaggi di programmazione?
- La forma normale di Chomsky è una tecnica, per descrivere fenomeni
sintattici in modo formale, che usa dei formalismi per limitare
l’ambiguità.

5
A.A. 18/19 Linguaggi - JEREGHI

Grammatica libera: una grammatica libera dal contesto è data dalla


quadrupla {insieme finito di simboli non terminali, insieme di simboli
terminali, insieme finito di produzioni, un simbolo iniziale}, visto anche
come un mezzo formale essenziale per definire la sintassi di un linguaggio.

La forma normale di Backus e Naur (BNF) definisce la grammatica libera


dal contesto di un linguaggio di programmazione con un insieme di caratteri
ridotto.

Per semplificare la rappresentazione si usa la Extended BNF che aggiunge


la possibilità di rappresentare opzioni.

Il linguaggio generato da una grammatica è … .

Una grammatica G è ambigua se esiste almeno una stringa di LG che ammette più
di un albero di derivazione.
Un albero di derivazione testimonia la correttezza sintattica della stringa di
partenza.
Ambiguità: una espressione con più parse tree.

Una grammatica ambigua è possibile renderla non ambigua in modo tale che si
possa utilizzare per tradurre (compilare) in modo univoco un programma.

Vincoli sintattici contestuali quali:


1. Numero di parametri uguali al numero di parametri formali;
2. un identificatore deve essere dichiarato prima dell’uso;
3. compatibilità dei tipi in un assegnamento.
… appartengono alla sintassi (perché vincoli sintattici) ma quest’ultima
è definita da BNF - strumento formale non in grado di esprimere vincoli che
dipendono dal contesto (delle dichiarazioni) - allora i vincoli sintattici
contestuali sono semantici, più precisamente fanno parte della semantica
statica.
Quindi non tutti gli aspetti sintattici di un linguaggio sono
descrivibili mediante grammatiche; i controlli di semantica statica
intervengono per eliminare dai programmi legali quelle stringhe che, pur
corrette secondo la grammatica, non rispettano gli ulteriori vincoli
contestuali.

Difficile trovare il giusto equilibrio tra esattezza e flessibilità, in


modo da rimuovere ambiguità, ma anche lasciare spazio all’implementazione.

Semantica
Come la semantica descrive il significato di un linguaggio di
programmazione? Quali punti di vista dobbiamo utilizzare ?

Metodi formali per la semantica:

6
A.A. 18/19 Linguaggi - JEREGHI

- Denotazionale (descrizione delle funzionalità);


- Operazionale (descrizione delle trasformazioni di stato);
- Assiomatica (descrizione delle proprietà).

Ogni forma di semantica descrive l’effetto dell’esecuzione del programma


sulla memoria.

La semantica denotazionale è l’applicazione di tecniche sviluppate per


la semantica del linguaggio logico-matematico [costruendo oggetti matematici
(chiamati denotazioni) i quali esprimono il significato delle espressioni del
linguaggio], quindi il significato di un programma è dato da una funzione la
quale esprime il comportamento I/O del programma stesso.
Inoltre, viene definita come la semantica più astratta con cui
descrivere i programmi sviluppata originariamente da Scott Strachey (1970).

La semantica operazionale specifica il comportamento della macchina


astratta, ossia ne definisce formalmente l’interprete facendo riferimento ad
un formalismo di più basso livello. Le varie tecniche operazioni differiscono
appunto il formalismo adottato; alcune semantiche fanno riferimento ad automi
formali, altre a sistemi di regole logico-matematiche, altre ancora a sistemi
di transizioni che specificano le trasformazioni di stato indotte da un
programma.

Non tutti gli alberi di derivazione corrispondono a programmi legali:


l’analisi di semantica statica ha il compito di selezionare quelli che
soddisfano i vincoli contestuali del linguaggio stesso. Questi compongono la
sintassi astratta del linguaggio.

Come la descrizione sintattica di un linguaggio possa essere utilizzata


per tradurre automaticamente un programma ?

Analisi lessicale
L’analisi lessicale (scanning) è la prima fase della traduzione ed il
suo scopo è quello di leggere sequenzialmente i simboli di ingresso di cui è
composto il programma e di raggruppare tali simboli in unità logicamente
significative dette token (informazione astratta). I token particolarmente
significativi sono: le parole riservate del linguaggio, gli operatori, le
parentesi aperte e chiuse, inoltre i token corrispondono a specifiche
categorie sintattiche.
Più precisamente i token rappresentano coppie di nome (simbolo astratto
che rappresenta una specifica categoria sintattica: identificatori, operatori)
e valore (sequenza di simboli del testo di ingresso: lo specifico
identificatore, lo specifico operatore).

Una volta ottenuta la sequenza di token, l’analisi sintattica avviene su


una grammatica propriamente libera da contesto che usa come simboli terminali
i token prodotti dall’analisi precedente.

7
A.A. 18/19 Linguaggi - JEREGHI

Lo strumento tecnico che si usa per l’analisi lessicale è riconducibile


ad una particolare classe di grammatiche generative (grammatiche regolari).

Analisi sintattica
L’albero di derivazione viene sottoposto ai controlli relativi ai
vincoli contestuali del linguaggio: controllo delle dichiarazioni, i tipi, il
numero di parametri delle funzioni ecc. A questa fase agli identificatori
vengono associati informazioni come il tipo, il luogo di dichiarazioni e altre
utili (per esempio il suo scope), ma per non creare duplicati vengono create
strutture dati apposta fuori dall’albero di derivazione. Una di queste
strutture viene chiamata tabella dei simboli (cioè degli identificatori).

Generazione della forma intermedia


Dato che il compilatore viene realizzato per generare codice non in un
solo linguaggio oggetto ma per una serie di linguaggi ed il codice generato
dalla visita dell’albero di derivazione aumentato non è pronto per essere
trasformato in codice nel linguaggio oggetto perché rimangono da fare molte
ottimizzazioni è opportuno generare codice in una forma intermedia scelta in
modo da essere indipendente sia dal linguaggio sorgente che da quello oggetto.

Ottimizzazione del codice


Tipiche operazioni che possono essere effettuate sono:
- Rimozione del codice inutile (dead code);
- Espansione in-line delle chiamate di funzioni;
- Fattorizzazione delle sottoespressioni;
- Ottimizzazione dei cicli

Rapporto tra parser e scanner


Il controllo del processo di traduzione del codice di un programma viene
mantenuto dall’analizzatore sintattico (parser) che richiede all’analizzatore
lessicale (scanner) un nuovo token ogni volta che ha necessità di procedere in
avanti. In questo modo si riducono le operazioni di I/O e la necessità di
mantenere in memoria versioni diverse dello stesso programma.

Pragmatica
La pragmatica di un linguaggio di programmazione è indipendente dalla
sua definizione iniziale. Rientrano nella pragmatica tutti i suggerimenti di
stile di programmazione.

8
A.A. 18/19 Linguaggi - JEREGHI

Categorie sintattiche
1. Espressioni;
2. Comandi;
3. Dichiarazioni.

- Espressioni
Le espressioni sono il componente essenziale di ogni linguaggio perché
se esistono linguaggi dichiarativi nei quali non sono presenti i comandi, le
espressioni, numeriche o simboliche, sono presenti in tutti i linguaggi.

Un’espressione è un’entità sintattica la cui valutazione produce un


valore oppure non termina, nel qual caso l’espressione è indefinita.
La caratteristica essenziale di un’espressione è che la sua valutazione
produce un valore.

Categoria sintattica che denotano valori. Il linguaggio mi indica quali


valori possono essere manipolati.

Le espressioni aritmetiche sono costituite da: operatori, operandi,


parentesi e chiamate di procedura - elementi con i quali la grammatica
permette di comporre espressioni.

Per poter usare convenientemente le espressioni nel testo dei programmi,


sono necessarie delle notazioni lineari che permettano di scrivere
un’espressione come una sequenza di simboli. Queste differenziano a seconda di
come si rappresenta l’applicazione di un operatore ai suoi operandi. Si
possono distinguere tre tipi principali di notazione:
- In-fissa è la notazione più comunemente usata in matematica e, di
conseguenza, è quella adottata dalla maggior parte dei linguaggi di
programmazione;
- Pre-fissa, usando questo tipo di notazione non servono parentesi e
regole di precedenza fra gli operatori, purché sia nota l’arietà (cioè
il numero di operandi) di ogni operatore. Infatti non vi è ambiguità su
quale sia l’operatore da applicarsi a determinati operandi, dato che
questo è sempre quello che precede immediatamente gli operandi stessi;
- Post-fissa.
La scelta della notazione può determinare le regole di associatività e/o
precedenza, o incidere su altre caratteristiche delle espressioni.

Le regole di precedenza di un operatore per la valutazione di


un'espressione definiscono l’ordine in cui operatori “adiacenti” a diversi
livelli di precedenza vengono valutati.
Per la notazione infissa se non si usano estensivamente le parentesi
occorre chiarire le precedenze fra i vari operatori, ad esempio le convenzioni
matematiche: prima la moltiplicazione e poi la somma. Per evitare un uso
eccessivo di parentesi, i linguaggi di programmazione usano dunque delle

9
A.A. 18/19 Linguaggi - JEREGHI

regole di precedenza che specificano una gerarchia fra gli operatori


relativamente all’ordine di valutazione. Questa convenzione (matematica), in
genere, è rispettata per la maggior parte dei linguaggi di programmazione.

Le regole di associatività per la valutazione delle espressioni


definiscono l’ordine con cui operatori allo stesso livello di precedenza
vengono valutati. La maggior parte degli operatori aritmetici si associano da
sinistra a destra (l’operatore di esponenziazione associa spesso da destra a
sinistra).

Le regole di precedenza e associatività, nella notazione infissa, o la


struttura, nelle rappresentazione ad albero, non forniscono alcuna indicazione
su quale ordine seguire nel valutare i vari operandi di uno stesso operatore
(ossia i nodi allo stesso livello).

A partire da un albero il compilatore produce codice oggetto oppure


l’interprete valuta l’espressione. L’ordine di valutazione delle
sott-espressioni è importante per vari motivi:
- Operandi non definiti: è importante sapere se il linguaggio adotta una
valutazione lazy (si valutano solo gli operandi strettamente necessari)
oppure eager (tutti gli operandi sono comunque valutati
indipendentemente dall’espressione).
- Effetti collaterali: side effects funzionali cioè quando una funzione
cambia un suo parametro o una variabile locale.

Un effetto collaterale è un’azione che influenza i risultati (parziali o


finali) di una computazione senza però restituire esplicitamente un
valore al contesto nel quale essa è presente. La possibilità di effetti
collaterali fa sì che l’ordine di valutazione degli operandi sia
rilevante ai fini del risultato.
- Aritmetica finita: la precisione limitata dall’aritmetica del
calcolatore fa sì che cambiando l’ordine degli operandi si possano
ottenere risultati diversi (particolarmente rilevanti nel caso di
computazioni in virgola mobile).

10

Potrebbero piacerti anche