IFT313
Introduction aux langages formels
Froduald Kabanza
Département d’informatique Université de Sherbrooke
planiart.usherbrooke.ca/kabanza/cours/ift313
Introduction
IFT313 © Froduald Kabanza
2
Aperçu des processeurs de langages
Sujets
• C’est quoi un processeur de langage ?
• Le modèle analyse-synthèse
• Pourquoi les processeurs de langages sont faciles à implé- menter ?
• Des grammaires aux arbres syntaxiques
• Analyse descendante vs analyse ascendante
IFT313 © Froduald Kabanza
3
C’est quoi un processeur de langages ?
- Processeurs de langage et compilateurs sont synonymes.
- Un compilateur est un programme qui reçoit le code (texte) d’un programme dans un langage (langage source) et produit du code dans un autre langage (le langage cible)
messages d’erreurs
compiler compiler
code source code cible
code machine Pentium code machine SPARC - C
Examples :
fichiers .class code Java - JAVA
- Scheme
IFT313 © Froduald Kabanza
4
Pourquoi compiler ?
- Les ordinateurs exécutent du code machine différent du code source.
- Ou le programme est exécuté par une machine virtuelle (Java) dans un code (.class) différent du code source (fichiers .java)
- Faire l’interface entre deux applications différentes: par exemple une application produit des données en format XML, une autre doit les af- ficher ou les traduire en requêtes pour une base données.
IFT313 © Froduald Kabanza
5
Compilation vs. Conversion de fichiers
- Fondamentalement c’est la même chose !
- La différence est dans le degré de complexité:
+ Un langage de programmation a un “sens” ou une sémantique qui doit être préservée.
+ Mais un convertisseur d’images en formats GIF vers un format JPEG, qui est aussi essentiellement un convertisseur de fichiers, doit aussi préserver les propriétés de l’image, mais n’est pas un compilateur pour autant!
+ En fait, la différence principale est dans la complexité de la syn- taxe et la sémantique du langage source et du langage cible.
IFT313 © Froduald Kabanza
6
Schéma d’un compilateur traditionnel
Code source
avec des macros
Préprocesseur Préprocesseur
cppcpp
Programme source
compilateur
cc ou gcc
compilateur
cc ou gcc
Programme assembleur cible
Assembleur Assembleur
code machine Chargeur / éditeurs de liens Chargeur / éditeurs de liens
Exécutable
Libraires et
fichiers objets
IFT313 © Froduald Kabanza
7
De nombreuses cas sont bien plus simples
Code source (ex.: .java, .xml)
Compilateur / Processeur de langage (e.x.: javac, xml2html)
Compilateur / Processeur de langage (e.x.: javac, xml2html)
Code cible
(ex.: .class, .html)
IFT313 © Froduald Kabanza
8
Modèle analyse-synthèse de la compilation
- Il y a deux phases principales d’un processeur de langage:
+ analyse (front-end) + synthèse (back-end)
- La phase analyse vérifie la syntaxe du code source et produit une représentation intermédiaire du code source (arbre sémantique) . Cette phase d’analyse comprend trois sous-phases :
Analyse lexicale (lexical analysis ou scanning)
Analyse syntaxique (syntactic analysis ou parsing)
Analyse sémantique (semantic analysis)
IFT313 © Froduald Kabanza
9
Modèle analyse-synthèse de la compilation
- La phase de synthèse génère le code cible à partir de la représentation intermédiaire.
- Pour des cas simples, les deux phases analyse/synthèse sont combinées.
code source Représentation
(sémantique) intermédiaire code cible
analyse (front-end) analyse (front-end)
synthèse (back-end) synthèse (back-end)
compilateur
IFT313 © Froduald Kabanza
10
Modèle analyse-synthèse de la compilation
Exemple
position = initial + rate * 60 Analyse lexicale (scanner) Analyse lexicale (scanner)
Analyse syntaxique (parser) Analyse syntaxique (parser)
id1 = id2 + id3 * 60
position id1
rate
initial id2 id3
=
+ id1 *
id2
id3
60 code source
arbre syntaxique table de symboles
unités lexicales (token)
IFT313 © Froduald Kabanza
11
Modèle analyse-synthèse de la compilation
Exemple (suite)
Analyse sémantique Analyse sémantique
Génération de code intermédiaire Génération de code intermédiaire
= +
id1 *
id2
id3
int2real 60
temp1 = int2real(60);
temp2 = id3 *temp1; temp3 = id2 + temp2; id1 = temp3
IFT313 © Froduald Kabanza
12
Modèle analyse-synthèse de la compilation
Exemple (suite)
Optimisation du code Optimisation du code
temp1 = id3 *60.0 id1 = id2 *temp1
Génération du code Génération du code
MOVF id3 , R2 MULF #60.0 , R2 MOVF id2 , R1 ADD R2 , R1 MOVF R1 , id1
-
Certaines de ces phases sont parfois combinées- Ce cours se limitera à l’analyse lexicale et à l’analyse syntaxique avec génération de code par une méthode simple (actions sémantiques).
IFT313 © Froduald Kabanza
13
Pourquoi les compilateurs sont faciles à im- plémenter ?
- Les compilateurs sont faciles à implémenter parce que :
+ Le code source est dans un langage; il a donc une structure bien définie (dans un manuel de référence du langage)
+ Le programme a aussi une sémantique (définie aussi dans un manuel de référence)
- À cause des ces deux facteurs, on peut définir une grammaire pour le lan- gage, avec des attributs sémantiques et utiliser la théorie des langages de programmation pour construire un compilateur.
IFT313 © Froduald Kabanza
14
Pourquoi les compilateurs sont faciles à im- plémenter ?
- Si id est un identificateur et exp est une expression alors id = exp est une instruction.
- Si exp est une expression et stat est une instruction alors les formes suivantes sont des instructions :
while (exp) stat if (exp) stat
- Un identificateur est une expression.
- Un nombre est une expression.
- Si exp1 et exp2 sont des expressions, il en est de même pour : exp1 + exp2,
exp1 * exp2 et (exp1)
Exemple : Structure simplifiée des instructions =, if et while
IFT313 © Froduald Kabanza
15
Grammaires
- La structure d’un langage de programmation est spécifiée de manière formelle par une grammaire.
- La théorie des langages de programmation nous donnent des techniques permettant de vérifier si un programme source respecte le langage défini par une grammaire. En d’autres mots, si le programme source est syntaxiquement correcte.
- La conception de compilateurs reposent fortement sur cette théorie et tech- niques.
- Presque tous les langages de programmation sont basés sur un type spécial de grammaires, très efficace, appelés grammaires hors-contexte.
IFT313 © Froduald Kabanza
16
Grammaire pour l’exemple précédent
1
stat ® id = exp2 stat ® if ( exp ) stat
3 stat ® while ( exp ) stat 4 exp ® id
5 exp ® num 6 exp ® ( exp ) 7 exp ® exp + exp 8 exp ® exp * exp
- C’est une grammaire hors-contexte (aussi connu sous le nom de forme Backus- Naur)
- Les symboles “if”, “while”, “num”, “id”, “=“, “(“, “)”, “=”, “+” et “*” sont des symboles terminaux.
- Les symboles stat et exp sont des non-terminaux.
- Le programme source contient seulement des symboles terminaux.
IFT313 © Froduald Kabanza
17
Arbre d’analyse
- Plus précisément, le flux de tokens générés par l’analyseur lexical est une séquence de terminaux.
- L’analyseur syntaxique vérifie que la séquence est effectivement dérivable de la grammaire en construisant un arbre d’analyse.
- Si la séquence n’est pas dérivable de la grammaire, ceci signifie qu’il y a une erreur (syntaxique) dans le code source.
IFT313 © Froduald Kabanza
18
Arbre d’analyse
- Le scanner le convertit en
- Le parser lit les tokens un à un et produit un arbre d’analyse:
Exemple : code source position = initial + rate * 60 id
1= id
2+ id
3* num
stat
id = exp
exp + exp
exp * exp
id2
id3 num
IFT313 © Froduald Kabanza
19
Dérivation
- Une règle de la grammaire est appelée une production.
- Intuitivement, le parser applique les règles de production pour générer l’arbre d’analyse : le symbole de gauche de la règle est remplacé par les symboles de la partie droite.
- Pour id1 = id2 + id3 * num, on a:
1 stat ® id = exp 2 stat ® if ( exp ) stat 3 stat ® while ( exp ) stat
4 exp ® id
5 exp ® num 6 exp ® ( exp ) 7 exp ® exp + exp 8 exp ® exp * exp
stat => id = exp
=> id = exp + exp => id = id + exp
=> id = id + exp * exp => id = id + id * exp => id = id + id * num
stat
= exp
exp + exp
exp * exp
id1
id2
id3 num
IFT313 © Froduald Kabanza
20
Dérivation
- L’application d’une production pour générer une nouvelle chaine de sym- boles est appelée une dérivation.
- Ainsi un arbre d’analyse est juste une réécriture de la séquence de dérivation.
- Dans l’exemple précédent, à chaque étape, on a chaque fois remplacé le symbole le plus à gauche.
Ça s’appelle la dérivation la plus à gauche (leftmost derivation).
IFT313 © Froduald Kabanza
21
Dérivation
- En faisant l’inverse, on a la dérivation la plus à droite (rightmost derivation).
stat => id = exp
=> id = exp + exp
=> id = exp + exp * exp => id = exp + exp * num => id = exp + id * num => id = id + id * num
- Les analyseurs syntaxiques utilisent soit la dérivation la plus à gauche, soit la dérivation la plus à droite.
- Chacune des deux approches a ses propres forces et faiblesses.
1 stat ® id = exp 2 stat ® if ( exp ) stat
3 stat ® while ( exp ) stat
4 exp ® id
5 exp ® num 6 exp ® ( exp ) 7 exp ® exp + exp 8 exp ® exp * exp
IFT313 © Froduald Kabanza
22
Analyses syntaxiques LL et LR
- Les analyseurs syntaxiques qui utilisent la dérivation la plus à droite sont en principe plus efficaces, mais plus difficiles à implémenter.
- Dans les deux cas, le parser lit le code source de gauche à droite et, en fonc- tion du ou des tokens suivants, décide de la production à appliquer pour la prochaine étape de dérivation.
- Les parsers qui utilisent une lecture de gauche à droite (Left-to-right) avec une dérivation la plus à gauche (Leftmost) sont appelés Left-to-right scan- ning Leftmost derivation ou LL tout court.
IFT313 © Froduald Kabanza
23
LL and LR parsing
- Si on veut mettre en emphase le nombre de tokens lus d’un coup enfin de déterminer la production à utiliser, on dit LL(k), où ‘k’ désigne le nombre de tokens lus (look ahead).
- De manière analogue, les parsers qui utilisent la dérivation la plus à droite sont appelés “Left to right scanning, Rightmost derivation, k character look ahead” or LR(k) tout court.
IFT313 © Froduald Kabanza
24
La question fondamentale dans l’analyse syntaxique
- Ainsi la question fondamentale dans l’analyse syntaxique est de déterminer la production de la grammaire à utiliser pour la prochaine dérivation.
- Les méthodes LL et LR donnent les solutions pour des grammaires hors- contexte.
- Le nombre ‘k’ de tokens lus en avance pose des restrictions supplémentaires sur la grammaire et, conséquemment, sur le langage qu’on peut compiler.
- Plus ‘k’ est élevé, plus la grammaire est puissante et plus efficace la méthode d’analyse devient.
- k=1 est suffisant pour la plupart des langages de programmation, moyennant quelques “raccourcis”.
IFT313 © Froduald Kabanza
25
Règles sémantiques
- En générant du code, un compilateur doit préserver la sémantique du code source.
- La sémantique est spécifiée en associant des ‘règles sémantiques’ aux pro- ductions de la grammaires, c-à-d., en utilisant des grammaires attribués.
- Exemple : de la notation infixée vers la notation préfixée
Production Règle sémantique
exp -> num exp -> id
exp -> exp1 + exp2 exp -> exp1 * exp2
exp.t = num.val exp.t = id.val
exp.t = ‘+’ || exp1.t || exp2.t exp.t = ‘*’ || exp1.t || exp2.t
t est l’attribut; “||” désigne le symbole de concaténation
IFT313 © Froduald Kabanza
26
Résumé
- L’analyse lexicale est possible parce que les « mots » ou « unités lexicales » (tokens) d’un langage de programmation sont spécifiées par des expressions régulières sur un alphabet (ASCII, Unicode, etc.)
- La compilation est possible parce que les langages ont une syntaxe (que nous spéci- fierons par des grammaires) et une sémantique (que nous spécifierons par des règles sémantiques).
- Les processeurs de langages sont fondamentaux en informatique. Un compilateur er- roné peut causer des pertes énormes voire des dangers, dépendamment de l’applica- tion ciblée par le code compilé.
- Il est important de concevoir les compilateurs en se basant sur une théorie et des tech- niques fiables.
- Les analyseurs lexicaux reposent sur la théorie des expressions régulières et des au- tomates finis.
- Les analyseurs syntaxiques reposent sur la théorie des grammaires, des automates à piles et des automates finis.
IFT313 © Froduald Kabanza
27
Résumé
- À la fin, la question fondamentale dans l’analyse syntaxique demeure “quelle production appliquer pour la prochaine dérivation?”
- Les théories et les techniques que nous verrons dans une optique de pro- cesseurs de langages sont très utiles pour d’autres domaines et important pour la culture générale d’un informaticien.
- Elles sont la base de biens de méthodes en intelligence artificielle
(traitement du langage naturelle) et en génie logiciel (outils de validation et de vérification automatique de programmes sources).
IFT313 © Froduald Kabanza
28