• Aucun résultat trouvé

D.5 Préservation pour les extensions noyau

4.4 Syntaxe des expressions

4.2 Syntaxe

Les figures 4.4 et 4.5 présentent notre langage intermédiaire. Il contient la plupart des fonctionnalités présentes dans les langages impératifs comme C.

Parmi les expressions, les constantes comportent les entiers et flottants, ainsi que le poin-teur NULLqui correspond à une valeur par défaut pour les pointeurs, et la valeur unité ( ) qui pourra être retournée par les fonctions travaillant par effets de bord uniquement.

Les accès mémoire en lecture et écriture se font au travers de valeurs gauches (left values ou lvalues) : comme en C, elles tiennent leur nom du fait que ce sont ces constructions qui sont à gauche du signe d’affectation. En plus des variables, on obtient une valeur gauche en accédant par nom à un champ ou par indice à un élément d’une valeur gauche, ou encore en appliquant l’opérateur* de déréférencement à une expression. Pour assister le typage, l’accès à un champ doit être décoré du type complet S, mais cette annotation est ignorée lors de l’évaluation. Les valeurs gauches correspondent aussi à l’unité d’adressage : c’est-à-dire que les pointeurs sont construits en prenant l’adresse d’une valeur gauche avec l’opérateur&. Les fonctions sont des expressions comme les autres, contrairement à C où elles sont forcément déclarées globalement. Cela veut dire qu’on peut affecter une fonction f à une

va-4.3. MÉMOIRE ET VALEURS 41

Instructions

i ::= PASS Instruction vide

| i;i Séquence

| e Expression

| DECLx = eIN{i } Déclaration de variable | IF(e){i }ELSE{i } Alternative | WHILE(e){i } Boucle | RETURN(e) Retour de fonction

Phrases

p ::= x = e Variable globale | e Évaluation d’expression

Programme

P ::= (p1,..., pn) Phrases

FIGURE4.5 : Syntaxe des instructions

riable x et l’appeller avec x(a1, a2). Il est aussi possible de déclarer une fonction au sein d’une fonction. Cependant cela ne respecte pas l’imbrication lexicale : dans la fonction interne il n’est pas possible de faire référence à des variables locales de la fonction externe, seulement à des variables globales. En mémoire les fonctions sont donc uniquement représentées par leur code : il n’y a pas de fermetures.

Enfin, on trouve aussi des expressions permettant de construire des valeurs composées : les structures et les tableaux.

Les instructions sont typiques de la programmation impérative. SAFESPEAK comporte bien sûr l’instruction vide qui ne fait rien et la séquence qui chaîne deux instructions.

Une expression peut être évaluée dans un contexte d’instruction, pour ses effets de bord. Remarquons que l’affectation est une expression, qui renvoie la valeur affectée. Cela permet d’écrire x ← (y ← z), comme dans un programme C où on écriraitx = y = z.

Il est également possible de déclarer une variable locale avec DECLx = vIN{i }. x est alors une nouvelle variable visible dans i avec pour valeur initiale v.

L’alternative et la conditionnelle sont classiques ; en revanche, on ne fournit qu’un seul type de boucle et pas de saut (instructiongoto).

Les opérateurs sont donnés dans la figure 4.6. Ils correspondent à ceux du langage C. La différence principale est que les opérations sur les entiers, flottants et pointeurs sont anno-tées avec le type de données sur lequel ils travaillent. Par exemple « + » désigne l’addition sur les entiers et « +. » l’addition sur les flottants. Les opérations de test d’égalité, en revanche, sont possibles pour les types numériques, les pointeurs, ainsi que les types composés de types comparables.

4.3 Mémoire et valeurs

L’interprète que nous nous apprêtons à définir manipule des valeurs qui sont associées aux variables du programme.

Opérateurs

binaires

�::= +,−,×,/,% Arithmétique entière | + .,−.,×.,/. Arithmétique flottante | +p,−p Arithmétique de pointeurs | ≤,≥,<,> Comparaison sur les entiers | ≤ .,≥ .,< .,> . Comparaison sur les flottants

| =,�= Tests d’égalité

| &,|,^ Opérateurs bit à bit | &&,|| Opérateurs logiques

| �,� Décalages

Opérateurs

unaires

�::= +,− Arithmétique entière | + .,−. Arithmétique flottante

| ∼ Négation bit à bit

| ! Négation logique

FIGURE4.6 : Syntaxe des opérateurs

La mémoire est constituée de variables (toutes mutables), qui contiennent des valeurs. Ces variables sont organisées, d’une part, en un ensemble de variables globales et, d’autre part, en une pile de contextes d’appel (qu’on appellera donc aussi cadres de pile, ou stack

frames en anglais). Cette structure empilée permet de représenter les différents contextes à

chaque appel de fonction : par exemple, si une fonction s’appelle récursivement, plusieurs instances de ses variables locales sont présentes dans le programme. Le modèle mémoire présenté ici ne permet pas l’allocation dynamique sur un tas. Cette limitation sera détaillée dans le chapitre 9.

La structure de pile des variables locales permet de les organiser en niveaux indépen-dants : à chaque appel de fonction, un nouveau cadre de pile est créé, comprenant ses para-mètres et ses variables locales. Au contraire, pour les variables globales, il n’y a pas de système d’empilement, puisque ces variables sont accessibles depuis tout point du programme.

Pour identifier de manière non ambigüe une variable, on note simplement x la variable globale nommée x, et (n, x) la variable locale nommée x dans le necadre de pile2.

Les affectations peuvent avoir la forme x ← e où x est une variable et e est une expression, mais pas seulement. En effet, à gauche de ← on trouve en général non pas une variable mais une valeur gauche (par définition). Pour représenter quelle partie de la mémoire doit être ac-cédée par cette valeur gauche, on introduit la notion de chemin ϕ. Un chemin est une valeur gauche évaluée : les cas sont similaires, sauf que tous les indices sont évalués. Par exemple,

ϕ= (5, x).p représente le champ « p » de la variable x dans le 5ecadre de pile. C’est à ce mo-ment qu’on évalue les déréférencemo-ments qui peuvent apparaître dans une valeur gauche.

Les valeurs, quant à elles, peuvent avoir les formes suivantes (résumées sur la figure 4.7) : • �c : une constante. La notation circonflexe permet de distinguer les constructions

syn-2. Les paramètres de fonction sont traités comme des variables locales et se retrouvent dans le cadre corres-pondant.

4.4. INTERPRÈTE 43 taxique des constructions sémantiques. Par exemple, à la syntaxe 3 correspond la va-leur �3.

Les valeurs entières sont les entiers signés sur 32 bits, c’est-à-dire entre −231à 231− 1. Mais ce choix est arbitraire : on aurait pu choisir des nombres à 64 bits, par exemple. Les flottants sont les flottants IEEE 754 de 32 bits [oEE08].

Il n’y a pas de distinction entre procédures et fonctions ; toutes les fonctions doivent renvoyer une valeur. Celles qui ne retournent pas de valeur « intéressante » renvoient alors une valeur d’un type à un seul élément noté ( ), et donc le type sera noté UNIT. Cette notation évoque un n-uplet à 0 composante.

• �& ϕ : une référence mémoire. Ce chemin correspond à un pointeur sur une valeur gauche. Par exemple, l’expression &x s’évalue en �& ϕ = �& (5,x) si x désigne lexicale-ment une variable dans le 5ecadre de pile.

[v1;...; vn] : un tableau. C’est une valeur composée qui contient un certain nombre (connu à la compilation) de valeurs d’un même type, par exemple 100 entiers. On ac-cède à ces valeurs par un indice entier. C’est une erreur (Ωar r ay) d’accéder à un tableau en dehors de ses bornes, c’est-à-dire en dehors de [0;n − 1] pour un tableau à n élé-ments. Pareillement, �[·] permet de désigner les valeurs tableau. Par exemple, si x vaut 2 et y vaut 3, l’expression [x; y] s’évaluera en la valeur �[2;3]

{l1: v1;...;ln: vn} : une structure. C’est une valeur composée mais hétérogène. Les dif-férents éléments (appelés champs) sont désignés par leurs noms li (pour label). Dans le programme, le nom de champ li est décoré de la définition complète de la struc-ture S. Celle-ci n’est pas utilisée dans l’évaluation et sera décrite au chapitre 5. Comme précédemment, on note �{·} pour dénoter les valeurs.

• �f : une fonction. On garde en mémoire l’intégralité de la définition de la fonction (liste

de paramètres, de variables locales et corps). Même si les fonctions locales sont pos-sibles, il n’est pas possible d’accéder aux variables de la portée entourante depuis la fonction intérieure (il n’y a pas de fermetures). Contrairement à C, les fonctions ne sont pas des cas spéciaux. Par exemple, les fonctions globales sont simplement des variables globales de type fonctionnel, et les « pointeurs sur fonction » de C sont remplacés par des variables de type fonction.

• Ω : une erreur. Par exemple le résultat l’évaluation de 5/0 est Ωdi v.

Les erreurs peuvent être classifiées en deux grand groupes : d’une part, Ωf i eld, Ωvar et Ωt ypsont des erreurs de typage dynamique, qui arrivent lorsqu’on accède dynamiquement à des données qui n’existent pas ou qu’on manipule des types de données incompatibles. D’autre part, Ωdi v, Ωar r ay et Ωptr correspondent à des valeurs mal utilisées. Le but du sys-tème de types du chapitre 5 sera d’éliminer complètement les erreurs du premier groupe.

4.4 Interprète

La figure 4.8 résume comment ces valeurs sont organisées. Une pile est une liste de cadres de piles, et un cadre de pile est une liste de couples (nom, valeur). Un état mémoire m est un couple (s, g ) où s est une pile et g un cadre de pile (qui représente les variables globales). On note |m| = |s| la hauteur de la pile (en nombre de cadres).

Enfin, l’interprétation est définie comme une relation · → · entre états Ξ ; ces états sont d’une des formes suivantes :

Valeurs

v ::= �c Constante | �& ϕ Référence mémoire | {l1: v1;...;ln: vn} Structure | [v1;...; vn] Tableau | �f Fonction | Ω Erreur

Chemins

ϕ::= a Adresse | ϕ�.l Accès à un champ | ϕ �[n] Accès à un élément

Adresses

a ::= (n, x) Variable locale

| (x) Variable globale

Erreur

Ω::= Ωar r ay Débordement de tableau | Ωptr Erreur de pointeur | Ωdi v Division par zéro | Ωf i eld Erreur de champ | Ωvar Variable inconnue | Ωt yp Données incompatibles