IFT313 Introduction aux langages formels

Slides:



Advertisements
Présentations similaires
Bratec Martin ..
Advertisements

NOTIFICATION ÉLECTRONIQUE
Fragilité : une notion fragile ?
SEMINAIRE DU 10 AVRIL 2010 programmation du futur Hôtel de Ville
Phono-sémantique différentielle des monosyllabes italiens
MAGGIO 1967 BOLOGNA - CERVIA ANOMALIES DU SOMMEIL CHEZ L'HOMME
droit + pub = ? vincent gautrais professeur agrégé – avocat
Transcription de la présentation:

IFT313 Introduction aux langages formels Froduald Kabanza Département d’informatique Université de Sherbrooke planiart.usherbrooke.ca/kabanza/cours/ift313 Introduction

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

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) compiler code source code cible messages d’erreurs Examples : code machine Pentium C code machine SPARC fichiers .class JAVA Scheme code Java IFT313 © Froduald Kabanza

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 afficher ou les traduire en requêtes pour une base données. IFT313 © Froduald Kabanza

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 syntaxe et la sémantique du langage source et du langage cible. IFT313 © Froduald Kabanza

Schéma d’un compilateur traditionnel Code source avec des macros Libraires et fichiers objets Préprocesseur cpp Chargeur / éditeurs de liens code machine Assembleur Exécutable Programme source Programme assembleur cible compilateur cc ou gcc IFT313 © Froduald Kabanza

De nombreuses cas sont bien plus simples Code source (ex.: .java, .xml) Compilateur / Processeur de langage (e.x.: javac, xml2html) Code cible (ex.: .class, .html) IFT313 © Froduald Kabanza

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

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. Représentation (sémantique) intermédiaire analyse (front-end) code source synthèse (back-end) code cible compilateur IFT313 © Froduald Kabanza

Modèle analyse-synthèse de la compilation Exemple code source position = initial + rate * 60 table de symboles position id1 rate initial id2 id3 Analyse lexicale (scanner) unités lexicales (token) id1 = id2 + id3 * 60 Analyse syntaxique (parser) = + * id1 id2 id3 60 arbre syntaxique IFT313 © Froduald Kabanza

Modèle analyse-synthèse de la compilation Exemple (suite) Analyse sémantique = + * id1 id2 id3 int2real 60 Génération de code intermédiaire temp1 = int2real(60); temp2 = id3 *temp1; temp3 = id2 + temp2; id1 = temp3 IFT313 © Froduald Kabanza

Modèle analyse-synthèse de la compilation Exemple (suite) Génération du code Optimisation du code MOVF id3 , R2 MULF #60.0 , R2 MOVF id2 , R1 ADD R2 , R1 MOVF R1 , id1 temp1 = id3 *60.0 id1 = id2 *temp1 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

Pourquoi les compilateurs sont faciles à implé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 langage, avec des attributs sémantiques et utiliser la théorie des langages de programmation pour construire un compilateur. IFT313 © Froduald Kabanza

Pourquoi les compilateurs sont faciles à implémenter ? Exemple : Structure simplifiée des instructions =, if et while 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) IFT313 © Froduald Kabanza

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

Grammaire pour l’exemple précédent stat ® id = exp stat ® if ( exp ) stat stat ® while ( exp ) stat exp ® id exp ® num exp ® ( exp ) exp ® exp + exp 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

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

Arbre d’analyse Exemple : code source position = initial + rate * 60 Le scanner le convertit en Le parser lit les tokens un à un et produit un arbre d’analyse: id1 = id2 + id3 * num stat id = exp exp exp + exp * exp id2 id3 num IFT313 © Froduald Kabanza

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: stat = exp + * id1 id2 id3 num stat ® id = exp stat ® if ( exp ) stat stat ® while ( exp ) stat exp ® id exp ® num exp ® ( exp ) exp ® exp + exp exp ® exp * exp stat => id = exp => id = exp + exp => id = id + exp => id = id + exp * exp => id = id + id * exp => id = id + id * num IFT313 © Froduald Kabanza

Dérivation L’application d’une production pour générer une nouvelle chaine de symboles 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

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. stat ® id = exp stat ® if ( exp ) stat stat ® while ( exp ) stat exp ® id exp ® num exp ® ( exp ) exp ® exp + exp exp ® exp * exp IFT313 © Froduald Kabanza

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 fonction 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 scanning Leftmost derivation ou LL tout court. IFT313 © Froduald Kabanza

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

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

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 productions 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 id exp1 + exp2 exp1 * exp2 exp.t = num.val id.val ‘ + ’ || exp1.t || exp2.t * t est l’attribut; “||” désigne le symbole de concaténation IFT313 © Froduald Kabanza

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écifierons 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 erroné peut causer des pertes énormes voire des dangers, dépendamment de l’application ciblée par le code compilé. Il est important de concevoir les compilateurs en se basant sur une théorie et des techniques fiables. Les analyseurs lexicaux reposent sur la théorie des expressions régulières et des automates finis. Les analyseurs syntaxiques reposent sur la théorie des grammaires, des automates à piles et des automates finis. IFT313 © Froduald Kabanza

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

Prochain cours Langages réguliers Automates finis Analyse lexicale par un automate fini Voir le plan de cours pour les lectures à faire IFT313 © Froduald Kabanza