Approfondimenti sui linguaggi di programmazione

Lezione 1 - Riepilogo modulo 2 e
approfondimenti
Corso — Programmazione
Linguaggi di programmazione— Approfondimenti sui linguaggi
di programmazione
Marco Anisetti
e-mail: marco.anisetti@unimi.it
web: http://homes.di.unimi.it/anisetti/
Ricapitolando M2(1)
• Da programmazione non strutturata a strutturata
• E' utile esercitarsi nella scrittura di codice non strutturato
per comprendere bene quali siano i problemi che introduce
• L'uso di goto espliciti rendono più evidente l'importanza
del flusso e il concetto di salto ad una istruzione
(embrione del salto a sottoprogramma)
• Costrutti di programmazione strutturata - leggibilità e
flusso di esecuzione evidente - confronto con versioni non
strutturate
• Associazione tra flusso del programma e flusso
dell'algoritmo (sequenzialità)
Ricapitolando M2(2)
• Linguaggi pseudoformali per la descrizione di programmi flow chart e pseudocodice
• Flow chart permette di avere ben evidente il flusso
• Pseudocodice si avvicina al codice che dovremo scrivere
permette di esercitarsi sulla logica della programmazione
• Tipi di dato e importanza delle strutture dati - dati ed
algoritmi
• Occupazione in memoria del codice e dei dati (presentata
incrementalmente)
• Relazione tra strutture dati e algoritmi
Ricapitolando M2(3)
• Relazioni di ricorrenza ed invarianti di ciclo - associate ad
errori nel codice
• Associazione tra tipi di dato e iterazioni (array e cicli)
• Operazioni su tipi di dato strutturato - dipendenza dalla
sequenzialità della memorizzazione della struttura
• Modularità, sottoprogrammi e chiamate - associazione tra
logica dei sottoprogrammi e progettazione del programma
• Utilizzo dello stack e motivazioni delle chiamate
• Keep it simple, suddividere in compiti semplici il più
possibile confinati (ad eccezione delle interfacce)
Ricapitolando M2(4)
• Ricorsione e strutture dati dinamiche
• Heap e allocazione dinamica della memoria, vantaggi e
svantaggi
• Gestione di strutture dinamiche
• Strutture dinamiche e valutazioni sulla loro efficacia
• Fasi della programmazione e relazione tra le fasi
• Importanza della raccolta delle specifiche e del modello
• Evitare retroazioni sulla fase di verifica
• Verifica empirica e formale
Lezione 2 - Principi della programmazione
strutturata
Corso — Programmazione
Linguaggi di programmazione— Approfondimenti sui linguaggi
di programmazione
Marco Anisetti
e-mail: marco.anisetti@unimi.it
web: http://homes.di.unimi.it/anisetti/
Programmazione strutturata e Top-Down
• Approccio alla progettazione di algoritmi
• Scomposizione gerarchica del problema
• Algoritmi chiari e ben strutturati
• Livelli di dettaglio crescente
• Mantenere, in fase di codifica, chiarezza e strutturazione
dell'algoritmo
• La programmazione strutturata rappresenta la naturale
estensione dell'approccio top-down al processo di scrittura
del codice
Programmazione strutturata e Top-Down
• Al crescere delle dimensioni di un programma, diventa
sempre più difficile per il programmatore contemplarlo nel
suo complesso. “Non si vede la foresta guardando degli
alberi”
• Scomporre il programma in blocchi di dimensione gestibile
• Disciplinare la codifica seguendo rigorosamente delle
convenzioni uniformi
• Limitare al minimo indispensabile le interazioni e le
interferenze tra blocchi distinti
Programmazione strutturata e Top-Down
• La programmazione strutturata è l'estensione dell'analisi
top-down
• Nasce dall'esigenza di gestire la complessità di un
programma al crescere delle sue dimensioni
• La programmazioe evolve perchè trainata da necessità di
leggibilità portabilità e manutenibilità
• Programmi sempre più complessi sviluppati con strumenti
più astratti e riusando codice
Attività di processo e attività di gestione
• Qualsiasi programma comprende due tipi di attività:
processo e gestione
• Attività di processo: dove vengono effettivamente svolti
i calcoli
• Attività di gestione: tutto ciò che è accessorio
all'effettivo svolgimento dei calcoli: prendere decisioni,
trasferire il controllo a un'altra parte del programma
(salti), allocare e deallocare variabili
• La strutturazione modulare mappa requisiti e
sottorequisiti e la struttura è di natura gerarchica
(rapporti chiari tra moduli)
Regole per la modularità(1)
• Ogni modulo dovrebbe avere una dimensione tale da
permettere al programmatore di vederlo tutto in una sola
schermata (tutto il suo flusso)
• Un modulo non dovrebbe contenere più di tre livelli di
strutture di controllo una dentro l'altra, e possibilmente di
meno
• Tutte le attività integrate in un modulo dovrebbero
riguardare la stessa fase concettuale dell'algoritmo che
viene realizzato
• Tutte le attività integrate nello stesso modulo dovrebbero
essere eseguite nelle stesse condizioni
Regole per la modularità(2)
• Tutte le attività integrate nello stesso modulo dovrebbero
lavorare sugli stessi dati (embrione di programmazione
orientata agli oggetti)
• Idealmente, ciascun modulo dovrebbe essere volto ad
ottenere un solo scopo specifico o a realizzare una ben
precisa funzionalità
• La programmazione strutturata pone dei vincoli ai modi in
cui l'esecuzione di attività di processo possa essere
controllata
• Ciascun blocco compresi i blocchi strutturati devono avere
un punto di ingresso e uno di uscita
Lezione 3 - Riepilogo M1 e
approfondimenti
Corso — Programmazione
Linguaggi di programmazione— Approfondimenti sui linguaggi
di programmazione
Marco Anisetti
e-mail: marco.anisetti@unimi.it
web: http://homes.di.unimi.it/anisetti/
Ricapitolando M1(1)
• UD1: Significato di programmabile vs. regolabile
• Ruolo del programmatore oggi - spendibilità delle
competenze e importanza delle competenze nel ciclo di
vita del software
• UD2: Concetti matematici di base
• Insiemi di oggetti e proprietà degli insiemi - funzioni su
insiemi di oggetti
• Valutazione di predicati e valori di verità - valutazione di
condizioni
• Induzione matematica per la valutazione di iterazione e
ricorsione
Ricapitolando M1(2)
• UD3: Linguaggio formale - Macchina a stati e architettura
di riferiento per il programmatore
• Definizione di un linguaggio formale e proprietà del
linguaggio e delle parole del linguaggio (riconoscitore e
generatore)
• Macchina a stati per definire comportamenti e soluzioni a
problemi (rapporto con il linguaggio)
Linguaggio regolare e AUTOMA (riconosciuto da qualche
ASFD o ASFND)
Ricapitolando M1(3)
• UD4: Algoritmo - programma - esecutore (insieme di
passi elementari da eseguire in un ordine predefinito)
• Decidibilità - trattabilità - intrattabilità
• Valutazione per il confronto tra algoritmi differenti
• Costosto computazionale e di spazio
• Classe di complessità e MdT deterministica o con oracolo
• UD5: Linguaggio per la descrizione di algoritmi
considerando un elaboratore elettronico come esecutore
• Grammatica sintassi e semantica
• Prima classificazione dei linguaggi di programmazione
dipendenti dalla distanza dall'hardware.
Ricapitolando M1: linguaggi
• Parse tree relativo alla grammatica e ambiguità (più alberi
per la stessa grammatica)
• Una stringa di terminali di una grammatica appartiene al
linguaggio se ammette un albero di derivazione (da sx
verso dx le foglie sono la stringa)
• Posso disambiguare modificando la grammatica (ma non
sempre)
• L'albero è la rappresentazione interna della stringa per un
linguaggio di programmazione
Grammatica ambigua (1)
[Grammatiche ambigue] Una grammatica G si dice ambigua
se esiste una stringa che ammette due o più alberi sintattici
distinti
• Esempio: grammatica G per espressioni aritmetiche:
• S→S |S + S|S − S|S ∗ S|S/S|(0|1|2|3|4|5|6|7|8|9)
• la stringa s = 6 + 5 ∗ 9 ammette due alberi sintattici distinti
Grammatica ambigua (2)
• Una grammatica ambigua non è adatta all'analisi
sintattica e alla traduzione automatica
• Una grammatica è adatta all'analisi sintattica se per tale
grammatica è possibile costruire un algoritmo
deterministico (analizzatore sintattico)
• Se ambigua il problema è inerentemente
non deterministico: esistono più alberi sintattici per la
stessa sequenza di input
• La forma delle regole deve essere tale da evitare il non
determinismo
Grammatica ambigua (3)
• Nel caso non si riesca a fornire una grammatica non
ambigua devono essere definite delle regole per
disambiguare attraverso convenzioni
• Un esempio classico è quello della
Dangling-Else ambiguity
• Consideriamo due produzioni
S ::= if E then S
S ::= if E then S else S
• Con S che è l'istruzione e E che è l'espressione da valutare
• Consideriamo: if E1 then if E2 then S1 else S2
• Questa costruzione genera due alberi ed è quindi ambigua
• Si risolve ad esempio imponendo la convenzione che l'else
appartiene all'if più vicino non chiuso
Ricapitolando M1: disambiguare
• Considerate l'esempio di grammatica ambigua visto in
precedenza:
< expr >::=< identificatore > | < numero > |− < expr >
|(expr)| < expr > + < expr > | < expr > ∗ < expr >
• Scrivere una versione non abigua della stessa grammatica
(introdurre nella grammatica la precedenza di * su +)
• Grammatica più complicata ma non ambigua:
< expr >::=< term > | < term > + < expr >
< term >::=< atom > | < atom > ∗ < term >
< atom >::=< letterale > |− < atom > |(< expr >)
< letterale >::=< identificatore > | < numero >
dove numero e identificatore saranno definiti
conformemente alla loro natura
Vincoli contestuali sintattici
• Esempio: il numero di parametri formali deve essere
uguale a quello dei parametri attuali (chiamata a
sottoprogramma)
• BNF non sa esprimere vincoli che dipendono dal contesto
(dipende dalla dichiarazione)
• Servirebbero grammatiche contestuali (riscrittura di un
non terminale solo in un determinato contesto)
• Esempi: dichiarazione prima di utilizzo, compatibilità tra
tipi
• Soluzione: si intende per sintassi tutto ciò che posso
descrivere in BNF il resto lo delego alla semantica
BNF nei linguaggi di programmazione
• Spesso il BNF di un vero linguaggio di programmazione
viene arricchito da altri elementi (EBNF)
• [] la frase è opzionale, []∗ oppure {} la frase è ripetibile
una, nessuna o più volte (operatore star)
• In alcuni dei libri consigliati trovate la BNF o le carte
sintattiche specifiche del linguaggio
• Si trovano esempi di linguaggi molto semplici in rete (es Il
linguaggio KISS) da cui si può partire per vedere come
vengono rappresentati gli elementi fondamentali del
linguaggio
• Tali elementi sono: espressioni matematiche, espressioni
booleane, costrutti strutturati, come inizia il programma e
come si dichiarano e utilizzano le funzioni.
BNF nei linguaggi di programmazione:
esempio(1)
• Esempio BNF parziale con KISS per la definizione di un
programma:
< program >::= PROGRAM < top − level decl >< main >0 .0
• Un programma è definito come un insieme di dichiarazioni
di primo livello, precedute dalla keyword PROGRAM,
seguite dal main e dal punto (obbligatorio)
< main >::= BEGIN < block > END
< block >::= [< statement >]∗
• Un blocco consiste a sua volta di zero o più statement,
che sono assegnazioni o strutture di controllo
BNF nei linguaggi di programmazione:
esempio(2)
• Come in Pascal le dichiarazioni di variabile sono
identificate dalla keyword VAR:
< top − level decls >::= [< data declaration >]∗
< data declaration >::= VAR < var − list >
• Le sezioni VAR possono essere ripetute, cioè apparire più
volte, a differenza del Pascal standard. Ovviamente la
< var − list > può essere una variabile singola nel caso più
semplice
< var − list >::=< ident > [, < ident >]∗
Programmi di parsing
• Dato un file di input contenente del testo e supponendo
una certa grammatica, controllare che il testo sia stato
generato dalla grammatica
• In aggiunta si chiede di capire come è stato generato quali
regole e quali alternative sono state usate durante la
derivazione
• Questo processo è chiamato parsing o analisi sintattica
• Programmi non semplici da scrivere dato che bisogna
scegliere tra alternative
• L'ideale sarebbe poter vedere in avanti per decidere
meglio (anche solo il simbolo successivo)
• Generatori di parser: genera un parser a partire dalla
descrizione della grammatica e codice associato alle
regole della grammatica (semantica)
Progettazione di un parser(1)
• Analisi di un flusso derivante di solito da un file
contenente un codice (in linea teorica posso parsare
qualsiasi cosa anche non del codice)
• Le funzionalità sono: i)Verificare che il flusso sia valido, ii)
Convertire il flusso in un Abstract Syntax Tree o parse
tree, iii) Agire se si trova un pattern specificato nello
stream (analisi semantica)
• I parser possono essere ottenuti in molti modi.
Inizialmente fatti a mano, ad oggi generati da un tool di
parser generator o compiler compiler
• Fasi di parsing:
Scanner: Legge il flusso ed identifica i token. Esempio
if (x = 5) genera 6 token guardando alle regole della
grammatica e converte in un formato che il parser potrà
più facilmente utilizzare (if ( <id>=<num>))
Parser: Prende i token e fa il match con le regole
grammaticali generando se richiesto un albero
Progettazione di un parser(2)
• Due tipi di algoritmi di parsing dipendenti da come
agiscono: 1) algoritmi top down e 2) algoritmi bottom up
• Top down parsing: reperire i token dal flusso uno alla
volta e confrontarli con le regole grammaticali più in alto
nell'albero delle regole (LL recursive descent, tail
recursive, earleys algorithm)
• Bottom up parsing: fanno l'opposto partendo dalle
regole in basso nella grammatica fino ad arrivare a quelle
sopra (LR, LALR, GLR, recursive ascendent algorithm)
• Alcuni algoritmi controllano avanti i token per evitare di
intraprendere strade errate (look ahead). Esempio LL(k)
con k uguale ai token avanti che si ispezionano. Più è
grande k più è lento il parsing (solito LL(1))
Lezione 4 - Analisi semantica di un
linguaggio di programmazione
Corso — Programmazione
Linguaggi di programmazione— Approfondimenti sui linguaggi
di programmazione
Marco Anisetti
e-mail: marco.anisetti@unimi.it
web: http://homes.di.unimi.it/anisetti/
Motivazione per il linguaggio
• Linguaggi facilmente meccanizzabili
• Sintassi univoca e ben definita
• Linguaggio definito formalmente
• Potere espressivo e paradigma implementato dal
linguaggio
• E' buona cosa non affezionarsi troppo ad un linguaggio
Semantica del linguaggio
• La sintassi la sappiamo descrivere e abbiamo appena
rivisto come farlo ed alcuni esempi di concreto utilizzo
(parsing)
• Abbiamo visto che in alcuni casi la sintassi non
contestuale non è in grado di stabilire dei vincoli sintattici
• Vediamo in seguito come fare a demandare questo
controllo alla fase di analisi semantica
Controlli semantici
• La semantica serve generalmente per determinare cosa fa
un programma
• Semantica statica (vincoli statici a compile time)
Controlli che la sintassi non sa fare
E' più sintassi che semantica
• Tre cose vengono aggiunte alla grammatica non
contestuale per formare una grammatica ad attributi:
Attributi: associati a simboli della grammatica con la
possibilità di possedere dei valori
Regola semantica: sequenza di azioni semantiche
Azione semantica: azione in grado di assegnare valori
agli attributi e di avere altri effetti
Semantica statica: attributi
• Per i simboli X della grammatica si associa un set di
attributi A(X ) dati dall'unione di due set disgiunti
• Attibuti sintetizzati usando le informazioni del parse tree
risalendo verso l'altro
• Attibuti ereditati usando le informazioni del parse tree
discendendo verso il basso
• Gli attributi sintetizzati realizzano un flusso informativo
ascendente nell'albero sintattico (dalle foglie verso la
radice)
• Gli attributi ereditati realizzano un flusso informativo
discendente (dalla radice verso le foglie) e laterale (da
sinistra verso destra e viceversa) nell'albero sintattico
Semantica statica(1)
• Le grammatiche ad attributi associano attributi semantici
a simboli sintattici e regole semantiche a regole sintattiche
• Aggiungono annotazioni alle produzioni in modo da dare
significato a cio' che generano.
• Gli attributi rappresentano il significato (valore) dei
simboli sintattici
• Le azioni semantiche sono il meccanismo che effettua il
calcolo di tali valori, ottenendo quindi il significato
• Il valore dell'attributo di un token è fornito
dall'analizzatore lessicale, mentre il valore dell'attributo di
un non-terminale è calcolato da regola semantica
Semantica statica: esempio(1)
• Un parse tree di una grammatica con attributi, è un parse
tree basato sulla grammatica con dei valori per gli attributi
• Se tutti gli attributi hanno valore si parla di albero <fully
attributed>
• Numeri binari calcoliamone il valore:
N → D|ND
D → 0|1
• Grammatica ad attributi con attributo value
D → 0 {D. value := 0}
D → 1 {D. value := 1}
N → D {N. value := D. value}
N1 → −N2 D {N1 . value := 2 ∗ N2 . value + D. value}
Semantica statica: esempio(2)
Semantica statica nel linguaggio di
programmazione
• Esempio: in Ada una procedura è
procedure foo
end foo;
• Come dire che il nome indicato all'inizio sia lo stesso della
fine (questione sintattica)
<procedure> → procedure <proc name>[1] <proc body>
end <proc name>[2]
• Predicato semantico: <proc name>[1].string = <proc
name>[2].string
• Esempio sui tipi:
<assign sum> → <var>[1] = <num>[1] + <num>[2]
• Consideriamo solo 2 tipi possibili
• Regola semantica:
<var>[1].type = (if ((<num>[1].type equal
int)and(<num>[2].type equal int))then int else real)
Semantica dinamica(1)
• Semantica dinamica (vincoli determinati durante
l'esecuzione)
• Esistono vari metodi per definire la semantica di un
linguaggio: algebrica, assiomatica, operazionale,
denotazionale
• Operazionale: Structured Operational Semantics (SOS)
definizione della semantica guidato dalla sintassi
• Si definiscono regole di transizione che specificano i passi
di computazione per un costrutto composto A op B in
termini di quelli dei componenti
• La forma è quella della deduzione naturale (premessa /
conseguenza)
Semantica operazionale
• Solitamente i costrutti del linguaggio modificano una
qualche nozione di stato quindi le regole che si utilizzano
sono regole definite su coppie < comando,stato >
• Una transizione è < comando,stato > →
< comando0 ,stato0 >
• Le regole definiscono induttivamente la relazione →
dicendo come si passa da una configurazione ad un altra
• Configurazioni e relazioni di transizione costituiscono un
Sistema di Transizione (Configurazioni,configurazioni
iniziali, configurazioni finali, relazione di transizione)
• Una sequenza di configurazioni da una iniziale ad una
finale è una sequenza di esecuzioni
Esempio di Semantica di un linguaggio
imperativo(1)
• Sintassi semplificata:
a::= n|X |a0 + a1 |a0 − a1 |a0 ∗ a1
b::= true|false|a0 = a1 |a0 ≤ a1 |¬b|b0 ∧ b1 |b0 ∨ b1
c::=skip|X := a|c0 ; c1 |if b then c0 else c1 |while b do c
endwhile
Esempio di Semantica di un linguaggio
imperativo(2)
• Semantica espressioni:
Numeri: < n,σ >→ n
Locazioni: < X ,σ >→ σ(X )
Somma (n somma di n0 e n1 ):
<a0 ,σ>→n0 <a1 ,σ>→n1
<a0 +a1 ,σ>→n
Esempio di Semantica di un linguaggio
imperativo(3)
• Le regole usano metavariabili a0 , n, X sui domini sintattici
• Posso instanziare queste regole usando particolari valori
espressioni o variabili
• Esempio di una moltiplicazione
<3,σ0 >→3 <2,σ0 >→2
<3∗2,σ0 >→6
• Le istanze delle regole si compongono in alberi di
derivazione: i) tutte le premesse di istanze di regole sono
conclusioni delle regole appena sopra, ii) le radici
dell'albero sono assiomi senza premesse
Esempio di Semantica di un linguaggio
imperativo(4)
• Esempio di albero:
<init,σ0 >→0 <4,σ0 >→4 <8,σ0 >→8 <6,σ0 >→6
<(init+4),σ0 >→4
<8+6,σ0 >→14
<(init+4)+(8+6),σ0 >→18
Esempio di Semantica di un linguaggio
imperativo(5)
• Altri esempi di semantica per la sequenza
<c0 ,σ>→σ 00 <c1 ,σ 00 >→σ 0
<c0 ;c1 ,σ>→σ 0
• Semantica ciclo while:
<b,σ>→false
<while b do c endwhile,σ>→σ
<b,σ>→true <c,σ>→σ 00 <while b do c endwhile,σ 00 >→σ 0
<while b do c endwhile,σ>→σ 0
Compilatore
• Sintassi espressa con grammatica ma semantica più
difficile da esprimere
• Un compilatore genericamente trasforma un linguaggio in
un altro. Se la trasformazione prevede come
linguaggio di target quello della macchina allora tenderà a
produrre un programma che potrà essere eseguito una
volta completo di tutte le sue parti
• Un compilatore lavora a fasi
• Front end: Analisi lessicale (produce token), sintattica
(analizza frasi di token) e semantica
• Back-end: Vedremo i dettagli più avanti nel modulo
Compiler compiler
• Compiler compiler come Yacc o Accent servono per
generare un processatore di linguaggi come un
compilatore, un interprete o un traduttore da una
descrizione di alto livello
• Si specifica la grammatica del linguaggio e il compiler
compiler crea un programma che processa l'input scritto
nel linguaggio
• Il programma generato analizza gerarchicamente l'input e
attacca una semantica ad ogni frase che riconosce data la
grammatica
• Spesso si usano varianti della BNF
• Un esempio il BNFC usa labelled BNF
Compiler compiler: associare la semantica
• Le azioni di associazione semantica avvengono dopo il
controllo sintattico del parser (rifiuta frasi non conformi
alla grammatica)
• Occorre associare le regole semantiche ai pattern sintattici
• Spesso queste regole vengono scritte direttamente nella
grammatica
• A volte è del codice C inserito in parentesi graffe che viene
ricopiato nel programma
Linguaggi di programmazione esoterici(1)
• Un linguaggio di programmazione esoterico è una
tipologia di linguaggi di programmazione particolarmente
complessi e volutamente meno chiari possibile
• Non hanno una vera utilità nel mondo reale, ma sono
generalmente concepiti per mettere alla prova i limiti della
programmazione su computer
• Alcuni, invece, sono concepiti come esercizio per
comprendere meglio il funzionamento di un calcolatore
• Wouter van Oortmerssen ha creato FALSE, un linguaggio
basato sul concetto di macchina a stack dotato di una
sintassi confusa, illeggibile ed estremamente concisa: il
compilatore occupa solamente 1024 byte
Linguaggi di programmazione esoterici(2)
• Questo ha in seguito ispirato Urban Muller a creare un
linguaggio ancora più conciso, il brainfuck, composto da
soli otto caratteri riconosciuti
• Hello word in brainfuck:
++++++++++[>+++++++>++++++++++>+++
>+<<<<-]>++.>+.+++++++.
.+++.>++.<<+++++++++++++++.>.+++.-----.--------.>+.>.
• Shakespeare la frase seguente indica un punto nel listato
che può essere raggiunto tramite un'istruzione simile a
GOTO:
Act I: Hamlet's insults and flattery.
• In RAPTOR è possibile generare un eseguibile o compilare
il programma
• Alla luce di quello che possiamo intuire ad oggi come farà
RAPTOR a generare un eseguibile o ad essere compilato?