Master M1 2005--2006 Projet de compilation
Évaluateur d'expressions arithmétiques




TP individuel, à finir à la maison et à rendre
par email au chargé de TP à J+3 au matin





Personnalisation d'emacs avec le mode tuareg.
Pour mieux profiter de l'environnement de développement emacs/ocaml, éditer le fichier ~/.emacs afin d'y insérer le contenu du fichier /homes/filliatr/TER-COMPIL/init-tuareg.el (avec ctrl-x i). Cette modification du fichier de configuration d'emacs permettra -- dès le prochain lancement de l'éditeur -- de charger automatiquement le mode tuareg d'Ocaml chaque fois que vous éditerez un fichier .ml (un résumé des raccourcis clavier de ce mode se trouve dans le guide de survie).

Le top-level d'Ocaml.
Pour ce premier TP -- et uniquement pour celui-là -- vous n'utiliserez que le top-level d'Ocaml pour évaluer vos programmes (pas de compilation avec ocamlc). Ce top-level sera lancé automatiquement dans emacs dès la première évaluation d'une phrase ou d'un tampon Ocaml (resp. avec les touches ctlr-x ctrl-e et ctrl-c ctrl-b).

Évaluateur d'expressions arithmétiques

Le but du TP est de réaliser un évaluateur d'expressions arithmétiques. La définition de type suivante en Ocaml permet de décrire la syntaxe abstraite de ces expressions:
        type expr =  Cst  of int 
                   | Sum  of expr * expr
                   | Diff of expr * expr 
                   | Prod of expr * expr 
                   | Quot of expr * expr
Exercice 1  Écrire une fonction eval_expr de type expr -> int qui calcule la valeur entière d'une expression.

corrige
Exercice 2  On étend maintenant nos expressions arithmétiques avec des variables locales pour stocker les résultats de calculs intermédiaires. Le type expr est étendu avec deux nouveaux constructeurs:
          type expr =   ...
                     | Var of string
                     | Letin of string * expr * expr
  
Modifier votre fonction eval_expr pour qu'elle prenne comme argument supplémentaire un environnement env qui lie les noms des variables locales à leurs valeurs entières. On utilisera un environnement env de type (string * int) list et la fonction List.assoc : 'a -> ('a * 'b) list -> 'b pour rechercher la valeur liée à une variable locale.

corrige

Exercice 3  Dans l'exercice précédent, la fonction eval_expr se termine en renvoyant l'exception Not_found, sans plus de précision, quand l'expression évaluée utilise une variable qui n'existe pas (ou qui n'est pas dans la portée de sa déclaration). Afin d'être plus précis sur l'origine de l'erreur, on définit une nouvelle exception VarUndef:
        exception VarUndef of string;;
Adapter votre fonction eval_expr pour lever cette exception lorsqu'une variable est mal utilisée et indiquer ainsi le nom de la variable posant problème.


corrige

Exercice 4  On ajoute maintenant la possibilité d'utiliser des variables globales dans nos expressions. Le type instr suivant décrit les instructions pour respectivement affecter une valeur à une variable globale, annuler la dernière affectation (si elle existe) d'une variable globale et afficher la valeur d'une expression:
        type instr =  Set of string * expr
                    | Unset of string
                    | Print of expr
Écrire une fonction eval_instr : instr -> unit qui prend en entrée une instruction et calcule sa valeur en lisant et écrivant si nécessaire dans un environnement liant les noms des variables globales à leurs valeurs entières (eval_instr (Unset "x") n'aura aucun effet si la variable globale "x" n'apparait pas dans l'environnement). On utilisera un environnement genv de type (string * int) list ref (c'est à dire une référence sur une liste de paires string * int). On utilisera la fonction List.remove_assoc : 'a -> ('a * 'b) list -> ('a * 'b) list pour supprimer la dernière valeur liée à une variable globale.

corrige

Parsing.
Afin de vous aider à tester vos fonctions eval_expr et eval_instr, vous pouvez récupérer le module Parsers en tapant la commande Unix:



cp /homes/filliatr/TER-COMPIL/tp1/parsers.cm* .




Ce module contient la définition des types expr et instr ainsi que les fonctions read_expr: string -> expr et read_instr: string -> instr qui permettent respectivement de transformer une chaîne de caractères de type string en une valeur de type expr ou instr. Pour utiliser ces fonctions dans le top-level d'Ocaml, vous devez simplement charger en mémoire le module à l'aide des directives suivantes:
# #load "parsers.cmo";;
# open Parsers;;
Les chaînes de caractères reconnues par la fonction read_expr seront par exemple de la forme 10*let x=(3+4) in x/2 ou simplement 45+3*8. Celles reconnues par la fonction read_instr seront de la forme set x=3+(let y=z*4 in y/2) ou unset x ou encore print x+9.

Exercice 5  Dans l'exercice précédent nous vous avons utilisé un environnement de variables globales de type (string * int) list ref. Cependant, quand le nombre de variables devient important, les opérations de recherche/suppression sur une liste sont trop coûteuses et on préfère alors utiliser des structures de données comme les tables de hachage où ces opérations sont plus efficaces.

L'objectif de cet exercice est de remplacer l'environnement global genv de l'exercice précédent par une table de hachage.

Rappel. L'idée de base d'une table de hachage est la suivante: pour chaque enregistrement à ajouter/rechercher/supprimer dans la table, on calcule une clé entière, entre 0 et M-1 (où M est la longueur de la table de hachage), et on utilise M listes pour stocker les enregistrements de même clé.
  1. Écrire une fonction hash : string -> int qui calcule la clé entière d'une chaîne de caractère. Une fonction possible est celle qui étant donné une chaîne s=a0a1... an retourne la clé:



    code(ak) est le code ASCII du caractère ak (ce code est donné en Ocaml par la fonction Char.code). On pourra prendre par exemple M=17 comme longueur de la table de hachage. Une autre possibilité est d'utiliser la fonction Hashtbl.hash de type 'a -> int qui associe un entier positif à des valeurs de types quelconques.

  2. On définit le type des tables de hachage de la façon suivante:
                 type t =  (string * int) list array;;
            
    Écrire une fonction nouveau : int -> t pour créer une nouvelle table de hachage dont la taille est donnée en argument à la fonction. Les fonctions de manipulation de tableaux dont vous aurez besoin pour cette question (et les suivantes) sont:

  3. Écrire une fonction ajoute : t -> string * int -> unit pour ajouter une nouvelle entrée (formée d'une chaîne de caractères et d'un entier) dans une table de hachage.
  4. Écrire une fonction trouve : t -> string -> int pour chercher la valeur associée à une chaîne de caractères donnée dans une table de hachage (trouve tbl s lève Not_found si s n'est liée à aucune valeur dans tbl).
  5. Écrire une fonction supprime : t -> string -> unit afin que supprime tbl s supprime la dernière valeur associée à s dans tbl et restaure la valeur précédente si elle existe (cette fonction n'a aucun effet si s n'est liée à aucune valeur dans tbl).
À l'aide de ces fonctions, modifier la fonction eval de l'exercice précédent pour qu'elle fonctionne avec un environnement global genv implanté à l'aide d'une table de hachage.

corrige

Exercice 6  Le but de cet exercice est de rendre plus modulaire le code de votre évaluateur d'expressions arithmétiques en découpant les parties de votre programme à l'aide des modules et des foncteurs d'Ocaml.
  1. La signature suivante définit l'interface d'un module implantant un environnement de variables globales pour des expressions arithmétiques entières.
             module type GlobalEnv =
                sig
                    type t
                    val nouveau : int -> t
                    val ajoute : t -> string * int -> unit
                    val trouve : t -> string -> int
                    val supprime : t -> string -> unit
                end
    
    En réutilisant les fonctions que vous avez écrites dans les exercices 3 et 4, écrire deux implantations AssocEnv et HashEnv pour cette interface. AssocEnv utilisera simplement une liste comme implantation du type t et HashEnv une table de hachage.

  2. Afin de rendre le code de votre évaluateur indépendant du choix d'implantation de l'environnement global, écrire un foncteur Eval (Genv : GlobalEnv) paramétré par un module dont l'interface est GlobalEnv et générer deux évaluateurs utilisant respectivement un environnement global implanté avec une table de hachage et avec une liste de paires de type string * int.

corrige


This document was translated from LATEX by HEVEA.