Un interprète Pseudo-Pascal |
Ce TD se présente comme le précédent : l’exercice consiste à écrire un fichier manquant pour un programme par ailleurs déjà complet.
Ici, le programme en question est un interprète pour Pseudo-Pascal. Vous n’avez à écrire ni analyseur syntaxique ni analyseur lexical, ni glue code pour assembler les morceaux. Tout cela est déjà fait et stocké dans l’archive pp.tar.gz. Le morceau manquant est en fait le noyau de l’interprète.
Il faut d’abord télécharger l’archive, puis la dépaqueter :
# tar xvfz pp.tar.gz
Cette opération crée un sous-répertoire petit qui contient tout ce qui est nécessaire pour le TD, y compris en particulier un Makefile.
# cd petit # make ... ocamlopt.opt -o compilo integer.cmx misc.cmx ...
Cependant, l’interprète ainsi produit ne fonctionne pas :
# ./compilo -ipp test/a.p Fatal error: exception Failure("Pas implémenté")
Vous noterez en passant l’utilisation de l’option -ipp pour appeller l’interprète du langage Pseudo-Pascal.
Le fichier interpretPP.ml contient le code d’un interprète extrêmement incomplet qui ne traite que l’instruction writeln :
open Printf open MIPSOps open Primitive open PP open Integer type value = VInt of int32 let asInt = function VInt i -> i type environment = value ref StringMap.t type definitions = PP.procedure StringMap.t type 'a interpreter = definitions -> environment -> environment -> 'a let interpret_primitive p actuals = match p, actuals with Writeln, [ v ] -> fprintf stdout "%ld\n%!" (asInt v); None _ -> failwith "Pas implémenté" let interpret_expression defs genv env = function EConst (ConstInt i) -> VInt i _ -> failwith "Pas implémenté" let interpret_call defs genv env callee actuals = let actuals = List.map (interpret_expression defs genv env) actuals in match callee with CPrimitiveFunction p -> interpret_primitive p actuals _ -> failwith "Pas implémenté" let interpret_condition defs genv env = function _ -> failwith "Pas implémenté" let rec interpret_instruction defs genv env = function IProcCall (callee, es) -> ignore (interpret_call defs genv env callee es) ISeq (is) -> List.iter (interpret_instruction defs genv env) is _ -> failwith "Pas implémenté" let interpret p = interpret_instruction StringMap.empty StringMap.empty StringMap.empty p.main
Vous pouvez déjà vérifier que ça marche sur le programme ultra minimal trivial.p :
trivial.p On obtient:
# ./compilo -ipp test/trivial.p 10
On pourra noter au passage que cet interprète incomplet ne considère que la suite d’instructions p.main qui constituent le point d’entrée du programme, ignorant les déclarations de variables globales et de fonctions p.globals et p.defs. L’interprète complet devra évidemment prendre celles-ci en compte.
Vous avez peut-être remarqué que les paramètres defs, genv et env ne sont pas utilisés : ils serviront justement plus tard à passer l’environnement (qui contient les variables globales et les fonctions) entre les fonctions de l’interprète. Si vous appellez vous-mêmes ces fonctions, n’oubliez pas de passer ces paramètres.
L’implémentation interpretPP.ml doit être conforme à l’interface interpretPP.mli :
interpretPPempty.mli
Elle doit donc fournir une fonction interpret qui, étant donnée une valeur de type PP.program, c’est-à-dire un arbre de syntaxe abstraite représentant un programme, l’exécute. Cette fonction ne renverra aucun résultat, mais son exécution pourra avoir des effets de bord : par exemple, elle pourra afficher une chaîne de caractères sur la sortie standard si le programme Pseudo-Pascal qu’on lui fournit contient une instruction writeln.
Pour écrire interpretPP.ml, on s’appuiera sur la syntaxe abstraite du langage Pseudo-Pascal, définie formellement dans PP.mli, et sur la description sémantique formelle big-step donnée dans le cours. On s’aidera également des signatures de fonctions données dans interpretPP.mli.
On suggère la démarche suivante:
On peut alors essayer cet interprète minimal en lui proposant un programme très simple, par exemple trivial2.p :
trivial2.p
On obtient:
# ./compilo -ipp test/trivial2.p 10
val lookup : environment -> environment -> string -> value ref
L’environnement est constitué de trois composantes:
Cet environnement est passsé par paramètre aux fonctions qui en ont besoin, d’où les signatures de fonctions dans l’interface interpretPP.mli.
Modifier l’évaluateur des expressions pour traiter le cas des variables, ainsi que l’évaluateur des instructions pour tenir compte de l’affectation de variable. Tester à l’aide d’un exemple simple, par exemple trivial3.p :
trivial3.p
On obtient encore:
# ./compilo -ipp test/trivial3.p 10
Compléter enfin l’interprète en traitant toutes les constructions et notamment les tableaux.
Vous pouvez à tout moment lancer une procédure de test complète à l’aide de la commande make test. L’interprète est alors lancé sur une série de programmes (fact.p, …) pour une série d’entrées standard (fact.in, …). Le résultat obtenu sur la sortie standard est stocké dans un fichier (fact.ipp, …) qui est confronté au résultat attendu (fact.ispim, …). L’ensemble des fichiers utiles aux tests se trouve dans le sous-répertoire test.
Ce document a été traduit de LATEX par HEVEA