Vous êtes sur la page 1sur 44

Université de Bordeaux

College de Science & Technologie

Création d’un langage de description pour les


automates programmés

Mémoire

Auteurs : Cliente :
Benjamin BORDEL Irène DURAND
Alexandre BROUSTE
Valentin LAHAYE Chargé de TD :
Nathan LHOTE

12 avril 2017
Avant-propos

Résumé
Durant notre première année de Master en informatique, nous avons été
chargés de réaliser un projet d’envergure dans le cadre du module PDP dis-
pensé par Philippe Narbel. Sur une période de trois mois :
— Nous avons tout d’abord établi un cahier des besoins avec notre cliente
Irène Durand ;
— Nous avons ensuite conçu une syntaxe proche des mathématiques,
permettant de décrire des automates programmés de termes ;
— Enfin, nous avons implémenté un compilateur pour ce langage géné-
rant du code Common Lisp.
Nous tâcherons dans ce mémoire de détailler clairement les besoins de notre
commanditaire, les solutions techniques retenues et les problèmes rencontrés.
Nous critiquerons notre implémentation tout en proposant des améliorations
de cette dernière.

Remerciements
Nous tenons tout d’abord à remercier Irène Durand, notre cliente, pour la
confiance dont elle a su nous gratifier et pour avoir eu la patience de répondre
à nos nombreuses interrogations. Nous remercions également Nathan Lhote,
notre chargé de TD, pour son soutien et ses conseils avisés tout au long de ce
projet. Merci également à tous nos professeurs et particulièrement à Philippe
Narbel pour ses cours de conception logicielle et à Marc Zeitoun pour son
initiation à la compilation.
Table des matières

Introduction 1

1 Langage de description 5

2 Analyse des besoins 13


2.1 Diagramme pieuvre . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 Diagramme FAST . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3 Architecture logicielle . . . . . . . . . . . . . . . . . . . . . . . 16
2.4 Planification des tâches . . . . . . . . . . . . . . . . . . . . . . 17

3 Détails d’implémentation 20
3.1 Analyseur lexical . . . . . . . . . . . . . . . . . . . . . . . . . 20
3.1.1 Objectif et présentation de Flex . . . . . . . . . . . . . 20
3.1.2 Exemple de décomposition . . . . . . . . . . . . . . . . 21
3.1.3 Tests unitaires . . . . . . . . . . . . . . . . . . . . . . . 21
3.2 Analyseur syntaxique . . . . . . . . . . . . . . . . . . . . . . . 22
3.2.1 Objectifs et présentation de Bison . . . . . . . . . . . . 22
3.2.2 Conflits grammaticaux . . . . . . . . . . . . . . . . . . 23
3.2.3 Gestion des fuites mémoires . . . . . . . . . . . . . . . 24
3.2.4 Tests unitaires . . . . . . . . . . . . . . . . . . . . . . . 25
3.3 Module de gestion d’arbres syntaxiques . . . . . . . . . . . . 26
3.3.1 Typage générique en langage C . . . . . . . . . . . . . 26
3.3.2 Durée de vie d’un noeud et système de référence . . . 28
3.3.3 Tests unitaires . . . . . . . . . . . . . . . . . . . . . . . 29
3.4 Analyseur sémantique . . . . . . . . . . . . . . . . . . . . . . 30
3.4.1 Tests unitaires . . . . . . . . . . . . . . . . . . . . . . . 33
3.5 Module de gestion des erreurs et avertissements . . . . . . . . 33

4 Critique de l’implémentation 36
4.1 Complexité . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
4.2 Perspectives d’évolution . . . . . . . . . . . . . . . . . . . . . 36

Conclusion 38
Table des figures

1 Automate fini de mots . . . . . . . . . . . . . . . . . . . . . . 1


2 Automate fini de termes . . . . . . . . . . . . . . . . . . . . . 3
3 Terme t résultat du traitement par A . . . . . . . . . . . . . . 4
1.1 Syntaxe retenue pour les états . . . . . . . . . . . . . . . . . . 7
1.2 Syntaxe retenue pour les prototypes . . . . . . . . . . . . . . . 8
1.3 Syntaxe retenue pour les nombres . . . . . . . . . . . . . . . . 9
2.1 Diagramme pieuvre . . . . . . . . . . . . . . . . . . . . . . . . 13
2.2 Diagramme FAST . . . . . . . . . . . . . . . . . . . . . . . . . 15
2.3 Architecture classique d’un compilateur . . . . . . . . . . . . 16
2.4 Architecture logicielle choisie . . . . . . . . . . . . . . . . . . . 17
2.5 Gantt initial . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.6 Gantt réel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
3.1 Exemple de règle Bison . . . . . . . . . . . . . . . . . . . . . . 23
3.2 Convention de nommage des fichiers de test invalides . . . . . 25
Introduction aux automates programmés de
termes
Un automate est un modèle mathématique pouvant être représenté par
un graphe orienté et étiqueté. Ce dernier est décrit par un ensemble d’états
(sommets du graphe) pouvant être reliés entre eux par des transitions (arêtes
du graphe). Un automate fini de mots est défini de la façon suivante :
 
A= Σ, Q, q0 , Qf , δ

où Σ est l’alphabet de l’automate, Q l’ensemble de ses états, q0 l’état initial,


F l’ensemble des états finaux (aussi dits acceptants) et δ l’ensemble des
transitions.
 
Σ = {a, b},
 
 
 

 Q = {0, 1, 2, 3}, 

 
 
 

 q0 = 0, 

 
A=  
F = {3},
 
 
 
 
 

 
(0, a) 1
 
 
 
 
 
(1, a) → 1

 

 
 δ =  

 
 (1, b) → 2 




(2, a) → 3
 

Figure 1 – Automate fini de mots


a

0 a 1 2 a 3
b

Ici, l’automate (1) permet de reconnaître les mots commençant par au moins
un a et terminant par ba ({aba, aaba, aaaba, ...} ⇔ a+ ba).

1
L’un des principaux problèmes rencontrés lors de l’utilisation de ce type
de structure est celui de la complexité en espace. En effet, pour certains pro-
blèmes mathématiques impliquant un nombre très important d’états, l’espace
occupé par la table des états et celle des transitions devient trop important
pour être contenu en mémoire. Afin de pallier à ce problème d’espace, il
est possible d’utiliser des automates programmés. Ces derniers consistent à
décrire l’ensemble des états (Q) et celui des transitions (δ) sous forme de
fonctions. L’existence d’un état ainsi que les transitions entre ces derniers
au sein de l’automate, est maintenant régie par une ou plusieurs propriétés
mathématiques énoncées sous forme de clauses.

De la même manière qu’il existe des automates finis et infinis de mots, il


existe des automates finis et infinis de termes. Ceux-ci permettent de traiter
des structures arborescentes et sont décrits comme suit :
 
A= F, Q, Qf , δ

où F est la signature (ou alphabet gradué) de l’automate. Cette signature,


tout comme l’alphabet des automates de mots, est de dimension variable n.
Celle-ci est définie de la façon suivante :

F = F0 ⊕ F1 ⊕ ... ⊕ Fn
s ∈ Fi ⇔ arite(s) = i | i ∈ [0, n]

En mathématiques, l’arité d’une fonction, ou d’une opération, est le nombre


d’arguments ou d’opérandes qu’elle requiert.

— Un symbole de F d’arité 0 est un terme, autrement appelé constante.


— Si f est un symbole d’arité n, et si t1 , t2 , ... tn sont des termes, alors
f (t1 , t2 , ..., tn ) est un terme.
— Tout terme s’obtient à partir des symboles d’arité 0, par un nombre
fini d’applications de la règle précédente.

Comme pour les automates de mots, Q est l’ensemble des états pouvant
être, dans notre cas, infini. Qf est l’ensemble des états acceptants et δ, l’en-
semble fini des fonctions de transition de l’automate. Ci-dessous un exemple
d’automate fini de termes reconnaissant les expressions booléennes valant
« vrai » :

2
 
F0 = {F, V }
 

 


 F = F1 = {non} , 

F2 = {ou, et}
 
 
 
 
 
 
 
Q = {vrai, f aux},
 
 
 
 
 

 Qf = {vrai}, 

 
 
A=    

 


F → f aux 




V → vrai
 
 
 
 
 
 
 
non(a) → vrai
 





 Si a = f aux 





f aux Si a = vrai
 
 
 
δ =
 
et(a, b) → vrai
 




 Si a = vrai et b = vrai 




f aux Si a = f aux ou b = f aux
 
 
 
 
 
 
 
ou(a, b) → vrai Si a = vrai ou b = vrai
 
 
 
 
 
 
 

f aux Si a = f aux et b = f aux 

Figure 2 – Automate fini de termes

3
non vrai

et f aux

ou vrai F f aux

V vrai
non f aux

V vrai

Figure 3 – Terme t résultat du traitement par A

Les attributs en italique présents sur chacun des noeuds sont le résultat
du traitement ascendant (de bas en haut) du terme par l’automate A (2). Le
terme t est accepté par l’automate.

4
Chapitre 1

Langage de description

Tel que résumé au début de ce mémoire, le but de ce projet est de réaliser


un langage de description pour les automates programmés de termes, accom-
pagné d’un compilateur. Nous avons choisi de nommer ce langage le langage
FLY en référence à l’appellation anglaise de ces structures (fly-automaton) [5].

Le langage FLY doit permettre de décrire un automate programmé de


termes selon le quadruplet (F, B, Q, T ).

F est la signature de l’automate. Cette signature est un ensemble com-


posé de symboles regroupés par arité. Chaque symbole peut être structuré.
Si tel est le cas, la cardinalité de la structure doit être précisée. Un symbole
est défini par un identif icateur (1). La syntaxe retenue est la suivante :

F := {
0 := { a, b },
1 := { c },
2 := { d[1] }
}

Ici, les symboles a et b sont d’arité 0, c et d sont respectivement d’arité 1 et


2. Le symbole d est structuré.

5
CHAPITRE 1. LANGAGE DE DESCRIPTION

B est l’ensemble des types primitifs de l’automate. Chaque type


primitif est défini par un identif icateur (1). Le type générique # est prédéfini
par le langage et, à ce titre, ne doit pas être redéfini dans B. La syntaxe
retenue est la suivante :

B := { ERROR, OK, SUCCESS }

Ici, nous déclarons trois types primitifs ERROR, OK et SUCCESS.

Q est l’ensemble des états de l’automate. Chaque état de l’automate


est défini par un identif icateur (1) et doit être typé. Les types sont sépa-
rables en deux catégories : les types primitifs et les types composés. Les types
primitifs ne contiennent aucune donnée et sont définis dans B. Les types com-
posés quant à eux contiennent des données. Ces derniers sont définis comme
suit :

où C est l0 ensemble des étiquettes






C



 Z
T = T1 × ... × Tn
où Pf est un sous − ensemble f ini




 Pf (T )
d0 éléments de T

Q contient également l’ensemble des états acceptants Qf . Chacun des élé-


ments de Qf doit avoir été préalablement défini dans Q. La syntaxe retenue
est la suivante (1.1).

6
CHAPITRE 1. LANGAGE DE DESCRIPTION

Q := { B := {
Primitif
u : OK, OK

v : (C x { N }), }

Etats finaux f := { u }

} Type
Etat

Figure 1.1 – Syntaxe retenue pour les états

Ici, le second état v est de type composé, c’est une relation entre une
étiquette et un ensemble de nombres : C × Pf (Z).

T est l’ensemble des transitions de l’automate. Chaque transition


est une fonction définie selon un prototype, un corps et retourne un état ins-
tancié déclaré dans Q.

Le prototype d’une transition est décrit selon un symbole, une structure,


une décoration ainsi qu’une liste de paramètres.
— Le symbole doit préalablement être défini dans F.
— La structure de la transition doit respecter la cardinalité définie pour
ce même symbole. Si le symbole n’est pas structuré, aucune structure
ne doit être définie.
— La décoration d’une transition est un élément optionnel défini par un
nombre (2).
— La liste des paramètres doit respecter l’arité du symbole. Chacun des
paramètres correspond à un élément de Q. Un paramètre peut être
abstrait, auquel cas il est de la forme qn où n correspond à sa posi-
tion dans la liste des paramètres (q0 étant le premier élément). Un
paramètre peut également être concret. Dans ce cas, il est de la forme
< identif icateur >. L’identificateur correspond à un état devant être
défini dans Q. Tout comme un paramètre abstrait, ce dernier est ac-
cessible dans le corps de la transition par sa position qn dans la liste
des paramètres.

7
CHAPITRE 1. LANGAGE DE DESCRIPTION

La syntaxe retenue est la suivante (1.2).

Symbole F := {

u -> ... , 0 := { u },

v[a, b, c] ^(1) ( <q> , q1 ) -> ... 2 := { v[3] }


Décoration }
Paramètres
Q := {
Etat
q : ...

Figure 1.2 – Syntaxe retenue pour les prototypes

Le corps d’une transition précise et instancie l’état retourné par cette der-
nière. Ce retour peut être direct ou conditionné selon des clauses. Une clause
est composée d’une expression booléenne suivie d’un retour de fonction. A
nouveau et pour chacune des clauses, ce retour peut être l’instanciation d’un
état de l’automate ou un bloc conditionnel. Un bloc conditionnel doit obli-
gatoirement commencer par une clause if et terminer par une clause else.
Entre ces deux éléments peut être défini un ensemble non borné de clauses
elif. La syntaxe retenue est la suivante :
... -> <q>(...),
... -> {
if (...) <q>(...)
elif (...) {
if (...) <q>(...)
else <q>(...)
}
elif (...) <q>(...)
else <q>(...)
}

8
CHAPITRE 1. LANGAGE DE DESCRIPTION

Ici, nos deux précédentes transitions (1.2) présentent pour la première un


retour direct d’état et pour la seconde un retour conditionné selon plusieurs
clauses imbriquées. L’état retourné q doit être défini dans Q.

Une expression est composée d’un opérateur, d’un membre gauche et d’un
membre droit. Ces deux membres doivent être de même type. Le tableau
suivant (1.1) illustre l’ensemble des opérateurs disponibles en fonction du
type des membres de l’expression dont il fait partie.

Définition 1. Un identificateur est une chaîne de caractères non vide et


non bornée, pouvant être composée de chiffres, de lettres et d’underscores. Un
identificateur ne peut pas commencer par un chiffre.

Définition 2. Un nombre est également une chaîne de caractères. L’en-


semble des caractères permettant de représenter un nombre varie en fonction
de la base dans laquelle celui-ci est défini. La base par défaut est la base dix.
Les bases deux, huit et seize sont également disponibles. La base deux permet,
entre autres, de représenter simplement des vecteurs de bits. Les caractères
disponibles pour la base seize sont insensibles à la casse. La syntaxe retenue
est la suivante (1.3).

Nombre
42 Base
00101010 ( 2 )

52 ( 8 )

42 ( 10 )

2A ( 16 )

Figure 1.3 – Syntaxe retenue pour les nombres

9
CHAPITRE 1. LANGAGE DE DESCRIPTION

C Z T1 × ... × Tn Pf (T ) Pf (C) Booléen


Est de type is × × × × ×
Non ! ×
Et and ×
Ou or ×
Ou exclusif xor ×
Egal = × × × × ×
Différent de =/ × × × × ×
Booléens

Inférieur < × ×
Etiquettes Ensemblistes Arithmétiques

Supérieur > × ×
Inférieur ou égal <= × ×
Supérieur ou égal >= × ×

× ×
Opérateurs

Appartient à in

Renommage -> ×
Addition + ×
Soustraction - ×
Multiplication * ×
Arithmétiques

Division / ×
Modulo % ×
Et bit à bit & ×
Ou bit à bit | ×
Ou exclusif bit à bit ^ ×
Décalage à gauche « ×
Décalage à droite » ×
Exclusion \ × ×
Ensemblistes

Intersection I × ×
Union U × ×
Union disjointe U+ × ×

Table 1.1 – Listing des opérateurs

10
CHAPITRE 1. LANGAGE DE DESCRIPTION

Exemple de syntaxe pour l’automate de termes reconnaissant les


expressions booléennes :

F := {
0 := {
FAUX,
VRAI
},

1 := {
non
},

2 := {
et,
ou
}
},

B := {
BOOLEEN
},

Q := {
v r a i : BOOLEEN,
faux : BOOLEEN,

f := {
vrai
}
},

T := {
FAUX −> <fa ux>( ) ,
VRAI −> <v r a i>( ) ,

non (<fau x>) −> <v r a i>( ) ,

11
CHAPITRE 1. LANGAGE DE DESCRIPTION

non (<v r a i>) −> <fau x>( ) ,

e t (<v r a i>, <v r a i>) −> <v r a i>( ) ,


e t ( q 0 , q1 ) −> <fau x>( ) ,

o r (<faux>, <faux>) −> <fau x>( ) ,


o r ( q 0 , q1 ) −> <v r a i>( )
}

12
Chapitre 2

Analyse des besoins

2.1 Diagramme pieuvre

F ichier source Extensibilité

FP1
F C1
F C3
Compilateur P ortabilité
F C2

F ichier généré Robustesse

Figure 2.1 – Diagramme pieuvre

— FP1 : Générer du code Common Lisp implémentant la bibliothèque


Autograph [1] à partir d’un fichier source contenant la description d’un
automate programmé de termes.
— FC1 : L’implémentation du compilateur doit être maintenable et ex-
tensible afin de permettre l’ajout de nouvelles fonctionnalités, telles
que la possibilité d’importer des signatures (F) pour les automates.

13
CHAPITRE 2. ANALYSE DES BESOINS

— FC2 : Le compilateur doit être robuste face aux erreurs de syntaxe et


autres retours de fonction non satisfaisants.
— FC3 : Le compilateur doit pouvoir s’exécuter dans un environnement
Linux et MacOSX.

14
CHAPITRE 2. ANALYSE DES BESOINS

2.2 Diagramme FAST

Réaliser une Décomposer le Lire le fi-


FP1 Analyseur lexical
décomposition fichier source chier source
analytique du en lexèmes
fichier source
Vérifier le respect Analyseur
des règles de syntaxique
grammaire

Construire un Module de
arbre syntaxique gestion d’arbres
syntaxiques

Vérifier le Analyseur
respect des types sémantique

Vérifier et
résoudre les
identificateurs

Générer du code Ecrire dans le Module de


Common Lisp fichier cible génération de code

Ecriture d’une
FC1 documentation
couvrant
l’ensemble
des modules

Ecriture de tests
FC2 unitaires couvrant
l’ensemble
des modules

Utilisation
FC3 exclusive de
fonctionnalités
ANSI C11 et POSIX

Figure 2.2 – Diagramme FAST

15
CHAPITRE 2. ANALYSE DES BESOINS

2.3 Architecture logicielle


Afin de proposer une architecture cohérente pour notre logiciel, nous
avons souhaité nous rapprocher un maximum de l’architecture classique d’un
compilateur. Celle-ci est la suivante (2.3).

Fichier Analyseur Analyseur


source lexical syntaxique

Analyseur Générateur Fichier


sémantique de code généré

Figure 2.3 – Architecture classique d’un compilateur

Notre compilateur s’articule autour d’une bibliothèque de fonctions conte-


nant les modules de parsing et de génération de code. Les fonctionnalités de
ces derniers sont ensuite appelées au travers d’un main, point d’entrée de
notre compilateur. De cette manière, le compilateur flyc peut être embar-
qué sans difficulté dans d’autres projets nécessitant le traitement de fichiers
FLY. Le schéma suivant (2.4) présente l’architecture finale de notre logiciel.

16
CHAPITRE 2. ANALYSE DES BESOINS

Module de gestion
d’arbres syntaxiques

Analyseur Analyseur Analyseur


lexical syntaxique sémantique

Fichier Générateur
Compilateur
source de code

Fichier
généré

Figure 2.4 – Architecture logicielle choisie

2.4 Planification des tâches


Au début du projet, nous avons divisé les différents modules de notre pro-
jet en plusieurs tâches. Nous avons pour cela utilisé l’outil Gantt qui nous a
permis de visualiser cette planification. Il distingue les tâches entre elles et
permet de les planifier en gardant une structure hiérarchique.

17
CHAPITRE 2. ANALYSE DES BESOINS

Figure 2.5 – Gantt initial

Compte tenu de la complexité inhérente au concept d’automates program-


més de termes, la compréhension et la prise en main de ces derniers nous a
pris plus de temps que prévu. Nous avons pris du retard dans la planifica-
tion initiale, notamment dans la conception du langage FLY qui a beaucoup
évolué tout au long du projet. Cependant, ce retard a été rattrapé lors du
développement de l’analyseur lexical et syntaxique.
Une fois le module de gestion d’arbres syntaxiques achevé, nous avons constaté
la nécessité d’une analyse sémantique (3.4). Le module de génération de code
prévu initialement pourra être implémenté dés lors qu’une interface stan-
dardisée faisant le lien avec la bibliothèque Autowrite aura été développée.
Ci-dessous, le Gantt avec le temps de développement réel de chacun des
modules (2.6).

18
CHAPITRE 2. ANALYSE DES BESOINS

Figure 2.6 – Gantt réel

19
Chapitre 3

Détails d’implémentation

3.1 Analyseur lexical


3.1.1 Objectif et présentation de Flex
L’analyse lexicale est la première étape de la compilation durant laquelle
le fichier source est lu puis décomposé sous forme de lexèmes (aussi appelés
tokens en anglais). La composition de ces derniers est le travail de l’analy-
seur syntaxique. Un analyseur lexical reconnaît par exemple les parenthèses
comme étant des lexèmes valides, mais ne garantit pas qu’une parenthèse
ouvrante est accompagnée d’une parenthèse fermante. Ainsi, l’ensemble des
lexèmes constitués est transmis à l’analyse syntaxique, étape suivante de la
compilation.

Flex est un outil développé par Vern Paxson permettant de générer des
analyseurs lexicaux. Cet outil prend en entrée un fichier contenant un en-
semble de paires constituées d’une expression régulière et d’un bloc d’ins-
tructions C dans lequel il est possible de retourner un lexème. Ce bloc est
exécuté à chaque fois que l’analyseur rencontre une chaîne de caractères
respectant l’expression régulière associée. En complément aux expressions
régulières, Flex propose également un mécanisme d’états permettant de re-
grouper ces dernières. Concrètement, les règles préfixées par "<COMMENT>" ne
seront disponibles que lorsque l’analyseur se trouve dans l’état "<COMMENT>".
Ce mécanisme, bien que très puissant, n’est néanmoins pas indispensable
et, utilisé à mauvais escient, tend à mélanger analyse lexicale et syntaxique.
C’est pourquoi, nous avons souhaité limiter son utilisation au maximum.

20
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

L’exemple suivant illustre une solution élégante mise en place afin d’éliminer
les commentaires multi-lignes sans avoir recours à un état :

Avec état : Sans état :

"/*" { BEGIN COMMENT; } "/*"([^*]|\*[^/])*"*/" { }


<COMMENT>.|\n { }
<COMMENT>"*/" { BEGIN INITIAL; }

3.1.2 Exemple de décomposition


Voici un exemple qui illustre la décomposition en lexèmes d’un fichier FLY
par l’analyseur lexical :

T := {
s(q0) -> <q>()
}

T ASSIGN {
LABEL ( STATE ) TRANSITION < LABEL > ( )
}

3.1.3 Tests unitaires


Pour vérifier le bon fonctionnement de notre analyseur lexical, nous avons
mis en place une batterie de tests unitaires dont le comportement est le sui-
vant :

— Lire un fichier source FLY ;


— Pour chacun des lexèmes retournés, le comparer à une liste de lexèmes
attendus ;
— Le test est un succès si tous les lexèmes attendus ont été retournés et
qu’aucun autre lexème n’est retourné par l’analyseur lexical ;
— Dans le cas ou l’une des deux précédentes conditions n’est pas respec-
tée, le test échoue.

21
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

3.2 Analyseur syntaxique


3.2.1 Objectifs et présentation de Bison
L’analyse syntaxique est la seconde étape de la compilation durant la-
quelle l’ordre des lexèmes retournés par l’analyseur lexical est vérifié. L’ana-
lyseur syntaxique assure que le flux de lexèmes reçu respecte la grammaire
algébrique qu’il définit. Une grammaire algébrique est décrite selon le qua-
druplet (N , Σ, P, S).

N est l’ensemble des symboles non-terminaux. Ces derniers s’ob-


tiennent par réduction d’une suite de symboles définie par la grammaire.

Σ est l’ensemble des symboles terminaux. Les symboles terminaux


constituent l’alphabet du langage décrit par la grammaire.

P est l’ensemble des règles de la grammaire. Elles associent un sym-


bole non-terminal à une suite de symboles terminaux et non-terminaux.

S est un élément de N . Ce symbole est appelé axiome et est le symbole


de départ de la grammaire.

En complément de la vérification du respect de la syntaxe, l’analyseur


syntaxique est aussi chargé de construire un arbre syntaxique représentant
de façon abstraite le contenu du fichier source.

Bison est un outil développé par la fondation GNU permettant de générer


des analyseurs syntaxiques sous forme d’automates à pile déterministes. Cet
outil prend en entrée un fichier contenant un ensemble de règles gramma-
ticales, chacune étant associée à un bloc d’instructions C comme l’illustre
le schéma suivant (3.1). Les symboles terminaux correspondent aux lexèmes
retournés par l’analyseur lexical.

22
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

Symboles non-terminaux

symbol : symbol-name Règles

{
$$ = flyc_ast_new(...);

} Symboles terminaux
| symbol-name ’[’ FLYC_TOK_INTEGER ’]’

{
Actions
$$ = flyc_ast_new(...);

Figure 3.1 – Exemple de règle Bison

3.2.2 Conflits grammaticaux


L’utilisation d’automates à pile déterministes dans l’implémentation d’un
analyseur syntaxique conduit à deux erreurs récurrentes : les conflits empi-
lement - réduction et réduction - réduction.

Les conflits empilement - réduction interviennent lorsque l’analyseur


syntaxique est en mesure à la fois d’empiler le lexème courant (afin de ré-
duire ultérieurement par une règle) ou de réduire immédiatement par une
autre règle. Dans notre cas, ce type de conflit s’est manifesté lors de l’écri-
ture des règles définissant les expressions. L’expression 1 + 2 - 3 illustre le
problème. En effet, il est possible de réduire en favorisant la soustraction au
profit de l’addition 1 + (2 - 3) ou l’inverse (1 + 2) - 3. Notre solution
consiste à obliger le parenthésage systématique des expressions afin de lever
les ambiguïtés.

23
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

Les conflits réduction - réduction interviennent lorsque l’analyseur


syntaxique est en mesure de réduire la pile de lexèmes constituée suivant
deux règles différentes. Dans notre cas, ce type de conflit s’est à nouveau
manifesté lors de l’écriture des règles définissant les expressions. L’exemple
suivant illustre le problème :

boolean-expression : ...
| arithmetic-expr-or-ini-or-state < arithmetic-expr-or-ini-or-state
| port-ini-or-state < port-ini-or-state
| ...
;

Ici, si l’analyseur syntaxique rencontre un état dans le membre de gauche,


celui-ci peut être réduit suivant la règle arithmetic-expr-or-ini-or-state
ou la règle port-ini-or-state. Le lexème d’avance < , commun aux deux
règles, ne permet pas de lever l’ambiguïté. Notre solution consiste à factoriser
l’élément commun aux deux règles (state) sous une seule et même règle. Le
résultat est le suivant :

boolean-expression : ...
| state < arithmetic-or-port-or-state
| arithmetic-expr-or-ini < arithmetic-expr-or-ini-or-state
| port-ini < port-ini-or-state
| ...
;

3.2.3 Gestion des fuites mémoires


Lors de l’implémentation de l’analyseur syntaxique, nous avons été confron-
tés à des cas de fuites mémoires causées par ce dernier. L’allocation sur le tas
des multiples noeuds de l’arbre syntaxique au cours du l’analyse syntaxique
entraîne des fuites mémoires inévitables lorsque celle-ci échoue. En effet, en
cas d’erreur, l’arbre en cours de construction n’est jamais remonté jusqu’à
l’utilisateur et ne peut donc jamais être libéré. Afin de pallier à ce problème,
nous avons dans un premier temps mis en place une pile dans laquelle chacun
des noeuds créés au cours de l’analyse syntaxique est référencé. Ainsi, une
fois terminée et dans le cas où celle-ci échoue, on dépile puis libère un à un

24
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

les noeuds.

Néanmoins, cette solution s’est avérée incomplète car, dans le cas où le


symbole d’avance maintenu par Bison au moment de l’erreur contient une
chaîne de caractères allouée sur le tas, celle-ci est inaccessible et ne peut
pas être empilée. La solution finale mise en place utilise le mécanisme de
destructeurs de Bison. Ceux-ci permettent de définir un comportement spé-
cifique en cas d’erreur ou d’arrêt forcé de l’analyseur syntaxique (appel à
la macro YYACCEPT ou YYABORT). Voici le destructeur chargé de libérer les
lexèmes contenant une chaîne de caractères :

%destructor {
free($$);
} <s>

3.2.4 Tests unitaires


Les tests de l’analyseur syntaxique sont nommés de façon à indiquer auto-
matiquement le retour attendu au moteur de test. La convention de nommage
retenue est le suivante (3.2).

Nom du test
Type (erreur ou avertissement)
Code de retour
Ligne

test_warning . in-warning . 21 - 42
test_error . in-error . 42 - 21

Figure 3.2 – Convention de nommage des fichiers de test invalides

Dans le cas où le test unitaire est un fichier FLY valide syntaxiquement,


le nommage de ce dernier doit respecter le format suivant : name.in

25
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

Le comportement des tests unitaires de l’analyseur syntaxique est le sui-


vant :

— Lire un fichier source FLY ;


— Lire le code de retour de l’analyseur syntaxique ;
— Si le test est valide syntaxiquement, le code de retour doit être 0 ;
— Dans le cas contraire, celui-ci doit être 1 et la structure d’erreur doit
contenir les valeurs placées dans le nom du fichier de test.

3.3 Module de gestion d’arbres syntaxiques


3.3.1 Typage générique en langage C
Afin de factoriser au maximum les différents traitements applicables aux
noeuds de l’arbre syntaxique (tels que la suppression ou l’affichage d’un
noeud), nous avons fait le choix d’un typage générique pour ces derniers.
En C, trois principales solutions existent pour répondre à ce besoin :

— Déclarer une structure par type de noeud puis utiliser le pointeur


générique void * dans le prototype des fonctions à factoriser. Ici, le
problème réside dans la perte d’identité de l’objet passé à une telle
fonction. En effet, le compilateur C n’est plus à même de vérifier au
moment de la compilation que le pointeur passé fait bien référence à
un noeud et non, par exemple, à une chaîne de caractères. Une telle
implémentation augmente donc considérablement le risque d’erreurs
de segmentation à l’exécution ;
— La deuxième solution consiste à factoriser le contenu de chacune des
précédentes structures au sein d’ une seule et même union. A nouveau,
cette solution comporte de nombreux défauts. En effet, une telle fac-
torisation conduit à un code non maintenable et parfaitement illisible.
L’union résultant de la combinaison de toutes les structures pourrait
être dans notre cas aisément composée d’une trentaine de champs ;
— La troisième et dernière solution combine les avantages des deux pré-
cédentes et est utilisée dans le noyau Linux. Ce deuxième point nous
a paru être un gage important de fiabilité, c’est pourquoi nous avons
choisi d’implémenter cette solution. Les détails de son fonctionnement
sont définis ci-dessous.

26
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

La solution choisie repose sur l’utilisation de la macro offsetof déclarée


dans l’interface stddef.h. Celle-ci évalue le décalage en octets d’un membre
d’une structure ou d’une union. Cette fonctionnalité est utilisée dans le noyau
Linux afin d’implémenter la macro containerof qui permet, à partir d’un
pointeur, d’un nom de structure ou d’union et d’un champ, d’obtenir l’adresse
de la structure englobante. Par exemple, pour une structure S définie de la
façon suivante :

struct S {
char *name;
struct T t;
};

Le code suivant permet d’obtenir une adresse valide pointer pointant sur la
structure englobante s à partir du pointeur p :

struct S s ;
struct T *p = &(s.t);
struct S *pointer = containerof(p, struct S, t);

Nous avons donc appliqué ce modèle au module de gestion d’arbres syn-


taxiques. Il convient dans un premier temps de définir une structure com-
mune à tous les noeuds. Celle-ci est la suivante :

typedef struct flyc_ast_s {


int line;

enum flyc_ast_e type;


unsigned refcount;
enum flyc_semantic_step_e passed;
} flyc_ast_t;

Le rôle du champ line est abordé dans la partie portant sur le module de
gestion des erreurs (3.5). Le champ type décrit le type du noeud courant
(type de la structure englobante). Le champ refcount est décrit dans la sec-
tion portant sur le système de référence (3.3.2). Pour finir, l’utilité du champ
passed est expliquée dans la partie portant sur l’analyse sémantique (3.4).

27
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

Nous pouvons ensuite définir les structures englobantes comme l’illustre


l’exemple suivant définissant le contenu des noeuds de type array.

struct flyc_ast_array_s {
flyc_ast_t instance;

unsigned length;
unsigned size;
flyc_ast_t **members;
};

La macro suivante permet de récupérer le noeud de type array englobant le


noeud générique passé en paramètre.

#define flyc_ast_to_array(ast) \
containerof(ast, struct flyc_ast_array_s, instance)

De l’extérieur, la fonction permettant de récupérer la longueur d’un tableau


est déclarée comme suit :

extern unsigned flyc_ast_array_get_length (const flyc_ast_t *node);

Cette dernière est implémentée de la manière suivante :

unsigned flyc_ast_array_get_length (const flyc_ast_t *node) {


assert(node && (node->type == FLYC_AST_ARRAY));
struct flyc_ast_array_s *container = flyc_ast_to_array(node);

return container->length;
}

3.3.2 Durée de vie d’un noeud et système de référence

Afin de réduire la complexité en espace engendrée par la duplication de


noeuds au sein de l’arbre syntaxique, nous avons mis en place un système

28
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

de référence. Ce système permet à chaque noeud de conserver un compteur


de référence. A la manière d’un ramasse-miettes, ce compteur permet de
savoir quand un noeud n’est plus référencé et peut être libéré. Pour implé-
menter ce système, nous avons ajouté un champ refcount dans la structure
flyc_ast_t commune à l’ensemble des noeuds. Ce compteur de référence est
manipulé à l’aide des primitives flyc_ast_decref et flyc_ast_incref per-
mettant respectivement la décrémentation et l’incrémentation du compteur.
C’est cette première primitive qui est chargée de libérer le noeud lorsque
celui-ci n’est plus référencé. Celle-ci est implémentée comme suit :

flyc_ast_t *flyc_ast_decref (flyc_ast_t *ast) {


assert(ast);

flyc_ast_setref(ast, &flyc_ast_decref);

if (--(ast->refcount) == (unsigned)0) {
flyc_ast_delete(ast);
ast = NULL;
}

return ast;
}

Ici la méthode flyc_ast_setref permet d’appeler récursivement la primitive


decref sur l’ensemble des fils du noeud courant.

3.3.3 Tests unitaires


Pour vérifier le bon fonctionnement de notre module de gestion d’arbres
syntaxiques, nous avons mis en place une batterie de tests unitaires couvrant
les fonctionnalités suivantes :

— Création d’un noeud de l’arbre ;


— Ajout d’un élément dans un tableau ;
— Fusion de deux tableaux ;
— Incrémentation et décrémentation des références d’un noeud de l’arbre.

29
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

3.4 Analyseur sémantique


L’analyse sémantique intervient après l’analyse syntaxique et avant la gé-
nération de code. Elle vérifie la cohérence (comme par exemple l’absence de
doublons) et résout les identificateurs dans l’arbre syntaxique. Ces identifi-
cateurs font référence à des éléments déclarés précédemment. Les éléments
devant être résolus sont stockés dans des champs de type void * contenant,
avant analyse sémantique, une chaîne de caractères puis, après résolution, une
référence vers un noeud de l’arbre syntaxique. On retrouve des références à
résoudre dans les trois cas suivants :

— Lorsqu’un type est un type primitif. La structure associée est donnée


en exemple ci-dessous ;
— Pour chacun des symboles présents dans les prototypes des transitions
de l’automate ;
— Pour chaque paramètre faisant référence à un état de l’automate (<
identif icateur >).
struct flyc_ast_type_s {
flyc_ast_t instance;

enum flyc_ast_type_e type;


union {
void *ref;
flyc_ast_t *members;
};
};

Le traitement d’un noeud par les primitives modifiant le compteur de réfé-


rence varie selon l’étape de sémantique franchie par ce noeud. En effet, après
résolution d’une référence, un champ void * doit être considéré comme un
champ de type flyc_ast_t *. De ce fait, il doit également être traité récursi-
vement lors d’un appel aux primitives d’incrémentation et de décrémentation.
Pour celà, nous avons ajouté à la structure commune à l’ensemble des noeuds,
un champ passed permettant de connaitre la dernière étape de sémantique
franchie par un noeud.

L’analyse sémantique est effectuée en six étapes décrites ci-dessous :

30
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

La première étape vérifie que l’automate considéré est valide : celui-


ci doit être au minimum composé d’un symbole, d’un état obligatoirement
acceptant et d’une transition.

La deuxième étape vérifie le respect des propriétés suivantes dans l’en-


semble F :
— Chaque groupement de symboles par arité doit être unique ;
— Chaque symbole est défini selon un identificateur unique.

La troisième étape vérifie qu’il n’existe aucun doublon dans l’ensemble


des types primitifs B.

La quatrième étape vérifie qu’il n’existe aucun doublon dans l’ensemble


des états Q de l’automate et, pour chaque état q de type primitif appartenant
à Q, résout sa référence dans B.

La cinquième étape vérifie les propriétés suivantes dans l’ensemble des


états acceptants de l’automate (f ) :
— Chaque élément de f doit être défini précédemment dans Q ;
— L’ensemble f ne doit pas contenir de doublons.

La sixième étape est chargée de vérifier la sémantique de chacune des


transitions de l’automate. Cette étape est séparée en trois sous-traitements :

L’étape 6.a résout le symbole de chacun des prototypes et vérifie les pro-
priétés suivantes :
— La cardinalité de la structure du symbole est respectée ;
— La structure ne contient pas de doublon ;
— L’arité du symbole est respectée ;
— Les états abstraits doivent être correctement ordonnés ;
— Les états concrets doivent être définis dans Q. Leurs références sont
alors résolues.

L’étape 6.b vérifie qu’il n’y a pas de doublons dans les transitions. Pour
ce faire, elle vérifie si deux transitions ont le même prototype. Les valeurs
des décorations sont comparées après changement de base ainsi, 10(2) et 2
sont identiques.

31
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

L’étape 6.c vérifie en plusieurs sous-traitements la sémantique du corps


de chacune des transitions de l’automate :

L’étape 6.c.a vérifie la sémantique des initialiseurs d’états. Cette étape


vérifie les propriétés suivantes :
— Si l’état retourné est de type abstrait, il doit être déclaré dans les
paramètres de la transition ;
— Si l’état retourné est de type concret, celui-ci doit être déclaré dans
l’ensemble Q. Sa référence est alors résolue ;
— Si l’état retourné est décrit par un type primitif, l’expression paren-
thésée associée doit être vide ;
— En revanche, si l’état retourné est décrit par un type composé, l’ex-
pression parenthésée est à son tour analysée :
— Si l’expression est un noeud de type parameter, l’étape 6.c.c est
appelée ;
— Si l’expression est un noeud de type initializer, l’étape 6.c.d
est appelée ;
— Sinon, l’étape 6.c.e est appelée.
Après analyse de l’expression, le type remonté par cette dernière doit
être identique à celui de l’état instancié.

L’étape 6.c.b est chargé de vérifier la sémantique de chacune des clauses


composant un bloc condionnel. L’étape 6.c.e est appelée afin d’analyser l’ex-
pression booléenne contenue dans la clause. Puis, l’étape 6.c.a ou 6.c.b est
appelée suivant si l’élément retourné par la clause est un initialiseur d’état
ou à nouveau un bloc conditionnel.

L’étape 6.c.c vérifie la sémantique de chacun des noeuds de type parameter.


— Si le paramètre est une étiquette, celle-ci doit être déclarée dans la
structure de la transition ;
— Si le paramètre est un état concret, celui-ci doit être défini dans l’en-
semble Q. Sa référence est alors résolue ;
— Si le paramètre est un état abstrait, il doit être déclaré parmis les
paramètres de la transition ;
— Si le paramètre est un état concret suivi d’un indice (q0[1 -> 2] par
exemple), celui-ci doit faire référence à un élément existant apparte-
nant au type composé de l’état.

32
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

L’étape 6.c.d vérifie la sémantique des noeuds de type initializer en


particulier ceux traitant des ensembles. En effet, tous les éléments d’un en-
semble doivent être de même type. Par exemple, l’initialiseur suivant est
invalide : { 1, 2, 3, C(4) }

L’étape 6.c.e quant à elle vérifie la sémantique des expressions. Pour


chaque expression, les membres gauche et droit doivent être de même type.
Si l’opérateur est suivi d’une sélection (I[1, 3] par exemple), le type du
membre droit doit correspondre à celui du membre gauche sur lequel la sé-
lection a été appliquée. Par exemple, pour un état q défini comme suit : q :
{ (C x N x { N }) }, la sélection I[1, 3] impose au membre droit d’être
de type { (C x { N }) }.

3.4.1 Tests unitaires


Les tests unitaires de l’analyseur sémantique fonctionne à la manière de
ceux mis en place pour l’analyseur syntaxique. L’intégralité des vérifications
effectuées par la sémantique et détaillées précédemment sont testées.

3.5 Module de gestion des erreurs et avertis-


sements
L’objectif de ce module est de fournir à l’utilisateur une gestion d’er-
reurs et d’avertissements normalisée pour l’ensemble des modules de la bi-
bliothèque FLYC. Il définit les deux structures suivantes :

Structure d’erreur : Structure d’avertissement :

typedef struct { typedef struct {


enum flyc_error_e code; enum flyc_warning_e code;
int line; int line;
} flyc_error_t; } flyc_warning_t;

Dans chacune de ces structures, le champ code permet de mémoriser le


code d’erreur ou d’avertissement retourné. Celui-ci peut ensuite être uti-
lisé afin d’obtenir une explication détaillée du problème rencontré à l’aide
des méthodes flyc_error_get_message et flyc_warning_get_message. Le

33
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

champ line quant à lui permet de préciser la ligne dans le fichier source à
laquelle l’erreur ou l’avertissement est survenu. Dans le cas où la localisa-
tion d’une erreur est impossible (out of memory par exemple), ce champ est
initialisé à l’aide de la macro suivante :

#define FLYC_LOCATION_UNDEFINED 0

La gestion des lignes est une tâche commune à l’analyseur lexical et syn-
taxique et au module de gestion d’arbres syntaxiques. L’analyseur lexical
doit tout d’abord compter les lignes à mesure qu’il décompose en lexèmes le
fichier source. Pour cela, nous avons ajouté l’instruction suivante en début
de notre fichier Flex :

%option yylineno;

Dans un second temps, celui-ci est chargé de communiquer à l’analyseur


syntaxique le numéro de la ligne courante pour chacun des lexèmes qu’il
retourne. Pour cela, il est tout d’abord nécessaire d’activer l’option suivante
en début de notre fichier Bison :

%locations

Celle-ci permet d’ordonner à Bison l’allocation d’une structure permettant


d’associer à chacun des symboles terminaux et non-terminaux de la gram-
maire un numéro de ligne. Nous pouvons par la suite initialiser cette structure
pour chacun des lexèmes retournés depuis l’analyseur lexical à l’aide de la
macro suivante :

#define YY_USER_ACTION yylloc.first_line = yylineno;

Le contenu de cette macro est automatiquement dupliqué au début de cha-


cun des blocs d’instructions C définis dans l’analyseur lexical.

L’analyseur syntaxique doit également initialiser la structure de localisation


pour chaque symbole non-terminal qu’il construit. Pour cela, nous avons
ajouté l’instruction suivante à chaque action de la grammaire :

34
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION

@$.first_line = @1.first_line;

Enfin, chaque noeud de l’arbre syntaxique contient également un champ line


initialisé par l’analyseur syntaxique à la création de ces derniers.

$$ = flyc_ast_new(@1.first_line, ...);

Pour chacun des modules, la rencontre d’une erreur provoque un arrêt


immédiat de la procédure en cours. En revanche, le comportement à adopter
en cas d’avertissement est laissé à l’appréciation de l’utilisateur. Pour ce
faire, chacune des fonctions de la bibliothèque FLYC prend en paramètre un
pointeur de fonction devant respecter la signature suivante :

typedef flyc_status_t (*flyc_warning_f)(flyc_warning_t, void *);

Le traitement des avertissements peut aussi être ignoré en utilisant la macro


suivante comme argument :

#define FLYC_WARNING_IGNORE ((flyc_warning_f)NULL)

L’arrêt de la procédure est conditionné selon le retour de la fonction dé-


finie par l’utilisateur. Si celle-ci retourne FLYC_FAILURE la procédure s’ar-
rête immédiatement et indique une erreur. A l’inverse, si celle-ci renvoie
FLYC_SUCCESS, le traitement se poursuit. Dans le cas de notre compilateur, le
traitement des avertissements en tant qu’erreurs s’applique lorsque l’option
Werror est précisée.

35
Chapitre 4

Critique de l’implémentation

4.1 Complexité
La complexité en temps pourrait être réduite facilement grâce à l’uti-
lisation de tables de hachage afin d’implémenter les noeuds de type array
dans l’arbre syntaxique. En effet, dans le cadre de l’analyse sémantique, nous
sommes régulièrement amenés à parcourir des tableaux tels que celui conte-
nant les symboles de l’automate. La complexité actuelle en O(n) pourrait
être réduite à une complexité théorique en O(1) concernant l’accès aux élé-
ments d’un tableau. Malgré tout, nous avons décidé de ne pas utiliser de
tables de hachage afin d’accélérer le développement du module de gestion
d’arbres syntaxiques. Nous aurions également pu utiliser la bibliothèque de
fonctions GLib développée par l’équipe en charge du projet Gnome, mais la
portabilité du compilateur serait alors compromise.

La complexité en espace est satisfaisante grâce à l’implémentation du


système de référence décrit précédemment (3.3.2).

4.2 Perspectives d’évolution


En complément à l’utilisation de tables de hachage, nous avons réfléchi
à l’implémentation de nouveaux modules afin d’améliorer notre compilateur.
L’un d’eux porte sur la possibilité de définir une signature commune à plu-
sieurs automates et d’importer cette dernière à l’aide d’une option de compi-
lation (s <fichier>). L’automate partiel contenant la signature est ensuite

36
CHAPITRE 4. CRITIQUE DE L’IMPLÉMENTATION

fusionné avec celui devant être compilé après l’étape d’analyse syntaxique.
Ce module permettrait par exemple de prédéfinir la signature clique-width [7]
régulièrement utilisée par notre cliente.

La phase d’analyse sémantique pourrait également être améliorée en uti-


lisant, par exemple, les conditions de type is afin de concrétiser d’avantage
d’états abstraits. Exemple :

s(q0) -> {
if (q0 is <q>) <q0>()
else ...
}

Ici, en considérant la condition is, il est possible de concrétiser le type de q0


afin de vérifier que celui-ci est correctement initialisé.

Enfin, le module de gestion d’erreurs et d’avertissements pourrait être


enrichi en proposant des messages plus verbeux contenant, par exemple, lors
d’une redéfinition, le nom de l’élément en cause.

37
Conclusion

Ce projet fut véritablement enrichissant sur plusieurs aspects. Tout d’abord,


nous avons appris comment clarifier un besoin, l’analyser, organiser notre
temps de travail et nous répartir des tâches.

Sur le plan technique, nous avons tous les trois beaucoup progressé dans le
domaine de la compilation. Ce projet nous a notamment permis de découvrir
une nouvelle étape, l’analyse sémantique. Nous avons également découvert
de nouvelles techniques de programmation avancées en langage C, telles que
la solution de typage générique présentée dans la partie traitant de l’implé-
mentation du module de gestion d’arbres syntaxiques (3.3).

L’aspect relationnel du projet nous a également beaucoup apporté. En


effet, celui-ci est l’un des premiers où nous avons eu à répondre aux besoins
d’un client. A travers cet exercice, nous nous sommes rendu compte qu’il est
difficile d’anticiper l’intégralité des besoins et d’établir des spécifications com-
plètes et précises dès le commencement d’un projet. Néanmoins, les objectifs
de maintenabilité et d’extensibilité fixés par notre cliente ont été atteints et
celle-ci est satisfaite du résultat. Malheureusement, le critère de portabilité
n’est que partiellement respecté. En effet, la phase de linkage lors de la com-
pilation de notre compilateur pose problème dans un environnement MacOSX
malgré le respect des normes POSIX [2].

38
Bibliographie

[1] TRAG - term rewriting automata and graphs. http://dept-info.


labri.fr/~idurand/trag/. Dernière consultation : 10/01/2017.
[2] Ieee standard for information technology-portable operating system inter-
face (posix)-part 1 : System application program interface (api)- amend-
ment d : Additional real time extensions [c language], 1999.
[3] B. Courcelle and I. Durand. Automata for the verification of monadic
second-order graph properties. Journal of Applied Logic, 10(4) :368 –
409, 2012. Selected papers from the 6th International Conference on Soft
Computing Models in Industrial and Environmental Applications.
[4] B. Courcelle and I. Durand. Model-Checking by Infinite Fly-Automata,
pages 211–222. Springer Berlin Heidelberg, Berlin, Heidelberg, 2013.
[5] B. Courcelle and I. A. Durand. Fly-automata, their properties and appli-
cations. In B. B.-M. et al., editor, 16th International Conference on Im-
plementation and Application of Automata, volume 6807 of Lecture Notes
in Computer Science, pages 264–272, Blois, France, July 2011. Springer
Verlag.
[6] I. Durand. Autowrite : A tool for checking properties of term rewrite sys-
tems. http://dept-info.labri.fr/~idurand/autowrite/. Dernière
consultation : 10/01/2017.
[7] X. Pujol. Largeur de clique. http://perso.ens-lyon.fr/eric.
thierry/Graphes2007/xavier-pujol.pdf. Dernière consultation :
06/02/2017.

39

Vous aimerez peut-être aussi