Université du Littoral – Côte d'Opale L3 Informatique
Aspects Théoriques de l'Informatique
TP / Mini-Projet : un calculateur en ligne
Introduction
Un analyseur lexical analyse un flux de caractères (typiquement un fichier source que vous voulez
compiler) et isole dans ce fichier les "tokens" c'est à dire les éléments "de vocabulaire" du langage
dans lequel est écrit votre code source. Par exemple parmi les tokens d'un source Java on va trouver
des identifiants comme des noms de variables ou de méthodes (a, compteur, val_max, c3,
CréerTableau, System…), des nombres (123, -45, 3.141592), des opérateurs (+, -, *, /, <, <=, …) et
autres éléments lexicaux (parenthèses, accolades, point-virgule, =, …). Comme vu en cours ces
classes de tokens sont définies généralement par des langages réguliers spécifiés par des
expressions rationnelles (ex : un identifiant appartient au langage décrit par : une lettre suivie de
autant de lettres, chiffres ou blanc soulignés qu'on veut). Au niveau supérieur, la succession de ces
token est contrainte par une grammaire, généralement algébrique, qui vérifie que le programme est
syntaxiquement correct (par exemple : à toute accolade fermante correspond une unique accolade
ouverte précédemment).
Dans le cadre de ce mini-projet, on va programmer un calculateur, qui va chercher ses instructions
de calcul dans un fichier. Chaque calcul s'écrit comme une unique ligne de "commandes", mais le
fichier à traiter peut contenir plusieurs lignes (plusieurs calculs), et on veut disposer de variables où
affecter des résultats temporaires, réutilisables dans les calculs suivants du fichier. Pour réaliser ce
calculateur il faut donc être capable d'analyser le contenu d'un fichier : notre calculateur se
comporte donc comme un interpréteur capable de reconnaître et de comprendre des instructions,
de manière similaire à ce que fait un compilateur (mais un compilateur doit en plus sortir une
traduction dans un autre langage). L'analyse du fichier se fait typiquement en 2 grandes étapes :
l'analyse lexicale, basée sur des grammaires régulières, pour reconnaître le vocabulaire (les
tokens) ; et l'analyse syntaxique, basée sur une grammaire algébrique, pour vérifier l'organisation
du vocabulaire et en comprendre le sens.
Note : pour faciliter le travail, on considérera que tout nombre est converti en flottant même s'il est
écrit sans point décimal, et que l'affectation à une variable est notée de manière préfixée, avec le
symbole d'affectation suivi de la variable puis de la donnée, par exemple = a 28 (et non pas a = 28).
Étape 1 : écrire un analyseur lexical "à la main"
Dans cette première étape on se limite à explorer le travail que réalise un analyseur lexical sur un
exemple simple.
Soit le langage L sur l'alphabet {a, b, c} spécifié par l'expression rationnelle : (a|b)*acab(c|a)+
où * désigne l'étoile des rationnels, + est l'étoile propre et | est l'opérateur d'union.
1. Trouver un automate fini déterministe reconnaissant L. Faites un automate complet, avec un
état puits (non terminal) : si on ne peut pas lire une lettre dans l'état courant, alors on va dans
l'état puits ; et si on est dans l'état puits, alors toute lettre renvoie dans l'état puits.
2. Coder cet automate en Java, sans bien sûr utiliser de librairie toute faite. Vous organiserez
votre code de la façon suivante :
- 1 -
Université du Littoral – Côte d'Opale L3 Informatique
saisie de la phrase à reconnaître ;
utilisation d'une variable mémorisant le numéro de l'état courant dans l'automate
itération sur chaque lettre de la phrase ;
à l'intérieur de l'itération on testera la lettre courante avec l'état courant pour
déterminer quel est le nouvel état ; Noter qu'un automate déterministe peut se
représenter comme un tableau 2D indéxé par [numéros d'état][ lettres] : changer
d'état devient très simple à implanter ;
Quand l'itération sur la phrase est terminée, alors il faut vérifier si l'état courant est
terminal (sinon c'est une erreur et le mot n'est pas reconnu).
ÉTAPE 2 : introduction à JFLEX
Jflex est un outil Java libre permettant de construire automatiquement un analyseur lexical (NB : il
existe d'autres outils de ce type). Jflex lit un fichier de description dans lequel on spécifie des
familles de tokens (en fait des "langages" dans le vocabulaire de la théorie des langages) par des
expressions rationnelles (exemple : les identifiants, les nombres entiers, les opérateurs
arithmériques). Pour chaque tolen reconnu dans une famille on indique quelle action doit être
effectuée, en général "retourner un code numérique pour le token reconnu". Les actions sont codées
sous la forme d'instructions Java. Un préambule permet d'indiquer du code Java (classes, variables,
méthodes) pour permettre de réaliser des actions aussi sophistiquées que nécessaire lorsqu'on
reconnaît un token. Des variables et des fonctions prédéfinies permettent :
d'obtenir le token reconnu, méthode yytext()
ainsi que par exemple la ligne (variable yyline) et la colonne du fichier où il se trouve (utile
pour les messages d'erreur d'un compilateur)
...
Lorsqu'on exécute Jflex sur le ficher de description, il construit à partir des expressions rationnelles
un automate fini non déterministe (NFA Non deterministic Finite Automaton) capable de
reconnaître les mots de chacun des langages de tokens, puis il va déterminiser cet automate (DFA
Deterministic Finite Automaton) et le minimiser. En sortie Jflex écrit un programme Java, nommé
par défaut Yylex.java, contenant l'automate, le code Java du préambule du fichier de description, et
les méthodes d'accès aux mots reconnus, numéros de ligne, etc.
Un fichier de description correspondant au langage de l'étape 1 est donné ci-dessous, et se compose
de 3 sections : le préambule qui est recopié tel quel dans le code du "lexer", la partie expressions
rationnelles (où l'on spécifie les familles de tokens) avec la définition de quelques options (type de
retour pour le code token, etc), enfin la partie actions l'on donne le code Java a exécuter pour
chaque expression rationnelle reconnue. Exemple :
- 2 -
Université du Littoral – Côte d'Opale L3 Informatique
// PREAMBULE recopié tel quel
import java.io.*;
class Token
{
public static void main(String[] args) throws FileNotFoundException,
IOException
{
// recupérer le fichier passé en argument de ligne de commande
FileInputStream fis = new FileInputStream(args[0]);
// créer l'analyseur
Yylex L = new Yylex(fis);
// le lancer sur tout le fichier
L.yylex();
}
}
%%
/* notez les 2 % : ici on définit les EXPRESSIONS RATIONNELLES */
/* (dans cette partie un commentaire ne peut pas commencer
en début de ligne) */
/* demander le suivi des numéros de ligne */
%line
/* si les tokens sont retournés, ils le sont comme des chaines */
%type String
/* les mots du langages à reconnaitre */
mot_correct = (a | b)*acab(c|a)+
/* autre langage : les "espaces", qu'on va ignorer */
/* (les crochets encadrent une énumération de choix possibles) */
whitespace = [ \n\t\r]
- 3 -
Université du Littoral – Côte d'Opale L3 Informatique
%%
/* notez les 2 % : ici dans la partie ACTIONS on dit quoi faire
quand on reconnaît un mot des langages définis ci-dessus */
/* si on est dans l'état initial de l'automate...*/
<YYINITIAL> {
/* ...et si on reconnaît un mot_correct alors */
/* afficher le mot reconnu et le numéro de ligne */
{mot_correct} { System.out.println("[line "+ yyline + "] "
+"Mot reconnu: " + yytext() ); }
/* si on reconnait un espace, on ne fait rien */
{whitespace} {}
/* si on reconnait autre chose on affiche erreur et le mot */
[^] { System.out.println("[line "+ yyline + "] "
+"Erreur: " + yytext() ); }
}
Sauver ce fichier comme test.lex, et créer l'analyseur en exécutant : jflex test.lex
Un fichier Yylex.Java est créé, que l'on va compiler : javac Yylex.java
On va exécuter notre classe du préambule : java Token source_etape1.txt
avec source_etape1.txt un fichier contenant des mots à analyser comme par exemple :
cabc
caba
aaabbbaaacabcac
accabc
cab
Note : voyez que par exemple le +, le * et le . sont des composants des expressions rationnelles. Si
on veut reconnaître des tokens contenant ces caractères, il y aurait une ambiguïté, que l'on lève en
préfixant avec anti-slash : ainsi * est l'itération rationnelle et \* est le caractère '*'.
Examinez les sorties de l'analyseur, et testez quelques modifications (changement du langage
reconnu, des instructions de sorties).
- 4 -
Université du Littoral – Côte d'Opale L3 Informatique
ÉTAPE 3 : analyse lexicale pour un calculateur en ligne de
commande
Pour notre calculateur, il faudra reconnaître les tokens :
token "nombre" : des constantes numériques sans signe, comme (13467, 12, 3.1415). S'il
s'agit de réels alors il y aura forcément au moins un chiffre à gauche et un chiffre à droite du
point.
token "ident" : des noms de variables définis comme le sont habituellement les identifiants
des langages de programmation.
autres tokens :
parenthèse ouvrante : token "po"
parenthèse fermante : token "pf"
le symbole d'affectation = : token "affect" (l'affectation sera préfixée : le symbole = est
placé devant le nom de variable suivi de la donnée à affecter)
les 4 opérations +, -, *, / : tokens "oplus", "ominus", "omult", "odiv". Notez que le "+" et
le "-" peuvent être compris comme des opérateurs binaires, comme dans 12 3, ou bien
unaires comme dans a = -3 (ce sera lors de l'analyse syntaxique que l'on décidera
laquelle des significations est la bonne)
la fin de ligne : token "eol" qui termine le calcul contenu sur cette ligne.
Utiliser JFLEX pour reconnaître ces tokens. L'analyseur devra afficher pour chaque ligne d'un
fichier source les tokens qu'il contient. Par exemple pour le source ci-dessous à gauche, on affichera
les lignes ci-dessous à droite :
+123.0 oplus nombre eol
((-4 *12) / 2.0) po po omoins nombre omult nombre pf odiv nombre pf eol
= a 6 affect ident nombre eol
(a+1) po ident oplus nombre pf eol
= total (4*a) affect ident po nombre omult ident pf eol
ÉTAPE 4 : préparation à l'analyse syntaxique
Reconnaître les tokens n'est pas suffisant pour notre calculateur, car on ne peut pas par exemple
vérifier si une expression est bien parenthésée : ça ne peut pas être fait par une grammaire régulière,
il faut une grammaire algébrique. Il existe des outils basés sur la théorie de la compilation qui
prennent une grammaire et génèrent automatiquement un analyseur lexical (par exemple CUP),
mais ici nous allons nous contenter de coder directement la grammaire pour vérifier ces contraintes.
Il faut donc ne pas se contenter d'afficher les tokens, mais pouvoir les récupérer et les analyser. On
va modifier la fonction main() du préambule et le code des actions dans la dernière partie du fichier
de description .lex :
L'appel à yylex() du main() se fait désormais dans une boucle : chaque appel va récupérer
- 5 -
1 / 8 100%
La catégorie de ce document est-elle correcte?
Merci pour votre participation!

Faire une suggestion

Avez-vous trouvé des erreurs dans linterface ou les textes ? Ou savez-vous comment améliorer linterface utilisateur de StudyLib ? Nhésitez pas à envoyer vos suggestions. Cest très important pour nous !