• Aucun résultat trouvé

SAOUDI Lalia Contrôle de type 2007/2008

N/A
N/A
Protected

Academic year: 2022

Partager "SAOUDI Lalia Contrôle de type 2007/2008"

Copied!
7
0
0

Texte intégral

(1)

SAOUDI Lalia Contrôle de type 2007/2008

V. Contrôle de type

1. Généralités :

Un compilateur doit contrôler que le programme source est conforme aux conventions syntaxiques et sémantiques du langage source. Ce contrôle appelé contrôle statique, assure que certaines sortes d’erreurs de programmation seront détectées et signalées. Citons quelques

contrôles statiques :

1- Contrôles de type : un compilateur doit signaler une erreur si un opérateur est appliqué à un opérande incompatible ; exemple : addition d’une variable table avec un fonction.

2- Contrôles de flot d’exécution : les instructions qui font que le flot d’exécution quitte une instruction doivent pouvoir transférer le contrôle quelque part .exemple :break 3- Contrôle d’unicité : dans certaines situations, un objet doit être défini exactement une

fois. Exemple : la déclaration des identificateurs ; les étiquettes de case.

4- Contrôle relatif aux noms : il arrive que le même nom doive apparaitre deux fois ou plus, le compilateur doit contrôler que le même nom est utilisé aux deux endroits.

2.Système de typage :

Un système de typage est une collection de règles permettant d’associer des expressions de type aux diverses parties d’un programme. Un contrôleur de type met en œuvre un système de typage, qui est spécifié de façon dirigé par la syntaxe.

3.Expression de type :

Le type de toute construction d’un langage est dénoté par une expression de type. De façon informelle, ou bien une expression de type est un type de base , ou bien elle est formé en appliquant un opérateur nommé constructeur de type à d’autres types de base.

Type de base

: booléen, entier, caractère, réel et erreur-de-type, signale les erreurs détectées

. Un type de base vide, dénotant l’absence de valeur permet le contrôle des instructions . Constructeur de type :

les tableaux : tableau(I,T) dénote le type du tableau dont ses éléments sont du type T.les produits :si T1 et T2 sont des expression de type leur produit cartésien T1 X T2 est une expression de type. Les structures :le type d’une structure est le produit des types de ses champs nommés , les pointeurs : ^T est une expression dénotant le type pointeur vers l’objet de type T, les fonctions :fait correspondre à un domaine D , un co-domaine A , ce type sera dénoté par D A

4. Contrôle de type statique et dynamique :

Le contrôle effectué par un compilateur est dit statique, tandis que le contrôle effectue lors de l’exécution du programme cible est dit dynamique.

En principe, tous les contrôles peuvent se faire dynamiquement, à condition que le code cible associé à chaque élément son type en plus sa valeur.

Un système de typage sain permet de n’effectuer aucun contrôle dynamique cherchant à détecter des erreurs de type, car il nous permet de détecter statiquement que de telles erreurs ne peuvent pas se produire lors de l’exécution du pgm. Un langage est dit fortement typé si son compilateur peut garantir que les programmes qu’il accepte s’exécuteront sans erreurs de type.

5. Spécification d’un contrôleur de type simple :

Nous spécifions on contrôleur de type pour un langage simple, dans lequel le type de tout identificateur doit être déclaré avant que cet identificateur soit utilisé.

(2)

SAOUDI Lalia Contrôle de type 2007/2008

Ce contrôleur de type est un schéma de traduction qui synthétise le type de toute expression à partir des types de ses sous expression.

Exemple : la grammaire suivante engendre des programmes, représentés par le non terminal P, qui consistent en une déclaration D suivie d’une seule expression E.

P D ; E

D D ;D / id : T

T caractere / entier / tableau[nb] de T /^ T E littéral / nb / id / E mod E / E[E]

Le langage a deux types de base : et un troisième type : Et deux constructeurs de type : tableau ( 1..nb, T) et pointeur(T).

Voici le schéma de traduction correspondant : P D ; E

D D ;D

D id : T { AjouterType(id.entrée, T.type)}

T caractere {T.type :=caractere}

Tentier {T.type :=entier}

Ttableau[nb] de T1 {T.type :=tableau(1..nb.val,T1.type)}

T^ T {T.type :=pointeur(T1.Type)}

5.1 Contrôle de type des expressions :

Les regles séantiques ci-dessous spécifient que les constantes représentées par les unités lexicales littéral et nb sont de types caracteres et entier :

E littéral { E.type := caractere}

E nb { E.type :=entier}

Nous utilisons une fonction Rechercher(e) pour retrouver le type stocké dans l’entrée référencée par e dans la table des symboles :

E id {E.type := rechercher(id.entrée)}

Une expression formée par application de l’opérateur mod à deux sous expressions de type entier a pour type entier ; dans les autres cas son type est erreur-de-type :

E E1 mod E2 { E.type := si E1.type= E2.type= entier alors entier sinon erreur-de-type}

Dans un accès à un élément de tableau E1[E2], l’expression E2 doit etre de type entier, auquel cas le résultat est le type t des éléments obtenu à partir du type tableau(s,t) de E1 :

E E1[E2]{

E.type := si E2.type=entier et E1.type= tableau(s,t) alors t sinon erreur-de-type}

Dans les expressions, l’opérateur postfixe ^ retourne l’objet référencé par son opérande. Le type de E^ est le type t de l’objet référencé par le pointeur E.

E E1 ^ { E.type := si E1 .type=pointeur(t) alors t sinon erreur de type}

5.2 Contrôle de type des instructions :

Les instructions n’ont pas de valeur associée , le type de base affecté est vide.

Les instructions que nous prenons en compte sont l’affectation, la conditionnelle et la boucle.

Nous avons toujours besoin des règles précédentes pour le contrôle des expressions, car les instructions peuvent contenir des expressions.

Iid := E {I.type := si id.type=E.type alors vide sinon erreur-de-type}

I si E alors I1 { si E.type=booléen alors I1.type sinon erreur-de-type}

I tant que E faire I1 { si E.type=booléen alors I1.type sinon erreur-de-type}

I I1 ; I2 { si I1.type=vide and I2.type=vide alors vide sinon erreur-de-type}

(3)

SAOUDI Lalia Contrôle de type 2007/2008

5.3 Contrôle de type des fonctions :

L’application d’une fonction à un argument peut être représentée par la production : E E ( E) ( une expression est l’application d’une expression à une autre expression) Les règles associant des expressions de type au non terminal T peuvent être étendues par la production et l’action : TT1’’T2 {T.type := T1.type T2.type}

Règle qui contrôle le type d’une application de fonction est :

E E1(E2) {E.type :=si E2.type=s et E1.type= st alors t sinon erreur-de-type}

La généralisation aux fonctions à plus d’un argument se fait en construisant un type produit cartésien des argument :, en effet n arguments de type T1…..Tn peuvent être considérés comme un seul argument de type T1 X T2………Tn’’ Tn+1

6. Equivalence des expressions de type :

L’algorithme de typage effectue de nombreux tests d’égalité entre types, il est donc important de définir avec précision les conditions dans lesquelles deux expressions de type sont équivalentes.

La notion d’équivalence de type mise en œuvre dans un compilateur donné peut souvent être expliquée à l’aide des concepts d’équivalence structurelle et d’équivalence par nom.

6.1 Equivalence structurelle des expressions de type :

Dans l’équivalence structurelle des expressions de type, ou bien les deux expressions sont le même type de base, ou bien elles sont formées en appliquant le même constructeur à des types structurellement équivalents, en d’autres termes, deux expressions de type sont structurellement équivalentes si et seulement si elles sont identiques.

L’algorithme suivant teste l’équivalence structurelle : Fonction EquivS(s,t) : booleen ;

Début

Si s et t sont le même type de base alors retourner vrai

Sinon si s=tableau(s1,s2) et t= tableau(t1,t2) alors retourner EquivS(s1 ,t1) et EquivS( s2, t2) Sinon si s= s1 Xs2 et t= t1 X t2 alors retourner EquivS(s1, t1) et EquivS( s2, t2)

Sinon si s=pointeur (s1) et t =pointeur( t1) alors retourner EquivS(s1, t1) Sinon si s= s1s2 et t= t1t2 alors retourner Equiv(s1,t1)et EquivS(s2, t2) Sinon retourner faux ; Fin

Maintenant imaginons le boulot du compilateur C lorsqu’ il se trouve confronté à un truc du genre st.f(p[i])

Il faut alors trouver une représentation pratique pour les expressions de type

Il est possible, de trouver une représentation pour les expressions de type qui soit plus compacte, une partie de l’information issue d’une expression de type est encodée comme une séquence de bits, qui peut ensuite être interprétée dans son ensemble comme un entier . le codage est tel que des entiers distincts représentent des expressions de type non structurellement équivalentes. Le test d’équivalence structurelle peut alors être accéléré en testant tout d’abord la non –équivalence structurelle par une comparaison des représentations entières des types, et n’appliquant ensuite l’algorithme précédent que si les entiers sont égaux.

Exemple :codage des expressions de type par Ritchie et Johnson pour un compilateur C : On considère les constructions de types suivantes :

Pointeur(t ) : un pointeur vers le type t

Fretourne(t ) : une fonction dont les arguments ne sont pas spécifiés qui retourne un objet de type t Tableau(t ) : un tableau de taille indéterminée d’objets de type t

Ces constructeurs étant unaires, les expressions de type formées en les appliquant à des types de base ont une structure très uniforme ;Par exemple

caractère

(4)

SAOUDI Lalia Contrôle de type 2007/2008

fretourne(caratère)

pointeur(fretourne(caractere))

tableau (pointeur(fretourne(caractere)))

Chaque constructeur peut être représentée par une séquence de bits selon un procédé d’encodage simple. De même que chaque type de base :

Constructeur codage type de base codage

Pointeur 01 entier 0010

Tableau 10 booléen 0000

fretourne 11 caractère 0001

Réel 0011

Les expressions de type peuvent maintenant être encodées comme des séquences de bit

expression de type codage

caractère 000000 0001

fretourne(caractère) 000011 0001

pointeur(fretourne_(caractère )) 000111 0001 tableau(pointeur(fretourne(caractère))) 100111 0001

Cette méthode a au moins deux avantages :économie de place et il y a une trace des différents constructeurs appliqués.

Des noms pour les expressions de type :

Dans certains langages, on peut donner des noms aux types, par exemple : Type lien=^cellule ;

Var suivant :lien ; Dernier :lien ;

P :^cellule ; Q,R :^cellule ;

On déclare que l’identificateur lien est un nom pour le type ^cellule.

Est-ce que les variables suivant, dernier ;P,Q,R ont des types identiques ? cela dépend de Lorsque l’on accepte des noms dans les expressions de types, deux notions d’équivalence des expressions de type sont possible, selon la façon dont on traite ces noms.

L’équivalence par nom :

6.2 considère chaque nom de type comme un type distinct , deux expressions de type sont donc équivalentes par nom si et seulement si elles sont identiques.

Dans l’exemple les variables suivant et dernier sont du même type, les variables p,q,r one également le même type, mais pas suivant et p.

Dans le cas de l’équivalence structurelle, les noms sont remplacés par les expressions de type qu’ils désignent, deux expressions de type contenant des noms sont donc structurellement équivalentes si l’on obtient deux expressions de type structurellement équivalentes après y avoir remplacé tous les noms. dans l’exemple les cinq variables ont toutes le même type.

7. Conversions de type :

Considérons une expression comme x + i où x est de type réel et i de type entier. Puisque les représentations des entiers et des réels ne sont pas les mêmes dans un calculateur et que l’on utilise des instructions machine différentes pour les opérations sur les entiers et les réels, le compilateur peut avoir à convertir un des opérandes de + pour assurer que les deux opérandes sont du même type.

La définition du langage spécifie quelles sont les conversions nécessaires , lorsqu’un entier est affecté à un réel , ou vice versa, la conversion se fait vers le type de la partie gauche de l’affectation.

Dans les expressions , la transformation consiste habituellement à convertir l’entier en un nombre réel, le contrôleur de type peut être utilisé pour insérer ces opérations de conversion dans la représentation intermédiaire du programme source, par exemple, la notation postfixée de x +i pourrait etre :

x i EntToreal + real

la conversion d’un type à un autre est dite implicite si elle est censé etre réalisée automatiquement par le compilateur, qui ne donne lieu à aucune perte d’information.

La conversion est dite explicitesi le programmeur a quelque chose à écrire pour la conversion.

Exemple : considérons les expresions formés en appliquant un opérateur arithmétique op à des constantes et des identificateurs

(5)

SAOUDI Lalia Contrôle de type 2007/2008

Enb E.type := entier

Enb.nb E.type := réel

Eid E.type :=

E E1 op E2 E.type := si ….. ;

Dans ce fragment de code : for I := 1 to N do X[i] :=1 X est un tableau de réels que l’on veut remplir par 1, sur un compilateur Pascal Bently constata que ce code s’exécutait en 48,4 NMS, tandis que for I := 1 to N do X[i] :=1.0 ne prenait que 5.4 N MS, pourquoi ?

8.Surcharge des fonctions et des opérateurs :

Un symbole surchargé est un symbole qui a des significations différentes suivant le contexte . Exemple : en mathématique l’opérateur + est surchargé, car + dans A+B a des significations différentes selon que A et B sont des entiers, des réels, des complexes ou des matrices. En ada , les parenthèses () sont surchargées A(I) est un tableau , fonction avec l’argument I , ou une conversion explicite de l’expression I vers le type A.

Les opérateurs arithmétiques sont surchargés dans la plupart des langages de programmation.

Toutefois, lorsqu’elle fait intervenir comme +, la surcharge peut être résolue en ne considérant que les arguments de l’opérateur, l’analyse par cas est similaire à celle de la règle sémantique associée à EE1 op E2, où le type de E est déterminé par les types possibles de E1 et E2.

Fonctions polymorphes :

Le terme polymorphe peut être s’appliquer à tout morceau de code que l’on peut exécuter avec des arguments de types différents, on peut donc également parler de fonctions et d’opérateurs

polymorphes

Les opérateurs pour l’indiçage de tableau, l’application de fonction et la manipulation de pointeur sont en général polymorphes ( pourquoi ?)

Cette section aborde les problèmes que l’on rencontre lors de la conception d’un contrôleur de type pour un langage avec fonctions polymorphes.

Pourquoi des fonctions polymorphes ?

Les fonctions polymorphes sont attrayantes car elles sont facilitent l’implantation d’algorithmes manipulant des structures de données indépendamment des types des éléments de la structure, exemple : il est pratique d’avoir un programme qui détermine la longueur d’une liste sans qu’il ait à reconnaitre les types des éléments de la liste.

Des langages comme pascal exigent la spécification complète des types des paramètres de fonction ; par exemple une fonction déterminant la longueur d’une liste d’entiers ne peut être appliquée à une liste de réels. Dans les langages permettent les fonctions polymorphes tel ML, on peut écrire une fonction longueur qui s’applique à toutes les listes.

Variables de type :

Les variables représentant des expressions de type nous permettent de parler de type sans les spécifier davantage

Une application importante des variables de type est le contrôle de la compatibilité dans l’utilisation des identificateurs, dans les langages qui n’exigent pas que les identificateurs soient déclarés avant d’être utilisé. on peut utiliser une variable pour représenter le type d’un identificateur non déclaré, puis en analysant le programme si cet identificateur est utilisé , par exemple comme un entier dans une instruction et comme un tableau dans une autre ;une telle utilisation est signalée comme une erreur.

(6)

SAOUDI Lalia Contrôle de type 2007/2008

Inférence de type :est le problème de la détermination du type d’une construction du langage à partir de la façon dont elle est utilisée, ce terme est souvent appliqué au problème qui consiste à inférer le type d’une fonction à partir de son corps.

Exemple : les techniques d’inférence de type peuvent être appliquées aux pgms écrits dans les langages tels C et Pascal.

Type lien= ^ cellule ;

Procedure Apliste ( pliste :lien ; procedure p) ;

Begin While pliste<> nil do begin p(pliste); pliste:= pliste^.suivant end end;

La procédure Apliste appliqué son paramètre p à chaque cellule d’une liste, par exemple, p peut être utilisée pour initialiser ou pour imprimer l’entier contenu dans une cellule. Bien que les types des arguments de p ne soient pas spécifient, nous pouvons inférer de l’utilisation de p dans l’expression p(pliste que le type de p doit être : lien vide

Un langage avec fonctions polymorphes :

Pour parler précisément de l’ensemble des types auxquels peut être appliquée une fonction polymorphe , nous utiliserons le symbole V , avec la signification de « pour tout type » : Exemple : function deref(p) ;

Begin return p^ end ; nous écririons : V α. Pointeur(α)α

Le langage que nous allons utiliser pour le contrôle des fonctions polymorphes est engendré par la grammaire suivante :

P D ; E

D D ; D | id : Q

Q V variable-de-type. Q |T

T T’’T| T XT| constructeur_unaire (T) | type-de-base| variable-de-type|(T) E E(E) | E,E | id

Exemple : deref : V α. Pointeur (α)  α ; Q : pointeur(pointeur(entier) ; deref(deref(q) )

Portée des identificateurs

On appelle portée d’un identificateur la(es) partie(s) du programme où il est valide et a la signification qui lui a été donnée lors de sa déclaration.

Exemple : portée d‘une variable dans un bloc d’instructions :

{ int a ;….. |

{int b ;..} | portée de b | portée de a

} |

Portée des paramètres d’une fonction.

Règles sémantiques de la portée :

Règle 1 : utiliser chaque identificateur seulement dans sa portée

Règle 2 : ne pas déclarer plus d’une fois un identificateur de type t et de nom name dans une même portée

9. Table des symboles :

Les vérifications sémantiques se réfèrent aux propriétés des identificateurs dans un programme,c.à.d leur portée ou leur type.

*besoin d’un environnement pour stocker les informations propres aux identificateurs=table des symboles

*chaque entrée dans la table des symboles contient :

-le nom de l’identificateur+ des infos supplémentaires se rapportent à l’identificateur : nature,type,attribut particuliers

Nom Nature Type attribut

Foo Fun Int,intint Ext

(7)

SAOUDI Lalia Contrôle de type 2007/2008

Temp Variable Char const

Comment capturer les informations sur la portée dans une table des symboles ? Principe :

-il existe une hiérarchie d’environnements (de portées) dans un programme -utiliser une hiérarchie similaire de tables de symboles.

-chaque table de symboles contient les symboles déclarés dans l’espace de portée associé.

Associer chaque définition de X avec l’entrée de table de symboles appropriée Implémentation de la table de symboles

La structure de données la plus simple et la plus facile à mettre en œuvre pour implanter une table de symboles est une liste linéaire d’enregistrements, on ajoute les nouveaux noms à la liste dans l’ordre où on les rencontre.

Inconvénient : inefficace pour les grandes tablescar besoin de scanner la moitié de la liste en moyenne.

Implémentation à base de table de hachage : La structure de données comporte deux parties :

1. Une table d’adressage dispersée consiste en un tableau de m pointeurs vers des entrées dans la table.

2. Les entrées dans la table sont organisées en m listes chainées distinctes, appelées

compartiments, chaque enregistrement de la table de symboles apparait dans exactement une de ces listes

Pour déterminer si une entrée existe pour la chaine C dans la table de symboles, nous appliquons à C une fonction de hachage h, telle que h(C) retourne un entier comris entre 0 et m-1. Si C est présent dans la table de symboles, il est alors dans la liste numérotée h(C) . si C n’est pas encore dans la table de symboles, on l y entre en créant un enregistrement pour C qui est chainé en tête de la liste numérotée h(C).

Références

Documents relatifs

J’espère que vous prenez bien soin de vous et qu’en tant que bon citoyen, vous appliquez bien les mesures civiques (on pourrait se poser la question de savoir quel

We derive Seiberg-Witten curves for these little string theories which can be interpreted as mirror curves for the corresponding Calabi-Yau manifolds.. Under fiber-base duality

[r]

Ou Chantal réussira le cours si et seulement si Réginald l'échoue, ou Paul le réussira s'il étudie avec méthode et n'est pas fatigué La correction est uniquement accessible

Ce questionnaire comprend 4 questions réparties sur 12 pages.. e) Pour la réaction suivante, dessinez les structures des intermédiaires A-C et celle du produit final D.

Il y a deux réductions pour la case M[A, c], donc ce n’est pas une grammaire LL(1) On ne peut pas utiliser cette méthode d ‘analyse, pour pouvoir choisir entre la production A

Un arbre syntaxique pour une définition S-attribuée peut toujours être décoré en évaluant les règles sémantiques pour les attributs de chaque nœud du bas vers le haut,

Bien voir comment se fait l’évolution dans le diagramme suivant le type de radtioactivité. Zone