540 likes | 684 Views
Implementazione di Linguaggi 2 PARTE 6. Testo: A.V. Aho, R. Sethi, J.D.Ullman Compilers Principles,Techniques and Tools, Addison Wesley. Type Checking. Si divide in statico se eseguito durante la compilazione e dinamico se eseguito durante l’esecuzione del programma.
E N D
Implementazione di Linguaggi 2PARTE 6 Testo: A.V. Aho, R. Sethi, J.D.Ullman Compilers Principles,Techniques and Tools, Addison Wesley
Type Checking Si divide in statico se eseguito durante la compilazione e dinamico se eseguito durante l’esecuzione del programma. È affiancato da altri controlli: • Flow-of-control: goto return exit … • Unicità di definizione: identificatori nello stesso scope, label di switch/case stat. • controlli contestuali dei nomi es. (Modula) PROCEDURE P;…END P;
Ruolo del Type Checker Sostanzialmente controllare che le operazioni vengano applicate a tipi compatibili.
Type Expressions (TE) Sono espressioni che servono per definire i tipi di dato di un linguaggio. • usano operatori detti type constructors e operandi detti tipi base. • Le TE denotano i tipi di un linguaggio e sono fortemente dipendenti da esso.
Type Expressions (TE) Formalmente: • Un tipo base (int, float, integer, real, complex, …) è una type expression; • Un tipo può avere un nome, per cui il nome di un tipo è una type expression; • Un costruttore di tipo applicato ad una type expression genera una nuova type expression:
Type Expressions (TE) • Costruttori di tipo • Array: se T è una TE allora array(I,T) è una TE che denota un tipo array con tipo base T e tipo indice I • Prodotti cartesiani: se T1 e T2 sono TE allora T1T2 è una TE che denota l’insieme delle coppie (t1,t2) con t1T1 e t2T2 ( associa a sinistra) • Record: il prodotto del tipo dei suoi campi, differisce dal prodotto per la commutativitò degli elementi dovuta alla selezione mediante nome (field label) ES. {f1: 1;…; fn: n} un record con campi fidi tipoi
Type Expressions (TE) • Puntatori: se T è una TE allora pointer(T) è una TE che denota un riferimento (puntatore) ad un oggetto di tipo T • Funzioni: è il tipo di una funzione che mappa un tipo dominio D in un tipo rango R e denotato DR. • Class: è un misto tra il concetto di modulo e di tipo. È modellabile come un record con campi di tipo procedura e di tipo funzione detti metodi.
Type System • Un Type System è una collezione di regole per assegnare TE alle parti di un programma. • È interessante implementare type system con DGDS • Un Type Checker (TC) è un programma che implementa un Type System. • Ogni TC può essere applicato a run time purché il codice oggetto contenga informazioni che specificano il tipo di ogni dato.
Type System Un Type System è detto sound se • elimina la necessità di type checking a run time, cioè se il TC assegna un tipotype-error ad ogni dato del programma, allora nessun errore di tipo si può verificare durante l’esecuzione.
Type checking: Schema di traduzione DS Un semplice type checker: P D ; E D D ; D | id : T T char | integer | array[num] of T | ^T E literal | num | id | E mod E | E [E] | E^ Esempio key: integer; key mod 1999
Type checking: Schema di traduzione DS Aggiungo il tipo base type_error: P D ; E D D ; D D id : T addtype(id.entry,T.type) T char T.type:=char T integer T.type:=integer Tarray[num] of T1T.type:=array(1..num.val,T1.type) T ^T1T.type:=pointer(T1.type)
Type Checking di Espressioni E literal E.type:=char E num E.type:=integer E id E.type:=lookup(id.entry) EE1 mod E2E.type:= if E1.type=integer & E2.type=integer then integer else type_error
Type Checking di Espressioni E E1 [E2] {E.type:= if E2.type=integer & E1.type=array(s,t) then t else type_error} E E1^ {E.type:= if E1.type=pointer(t) then t else type_error}
Type Checking di Istruzioni S id := E S.type:= if id.type=E.type then void else type_error S if E then S1 S.type:= if E.type =boolean then S1.type else type_error S while E do S1 S.type:= if E.type=boolean then S1.type else type_error S S1 ; S2S.type:= if S1.type=void & S2.type=void then S.type=void else type_error
Type Checking di Funzioni T T1 ’’ T2 T.type:=T1.typeT2.type E E1 (E2) E.type:=if E2.type= s & E1.type= st then t else type_error Le funzioni n-arie f(t1:T1,t2:T2,…,tn:Tn) vengono assimilate a funzioni unarie di tipo T=T1T2…Tn root: (real real) real real
Equivalenza di Tipo I linguaggi usano diversi modi e definizioni per stabilire se due tipo sono equivalenti. Gli approcci principali sono: • Equivalenza strutturale (Fortran, C, C++, …) • Equivalenza per nome (Modula, Oberon, Ada, Pascal, …) • Misti.
Equivalenza di Tipo Esempio in Modula-2. • Dati: TYPE A=ARRAY[0..n] OF REAL; B=ARRAY[0..n] OF REAL; Definiscono due tipi non equivalenti, perché dotati di nome diverso (equivalenza per nome). Tuttavia, A e B sono identici e quindi strutturalmente equivalenti.
Equivalenza di Tipo: per Nome Due variabili hanno tipo equivalente per nome: • se appartengono alla stessa dichiarazione, • se appaiono in dichiarazioni diverse ma abbinate allo stesso identificatore di tipo. • nei linguaggi Oberon-like si ha che due nomi di tipo T e T’ dichiarati uguali denotano due tipi T e T’ identici. NOTA: Linguaggi come PL/M considerano i tipi T e T’ definiti in 3. non equivalenti.
Equivalenza di Tipo: per Nome Esempi: • VAR a, b: ARRAY[1..n] OF REAL; (*ab stessa dichiarazione*) • PROCEDURE O(a:T); VAR b:T (* a e b sono equivalenti perché usano lo stesso type id T*) • TYPE T’ = T (* T e T’ denotano due tipi identici perché dichiarati uguali *)
Equivalenza di Tipo: per Nome Due tipi sono compatibili se: • sono identici, oppure • uno è sottorango dell’altro, oppure • entrambi sono sottorango di uno stesso tipo. Un sottorango è definito restringendo ad un intervallo l’insieme dei valori possibili di un tipoordinalediscreto (es. integer o elencazione). Il concetto di sottorango è un caso particolare di sottotipo, un concetto che vedremo in seguito.
Equivalenza di Tipo: per Nome Esempi. TYPE link = ^cell; VAR next : link; last : link; p : ^cell; q : ^cell; Equivalenza per nome: • next e last hanno lo stesso tipo perché sono associati alla stessa type expression, • p, q, r hanno lo stesso tipo, • p e next non sono equivalenti perché associati a type expression diverse.
Compatibilità per Assegnamento Un’espressione E di tipo T2 è detta compati-bile rispetto all’assegnamento ad un tipo T1 sse: • T1 e T2 sono identici e nessuno è di tipo file, • T1 è real e T2 integer, • T1 e T2 sono tipi ordinali compatibili e il valo-re di E appartiene all’intervallo chiuso speci-ficato da T1 , • T1 e T2 sono tipi stringa compatibili.
Equivalenza di Tipo: Strutturale Due type expression E1 e E2 sono struttural-mente equivalenti se: • E1 e E2 sono lo stesso tipo base, oppure • E1 e E2 sono ottenute applicando lo stesso costruttore di tipo a tipi strutturalmente equivalenti. Quindi due tipi sono strutturalmente equivalenti se sono identici.
Equivalenza di Tipo: Strutturale Esempi. • la type expression integer è equivalente solo a integer • Pointer(integer) è equivalente solo a pointer(integer)
Equivalenza di Tipo: Strutturale Spesso è possibile codificare le type expression in modo da semplificare la verifica di equivalenza strutturale. Famosa è quella adottata nel compilatore C di Ritchie e Johnson. • Costruttori di tipo: pointer(t), freturns(t) e array(t).
Codifica delle type expression. Poiché tutti i costruttori di tipo considerati sono operatori unari le type expression formate applicando questi costruttori ai tipi base hanno una struttura molto uniforme: char freturns(char) pointer(freturns(char)) array(pointer(freturns(char)))
Algoritmo Ricorsivo per Equivalenza Strutturale FUNCTION SEQ(s,t):BOOLEAN; BEGIN IF s & t are the same basic type THEN RETURN TRUE ELSIF s=array(s1,s2) & t=array(t1,t2) THEN RETURN SEQ(s1,t1) & SEQ(s2,t2) ELSIF s=s1s2 & t=t1t2 THEN RETURN SEQ(s1,t1) & SEQ(s2,t2) ELSIF s=pointer(s1) & t=pointer(t1) THEN RETURN SEQ(s1,t1) ELSIF s=s1s2 & t=t1t2 THEN RETURN SEQ(s1,t1) & SEQ(s2,t2) ELSE RETURN FALSE END
Cicli nelle rappresentazioni di tipi Molte strutture dati sono definite ricorsivamente: Esempio. Linguaggi che permette di assegnare un nome a un tipo. TYPE link= ^cell: cell= RECORD info:INTEGER; next: link END;
Cicli nelle rappresentazioni di tipi I nomi di tipo definiti ricorsivamente possono esse-re sostituiti se siamo disposti ad introdurre i cicli nel type graph. • Sostituendo link con pointer(cell) cell = record x x x info integer next pointer cell
Cicli nelle rappresentazioni di tipi Possiamo eliminare il riferimento a cell nel type graph: cell = record x x x info integer next pointer
Cicli nelle rappresentazioni di tipi Esempio. Il C evita i cicli nei type graph usando l’equivalenza strutturale per tutti i tipi tranne i record. Struct cell{ int info; struct cell *next; };
Conversioni di Tipo • Consideriamo l’espressione: x +i con x di tipo real e i di tipo integer • i e x hanno rappresentazioni diverse, • le istruzioni macchina usate per real e integer sono diverse, • il compilatore deve convertire gli operandi del + per renderli dello stesso tipo. Il type checker può inserire le conversioni nella rappresentazione intermedia del programma.
Conversioni di Tipo La conversione da un tipo ad un altro è detta implicita se è fatta in modo auto-matico dal compilatore. Le conversioni implicite vengono anche dette coercions. La conversione è detta esplicita se il programmatore deve scrivere qualcosa per causare la conversione.
Monomorfismo e Polimorfismo In molti linguaggi tipati staticamente (es. Modula) una variabile T può ricevere valori solo di tipo T. I tipi sottorango di Modula verificano banalmente questa proprietà detta monomorfismo. I linguaggi object-oriented sono polimorfi, nel senso che le variabili possono ricevere valori di più di un tipo.
Generi di Polimorfismo Cardelli e Wegner hanno classificato il polimorfismo in • Universale suddiviso in • Parametrico (generici) • Inclusione (object-oriented) • Ad Hocsuddiviso in • Overloading • Coercion
Polimorfismo Parametrico e Ad Hoc Nel polimorfismo parametrico una funzione opera uniformemente su un insieme di tipi che hanno una struttura comune Nel polimorfismo ad hoc una funzione opera su tipi distinti e su ciascuno si comporta in maniera indipendente dagli altri
Polimorfismo Parametrico Nel polimorfismo parametrico l’uniformità è ottenuta mediante parametri di tipo. Il polimorfismo universale opera, in generale, su un insieme potenzialmente infinito di tipi. Nel polimorfismo di inclusione un oggetto appartiene a molte classi diverse ma legate tra loro da ua relazione di inclusione.
Sottotipazione E’ un concetto legato a quello di tipo e di polimorfismo che definisce molti aspetti del type checking. E’ una relazione di preordine su tipi basata sul principio di subsumption: se è un sottotipo di allora un valore di tipo è accettato in ogni contesto in cui è previsto un valore di tipo . In formule: ⊦ e: <: ___________ ⊦ e:
Sottotipazione strutturale e per nome L’equivalenza di tipo adottata influisce anche sulla sottotipazione. Strutturalmente diciamo che 1 2 è un sottotipo di 12 se 1<:1 e 2<:2. Come detto per i record e le classi si adotta in genere una sottotipazione per nome basato sull’ idea di type extension: una classe C2 estende C1 se C2 ha i tutti i campi di C1 e ne cambia i tipi in modo compatibile (i)(i[1,n]i<:i) _________________________ {f1: 1;…; fn: n}<: {f1: 1;…; fn: n}
Sottotipazione strutturale e per nome 2 (i)(i[1,n]i<:i) _________________________ {f1: 1;…; fn: n}<: {f1: 1;…; fn: n} n<m __________________________ {f1: 1;…; fm: m}<: {f1: 1;…; fn: n}
Conversioni (coercions) Coercion: data l’espressione: x + i Con x reale e i intero, essendo le rappresentazioni interne di x e i diverse e diversi gli algoritmi aritmetici, occorre effettuare una conversione dal tipo piu’ ristretto a quello piu’ generale. In notazione postfissa l’espressione diviene: x i inttoreal real+ Le conversioni sono implicite se effettuate dal compilatore, esplicite se comandate dall’utente. FOR i:=1 TO N DO x[i]:=float(1) FOR i:=1 TO N DO x[i]:=1.0 Possono avere una differenza di 8.96 unità di tempo per iterazione
Conversioni (coercions) 2 Enum E.type:=integer E num . num E.type:=real Eid E.type:=lookup(id.entry) E E1 op E2 E.type:=IF E1.type=int&E2.type=int THEN int ELSIF E1.type=int & E2.type=real THEN real ELSIF E1.type=real&E2.type=int THEN real ELSIF E1.type=real&E2.type=real THEN real ELSE type.error
Overloading 2 L’espressione a+b può indicare somma tra interi, reali, doppia precisione, complessi, matrici o unione tra dati di tipo SET. In questo caso si dice che il simbolo + è overloaded. Un overloading è detto risolvibile se è possibile dedurre un unico tipo per una data espressione.
Overloading Ovviamente, questo non è sempre possibile function “*” (i,j:integer) return complex; function”*”(x,y:integer) return complex; Da cui si deducono I possibili tipi di “*”: intintint intintcomplex complexcomplexcomplex Dati 2,3 e 5, il valore di 3*5 puo’ essere sia int che complex es: 2*(3*5)3*5:int; mentre (3*5)*z & z:complex 3*5:complex
Overloading di funzioni Type checking di funzioni overloaded E’ E E’.types:=E.type E id E.types:=lookup(id.entry) E E1(E2) E.types:={t| sE2.types: st E1.types} La terza regola asserisce che se s è un tipo di E2 e uno dei tipi di E1 mappa s in t, allora t è uno dei tipi di E1. Qualora si riscontri una incompatibilità che porti a E.types= si avrà un messaggio di errore.
Overloading di funzioni 2 Type checking di funzioni overloaded E:{I,c} E:{i} *:{iii, iic, ccc} E:{i} 3:{i} 5:{i} Come restringere l’insieme dei tipi possibili Ada e altri linguaggi richiedono un unico tipo per ogni espressione completa. Se questo non e’ possibile si dichiara un errore. Si usa un attributo types che un set di tipi possibili (feasible)
Overloading di funzioni 3 Type checking di funzioni overloaded E E E’.types:=E.types E.uniq:=IF E’.types={t} THEN t ELSE typerr E’.cod:=E.cod Eid E.types:=lookup(id.entry) E.cod:=gen(id.lexeme’:’ E.uniq) EE1(E2) E.types:={s’|sE2.types & ss’E1.types} t:=E.uniq; S:={s|sE2.types& stE1.types} E2.uniq:=IF S={s} THEN s ELSE typerr; E1.uniq:=IF S={s} THEN s t ELSE typerr; E.cod:=E1.cod||E2.cod||gen(‘apply’)’:’,E.uniq); L’attributo erditato uniq (unique) e’ usato con l’attributo sintetizzato code. La definizione e’ implementata da due visite depth-first dell’albero sintattico • nella prima si calcola types bottom up • nella seconda si propaga verso il basso uniq e al ritorno della visita si genera cod.
Ereditarietà e sottotipazione La procedura seguente implementa il concetto di recordtype extension di Wirth (Oberon) PROCEDURE RecordType(VAR typ: Struct) VAR adr,sise:INTEGER;fld,fld0,fld1:Object; ftyp,btyp:Struct;base:Item; BEGIN adr:=0; typ:=NewStr(record); typ.BaseTyp:=NIL; typ.n:=0;
Ereditarietà e sottotipazione 2 IF sym=lparen THEN Get(Sym); (*record extension*) IF sym=ident THEN qualident(base); IF (base.mode=Typ) & (base.typ.form=Record) THEN typ.BaseTyp:=base.typ;typ.n:=base.typ.n+1 adr:=base.typ.size ELSE Error(..) END; ELSE Error(..); END; CheckSym(rparen) END;