• Aucun résultat trouvé

Introduction Introduction aux langages formels IFT313

N/A
N/A
Protected

Academic year: 2022

Partager "Introduction Introduction aux langages formels IFT313"

Copied!
28
0
0

Texte intégral

(1)

IFT313

Introduction aux langages formels

Froduald Kabanza

Département d’informatique Université de Sherbrooke

planiart.usherbrooke.ca/kabanza/cours/ift313

Introduction

(2)

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

(3)

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

(4)

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.

(5)

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.

(6)

IFT313 © Froduald Kabanza

6

Schéma d’un compilateur traditionnel

Code source

avec des macros

Préprocesseur Préprocesseur

cpp

cpp

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

(7)

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)

(8)

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)

(9)

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

(10)

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)

(11)

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

(12)

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).

(13)

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.

(14)

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

(15)

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.

(16)

IFT313 © Froduald Kabanza

16

Grammaire pour l’exemple précédent

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

- 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.

(17)

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.

(18)

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

(19)

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

(20)

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).

(21)

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

(22)

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.

(23)

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.

(24)

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”.

(25)

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

(26)

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.

(27)

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).

(28)

IFT313 © Froduald Kabanza

28

Prochain cours

- Langages réguliers - Automates finis

- Analyse lexicale par un automate fini

- Voir le plan de cours pour les lectures à faire

Références

Documents relatifs

 Pour ce faire la partie gauche de la production (un non-terminal) est enlevée de la pile et remplacée par la partie droite de la production. (q, ε, A)  (q, a) pour chaque

- On vient de voir qu’en calculant First(S) ou First(XYS) on doit tenir compte des non-terminaux qui pourraient dériver la chaîne vide et de ceux qui pourraient les suivre dans

 Parce que les deux règles ont la même fonction d’analyse (c-à-d., la fonction correspondant au non terminal dans la partie gauche de chaque production).. Si elle partagent le

– Si le symbole est un non terminal, l’attribut pourrait être une donnée calculée en utilisant les actions sémantiques. – Une grammaire avec des attributs est appelée

• Pour aller au-delà, il faut utiliser d’autres outils ou des méthodes adhoc pour évaluer les attributs (avec l’aide

• Pouvoir programmer un analyseur syntaxique récursif pour une grammaire donnée. • Connaître les fondements d’un générateur d’analyseur syntaxique LL tel

AFD pour préfixes viables, avec état initial I0 et fonction de transition goto Initialement la pile contient I0.. L’état I2 contient élément Shift (T ® T.*F) et un élément

- Pour montrer que la grammaire n’est pas LR(0), on montre que l’AFD correspon- dant pour les préfixes viables a au moins un état contenant à la fois un élément shift et