Structure basique de yylex(), préparation au couplage avec bison.
Format spécification Bison, couplage yyparse() et yylex(), tokens.
Makefile et prototypes de spécification du cours, gestion d'erreurs.
Exemple corrigé d'une version combinée Flex et Bison des 2 premiers exercices
L'objectif de cet exercice est de démystifier ce qu'est un analyseur lexical et de pouvoir en cas de besoin écrire le sien sans utiliser lex/flex.
Les éléments suivants sont fournis:
Lire et comprendre le code de l'analyseur.
Compiler, exécuter et expliquer ...
stderr
garantit une impression immédiate, même sans \n ou flushImplanter un analyseur lexical yylex1.c
contenant les affichages suivants:
ART
: pour "a" "an" "the"TRANS_VRB
: pour "cut" "cuts"COMM_NOUN
: pour "bill" "bills"PROP_NOUN
: pour "Bell" "Verizon"L'exercice devient vite fastidieux ! C'est pourquoi on peut aimer l'outil flex même avec une utilisation simpliste des expressions régulières.
Corrigé : yylex1.c
En reprenant l'analyseur lexical simple de l'exercice précédent, on se concentre ici sur l'utilisation de bison pour développer une analyse syntaxique au dessus de l'analyse lexicale.
On reprend les éléments fournis dans l'exercice précédent
On donne de plus une spécification simple Bison d'analyseur syntaxique gram0.bison
gram0.bison
définit un axiome Text comme une liste
sans structuration d'unités lexicales. Il y a 3 unités lexicales : "WORD", '\n' et '.' > bison -d -o gram.tab.c gram0.bison
> gcc gram.tab.c yylex0.c > a.out < test.data
yylex0.c
pour que cela marche :
yylex0.c
(=> compile OK)Corrigé : yylex0-bis.c.
L'analyseur lexical seul s'obtient avec gcc -DFLEXALONE yylex0-bis.c
.
Ajouter les catégories lexicales ART
, TRANS_VRB
, COMM_NOUN
, PROP_NOUN
comme dans l'exercice précédent.
On peut reprendre le corrigé de l'exercice précédent yylex1.c ou sa forme adaptée yylex1-bis.c.
Enrichissez par étape la grammaire "gram0.bison" pour :
Corrigé : gram1.bison.
Corrigé : gram2.bison.
Corrigé : gram3.bison.
Corrigé : gram4.bison.
Corrigé : gram5.bison.
Refaire l'exercice en remplaçant l'analyseur lexical manuel par un analyseur lexical Flex.
Ceci constitue l'exercice 4. Flexification de "Bison sans Flex"
à traiter après l'exercice 3.
L'écriture manuelle d'un analyseur lexical est peu optimisée, et rapidement fastidieuse. Il est plus commode de le préparer avec Flex; puis de le combiner avec un analyseur syntaxique préparé par Bison.
On recommande le schéma générique suivant pour la construction d'analyseurs syntaxiques/lexicaux Flex+Bison :
> make NOM
" yywrap()
", "yyerror()
" et "main()
" doivent être écrites dans les fichiers .flex ou .bison> make lex-NOM
" génère l'analyseur lexical seul. "> make NOM
" aura le même effet si le fichier "NOM.bison" n'existe pas,
> make parse-NOM
" génère l'analyseur syntaxique ou une erreur si le fichier "NOM.bison" n'existe pas.
Analyser les fichiers fournis.
On se donne les spécifications et fichiers de test suivants :
Analyser les fichiers fournis.
Créer l'analyseur lexical seul : > make lex-flexbison0
Créer l'analyseur syntaxique > make flexbison0
, ou > make parse-flexbison0
Exécuter et comprendre les résultats.
Améliorer la grammaire précédente en définissant un Text
comme une succession de Sentence
.
Chaque
Sentence
a une forme simple :
Sentence
commence par une majuscule, ou par une chaîne entre quotesSentence
se termine par un point.Corrigé : flexbison1.bison et flexbison1.flex.
Il ne s'agit pas ici des techniques de traces (echo(),print(),...) utilisées lors de la mise au point d'une grammaire, mais de la gestion des erreurs lexicales ou syntaxiques dans les données analysées par un analyseur déjà au point. Comment localiser l'erreur ? Comment la remonter vers l'utilisateur ? Peut-on continuer l'analyse après une première erreur ? ...
Le problème devient vite assez complexe à gérer (et la documentation pas très accessible). On se contentera ici de donner un schéma relativement simple de traitement des erreurs syntaxiques qui permet de couvrir une base déjà assez large de problèmes.
La détection d'erreurs lexicales peut être facilement gérée par l'utilisation d'une règle-balai dans Flex. On a alors deux stratégies :
. printf("Caractère Invalide");
. /*printf("Caractère Invalide");*/ return(yytext[0]);
En pratique, le meilleur choix est généralement de ne pas cacher l'erreur lexicale et de reporter au niveau syntaxique. Dans un cas, comme dans l'autre on tente de poursuivre l'analyse lexicale pour détecter plusieurs erreurs dans une même passe.
Quand l'analyseur syntaxique Bison détecte une portion de texte erronée, c'est-à-dire qu'aucune règle de grammaire ne permet de traiter le token lu pour espérer reconstruire un arbre syntaxique complet, on a alors :
%error-verbose
donne des messages plus explicites ou en tout cas plus complets
Texte : /* mot vide */ | Texte Ligne '\n' { printf("Ligne Correcte"); } | Texte error '\n' { yyerrok; printf("Ligne Incorrecte ignorée");} ;
Ici les fins de lignes servent de resynchronisation après erreur.
Quelques variations :
Instr : ... | error ';' /* on error, skip until ';' is read */ ; Parent_expr : '(' expr ')' | '(' error ')' /* resync with the next ')', */ /* may be not the matching one */ ;
Améliorer la grammaire traitant un Text
en ajoutant un traitement d'erreur
au niveau syntaxique. Faire en sorte que l'analyseur ne s'arrête pas à la
première erreur mais analyse l'ensemble du texte en signalant des erreurs successives.
Mettre en particulier en oeuvre la remontée d'erreur avec la règle-balai
au niveau lexical.
Expérimenter éventuellement l'option Flex %option yylineno
pour remonter et imprimer
un numéro de ligne en cas d'erreur.
Étendre pas à pas la grammaire pour faire disparaître des cas d'erreur. Par exemple gérer les ponctuations (, ' ...),
Corrigé :
flexbison2.flex
yylineno
error
et resynchronisation sur le '.' pour continuer l'analyse après erreur
%error-verbose
pour avoir des messages d'erreur plus complet
A titre d'exemple, on donne ici le corrigé d'une version Flex+bison
de l'exercice 2. Premiers pas : Bison sans Flex.
Ceci illustre l'utilisation du Makefile générique et
des fichiers prototypes proposés comme modèles pour le cours.
Écrire une spécification Flex équivalente à l'analyseur lexical manuel yylex0.c de l'exercice 1.
Utiliser le nom gram0.flex.
Corrigé : gram0.flex.
Construire et tester l'analyseur lexical seul avec "gram0.flex".
> make lex-gram0 > lex-gram0 < test.data
Construire et tester l'analyseur syntaxique avec "gram0.flex" et la spécification "gram0.bison" de l'exercice 2.
> make gram0 > gram0 < test.data
Étendre la spécification Flex en ajoutant les autres catégories lexicales comme dans l'exercice 1 avec yylex1.c.
Corrigé : gram1.flex.
Construire et tester les versions successives des grammaires de l'exercice 2.
Corrigés des grammaires de l'exercice 2 : gram1.bison, gram2.bison, gram3.bison, gram4.bison, gram5.bison
--- pour X = 1..5 > ln -s gram1.flex gramX.flex > make gramX > gramX < test.data