tabular383


Rappels sur les grammaires: I

Introduite par Chomsky pour traiter le langage naturel, une grammaire est un quadruplet tex2html_wrap_inline478 , où:

tex2html_wrap_inline480
est un alphabet fini de terminaux'',<BR> <DT><STRONG> <IMG WIDTH=22 HEIGHT=27 ALIGN=MIDDLE ALT="tex2html_wrap_inline482" SRC="img4.gif" > </STRONG> <DD> est un ensemble fini de symbolesnon terminaux'', avec tex2html_wrap_inline484
On écrit V pour tex2html_wrap_inline488 .
S
est un non terminal distingue nomme le symbole de d&#233;part'' (start symbol'')
tex2html_wrap_inline492
est une relation finie sur tex2html_wrap_inline494 qui définit les r&#232;gles'' (productions'') de la grammaire.
Une règle tex2html_wrap_inline496 s'écrit aussi tex2html_wrap_inline498 .


Rappels sur les grammaires: II

On classifie les grammaires selon la forme de tex2html_wrap_inline492 :

type 0
tex2html_wrap_inline492 quelconque
type 1
(contextuelles) pour toute règle tex2html_wrap_inline498 on a tex2html_wrap_inline506 .
type 2
(libres de contexte) règle de la forme tex2html_wrap_inline508 .
type 3
(rationnelles) règle de la forme tex2html_wrap_inline510 ou tex2html_wrap_inline512 avec a terminal.

On s'intéresse pour les langages de programmation a des grammaires de type 2.
N.B.: il existent d'autres systèmes de production, comme les L-systems utilisés en biologie.

Rappels sur les grammaires: III

Si tex2html_wrap_inline498 est une règle, on peut alors l'appliquer a n'importe laquelle séquence tex2html_wrap_inline522 pour obtenir tex2html_wrap_inline524 et on écrit alors

displaymath516

On écrira tex2html_wrap_inline526 , s'il existent tex2html_wrap_inline528 , avec tex2html_wrap_inline530 , t.q. tex2html_wrap_inline532 .

Le langage engendre par une grammaire G est

displaymath517

Résultats fondamentaux:

lemma71

Conséquences:


Arbre de dérivation, ambiguïtés

definition79

definition85

Ex: le mot 1+1+1 dans la grammaire

displaymath568

De façon équivalente, une grammaire est ambiguë si elle permette deux dérivations gauches différentes pour un même mot w.


Backus Naur Form (BNF)

La forme normale de Backus'' est une extension du formalisme des grammaires libre de contexte qui n'ajoute pas de pouvoir expressif (on ne d&#233;finit pas une classe plus large de langages), mais qui permet des d&#233;finitions plus concises.<BR> <P> A la structure des r&#232;gles on ajoute les notations suivantes: <P> <DL ><DT><STRONG>&#233;toile de Kleene</STRONG> <DD>: on peut &#233;crire <P> <IMG WIDTH=292 HEIGHT=14 ALIGN=BOTTOM ALT="displaymath588" SRC="img30.gif" > <P> a la place de <P> <IMG WIDTH=414 HEIGHT=14 ALIGN=BOTTOM ALT="displaymath589" SRC="img31.gif" > <P> <DT><STRONG>option</STRONG> <DD>: on peut &#233;crire <P> <IMG WIDTH=293 HEIGHT=20 ALIGN=BOTTOM ALT="displaymath590" SRC="img32.gif" > <P> a la place de <P> <IMG WIDTH=405 HEIGHT=14 ALIGN=BOTTOM ALT="displaymath591" SRC="img33.gif" > <P> <DT><STRONG>cas</STRONG> <DD>: on peut &#233;crire <P> <IMG WIDTH=322 HEIGHT=20 ALIGN=BOTTOM ALT="displaymath592" SRC="img34.gif" > <P> a la place de <P> <IMG WIDTH=276 HEIGHT=71 ALIGN=BOTTOM ALT="displaymath593" SRC="img35.gif" > <P> </DL> Bien entendu, il faudra faire attention a ne pas m&#233;langer les symboles BNF avec les terminaux de <IMG WIDTH=12 HEIGHT=13 ALIGN=BOTTOM ALT="tex2html_wrap_inline480" SRC="img3.gif" > . Dans l'&#233;nonc&#233; du projet, par exemple, on a pris soin de distinguer tout symbole <em>s</em> de <IMG WIDTH=12 HEIGHT=13 ALIGN=BOTTOM ALT="tex2html_wrap_inline480" SRC="img3.gif" > en l'&#233;crivant '<em>s</em>', comme pour les caract&#232;res de Ocaml. Mais s'il n'y a pas d' ambigu&#239;t&#233;, on ne se fatiguera pas a faire la distinction. <HR><H1><A NAME="SECTION00060000000000000000"> Analyse descendante</A></H1> <P> Le probl&#232;me de l'analyse est celui de prendre une grammaire <I>G</I>, et de construire un algorithme capable, pour toute cha&#238;ne <I>w</I> de terminaux, de d&#233;cider si elle appartient ou non a <I>L</I>(<I>G</I>). De plus, on demande de reconstruire un arbre de d&#233;rivation pour <I>w</I> dans <I>G</I> en cas de r&#233;ponse affirmative.<BR> <P> Certains langages permettent de r&#233;soudre ce probl&#232;me a l'aide d'une m&#233;thode fort simple, l'analyse dite <em>descendante</em>.<BR> <P> Cette m&#233;thode cherche a construire une d&#233;rivation gauche directement a partir du symbole initial, ou de fa&#231;on &#233;quivalente, cherche a construire un arbre de d&#233;rivation a partir de la racine avec une exploration en profondeur d'abord de gauche a droite.<BR> <P> Pour cela, l'analyseur doit pouvoir d&#233;cider, en s'aidant &#233;ventuellement avec les prochains <I>k</I> symboles en entr&#233;e, quelle est la prochaine production dans la d&#233;rivation gauche que l'on reconstruit.<BR> <P> Vue ladistance'' entre le terminal que l'on voit en entrée et la production a choisir, il n'y a pas trop de chance pour que la méthode fonctionne (en particulier toute grammaire ambiguë posera des problèmes), mais si on réussit, alors le langage de la grammaire est dans la classe LL(k) (langages analysables en parcourant l'entrée de gauche (L) a droite et en reconstituant une dérivation gauche (L)).


Exemple OcamlLex

Voyons un exemple de grammaire LL(1) et un analyseur construit a la main en Ocaml:

displaymath618

exception Parse_Error;;
type token = Int | PLUS | Eof;;
let descente gettoken =
 let tok = ref (gettoken()) in let advance () = tok := gettoken() in
 let eat t = if (!tok = t) then advance() else raise Parse_Error in
 let check t = if (!tok = t) then () else raise Parse_Error in
 let rec ntS () = (ntE(); check(Eof))
 and ntE () = match !tok with  Int -> (eat(Int); ntE'())
                         |  _   -> raise Parse_Error
 and ntE' () = match !tok with PLUS -> (eat(PLUS);eat(Int); ntE'())
                         |  _      -> () ( transition vide )
in ntS();;

( un lexeur bidon vite fait ) let gettoken_of l = let data = ref l in let f () = let t = List.hd !data in data:= List.tl !data; t in f;;

descente (gettoken_of [Int;PLUS;Int;PLUS;Int;Eof]);;


Analyseurs LL(1): choisir la production en regardant le prochain token

Pour réaliser un analyseur LL(1), il s'agit de remplir une table de la forme

displaymath622

en mettant une règle de production tex2html_wrap_inline626 dans la case tex2html_wrap_inline628 si le fait de voir le token tex2html_wrap_inline630 indique qu'on peut appliquer la règle de production tex2html_wrap_inline626 .
S'il n'y a pas de cases avec plus d'une entrée une fois le remplissage fini, on aura réussi et on pourra implémenter l'analyseur.


NULLABLE, FIRST, FOLLOW

comment remplir les cases?
pour cela on a besoin de calculer pour les symboles de la grammaire trois ensembles:

NULLABLE(X)
vrai ssi X peut produire la chaîne vide ( tex2html_wrap_inline636 )
FIRST(X)
ensemble de symboles terminaux qui peuvent paraître en première position dans une chaîne tex2html_wrap_inline638 dérivable à partir de X
FOLLOW(X)
ensemble de symboles terminaux t qui peuvent paraître immédiatement après X dans une dérivation (i.e. il existe une dérivation contenant Xt, ce qui peut arriver aussi si elle contient XYZt et Y et Z sont nullable).


Définition formelle de NULLABLE, FIRST, FOLLOW

On peut voir ces ensembles comme des relations, et alors


Définition formelle de NULLABLE, FIRST, FOLLOW

Enfin, Follow est la plus petite relation Fo telle que

displaymath664

tabular388

N.B.: ce qui fait que ce point fixe existe toujours et est atteignable de cette sorte est la continuité (par rapport a une topologie bien choisie) des opérations ensemblistes utilisées dans la définition.


Construction de la table LL(1)

Une fois calcules Nullable(X), First(X) et Follow(X) pour tout symbole, il est facile de calculer aussi Nullable et First sur une séquence de symboles:

Nullable( tex2html_wrap_inline672 )
est vrai ssi Nullable( tex2html_wrap_inline674 ) est vrai pour tex2html_wrap_inline676 ;
First( tex2html_wrap_inline678 )
est First( tex2html_wrap_inline680 ) si tex2html_wrap_inline680 n'est pas nullable
First( tex2html_wrap_inline678 )
est tex2html_wrap_inline686 si tex2html_wrap_inline680 est nullable

Et on peut finalement remplir notre table

displaymath622

de la façon suivante, on analyse toute production tex2html_wrap_inline626 et

Si la table, après remplissage, n'a pas d'entrées multiples, alors la grammaire est analysable par l'analyseur et le langage est dans la classe LL(1).

Transformations préliminaires: élimination de l'ambiguïté

L'existence d'une grammaire non ambiguë pour un langage donne L n'est pas décidable. Cependant, dans les cas les plus fréquents, on sait éliminer l'ambiguïté assez facilement:

Exemple: on peut réécrire la grammaire (ambiguë)

displaymath708

en imposant une priorité sur les opérateurs et une associativité, comme:

displaymath709

qui reconnaît le même langage, mais n'est pas ambiguë.


Transformations préliminaires: derecursivisation

Une grammaire recursive a gauche pose toujours de problèmes pour LL(1): si on a deux productions tex2html_wrap_inline718 et tex2html_wrap_inline720 , alors il y aura toujours une double entrée dans les cases (E,t) pour tex2html_wrap_inline724 .

Mais on sait éliminer la récursion a gauche de toute grammaire: toute production

displaymath712

peut être remplacée par les productions (équivalentes, si X' est un symbole nouveau) :

displaymath713


Transformations préliminaires: factorisation gauche

Un autre exemple de problème pour LL(1) apparaît si on a deux productions tex2html_wrap_inline732 et tex2html_wrap_inline734 qui ont le même préfixe tex2html_wrap_inline736 : l'analyseur ne saura pas non plus dans ce cas faire le bon choix.

On sait améliorer la situation en factorisant'' a gauche le pr&#233;fixe commun: toute production <P> <P> <IMG WIDTH=330 HEIGHT=20 ALIGN=BOTTOM ALT="displaymath726" SRC="img67.gif" > <P> <P> peut &#234;tre remplac&#233;e par les productions (&#233;quivalentes, si X' est un symbole nouveau) : <P> <P> <IMG WIDTH=323 HEIGHT=43 ALIGN=BOTTOM ALT="displaymath727" SRC="img68.gif" > <P> <P> <HR><H1><A NAME="SECTION000160000000000000000"> Transformations pr&#233;liminaires: factorisation gauche, II</A></H1> <P> Voyons un exemple bien connu: <P> <P> <IMG WIDTH=401 HEIGHT=20 ALIGN=BOTTOM ALT="displaymath738" SRC="img69.gif" > <P> <P> devient <P> <P> <IMG WIDTH=347 HEIGHT=43 ALIGN=BOTTOM ALT="displaymath739" SRC="img70.gif" > <P> <P> N.B.: ce n'est pas LL(1) non plus, mais on sait mieux g&#233;rer ce deuxi&#232;me cas que le premier (ex. en donnant priorit&#233; au caselse'')


Un exemple complet

tabular393

On veut analyser

displaymath742

Cette grammaire est ambiguë, donc nous allons appliquer notre technique de desambiguation.


Un exemple complet: desambiguation

En imposant que * lie plus que + et qu'on associe a gauche, on obtient la grammaire non ambiguë:

displaymath744

Mais cette grammaire est recursive a gauche, donc ...


Un exemple complet: derecursivisation

En derecursivisant, on arrive a:

displaymath746

On va maintenant calculer Nullable, First et Follow dans l'ordre


Calcul de Nullable

displaymath748


Calcul de First

On sait que :

displaymath750

On calcule First:

displaymath751


Calcul de Follow

On sait que :

displaymath754

On calcule Follow:

displaymath755


La table de l'automate

On peut finalement construire la table de l'automate: On sait que :

displaymath758

On construit alors:

displaymath759


Enfin, notre programme d'analyse en Ocaml

exception Parse_Error;;
type token = Int | PLUS | TIMES | LPAR | RPAR | Eof;;
let descente gettoken =
 let tok = ref (gettoken()) in
 let advance () = tok := gettoken() in
 let eat t = if (!tok = t) then advance() else raise Parse_Error in
 let check t = if (!tok = t) then () else raise Parse_Error
 in
 let rec ntS () = match !tok with
                            Int  -> (ntE(); check(Eof))
                         |  LPAR -> (ntE(); check(Eof))
                         |  _    -> raise Parse_Error
and ntE () = match !tok with Int -> (ntT(); ntE'()) | LPAR -> (ntT(); ntE'()) | _ -> raise Parse_Error and ntE' () = match !tok with PLUS -> (eat(PLUS); ntT(); ntE'()) | RPAR -> () ( transition vide ) | Eof -> () ( transition vide ) | _ -> raise Parse_Error and ntT () = match !tok with Int -> (ntF();ntT'()) | LPAR -> (ntF();ntT'()) | _ -> raise Parse_Error and ntT' () = match !tok with TIMES -> (eat(TIMES); ntF(); ntT'()) | PLUS -> () ( transition vide ) | RPAR -> () ( transition vide ) | Eof -> () ( transition vide ) | _ -> raise Parse_Error and ntF () = match !tok with Int -> (eat(Int)) | LPAR -> (eat(LPAR);ntE();eat(RPAR)) | _ -> raise Parse_Error in ntS();;

Enfin, notre programme d'analyse en Ocaml, avec traitement d'erreurs

( on assume que l'on nous passe un analyseur lexical gettoken )
exception Parse_Error;;
type token = Int | PLUS | TIMES | LPAR | RPAR | Eof;;
let descente gettoken =
 let position = ref 1 and tok = ref (gettoken()) in
 let advance () = tok := gettoken(); position := !position+1 in
 let eat t = if (!tok = t) then advance() else raise Parse_Error in
 let check t = if (!tok = t) then () else raise Parse_Error
 in
 let perror s = print_string ("Expecting "^s^" at position: ");
                print_int !position; print_newline(); raise Parse_Error in
 let rec ntS () = match !tok with
                    Int  -> (ntE(); check(Eof))
                 |  LPAR -> (ntE(); check(Eof))
                 |  _    -> perror "int,("
 and ntE () = match !tok with
                    Int  -> (ntT(); ntE'())
                 |  LPAR -> (ntT(); ntE'())
                 |  _    -> perror "int,("
 and ntE' () = match !tok with
                    PLUS -> (eat(PLUS); ntT(); ntE'())
                 |  RPAR -> () ( transition vide )
                 |  Eof  -> () ( transition vide )
                 |  _    -> perror "+,),eof"
 and ntT () = match !tok with
                    Int  -> (ntF();ntT'())
                 |  LPAR -> (ntF();ntT'())
                 |  _    -> perror "int,("
 and ntT' () = match !tok with
                    TIMES -> (eat(TIMES); ntF(); ntT'())
                 |  PLUS  -> () ( transition vide )
                 |  RPAR  -> () ( transition vide )
                 |  Eof   -> () ( transition vide )
                 |  _     -> perror "+,*,),eof"
 and ntF () = match !tok with
                    Int  -> (eat(Int))
                 |  LPAR -> (eat(LPAR);ntE();eat(RPAR))
                 |  _    -> perror "int,("
in ntS();;

Un exemple complet, executable.

Cliquez ici pour un exemple complet, avec lexeur produit par OcamlLex.

About this document ...

This document was generated using the LaTeX2HTML translator Version 96.1 (Feb 5, 1996) Copyright © 1993, 1994, 1995, 1996, Nikos Drakos, Computer Based Learning Unit, University of Leeds.

The command line arguments were:
latex2html -split 0 Slides01.tex.

The translation was initiated by Roberto Di Cosmo on Wed Nov 3 20:13:20 MET 1999


Roberto Di Cosmo
Wed Nov 3 20:13:20 MET 1999