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
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
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.
F = F0 ⊕ F1 ⊕ ... ⊕ Fn
s ∈ Fi ⇔ arite(s) = i | i ∈ [0, n]
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
3
non vrai
et f aux
ou vrai F f aux
V vrai
non f aux
V vrai
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
F := {
0 := { a, b },
1 := { c },
2 := { d[1] }
}
5
CHAPITRE 1. LANGAGE DE DESCRIPTION
6
CHAPITRE 1. LANGAGE DE DESCRIPTION
Q := { B := {
Primitif
u : OK, OK
v : (C x { N }), }
Etats finaux f := { u }
} Type
Etat
Ici, le second état v est de type composé, c’est une relation entre une
étiquette et un ensemble de nombres : C × Pf (Z).
7
CHAPITRE 1. LANGAGE DE DESCRIPTION
Symbole F := {
u -> ... , 0 := { u },
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
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.
Nombre
42 Base
00101010 ( 2 )
52 ( 8 )
42 ( 10 )
2A ( 16 )
9
CHAPITRE 1. LANGAGE DE DESCRIPTION
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+ × ×
10
CHAPITRE 1. LANGAGE DE DESCRIPTION
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>( ) ,
11
CHAPITRE 1. LANGAGE DE DESCRIPTION
12
Chapitre 2
FP1
F C1
F C3
Compilateur P ortabilité
F C2
13
CHAPITRE 2. ANALYSE DES BESOINS
14
CHAPITRE 2. ANALYSE DES BESOINS
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
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
15
CHAPITRE 2. ANALYSE DES BESOINS
16
CHAPITRE 2. ANALYSE DES BESOINS
Module de gestion
d’arbres syntaxiques
Fichier Générateur
Compilateur
source de code
Fichier
généré
17
CHAPITRE 2. ANALYSE DES BESOINS
18
CHAPITRE 2. ANALYSE DES BESOINS
19
Chapitre 3
Détails d’implémentation
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 :
T := {
s(q0) -> <q>()
}
T ASSIGN {
LABEL ( STATE ) TRANSITION < LABEL > ( )
}
21
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
22
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
Symboles non-terminaux
{
$$ = flyc_ast_new(...);
} Symboles terminaux
| symbol-name ’[’ FLYC_TOK_INTEGER ’]’
{
Actions
$$ = flyc_ast_new(...);
23
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
boolean-expression : ...
| arithmetic-expr-or-ini-or-state < arithmetic-expr-or-ini-or-state
| port-ini-or-state < port-ini-or-state
| ...
;
boolean-expression : ...
| state < arithmetic-or-port-or-state
| arithmetic-expr-or-ini < arithmetic-expr-or-ini-or-state
| port-ini < port-ini-or-state
| ...
;
24
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
les noeuds.
%destructor {
free($$);
} <s>
Nom du test
Type (erreur ou avertissement)
Code de retour
Ligne
test_warning . in-warning . 21 - 42
test_error . in-error . 42 - 21
25
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
26
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
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);
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
struct flyc_ast_array_s {
flyc_ast_t instance;
unsigned length;
unsigned size;
flyc_ast_t **members;
};
#define flyc_ast_to_array(ast) \
containerof(ast, struct flyc_ast_array_s, instance)
return container->length;
}
28
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
flyc_ast_setref(ast, &flyc_ast_decref);
if (--(ast->refcount) == (unsigned)0) {
flyc_ast_delete(ast);
ast = NULL;
}
return ast;
}
29
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
30
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
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
32
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
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;
%locations
34
CHAPITRE 3. DÉTAILS D’IMPLÉMENTATION
@$.first_line = @1.first_line;
$$ = flyc_ast_new(@1.first_line, ...);
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.
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.
s(q0) -> {
if (q0 is <q>) <q0>()
else ...
}
37
Conclusion
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).
38
Bibliographie
39