Univ. Lille1 - Licence Informatique 2ème année 2012-2013 Algorithmes et Programmation Impérative 2 TP : Les expressions arithmétiques Objectifs : Travailler sur les arbres binaires. Les utiliser pour représenter les expressions arithmétiques et les évaluer. 1 Le matériel Pour ce TP vous aurez besoin du module sur les arbres binaires, ainsi que du module Lexemes que vous avez déjà utilisé pour l'évaluation d'expressions postxées. Attention, le module Lexemes dont vous aurez besoin est légèrement modié par l'ajout de deux lexèmes pour les parenthèses. Cette version modiée se trouve dans le matériel que vous allez récupérer. Question 1 Récupérez le chier expression_arithmetique.zip et décompressez-le dans votre répertoire de travail. Vous devez obtenir 1. un chier calculette.ml contenant le code source d'un programme que vous compilerez en n de TP, 2. les deux chiers sources d'une nouvelle version du module Lexemes, 3. le chier interface d'un module dont vous réaliserez l'implémentation, 4. et la documentation au format HTML de ce dernier module ainsi que du nouveau module Lexemes. Question 2 Faîtes aussi une copie des deux chiers d'interface et d'implémentation du module Arbre_binaire dans votre répertoire de travail. Question 3 Recopiez le texte ci-dessous dans un chier nommé Makefile. MODULES = lexemes arbre_binaire expression_arithmetique MODULESCMI = $(addsuffix.cmi, $(MODULES)) MODULESCMO = $(addsuffix.cmo, $(MODULES)) calculette: calculette.ml $(MODULESCMO) ocamlc -o $@ $(MODULESCMO) $< expression_arithmetique.cmo: expression_arithmetique.ml $(MODULESCMI) $(MODULESCMO) ocamlc -c lexemes.cmo arbre_binaire.cmo expression_arithmetique.ml %.cmo: %.ml %.cmi ocamlc -c $< %.cmi: %.mli ocamlc -c $< Rappel : les actions qui suivent les cibles doivent être précédées d'une tabulation et non d'espaces. 1
Ce Makefile contient quatre cibles accompagnées de dépendances et des actions pour réaliser ces cibles. 1. La première cible pour compiler le programme nal calculette. Il vous faudra auparavant réaliser l'implémentation du module Expression_arithmetique. Cette cible dépend du chier source calculette.ml et des chiers compilés des modules Lexemes, Arbre_binaire et Expression_arithmetique. 2. La deuxième cible pour compiler l'implémentation du module Expression_arithmetique. Cette cible dépend évidemment de son chier source, mais aussi de la version compilée de son interface, et des chiers compilés des deux autres modules. 3. La troisième cible est destinée à compiler les implémentations des deux autres modules. 4. Et la dernière cible pour produire les versions compilées des interfaces. Ce Makefile sera bien utile pour les compilations que vous aurez à eectuer dans la suite. Question 4 À l'aide de ce Makefile, compilez les interfaces et implémentations des modules Lexemes et Arbre_binaire. 2 Les expressions arithmétiques complètement parenthésées Les expressions arithmétiques considérées dans ce TP sont des expressions inxées complètement parenthésées : inxées parce que leur écriture place l'opérateur arithmétique entre les deux opérandes, autrement dit c'est l'écriture communément employée ; complètement parenthésées parce qu'hormis les nombres toutes les expressions composées de deux opérandes et un opérateur sont parenthésées. Ainsi il n'y a pas de problème de priorité des opérateurs, c'est le parenthésage qui le xe. On peut décrire les expressions arithmétiques à l'aide d'une grammaire qui décrit la syntaxe des expressions correctes. Expr ::= Nombre ( Expr Op Expr ) Op ::= + - * / % Nombre ::= [0-9]+ La première ligne de cette grammaire indique que syntaxiquement il existe deux sortes d'expressions arithmétiques inxées complètement parenthésées : 1. les nombres 2. et les expressions débutant par une parenthèse ouvrante, suivie d'une expression arithmétique, suivie d'un opérateur, suivi d'une autre expression arithmétique, suivi enn d'une parenthèse fermante. La deuxième ligne de la grammaire précise quels sont syntaxiquement les opérateurs autorisés. Ici, les opérateurs usuels de l'addition et de la soustraction (+ et -), l'astérisque (*) pour la multiplication, et deux opérateurs pour la division entière : la barre oblique (/) pour le quotient et le pourcent (%) pour le reste. Et la dernière ligne indique que les nombres s'écrivent avec un nombre quelconque non nul de symboles compris entre 0 et 9. En particulier, le symbole - n'est pas autorisé, et les nombres négatifs ne sont donc pas autorisés dans nos expressions. 2
Quelques exemples d'expressions arithmétiques correctes : 12, 3, 5,... : ce sont des nombres (positifs) ; (12 + 3) : on trouve bien une parenthèse ouvrante, une expression arithmétique ( 12), un opérateur (+), une expression arithmétique (3) et une parenthèse fermante ; ((12 + 3) 5) : (12 + 3) et 5 sont deux expressions arithmétiques correctes séparées par un opérateur, le tout encadré par des parenthèses. En revanche, les expressions ci-dessous ne sont pas correctes du point de vue de la syntaxe décrite par la grammaire : (12) : les nombres ne doivent pas être parenthésés ; 12 + 3 : les expressions composées à l'aide d'un opérateur doivent être parenthésées ; (12 + 3 5) : à chaque opérateur doit correspondre un couple de parenthèses. 3 Le module Expression_arithmetique Dans cette partie, vous allez réaliser l'implémentation du module Expression_arithmetique, que vous mettrez naturellement dans un chier expression_arithmetique.ml. Durant cette implémentation, il vous faudra valider chacune des fonctionnalités réalisées. Cette validation se fera par des tests au niveau d'un interpréteur du langage dans lequel les modules Lexemes et Arbre_binaire auront préalablement été chargés. > ocaml lexemes.cmo arbre_binaire.cmo Question 5 Avant de commencer, étudiez la documentation du module. 3.1 Le type La structure récursive binaire des expressions arithmétiques amène naturellement à les représenter par des arbres binaires. Les n uds de ces arbres sont étiquetés par 1. des opérateurs pour les n uds internes, 2. des nombres entiers pour les feuilles. Pour les étiquettes, vous utiliserez le type lexeme du module Lexemes. Question 6 Déclarez le type t pour les expressions arithmétiques, ainsi que l'exception Expression_incorrecte. Remarque : Cette déclaration de type pour les expressions arithmétiques inclut des arbres qui ne correspondent à aucune expression arithmétique. 1. l'arbre vide ; 2. les arbres dont certain n uds n'ont qu'un seul ls ; 3. les arbres étiquetés par les lexèmes Par_ouvrante et Par_fermante ; 4. les arbres dont certaines feuilles sont étiquetés par des opérateurs ; 5. et les arbres dont certains n uds internes sont étiquetés par des nombres. Dans l'immédiat, vous ferez l'hypothèse que les arbres manipulés par les fonctions suivantes (hormis la dernière) ne font pas partie de l'un des cas énumérés ci-dessus. Ce sera le rôle du constructeur de_chaine de s'assurer de ne produire que des arbres valides. 3
Question 7 Dans un chier, nommé par exemple exples_expressions.ml, déclarez une variable de type Expression_arithmetique.t pour chacune des expressions suivantes : 1. expr 1 = 12 2. expr 2 = 3 3. expr 3 = 5 4. expr 4 = (12 + 3) 5. expr 5 = (3 5) 6. expr 6 = ((12 + 3) 5) 7. expr 7 = (12 + (3 5)) Vous utiliserez ces déclarations pour vérier les fonctions que vous réaliserez dans la suite. 3.2 Conversion en chaîne de caractères Question 8 Réalisez la fonction en_chaine. Question 9 Vériez votre fonction avec les expressions déclarées dans le chier exples_expressions.ml. 3.3 Évaluation Question 10 Réalisez la fonction evalue. Question 11 Vériez votre fonction avec les expressions déclarées dans le chier exples_expressions.ml. 3.4 Analyse syntaxique Vous voici arrivés au point le plus délicat de ce TP. Il consiste à réaliser la dernière fonction de notre module : de_chaine. Cette fonction est chargée de construire une expression arithmétique, c'est-à-dire un arbre, à partir de son écriture linéaire sous forme de chaîne de caractères. Cette construction se fait en deux étapes. 1. Une première étape d'analyse lexicale qui a pour but de décomposer la chaîne de caractères en éléments lexicaux (lexèmes) de base, à savoir : parenthèses ouvrante et fermante, nombres et opérateurs, tout en vériant que la chaîne de caractères ne contient bien que des caractères autorisés. C'est la fonction lexemes du module Lexemes qui est chargée de ce travail. Si tout se passe bien, elle renvoie une liste de lexèmes. Dans le cas contraire elle déclenche une exception Caractere_non_autorise. 2. La seconde étape d'analyse syntaxique qui est chargée de vérier la conformité de la liste de lexèmes produite par l'analyse lexicale à la grammaire qui décrit la syntaxe des expressions arithmétiques inxées complètement parenthésées. Si cette liste est bien conforme, la fonction renvoie un arbre représentant l'expression décrite par la chaîne. Dans le cas contraire, elle déclenche l'exception Expression_incorrecte. Question 12 Réalisez la fonction de_chaine. Question 13 Vériez la validité de cette fonction avec les expressions valides et non valides de votre choix. Par exemple 4
en_chaine (de_chaine "((12 + 3) * 5)" en_chaine (de_chaine "(12 + 3 * 5)" 3.5 Compilation Question 14 Compilez l'implémentation du module que vous venez de réaliser. Utilisez votre Makefile. 4 Le programme calculette Question 15 Compilez le programme calculette. Question 16 Eectuez des tests. Les expressions arithmétiques que vous passez sur la ligne de commandes peuvent avoir besoin d'être entourées de ". >./calculette "((12 + 3) * 5)" 75 5